STM32F103ZET6驱动4.3寸TFTLCD触摸屏:FSMC配置与触摸校准实战

发布时间:2026/11/4 21:27:21
STM32F103ZET6驱动4.3寸TFTLCD触摸屏:FSMC配置与触摸校准实战

简介:这份资源面向使用STM32F103ZET6进行嵌入式显示与触控开发的工程师及学习者,提供4.3英寸TFT LCD彩色液晶触摸屏的完整驱动例程,解决从裸机并口驱动到触摸交互的工程落地问题。例程中TFT LCD采用16位8080并口驱动芯片NT35510,触摸部分通过I2C总线对接GT968/GT1151触摸芯片,并将正点原子KEIL工程移植到STM32CUBEIDE环境,便于习惯CubeIDE的开发者直接编译调试。压缩包为rar格式,整体约73.02MB,文件类型明细上游暂未提供,但可预期包含工程源码、驱动文件与配置资源,适合按模块对照学习。目前已有962人学习下载,说明该方案在同类开发中具备一定参考价值。读者可从中获得并口时序配置、NT35510初始化流程、I2C触摸读取与坐标处理等可复用代码,并借助CubeIDE工程结构快速搭建自己的显示触控原型,减少底层驱动调试时间。

1. 一块 4.3 寸屏点亮之后,我才算真正摸到 STM32 的边

去年帮一个做工业面板的朋友看代码,他丢过来一个压缩包,说屏能亮但触摸坐标死活对不上,让我帮忙看看。解压之后发现是 STM32F103ZET6 驱动 4.3 寸 TFTLCD 触摸屏的完整工程,从底层 FSMC 时序到触摸校准全在里面。这类资源对刚上手 STM32 的人来说价值很直接:它不是一个点灯 Demo,而是一条从 GPIO 到 FSMC 到触摸采样的完整链路,能让你看清一块屏是怎么被 MCU 一步步驱动的。适合谁?适合已经会写基础 GPIO 程序、想搞明白外部总线怎么用、想给项目加一块彩屏交互界面的开发者。下面我按自己拆工程的顺序,把这份资源讲透。

2. 工程结构与技术选型:为什么是 FSMC 而不是软件模拟

2.1 目录骨架与文件职责

解压后典型的工程目录长这样,不同 IDE 版本会有细微差异,但核心分层基本一致:

STM32F103ZET6-TFTLCD-TOUCH-4.3INCH/ ├── CORE/ # 内核启动文件、core_cm3 相关 ├── HARDWARE/ │ ├── LCD/ # 屏幕驱动:初始化、画点、字符、图片 │ ├── TOUCH/ # 触摸驱动:采样、校准、坐标转换 │ └── LED/ # 板载指示灯,调试用 ├── SYSTEM/ │ ├── delay/ # 延时函数,SysTick 实现 │ ├── sys/ # 系统时钟配置 │ └── usart/ # 串口,打印调试信息 ├── USER/ │ ├── main.c # 主流程 │ └── stm32f10x_it.c # 中断服务 └── STM32F10x_FWLib/ # 标准外设库

这个分层是标准库时代的经典写法:HARDWARE 放外设驱动,SYSTEM 放系统级支撑,USER 放业务。好处是移植时只动 HARDWARE 和 USER,SYSTEM 基本不用碰。我一般接手这类工程先看 main.c 和 HARDWARE 下的头文件,能快速判断驱动接口设计得干不干净。

2.2 选 FSMC 的底层逻辑

4.3 寸屏常见分辨率 480×800 或 480×272,像素点动辄几十万。如果用 GPIO 软件模拟并口时序,刷一屏要几十毫秒甚至上百毫秒,刷新率低到肉眼可见撕裂。STM32F103ZET6 自带 FSMC(灵活静态存储控制器),可以把屏幕的 8080 并口映射成一块外部 SRAM 地址,写数据就是往固定地址写一个 16 位值,硬件自动拉时序,速度差一个数量级。

工程里通常这样定义屏幕的基地址:

/* LCD 的 FSMC 地址映射,A6 作为 RS 信号线 */ #define LCD_BASE ((u32)(0x6C000000 | 0x0000007E)) #define LCD ((LCD_TypeDef *) LCD_BASE) typedef struct { u16 LCD_REG; /* 写命令 */ u16 LCD_RAM; /* 写数据 */ } LCD_TypeDef;

0x6C000000是 FSMC 的 Bank1 区域,0x7E是地址偏移,利用 A6 地址线区分命令和数据。这样LCD->LCD_REG = cmd就是发命令,LCD->LCD_RAM = data就是发数据,编译器生成的存储指令直接触发 FSMC 硬件时序,CPU 不参与拉线。参数上要盯住 FSMC 的读写时序配置,后面避坑章节会细说。

2.3 触摸部分为什么单独成模块

触摸和显示是两套独立硬件:显示走 FSMC 并口,触摸走 SPI 或模拟采样(取决于屏的触摸芯片)。工程把 TOUCH 单独拆出来,是因为触摸需要校准——出厂坐标和屏幕像素坐标不是线性对应的,必须采几个点算出比例系数和偏移量。这个校准结果通常存到 Flash 或 EEPROM,下次上电直接读。模块拆开之后,换屏只改 TOUCH 的采样函数,校准逻辑不用动。

3. 核心驱动拆解:从 FSMC 初始化到画出一个像素

3.1 FSMC 时序配置,参数错一个就白屏

这是整个工程最关键的初始化,也是最容易翻车的地方。标准库写法大致如下:

void LCD_FSMC_Init(void) { FSMC_NORSRAMInitTypeDef init; FSMC_NORSRAMTimingInitTypeDef timing; GPIO_InitTypeDef gpio; /* 使能 FSMC 和 GPIO 时钟 */ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE | RCC_APB2Periph_GPIOF | RCC_APB2Periph_GPIOG, ENABLE); /* 数据线 D0~D15 配成复用推挽 */ gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; gpio.GPIO_Pin = GPIO_Pin_All; GPIO_Init(GPIOD, &gpio); GPIO_Init(GPIOE, &gpio); /* 控制线:NOE/NE/NWE 等 */ gpio.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_Init(GPIOG, &gpio); /* 读写时序,单位是 HCLK 周期 */ 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_DataWidth_16; init.FSMC_ExtMode = FSMC_ExtMode_Disable; init.FSMC_MemoryType = FSMC_MemoryType_SRAM; init.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16; init.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; init.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low; init.FSMC_WrapMode = FSMC_WrapMode_Disable; init.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState; init.FSMC_WriteOperation = FSMC_WriteOperation_Enable; init.FSMC_WaitSignal = FSMC_WaitSignal_Disable; init.FSMC_WriteBurst = FSMC_WriteBurst_Disable; init.FSMC_ReadWriteTimingStruct = &timing; init.FSMC_WriteTimingStruct = &timing; FSMC_NORSRAMInit(&init); FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); }

AddressSetupTime和DataSetupTime是重点。前者是地址建立时间,后者是数据保持时间,单位都是 HCLK 周期。72MHz 主频下,一个周期约 13.9ns。屏的 datasheet 里会写最小建立/保持时间,比如地址建立 10ns、数据保持 50ns,换算下来AddressSetupTime至少 1,DataSetupTime至少 4。工程里给 2 和 5 是留了余量。如果屏能亮但花屏、闪屏,先怀疑这两个值偏小;如果完全白屏,先查片选和 RS 线接没接对。

3.2 写命令与写数据,一个地址偏移的事

有了上面的结构体映射,底层读写就两行:

/* 写寄存器地址(命令) */ void LCD_WR_REG(u16 regval) { LCD->LCD_REG = regval; } /* 写寄存器数据 */ void LCD_WR_DATA(u16 data) { LCD->LCD_RAM = data; } /* 读数据,用于读 ID 或读显存 */ u16 LCD_RD_DATA(void) { return LCD->LCD_RAM; }

逻辑很直白:往LCD_REG地址写触发 RS=0,往LCD_RAM地址写触发 RS=1,FSMC 自动完成并口时序。参数regval是屏控制器的寄存器编号,data是要写入的值。读操作要注意,部分屏控制器读数据前需要插入 dummy 读,工程里如果读 ID 不对,先看是不是少了这一步。

3.3 画点函数,所有图形的地基

/* 在指定坐标画一个颜色点 */ void LCD_DrawPoint(u16 x, u16 y, u16 color) { LCD_SetCursor(x, y); /* 设置光标位置 */ LCD_WR_REG(0x2C); /* 写显存命令 */ LCD_WR_DATA(color); /* 写入颜色值 */ } /* 设置光标,不同控制器命令号不同,这里以常见 ILI9486 为例 */ void LCD_SetCursor(u16 x, u16 y) { LCD_WR_REG(0x2A); /* 列地址设置 */ LCD_WR_DATA(x >> 8); LCD_WR_DATA(x & 0xFF); LCD_WR_REG(0x2B); /* 行地址设置 */ LCD_WR_DATA(y >> 8); LCD_WR_DATA(y & 0xFF); LCD_WR_REG(0x2C); /* 准备写显存 */ }

0x2A和0x2B是列/行地址寄存器,0x2C是显存写入。颜色是 RGB565 格式,16 位一个像素。画点函数被字符显示、画线、画矩形全部复用,所以它的正确性决定整个显示层。如果点画出来位置偏移,多半是行列地址的起始偏移没设对,有些屏的显存比可视区域大,需要加 offset。

3.4 触摸采样与校准,坐标对不上的根因

触摸模块通常先读原始 AD 值,再套校准系数:

/* 读一次触摸原始坐标,返回是否按下 */ u8 TP_Read_XY(u16 *x, u16 *y) { u16 xtemp, ytemp; if (!TP_Is_Pressed()) return 0; /* 没按下直接返回 */ xtemp = TP_Read_Ad(0x90); /* 读 X 通道 */ ytemp = TP_Read_Ad(0xD0); /* 读 Y 通道 */ *x = xtemp; *y = ytemp; return 1; } /* 用四点校准算出的系数做线性变换 */ void TP_Convert(u16 raw_x, u16 raw_y, u16 *px, u16 *py) { *px = (u16)((float)raw_x * tp_xfac + tp_xoff); *py = (u16)((float)raw_y * tp_yfac + tp_yoff); }

tp_xfac、tp_xoff这些系数由校准流程算出:屏幕上依次显示四个十字,用户点中后记录原始 AD 值,用最小二乘或简单比例算出斜率和截距。校准结果一般写进 Flash 指定页,上电先读,读不到再进校准。这里最常见的坑是 X/Y 方向反了或者镜像了,本质是屏的安装方向和触摸芯片的坐标轴定义不一致,改系数符号就能解决。

4. 跑起来:编译、下载与第一次点亮

4.1 环境准备与工程打开

这份工程基于标准外设库,常见用 Keil MDK 打开。需要确认三件事:装好对应芯片的器件支持包,工程里引用的标准库路径没丢,下载器配置成 SWD。打开工程后先编译一次,看有没有缺头文件的报错,缺的话检查 Include Paths 里 HARDWARE 和 SYSTEM 的路径是否都在。

# 如果用的是命令行编译链,大致流程如下 # 1. 确认 arm-none-eabi-gcc 可用 arm-none-eabi-gcc --version # 2. 进入工程目录,按 Makefile 编译 make clean && make # 3. 用 openocd 下载 openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \ -c "program build/firmware.elf verify reset exit"

用 Keil 的话直接点 Build 再 Download 即可。下载前确认 BOOT0 接地,否则可能进不了用户程序。

4.2 上电自检顺序

第一次跑建议按这个顺序验证,出问题好定位:

  1. 串口先初始化,打印一行版本信息,确认 MCU 活着。
  2. 读屏 ID,和预期控制器型号比对,读不到说明 FSMC 或接线有问题。
  3. 全屏刷一个纯色,确认显存写入通路正常。
  4. 画几个字符和矩形,确认坐标和颜色。
  5. 进触摸校准,点四个十字,看坐标是否跟手。

每一步失败对应的排查方向不同,别一上来就怀疑屏坏了。我见过不少情况是排线没插紧或者背光没供上,屏本身是好的。

4.3 关键参数速查

参数典型值说明
主频72MHzF103 标准配置
FSMC 数据宽度16 位RGB565 一个像素
地址建立时间2 周期偏小易花屏
数据保持时间5 周期偏小易闪屏
触摸采样通道X:0x90 Y:0xD0随芯片不同
校准存储位置Flash 末页掉电不丢

5. 避坑与排查:那些让我熬夜的现场问题

5.1 白屏但背光亮

现象:上电背光正常,屏幕全白,串口能打印。原因多半是 FSMC 片选或 RS 线接错,或者FSMC_WriteOperation没使能。解决:先用万用表量片选脚在写操作时有没有拉低,再确认结构体里FSMC_WriteOperation_Enable已设置。如果读 ID 返回 0xFFFF 或 0x0000,基本可以锁定通信没通。

5.2 花屏、闪屏、局部错位

现象:能显示但颜色错乱,或者刷新时闪烁。原因通常是 FSMC 时序偏紧,DataSetupTime不够。解决:把DataSetupTime从 5 加到 8 甚至 10 试,AddressSetupTime从 2 加到 3。另外检查 HCLK 分频,如果系统时钟不是 72MHz,时序周期会变,参数要重算。

5.3 触摸坐标反了或偏移

现象:点左上角,光标跑到右下角。原因是触摸芯片坐标轴和屏幕方向不一致。解决:在校准系数上做处理,X 方向取反就tp_xfac = -tp_xfac,镜像就调整偏移量。更稳的做法是校准前先判断原始值随点击位置的变化趋势,自动决定符号。

5.4 校准数据每次上电丢失

现象:每次重启都要重新校准。原因是校准结果只存在 RAM 里,没写 Flash。解决:在TP_Adjust结束后调用 Flash 写函数,把系数写到指定页,上电时先读该页,校验魔数正确才使用。注意 Flash 写之前要解锁,写完再上锁。

5.5 刷屏慢到肉眼可见

现象:整屏填充要好几秒。原因可能是逐点写没有用连续写模式,或者每次画点都重设光标。解决:填充时设一次光标区域,然后连续往LCD_RAM写,利用地址自增;或者开 DMA 搬运。F103 的 FSMC 配合连续写,整屏刷新能压到几十毫秒级。

6. 二次开发:把这份工程改成你自己的界面框架

拿到能跑的工程只是起点,真正要用起来得做几件事。第一,把 LCD 驱动抽象成统一接口,比如lcd_draw_point、lcd_fill_rect、lcd_draw_text,上层业务只调接口,换屏时只改底层。第二,触摸校准结果做成配置项,支持多点校准提升精度,我一般会采 5 个点做最小二乘,比四点更稳。第三,加一个简单的图层或脏矩形机制,只刷变化区域,别整屏重绘,这在低主频 MCU 上差别很大。

改造时有个验证习惯值得坚持:每改一层驱动,先跑一个固定测试图案——四角标记加中心十字加渐变条,肉眼能快速判断坐标、颜色、刷新是否正常。下面这个自检函数我几乎每个屏项目都会留一份:

/* 屏幕自检:四角 + 中心 + 边框,快速定位坐标和颜色问题 */ void LCD_SelfTest(void) { u16 w = LCD_GetWidth(); u16 h = LCD_GetHeight(); LCD_Clear(WHITE); LCD_DrawRect(0, 0, w - 1, h - 1, BLACK); /* 边框 */ LCD_DrawPoint(2, 2, RED); /* 左上 */ LCD_DrawPoint(w - 3, 2, GREEN); /* 右上 */ LCD_DrawPoint(2, h - 3, BLUE); /* 左下 */ LCD_DrawPoint(w - 3, h - 3, YELLOW); /* 右下 */ LCD_DrawLine(w / 2 - 20, h / 2, w / 2 + 20, h / 2, BLACK); LCD_DrawLine(w / 2, h / 2 - 20, w / 2, h / 2 + 20, BLACK); }

LCD_GetWidth和LCD_GetHeight从屏参数宏里取,改屏只改宏。四角颜色固定,一眼能看出有没有镜像或偏移;中心十字验证画线函数。这个函数跑通,说明显示链路基本没问题,再去调触摸。

触摸这块的改造重点是校准流程的健壮性。我踩过的坑是校准过程中手指抖动导致采点偏差,后来在采点前加了多次采样取中值,并且要求连续几次读数稳定才记录。校准系数算完先做一次回代验证,误差超过阈值就提示重校,别直接存 Flash。这套流程写进TP_Adjust里,后面换任何电阻屏都能复用。

最后说个习惯:每次改完 FSMC 时序或者换屏,我一定先跑自检函数再跑业务代码,绝不直接上业务界面调试。因为显示层的问题会伪装成业务 bug,浪费的时间远多于多跑一次自检。希望帮到你。

本文还有配套的精品资源,点击获取