简介:面向STM32嵌入式开发者的4.3英寸TFT LCD触摸屏驱动例程包,定位清晰:适合学习STM32F103ZET6控制彩色液晶屏与触摸交互的入门用户,也适合需要快速从KEIL迁移至STM32CubeIDE的工程师。例程基于NT35510控制器,通过16位8080并口驱动屏幕,触摸芯片GT968/GT1151采用I2C总线通信,代码由正点原子KEIL工程完整移植到STM32CubeIDE环境,可直接用HAL库打开编译。压缩包整体约73.02MB,上游未提供文件总数及类型明细;结合资源描述可知,例程内容包含基于CubeIDE的完整工程源码及屏幕/触摸驱动实现,可配合CSDN博文《STM32 驱动4.3寸TFT LCD 触摸屏》使用。目前已有956人学习下载。通过该例程能快速掌握LCD初始化、画点画线、字符显示、触摸坐标读取等流程,深入理解8080并口时序与I2C触摸芯片配置方法,大幅缩短自行移植和调试时间。 拿到“STM32F103ZET6-TFTLCD-TOUCH-4.3INCH.rar”这个工程压缩包的时候,很多人第一反应是解压、打开工程、点编译,然后烧进去看效果。但实际折腾过的人都知道,这套组合——STM32F103ZET6主控配4.3寸TFTLCD加触摸——说大不大,说小也绝对不小。你要是没搞明白驱动原理和硬件映射关系,轻则白屏点不亮,重则把屏烧了或者MCU引脚冲突到怀疑人生。这期就以这个经典的rar工程为引子,把背后涉及到的FSMC并口驱动、电阻/电容触摸坐标换算、引脚重映射和PWM背光调节这些核心点,一层层拆开来讲清楚。不管你是刚入手STM32的新手,还是准备把LCD触摸屏塞进智能小车、仪表盘项目的进阶玩家,这篇都能给你点实在的参考。
1. 解压后先别急着编译:工程结构与板卡连线速览
1.1 工程包里通常藏着什么
这类以芯片型号+外设名命名的rar包,一般不是某个官方SDK,而是前人已经调通的一套完整工程模板。解压后你大概率会看到几个固定目录:USER(存放main.c、stm32f10x_it.c、系统配置文件)、CORE(存放启动文件startup_stm32f10x_hd.s和内核相关头文件)、SYSTEM(正点原子风格的delay、sys、usart底层)、HARDWARE(lcd、touch、key、led等外设驱动),以及FWLIB或STM32F10x_FWLib(标准外设库的源码)。这种目录结构本身就是一套很好的学习范本,它把硬件相关代码和业务逻辑分开了,后期你要移植到智能车或者做项目改造,只改HARDWARE和USER里的东西就够了。
打开main.c之前,我建议你先做两件事:第一,看HARDWARE/lcd.h里的屏幕分辨率宏定义,确认是不是480x272;第二,看lcd.c里的LCD控制器型号,比如LCD_SSD1963、LCD_R61509V之类。4.3寸屏的控制器型号比较杂,有SSD1963、R61509V、ILI9806等,不同控制器的初始化寄存器序列不一样,如果工程里的型号和实物对不上,屏幕是死活点不亮的。
1.2 4.3寸屏到底怎么接到STM32上
4.3寸TFTLCD模块几乎没有用SPI接口的,基本都是16位8080并口,触摸部分要么是四线电阻屏(通过XPT2046或ADS7843这类ADC芯片转SPI),要么是电容屏(通过FT5x06、GT9147等I2C接口芯片)。所以你会在模块背面看到一大排排针,包括FSMC_D0~D15、FSMC_Ax、LCD_RS、LCD_WR、LCD_RD、LCD_RST、LCD_CS、T_CS、T_CLK、T_DIN、T_DOUT、T_IRQ等。
这里很多人第一次会懵:为什么LCD的RS(寄存器/数据选择)要接到FSMC地址线上?为什么数据线叫FSMC_D而不是GPIO?原因正是这一节要讲的核心——FSMC外设的硬件自动读写机制。简单讲,STM32内部把外部LCD当作一块静态存储器来访问,你往某个地址写数据,FSMC会自动把数据总线和控制线按8080时序拉高拉低,不需要CPU一条一条指令去翻转GPIO。所以,先把模块和开发板的连线理清楚,哪个引脚对应哪个功能,再去看代码,你会觉得逻辑顺很多。
1.3 三分钟确认工程和你的板子匹配
拿到工程,先把HARDWARE/lcd.h和HARDWARE/touch.h打开,对照里面宏定义的引脚号和你手上的开发板原理图。检查顺序记住三样:片选(CS)、复位(RST)、背光(BL)。背光引脚在很多板子上直接接3.3V或者由某个GPIO控制,如果工程里把背光接到了某个PWM引脚(比如PB1),那说明作者可能做了背光亮度调节功能,你要确认这个引脚没被其他外设占用。我的习惯是先在纸上画一遍引脚映射表,花五分钟做这件事,能省掉后面三小时查线的时间。
2. FSMC这条高速公路:4.3寸屏并口驱动为什么非得这么干
2.1 为什么4.3寸屏偏爱FSMC而不是普通GPIO
很多人一开始不理解,TFTLCD不能像OLED(SSD1306)那样用I2C或者SPI吗?答案是:屏幕分辨率越高,需要的像素数据量越大,低速串口根本喂不饱。480x272像素、16位色深,一帧全屏数据是480x272x2约等于261KB,就算只在局部刷新,用18MHz的SPI刷图也会明显看到撕裂和拖影,而16位并口在FSMC的50MHz时钟下刷起来就流畅得多。
FSMC是“灵活的静态存储控制器”,它本来是为了扩展外部SRAM、NOR Flash而设计的,但8080并口的LCD时序跟SRAM读写时序非常接近——都有片选、读使能、写使能、地址/数据总线,所以直接拿来驱动LCD属于“杀鸡用牛刀但牛刀确实更好使”。这就是为什么绝大多数32位MCU方案驱动大屏都默认走FSMC,而不是GPIO模拟时序。GPIO模拟并非不行,但每个像素都要多条指令翻转IO,CPU占用率直接拉满,系统干不了别的事。
2.2 8080时序在FSMC里是怎么“翻译”的
8080并口通信的关键在于:写命令和写数据要分两个阶段,靠RS引脚区分——RS拉低表示当前总线上是命令/寄存器地址,RS拉高表示当前总线上是要写入或读出的数据。在FSMC驱动方案里,RS引脚直接连到一根FSMC地址线,比如FSMC_A0。这样,CPU访问地址0x60000000时,A0=0,对应命令周期;访问0x60000002时,A0=1,对应数据周期。
代码里通常会出现这两句宏定义:
#define LCD_CMD_ADDR *(volatile uint16_t *)0x60000000 #define LCD_DATA_ADDR *(volatile uint16_t *)0x60000002往LCD_CMD_ADDR写值,FSMC就会自动完成片选拉低、地址建立、写使能脉冲这套动作,然后总线上的数据被LCD当作命令接收;往LCD_DATA_ADDR写值,就被当作要显示的数据或要写入的寄存器值。这个机制理解透之后,你再看LCD_WriteReg、LCD_WriteData这些函数,本质上就是不同地址的指针赋值而已。
2.3 关键初始化代码逐行拆解
FSMC驱动LCD的初始化分三步:使能GPIO时钟和FSMC时钟、配置FSMC的NOR/SRAM Bank1时序参数、把LCD控制器内部寄存器配置成正确的显示模式。
GPIO部分要特别注意,FSMC_D0~D15复用在GPIOD和GPIOE上,控制线A0、NE1、NOE、NWE分别复用在不同GPIO上,这些引脚必须配置为GPIO_Mode_AF_PP(复用推挽输出)。接下来是FSMC时序结构体:
FSMC_NORSRAMInitStructure.FSMC_AddressSetupTime = 0x01; // 地址建立时间 FSMC_NORSRAMInitStructure.FSMC_DataSetupTime = 0x05; // 数据建立时间 FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM; FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1; // NE1片选这几个时间参数是调屏的重点,设得太短,高速读写下数据建立不够,容易花屏;设得太长,速度变慢,刷新率上不去。经验值上,4.3寸屏(SSD1963/R61509V)用AddressSetupTime=1、DataSetupTime=5起步,如果出现花屏或颜色错乱,优先把DataSetupTime调到8~10试试。很多人看到初始化代码里还有一大段LCD_Init,里面是一连串寄存器写值操作,那些其实不需要背,关键是知道它是让LCD控制器处于RGB565、480x272、逐行扫描等正确状态即可。
3. 触摸坐标的换算:从ADC裸值到屏幕像素的“变形记”
3.1 触摸IC是怎么把“按压”变成数字的
4.3寸模块的常见配置是四线电阻屏配XPT2046触摸IC,这个IC通过SPI接口和MCU通信。它的工作原理不复杂:电阻屏的上下两层导电膜,按压时在触点位置短接;XPT2046通过切换X+、X-、Y+、Y-的供电方向,分别在两个方向上测出分压值,这个电压经过内部ADC转成12位数字量,就是“原始坐标”。
用SPI读取XPT2046时,控制字节里关键位包括起始位、通道选择(A2~A0)和分辨率(SER/DFR)。比如要读X方向坐标,控制字通常设为0xD0(也就是通道Y,用于测量X分压),读Y方向坐标用0x90。代码里的TP_Read_AD函数核心就几条语句,但有一个细节很多人容易忽略:连续读取时需要给一点延时或者做多次采样取平均值。触摸屏的ADC值在按压抖动时变化非常大,不做滤波的话,你画的线会是毛刺状的。
3.2 两点校准的数学原理
直接读出的X/Y原始ADC值,跟LCD像素坐标存在一个线性关系:像素坐标 = 原始值 × 比例因子 + 偏移量。这就是为什么工程里touch.h会有TOUCH_AD_LEFT、TOUCH_AD_RIGHT、TOUCH_AD_TOP、TOUCH_AD_BOTTOM这些宏定义——它们就是用两点校准法标定出来的边界值。
两点校准的操作方式:屏幕上先显示左上角和右下角两个十字标记,让你用笔分别点击,记录下这两点的ADC值,然后反推出映射关系。比如屏幕分辨率480x272,左上角触点的ADC值是(X1, Y1),右下角是(X2, Y2),那么:
- X方向比例因子 = 480 / (X2 - X1)
- Y方向比例因子 = 272 / (Y2 - Y1)
- X偏移量 = -X1 × 比例因子
- Y偏移量 = -Y1 × 比例因子
拿到这四个参数之后,运行时每个触摸点都做一次线性变换,就能把ADC值变成屏幕像素坐标。这个换算不是“可选项”,是必选项。不校准直接用的后果就是:你点屏幕左边的按钮,系统以为你点的是右边,十个新手有八个会在这上面卡住。
3.3 校准参数怎么用:宏定义和运行时修正
工程里的校准参数有两种存在形式:一种直接写死在头文件宏定义里,适合屏和触摸屏固定、没有批量生产的场景;另一种是开机进校准模式,把参数存到EEPROM或Flash的特定扇区,适合需要换屏或者现场调试的项目。如果你只是想跑通例程,直接用头文件里的宏定义即可;但如果你发现点按位置总偏,不要急着改代码,先用工程自带的“触摸校准”例程重新采一次值。
另外,触摸屏有一个特性叫“负载效应”——就是按压位置不同,ADC值会有轻微非线性变化,这在四线电阻屏上尤其明显。廉价4.3寸电阻屏的边角位置偏差个十几二十个像素很正常,所以如果你要做按键密集的UI,建议按键做大一点,或者做“长按确认”逻辑来防误触。我自己的项目里一般会把触摸判定区域比视觉按键区域外扩10像素,体验会好很多。
4. 显示中文、呼吸灯和GPIO重映射:把例程改成自己的东西
4.1 让屏幕显示中文:字库和编码绕不开的三件事
TFTLCD例程里默认只带英文ASCII字库,想显示中文,你得先解决三件事:字库、取模格式、编码转换。常用的做法是把16x16点阵中文字库(比如标配的GB2312字库)烧到外部SPI Flash里,需要显示某个汉字时,先算出它在字库文件中的偏移地址,读出点阵数据,再交给LCD驱动去描点。
汉字在GB2312里的定位公式是很多人的坎。一块16x16点阵字库文件里,每个汉字占用32字节。假设要显示“嵌”字,首先要拿到它的GBK机内码(在字符串里实际存储的两个字节),然后查区位码表计算出它在字库文件中的偏移。工程里常见的一个取字模函数会做这样的运算:
uint32_t offset = ((GBKH - 0xA1) * 94 + (GBKL - 0xA1)) * 32;这段代码的含义是:GB2312编码的汉字按区(94区)和位(94位)排列,减去0xA1得到区位索引,再加上字库文件的偏移基数。理解了这个公式,你换任意大小的字库(比如24x24、32x32)都能举一反三——只是点阵字节数从32变成72或128。如果不做编码转换,直接从UTF-8编码的字符串里截取汉字显示,就会出现乱码甚至数组越界。常见做法是在工程里用C标准库或自带的编码转换函数,或者干脆在源码里用**开头的GBK编码字符串常量,把编辑器编码设置为GB2312。
4.2 TIM3呼吸灯:从引脚重映射到PWM占空比调节
工程跑通LCD之后,很多人会顺手做个小实验——让屏幕背光或者LED“呼吸”起来。这就要用到TIM3的PWM输出了。但这里藏着一个比较容易忽略的点:TIM3的PWM通道默认映射在PA6、PA7、PB0、PB1上,但如果你用的开发板把PA6/PA7已经连到别的东西上了(比如SPI1或ADC),你就需要用到引脚重映射功能,把TIM3_CH1重映射到另一个引脚上。
引脚重映射的操作本身很简单,但要先打开AFIO时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 开启AFIO时钟 GPIO_PinRemapConfig(GPIO_Remap_TIM3, ENABLE); // 全重映射这之后TIM3_CH1会从PA6跑到PC6,CH2从PA7跑到PC7,等等。很多人忘了开AFIO时钟,结果重映射函数一调用就进HardFault。PWM调光亮度的核心是改占空比,而呼吸灯无非就是让CCR寄存器值随时间从0慢慢加到ARR再降回来。比如ARR设为999,代表PWM周期是1000个计数单位,那CCR=500时背光就是半亮。用一个简单的三角波算法或者sin曲线算法去更新CCR,就能实现顺滑的呼吸效果。注意一点:更新CCR值尽量放在PWM更新中断或者主循环里定时执行,不要在PWM输出中断里做浮点运算,否则会影响时序稳定性。
4.3 把LCD+触摸模块拆出来,塞进智能车项目
到了这个阶段,你可以把整个工程里跟自己需求无关的东西砍掉,只保留LCD驱动、触摸驱动、延时函数,再做成一个模块化的库。比如做智能小车的时候,在LCD上显示速度曲线、电池电压、转向角度,用触摸屏做“动画菜单”选择各种模式——这些都可以复用之前调通的代码。
我的建议是:把LCD和触摸屏的驱动文件从例程中单独拿出来,放进你自己工程的一个BSP目录,同时把所有硬件相关的宏定义集中到bsp_lcd.h和bsp_touch.h。这样当你换屏或者换主控(比如从F103换到F407)时,只需要改这两个头文件里的引脚映射和FSMC时序参数,显示层和业务层的代码完全不用动。这一步做得好,后续项目迭代的效率会提升一个档次。
5. 实测翻车现场:白屏、漂移、引脚打架的排查实录
5.1 白屏:先查背光,再查FSMC时序
白屏是这类工程最常见的故障,但白屏也分“纯白”和“有背光但无显示”两种。纯白说明背光亮了但LCD没有收到正常的数据/命令;如果连背光都不亮,问题九成出在硬件供电或背光控制引脚上。先用万用表量一下LCD模块的VCC是不是3.3V或5V(视模块要求),再查背光引脚有没有电压。背光正常的情况下,白屏排查顺序:
- 确认
LCD_CS是否接在FSMC_NE1上,代码里使用的Bank是否和硬件一致; - 确认
LCD_RS用的是FSMC地址线还是普通GPIO,两者对应完全不同的驱动方式; - 确认FSMC时序参数是否过短,特别是
FSMC_DataSetupTime; - 最后检查LCD控制器型号是否匹配。
我遇到过一次特别隐蔽的白屏:工程用的是ILI9806控制器驱动,但实际屏是R61509V,一个功能完全一样但寄存器初始化序列不同的控制器,导致整个屏幕只有背光亮,别的什么都没显示。这个教训是——不要只看屏幕尺寸和分辨率,一定要核实控制器型号。
5.2 触摸漂移:先怀疑电源,再怀疑算法
触摸屏出现“点不准”“越来越飘”的问题,多数不是算法写错了,而是参考电压不稳定。XPT2046的分压测量完全依赖于VCC的稳定度,如果给触摸IC供电的引脚上还挂了背光LED或者电机驱动,电流一波动,ADC参考电压跟着抖,坐标自然飘。解决办法很简单:触摸IC的VCC单独走线,或者在靠近触摸IC的电源引脚处加一颗10uF钽电容和0.1uF陶瓷电容去耦。另外,如果电阻屏用了很久,屏幕表面有污渍或者导电膜老化,校准数据也会慢慢失效,这种情况下重新校准只能缓解,换屏才是根本。
软件层面还有一个坑:读取触摸坐标时没有做“有效判断”就上报。实际按压时,触点接触会有几十毫秒的抖动,这期间读到的坐标是跳变的。我习惯在读取函数里加一个简单的“连续两次采样差值小于阈值才算有效”的滤波逻辑,实测能消除大部分触摸漂移的观感问题。
5.3 引脚冲突:重映射和JTAG这两个老冤家
用STM32F103ZET6这种144脚的大芯片,引脚资源一般不会太紧张,但依然要小心两种冲突。第一种是FSMC引脚和JTAG引脚打架:STM32F103的JTAG默认占用PB3、PB4、PA15等引脚,而这些引脚有时候会被复用到FSMC地址线或者TIM3重映射通道上。不能用JTAG调试。第二种是TIM3重映射后跑到PC6~PC9,而这几个引脚可能和SDIO或FSMC的数据线共用。我的习惯是每次做引脚分配时,先打开芯片的数据手册,在引脚功能表上把占用情况标一遍,特别是要对照GPIO_Remap表格确认重映射后的引脚没有和别的外设冲突。
最后说一个关于电源的重要提醒:4.3寸TFTLCD的背光电流不小,峰值能到200mA以上。如果你用开发板上的3.3V LDO给屏幕和MCU同时供电,亮屏瞬间压降很容易导致MCU复位,表现就是“一上电就白屏重启,循环不断”。这种问题不是代码能解决的,要么给屏幕单独供电,要么换一个输出电流足够的电源方案。我自己后来做带屏项目,都是直接上5V输入、一路给屏供电、一路给MCU供电,再共用GND,从此再没遇到“一亮屏就重启”的毛病。
这套STM32F103ZET6 + 4.3寸TFTLCD + 触摸的组合包,看着只是一个rar压缩包,但能把里面的FSMC时序、触摸校准、引脚复用这几块真正吃透,你对整个STM32的硬件抽象和外设协作能力会上一个很大的台阶。别急着把工程跑通就完事,试着把里面的LCD驱动改成你自己项目的模块,试着调一调呼吸灯和触摸按钮的联动,你会发现自己对“嵌入式系统”这四个字的理解变得具体多了。
本文还有配套的精品资源,点击获取