简介这份资源面向已具备STM32基础、希望从标准库或HAL例程过渡到STM32CubeIDE开发环境的嵌入式学习者提供一套4.3英寸TFT LCD彩色触摸屏的完整驱动例程。工程中TFT LCD采用16位8080并口方式驱动NT35510显示芯片触摸部分则通过I2C总线读取GT968/GT1151触摸芯片覆盖了显示初始化、像素与字符绘制、触摸坐标采集等关键环节可帮助读者理解并口时序配置与I2C触摸驱动的协同调试方法。压缩包为rar格式整体约73.02MB文件数量与类型明细上游暂未提供从体量看应包含完整的CubeIDE工程源码及配套文件。目前已有962人学习下载适合需要将正点原子Keil工程迁移至CubeIDE、或对照实物调试LCD与触摸功能的开发者参考借鉴。1. 一块 4.3 寸屏点亮之前我踩过的那些坑手里攥着一块 4.3 寸 TFT 触摸屏和一块 STM32F103ZET6 核心板很多人第一反应是“接上线就能亮”。真上手才发现背光不亮、花屏、白屏、触摸坐标乱飞一个都跑不掉。这份资源就是一个把 STM32F103ZET6 和 4.3 寸 TFTLCD 触摸屏跑通的完整工程包里面包含底层驱动、初始化时序、显示刷新和触摸采样几块内容。它适合两类人刚拿到开发板、想先把屏点亮建立信心的新手以及需要在这套硬件上做界面、做交互、做二次开发的熟手。下面我按“这项目能干什么 → 怎么跑起来 → 核心代码怎么读 → 怎么改”的顺序把这份工程拆开讲清楚。2. 工程定位与技术选型为什么是 F103ZET6 配 4.3 寸屏2.1 这套硬件组合的典型场景STM32F103ZET6 是 Cortex-M3 内核里资源比较宽裕的一颗144 引脚、512KB Flash、64KB SRAM带 FSMC 外部存储器接口。4.3 寸 TFTLCD 一般是 480×272 或 800×480 分辨率16 位并口或者 SPI 接口。这套组合在工业面板、仪器仪表、小型人机界面里非常常见原因很直接FSMC 可以把屏幕当成一片外部 SRAM 来读写刷屏速度比软件模拟并口快一个量级CPU 不用一位一位地翻转 IO。工程里屏幕走的是 16 位并口接 FSMC 的方案触摸部分通常是电阻屏配 XPT2046 这类触摸控制器走 SPI 读坐标。这个选型决定了后面代码的两个核心FSMC 的时序配置以及触摸控制器的 SPI 读写时序。搞懂这两块整个工程就没有黑匣子了。2.2 目录结构长什么样拿到压缩包解压后工程一般按 Keil MDK 的标准结构组织常见目录如下目录/文件作用Core/ 或 USER/main.c、系统时钟、中断入口HARDWARE/LCD/屏幕驱动 lcd.c、lcd.h含初始化与绘图函数HARDWARE/TOUCH/触摸驱动 touch.c、touch.h含坐标读取与校准HARDWARE/FSMC/ 或直接并入 LCDFSMC 时序与地址映射配置STM32F10x_FWLib/标准外设库源码SYSTEM/delay、sys、usart 等基础支撑OBJ/ 与 PROJECT/编译输出与 Keil 工程文件 .uvprojx不同版本可能把 FSMC 配置直接写进 lcd.c 的初始化函数里也可能单独拆一个文件读代码时先找LCD_Init这个入口顺着它往下看就能把整条链路串起来。2.3 关键引脚与地址映射并口屏接 FSMC 时数据线 D0~D15 接 FSMC 的 D0~D15控制线 RS 接一根地址线常见是 A6 或 A16CS 接片选 NEWR 接 NWERD 接 NOE。RS 接哪根地址线决定了代码里命令和数据的地址偏移。常见做法是/* 假设 RS 接 FSMC_A6基地址为 0x6C000000 */ #define LCD_BASE ((uint32_t)0x6C000000) #define LCD_REG_ADDR (LCD_BASE) /* RS0写命令 */ #define LCD_RAM_ADDR (LCD_BASE (1 7)) /* RS1写数据 */这里1 7是因为 A6 对应地址偏移的第 7 位每个地址单位 2 字节时按字对齐计算具体偏移要按实际接线核对。写命令就是往LCD_REG_ADDR写一个 16 位数写数据就是往LCD_RAM_ADDR写。这个映射一旦接错现象就是白屏或者花屏后面避坑章节会细说。3. 从零跑起来环境、编译与第一次点亮3.1 开发环境准备工程是 Keil MDK 的 .uvprojx需要装好 MDK5 和 STM32F1 的器件支持包。打开工程后先确认三件事器件型号选的是 STM32F103ZE调试器选 ST-Link 或 J-Link下载算法里 Flash 容量对得上 512KB。这三项任何一项不对编译能过但下载会失败。# 典型编译产物在 OBJ/ 目录下 OBJ/ ├── project.hex ├── project.axf └── project.map.map文件值得看一眼能确认代码段有没有超出 Flash、RAM 占用多少。F103ZET6 有 64KB SRAM如果工程里开了大数组做显存缓冲很容易把 RAM 吃满链接阶段就会报 region overflow。3.2 时钟与 FSMC 初始化顺序上电后第一件事是配时钟。屏幕走 FSMCFSMC 挂在 AHB 总线上必须先使能 FSMC 时钟再配 GPIO 复用最后写 FSMC 时序寄存器。顺序反了时序寄存器写不进去屏幕自然不亮。常见初始化骨架void LCD_Init(void) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); /* 先开 FSMC 时钟 */ LCD_GPIO_Config(); /* 数据线、控制线复用推挽 */ FSMC_NORSRAMInitTypeDef init; FSMC_NORSRAMTimingInitTypeDef timing; timing.FSMC_AddressSetupTime 0x02; /* 地址建立时间 */ timing.FSMC_AddressHoldTime 0x00; timing.FSMC_DataSetupTime 0x05; /* 数据建立时间屏慢就加大 */ timing.FSMC_BusTurnAroundDuration 0x00; timing.FSMC_CLKDivision 0x00; timing.FSMC_DataLatency 0x00; timing.FSMC_AccessMode FSMC_AccessMode_B; init.FSMC_Bank FSMC_Bank1_NORSRAM1; init.FSMC_DataWidth FSMC_NORSRAM_DataWidth_16b; init.FSMC_WriteOperation FSMC_WriteOperation_Enable; init.FSMC_ReadWriteTimingStruct timing; init.FSMC_WriteTimingStruct timing; FSMC_NORSRAMInit(init); FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); LCD_ReadID(); /* 读屏 ID确认通信正常 */ LCD_ConfigPanel(); /* 按屏型号写初始化序列 */ LCD_Clear(WHITE); }FSMC_DataSetupTime是最容易出问题的一个参数。屏的控制器手册里会给最小读写周期主频 72MHz 下一个 HCLK 周期约 13.9ns数据建立时间不够就会出现偶发花屏或者读 ID 读不到。调不通时先把这个值往大调能稳定再逐步压小。3.3 读 ID 与初始化序列LCD_ReadID是判断“线接对了没有”的第一道关卡。读到的 ID 和屏控制器型号对不上后面所有初始化都是白费。常见控制器有 ILI9341、NT35510、SSD1963 等不同控制器的初始化序列差别很大工程里一般用条件编译或者 ID 判断来分支。uint16_t id LCD_ReadID(); if (id 0x9341) { LCD_Init_ILI9341(); } else if (id 0x1963) { LCD_Init_SSD1963(); } else { /* ID 异常检查接线与 FSMC 时序 */ while (1); }初始化序列本质是一串“写命令 写参数”的组合比如电源配置、伽马校正、像素格式、扫描方向。这些值照控制器手册和应用笔记抄即可不要自己乱改改错一个寄存器就可能出现颜色反了或者显示区域偏移。3.4 触摸校准与坐标读取电阻触摸屏的原始 ADC 值和屏幕像素坐标不是线性对应的必须做校准。常见做法是屏幕上依次显示几个校准点让用户点采集原始值后算出一组变换系数/* 两点校准简化示意x_screen a * x_raw b */ void Touch_Calibrate(void) { /* 依次采集左上、右下两个校准点的原始坐标 */ Touch_GetRaw(raw1); Touch_GetRaw(raw2); cal.a (float)(SCREEN_W) / (raw2.x - raw1.x); cal.b -cal.a * raw1.x; /* y 方向同理 */ }校准系数一般存到 Flash 的某个扇区下次上电直接读不用每次校准。工程里如果没做存储每次复位都要重新校准这是新手最容易忽略的一步。4. 核心模块拆解显示刷新与触摸采样的代码链路4.1 写命令与写数据的底层封装所有上层绘图函数最终都落到两个最底层的操作写命令、写数据。这两个函数写对了上层就是纯逻辑。/* 写 16 位命令到 LCD */ void LCD_WriteReg(uint16_t reg) { *(__IO uint16_t *)LCD_REG_ADDR reg; } /* 写 16 位数据到 LCD */ void LCD_WriteData(uint16_t data) { *(__IO uint16_t *)LCD_RAM_ADDR data; } /* 设置绘图窗口后续连续写数据就落在窗口内 */ void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteReg(0x2A); /* 列地址设置命令 */ LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteData(x2 8); LCD_WriteData(x2 0xFF); LCD_WriteReg(0x2B); /* 行地址设置命令 */ LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteData(y2 8); LCD_WriteData(y2 0xFF); LCD_WriteReg(0x2C); /* 写显存命令之后连续写像素 */ }LCD_SetWindow是刷屏效率的关键。设好窗口后连续往LCD_RAM_ADDR写像素控制器会自动按窗口递增地址不用每写一个像素都重设坐标。填充一个矩形区域时用循环连续写比逐点设坐标快得多。4.2 刷屏与字符显示有了窗口设置清屏和填充矩形就是一层循环void LCD_Fill(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { uint32_t total (uint32_t)(x2 - x1 1) * (y2 - y1 1); LCD_SetWindow(x1, y1, x2, y2); while (total--) { LCD_WriteData(color); } }字符显示一般靠字模数组工程里常见 12×12、16×16、24×24 几套 ASCII 字模中文则用取模软件生成的点阵。显示一个字符就是按位判断为 1 画前景色为 0 画背景色。这里有个性能点如果每个像素都单独调LCD_WriteData函数调用开销不小熟手一般会把内层循环展开或者用 DMA 搬运但工程里为了可读性通常先保留直白写法。4.3 触摸采样与滤波触摸控制器通过 SPI 返回 12 位 ADC 值一次完整采样要读 X、Y 和压力。原始值抖动很大直接拿来算坐标会跳。常见做法是连续采多次取中值或者均值uint16_t Touch_ReadX(void) { uint16_t buf[5]; for (int i 0; i 5; i) { buf[i] XPT2046_Read(CMD_READ_X); } /* 简单冒泡取中值滤掉偶发尖峰 */ for (int i 0; i 4; i) for (int j 0; j 4 - i; j) if (buf[j] buf[j1]) { uint16_t t buf[j]; buf[j] buf[j1]; buf[j1] t; } return buf[2]; }中值滤波对单点尖峰很有效代价是多采几次。采样频率要求不高时这套够用如果要做滑动、手写这类连续轨迹还得加一阶低通或者做去抖否则轨迹会呈锯齿状。4.4 主循环里的显示与触摸协同主函数通常是一个大循环读触摸、判断是否落在某个区域、更新显示。这里要注意刷屏和触摸采样的节奏刷大面积区域时触摸响应会变慢因为两者都占 CPU。常见缓解办法是把触摸采样放在定时器中断里主循环只处理显示或者把刷屏拆成小块分帧更新。while (1) { if (Touch_Scan()) { /* 有有效触摸 */ uint16_t x, y; Touch_GetXY(x, y); /* 原始值转屏幕坐标 */ LCD_DrawPoint(x, y, RED); /* 简单落点反馈 */ } /* 其他界面逻辑 */ }Touch_Scan里一般会判断压力值是否超过阈值低于阈值视为未按下避免悬空误触。这个阈值和屏的贴合程度有关换一块屏可能要重新调。5. 避坑与排查白屏、花屏、触摸乱跳的真实原因5.1 上电白屏读 ID 返回 0 或 0xFFFF现象背光亮但屏幕全白串口打印读到的 ID 是 0 或者 0xFFFF。原因通常是 FSMC 没通或者地址映射错。先查 FSMC 时钟有没有使能再查 RS 接的是哪根地址线、代码里的偏移对不对。还有一种情况是数据线高低位接反读出来的 ID 是个看似合理但匹配不上的值。解决用万用表逐根核对 D0~D15 和 RS、CS、WR、RD确认LCD_REG_ADDR和LCD_RAM_ADDR的偏移与接线一致。5.2 显示花屏、颜色错位现象能显示内容但颜色不对红蓝互换或者整屏噪点。原因多半是像素格式没配对控制器初始化时设的是 RGB565代码却按 BGR 或者 18 位模式在写。也可能是 FSMC 数据建立时间太短高速写时偶发错位。解决先确认初始化序列里的像素格式寄存器再把FSMC_DataSetupTime调大两档观察是否改善稳定后再回调。5.3 触摸坐标乱飞或者点不准现象点屏幕左边坐标出现在右边或者手指没动坐标自己跳。原因一是校准系数没做或者做错二是原始采样没滤波三是 X/Y 方向接反。解决先打印原始 ADC 值看按下时数值是否随位置单调变化再做一次完整校准并把系数存 Flash最后加中值滤波。方向接反的话在坐标变换里交换 X/Y 或者取反即可。5.4 编译通过但下载后不运行现象Keil 编译零错误下载提示成功但板子没反应。原因常见于启动文件选错比如选了小容量型号的 startup、Flash 下载算法容量不匹配、或者 BOOT 引脚状态不对。解决核对器件型号与启动文件、确认下载算法为 512KB 版本、检查 BOOT0 接地从主 Flash 启动。5.5 刷屏时触摸失灵现象界面刷新时点屏幕没反应刷完才恢复。原因是刷屏占满 CPU触摸采样被饿死。解决把触摸采样移到定时器中断或者把大块填充拆成小段每段之间留出采样窗口。这是交互体验上很实际的一个坑界面越复杂越明显。6. 二次开发把这份工程改成自己的界面框架工程跑通之后真正的工作才开始。我的习惯是先抽一层薄薄的显示抽象把LCD_Fill、LCD_DrawPoint、字符显示这些原语包起来上层界面只调抽象层这样换屏或者换分辨率时改动集中在一处。具体做法是定义一个函数指针表或者一组ui_前缀的包装函数把坐标裁剪、颜色转换都收进去。typedef struct { void (*fill)(uint16_t, uint16_t, uint16_t, uint16_t, uint16_t); void (*point)(uint16_t, uint16_t, uint16_t); void (*text)(uint16_t, uint16_t, const char *, uint16_t, uint16_t); } UI_Driver; /* 上层只依赖这个结构体底层换实现不影响界面代码 */触摸这块建议把校准系数和采样滤波参数做成可配置项放在一个独立头文件里。换屏或者换外壳时只改参数不动逻辑。另外如果要做按钮、滑块这类控件别急着上复杂框架先用矩形区域加状态机实现一个控件就是“区域 当前状态 按下/抬起回调”在 F103 这种资源下反而更稳。验证方法上我一般分三步先用纯色填充确认整屏无坏点、无色偏再用坐标网格确认触摸线性度最后跑一个带按钮的小界面连续点击几十次看有没有丢点或者误触。三步都过这套驱动才算能进项目。血泪经验是每次改 FSMC 时序或者初始化序列都先把读 ID 和纯色填充这两步单独跑一遍别一上来就烧完整界面。从那以后我每次动底层配置都强制先过一遍“读 ID 三色填充”的最小验证省下大量对着花屏猜原因的时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?