STM32驱动TFTLCD实战:从白屏到刷屏的完整程序解析
发布时间:2026/10/14 0:59:50 锦皓数字建站

简介这份资源是面向STM32初学者与嵌入式显示开发者的TFTLCD显示程序基于ALIENTEK MINISTM32实验10整理经过实际测试可正常运行能帮助读者理解STM32驱动TFT液晶屏的完整流程。压缩包共40个文件以18个h头文件与11个c源文件为核心涵盖GPIO、SPI、驱动芯片初始化、缓冲区管理与图像绘制等模块另含uvproj、uvopt等Keil工程文件、hex烧录文件、s启动文件及bat、ini等辅助配置整体约87KB结构紧凑便于直接编译与移植。资源围绕ILI9341等常见驱动芯片展示了从引脚配置、驱动初始化到数据刷新显示的完整链路并附带README说明与实验代码适合对照学习外设接口使用与图形显示编程技巧。目前已有5183人学习下载可作为嵌入式显示入门的实践参考。1. 从一块白屏说起STM32 驱动 TFTLCD 到底难在哪很多人第一次把 TFTLCD 接到 STM32 上上电后屏幕要么全白、要么花屏、要么干脆没反应然后就开始怀疑人生。其实 TFTLCD 显示程序本身不复杂难的是时序匹配、初始化顺序和显存管理这三件事。这份资源就是一套亲测能跑的 STM32 TFTLCD 显示程序覆盖了从底层引脚配置到上层字符/图形绘制的完整链路适合正在做嵌入式显示项目、想快速点亮屏幕并跑通刷屏逻辑的开发者。它不依赖特定开发板核心驱动逻辑可以移植到常见的 STM32F1/F4 系列上只要屏幕控制器型号对得上基本能直接拿来改。下面我按实际拆包和复现的顺序把这份程序的结构、用法和容易翻车的地方讲清楚。2. 拆开看结构驱动层、中间层和应用层怎么分2.1 文件组织与各模块职责拿到一份 STM32 显示程序先别急着编译先看目录结构。这份资源的组织方式比较典型大致分三块底层硬件接口、LCD 驱动核心、上层绘图 API。底层通常是lcd_gpio.c或类似文件负责 FSMC 或软件模拟的引脚初始化驱动核心是lcd.c里面放着写命令、写数据、读状态这些基础函数上层是lcd_draw.c或gui.c提供画点、画线、显示字符和图片的接口。我一般会先确认它用的是 FSMC 还是普通 GPIO 模拟。FSMC 方式速度快适合 F4 系列和大屏GPIO 模拟灵活适合引脚受限的 F1 小容量型号。这份程序两种模式都有条件编译开关在lcd.h里通过宏定义切换。如果你用的是某公司常见的开发板屏幕控制器是 ILI9341 或 ST7789那基本不用改底层直接改引脚映射就行。提示先看lcd.h里的控制器型号宏确认和你手头屏幕一致。不一致的话初始化命令序列要换否则一定白屏。2.2 关键数据结构与显存管理TFTLCD 显示程序绕不开显存。这份资源没有外扩 SRAM所以用的是“直接写屏”模式不维护完整帧缓冲。画点函数直接算地址、写数据优点是省 RAM缺点是画复杂图形时闪烁明显。如果你用的是 F4 系列带 LTDC 控制器那另说但这份程序主要面向 FSMC 接口的并口屏。它定义了一个LCD_Init结构体里面存着宽度、高度、像素格式、读写时序参数。像素格式一般是 RGB565每个像素占两个字节。写点的时候先设列地址和页地址再连续写颜色值。这里有个细节很多屏幕要求列地址和页地址设置完后才能写 GRAM顺序反了就会花屏。程序里用LCD_SetCursor封装了这个过程调用前必须先LCD_WriteReg设置 0x2A 和 0x2B 寄存器。// lcd.c 中设置光标位置的核心逻辑 void LCD_SetCursor(uint16_t x, uint16_t y) { LCD_WriteReg(0x2A); // 设置列地址寄存器 LCD_WriteData(x 8); // 起始列高字节 LCD_WriteData(x 0xFF); // 起始列低字节 LCD_WriteData(x 8); // 结束列高字节单点写时与起始相同 LCD_WriteData(x 0xFF); // 结束列低字节 LCD_WriteReg(0x2B); // 设置页地址寄存器 LCD_WriteData(y 8); LCD_WriteData(y 0xFF); LCD_WriteData(y 8); LCD_WriteData(y 0xFF); LCD_WriteReg(0x2C); // 准备写 GRAM }这段代码里x 8和x 0xFF是高低字节拆分因为并口通信一次传 8 位或 16 位具体看你的 FSMC 数据宽度配置。如果数据宽度是 16 位那写寄存器时地址要左移一位这点在lcd.h的宏里已经处理了。参数说明x和y是屏幕坐标范围由LCD_Init里的宽高决定超出范围会写到无效区域屏幕可能没反应但不会报错。2.3 初始化序列与延时要求初始化序列是 TFTLCD 最容易翻车的地方。不同控制器的上电时序、复位脉冲宽度、寄存器配置顺序都不一样。这份程序里LCD_Init函数开头先做硬件复位拉低复位引脚至少 10ms再拉高然后延时 120ms 等内部稳压器稳定。这个延时不能省省了就会出现“第一次上电正常、复位后白屏”的玄学问题。接着是写初始化命令表。表里一般包含电源控制、伽马校正、内存访问控制、像素格式设置这些。内存访问控制寄存器0x36决定扫描方向如果你发现显示内容上下颠倒或左右镜像改这个寄存器的值就行。常见值是 0x48、0x88、0xE8分别对应不同的旋转方向。程序里默认是竖屏 0x48横屏需要改成 0x28 或 0xE8具体看屏幕排线方向。// 初始化命令表片段以 ILI9341 为例 static const uint16_t ILI9341_InitTable[] { 0xCF, 3, 0x00, 0xC1, 0x30, // 电源控制 B 0xED, 4, 0x64, 0x03, 0x12, 0x81, // 电源序列 0xE8, 3, 0x85, 0x00, 0x78, // 驱动时序 0x36, 1, 0x48, // 内存访问控制竖屏 0x3A, 1, 0x55, // 像素格式 RGB565 0x11, 0, // 退出睡眠 0x29, 0, // 开启显示 0xFFFF // 结束标记 };每个命令第一个字节是寄存器地址第二个字节是参数个数后面跟参数。0xFFFF是结束标记。写的时候先写命令再按个数写参数最后延时。注意0x11退出睡眠后必须延时至少 120ms 才能发0x29否则显示会异常。这个延时在程序里用LCD_DelayMs实现底层是 SysTick 或普通定时器别用空循环优化等级一高就被编译器删了。3. 跑通第一个刷屏程序从引脚配置到颜色填充3.1 FSMC 引脚映射与时钟使能如果你用的是 FSMC 接口第一步是使能时钟和配置引脚。以 STM32F103 为例FSMC 的片选、读写、地址线和数据线分布在 GPIOB、GPIOD、GPIOE 上。程序里LCD_GPIO_Init函数会把这些引脚配成复用推挽输出速度设为 50MHz。注意数据线要配成复用推挽地址线和控制线也是但背光引脚和复位引脚是普通推挽。// FSMC 引脚配置片段 void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); // 数据线 D0-D15 配置为复用推挽 GPIO_InitStructure.GPIO_Pin GPIO_Pin_All; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOE, GPIO_InitStructure); // 控制线 NOE/NWE/NE 配置 GPIO_InitStructure.GPIO_Pin GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9; GPIO_Init(GPIOD, GPIO_InitStructure); }这里GPIO_Pin_All把整个 GPIOE 都配了实际只用了 D0-D15 中的一部分但多配不影响。关键是 FSMC 的时钟要开RCC_AHBPeriph_FSMCF4 系列是RCC_AHB3Periph_FMC别搞混。配置完引脚后要初始化 FSMC 的 NOR/SRAM 控制器设置地址建立时间、数据保持时间这些参数。这些参数和屏幕手册里的时序要求对应设大了刷屏慢设小了数据出错。3.2 写命令与写数据的底层实现写命令和写数据是 TFTLCD 驱动里调用最频繁的两个操作。FSMC 方式下它们就是往特定地址写值。命令地址和数据地址由片选和地址线决定程序里用宏定义#define LCD_BASE ((uint32_t)0x6C000000) #define LCD_REG_ADDR (LCD_BASE) #define LCD_DATA_ADDR (LCD_BASE 0x20000) #define LCD_WriteReg(reg) (*(__IO uint16_t *)LCD_REG_ADDR (reg)) #define LCD_WriteData(data) (*(__IO uint16_t *)LCD_DATA_ADDR (data))0x6C000000是 FSMC 的 Bank1 NOR/SRAM 基地址0x20000是地址偏移对应 A17 地址线。如果你的硬件把 RS 引脚接在 A16 上那偏移就是0x10000。这个偏移量必须和硬件一致否则命令和数据会写反屏幕要么不亮要么乱码。我见过有人把 RS 接在 A18 上结果偏移设错调了一整天。注意FSMC 写数据时如果数据宽度是 16 位地址要按 16 位对齐也就是地址每次加 2。宏里用uint16_t *指针自动处理了但如果你手动算地址别忘了乘 2。3.3 颜色填充与刷屏速度优化跑通初始化后第一个测试通常是全屏填充某个颜色。程序里LCD_Clear函数接收一个 RGB565 颜色值然后循环写 GRAM。最朴素的写法是双重循环逐点写但这样刷一屏 320x240 要几十毫秒肉眼可见地慢。优化方法是利用 FSMC 的连续写特性设置好光标后连续写同一个颜色值中间不切换寄存器。void LCD_Clear(uint16_t color) { uint32_t total LCD_WIDTH * LCD_HEIGHT; LCD_SetCursor(0, 0); LCD_WriteReg(0x2C); // 写 GRAM 命令 while (total--) { LCD_WriteData(color); } }这段代码比逐点设置光标快很多因为省去了每次写点前的寄存器操作。实测在 72MHz 的 F103 上刷一屏 240x320 大约 15ms勉强够用。如果你觉得还不够快可以开 FSMC 的写 FIFO 或者用 DMA但这份程序没带 DMA 版本需要自己加。加 DMA 的时候注意源地址是颜色变量目标地址是LCD_DATA_ADDR传输宽度设成半字传输完成后再发下一个命令。4. 显示字符和图片取模、字库与数据流4.1 字符取模方式与字库结构显示字符需要字库。这份程序用的是常见的纵向取模、字节倒序格式字库文件是font.h里面存了 ASCII 字符的点阵数据。每个字符占 16 字节对应 8x16 像素。取模软件设置阴码、逐列式、顺向、C51 格式。如果你用其他取模方式显示出来会是乱码或者镜像。// 显示一个 ASCII 字符 void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t color, uint16_t bg) { uint8_t i, j; uint8_t temp; ch ch - ; // 偏移到字库起始 for (i 0; i 16; i) { temp asc2_1608[ch][i]; // 取一列数据 for (j 0; j 8; j) { if (temp 0x01) LCD_DrawPoint(x j, y i, color); else LCD_DrawPoint(x j, y i, bg); temp 1; } } }asc2_1608是字库数组ch - 是因为字库从空格开始存。内层循环逐位判断画前景色或背景色。如果不需要背景色可以把bg参数去掉只画前景点速度更快但会覆盖不掉旧内容。参数说明x、y是字符左上角坐标color是 RGB565 前景色bg是背景色。显示字符串就是循环调用这个函数每次 x 加 8。4.2 图片显示与数据流控制显示图片比显示字符简单因为不需要逐位判断。图片先用取模软件转成 RGB565 数组然后调用LCD_ShowPicture函数。这个函数设置好显示区域后把数组数据连续写入 GRAM。注意数组是const类型存在 Flash 里读的时候直接读不需要额外缓冲。void LCD_ShowPicture(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *pic) { uint32_t total width * height; LCD_SetCursor(x, y); LCD_WriteReg(0x2C); while (total--) { LCD_WriteData(*(uint16_t *)pic); pic 2; } }这里pic是uint8_t *每次取两个字节拼成一个 RGB565 像素。如果图片数据是大端存储需要交换高低字节。常见取模软件输出的是小端直接读就行。显示大图时注意 Flash 容量一张 240x320 的图片占 153600 字节F103C8T6 只有 64KB Flash放不下得外挂 SPI Flash 或者用 SD 卡。4.3 中文字库与 GB2312 编码显示中文需要中文字库这份程序没有内置完整中文字库但预留了接口。常见做法是用 GB2312 字库芯片或者自己生成小字库。如果你只需要显示几个固定汉字可以用取模软件生成 16x16 点阵数组然后仿照 ASCII 字符的显示逻辑写一个LCD_ShowChinese函数。GB2312 编码每个汉字两个字节区码和位码分别减 0xA0 得到索引。// 显示一个 16x16 汉字 void LCD_ShowChinese(uint16_t x, uint16_t y, uint8_t *ch, uint16_t color, uint16_t bg) { uint8_t i, j, temp; uint8_t index 0; for (i 0; i 32; i) { // 16x16 点阵占 32 字节 temp ch[i]; for (j 0; j 8; j) { if (temp 0x80) LCD_DrawPoint(x (i % 2) * 8 j, y i / 2, color); else LCD_DrawPoint(x (i % 2) * 8 j, y i / 2, bg); temp 1; } } }注意这里取模方式是逐行式、高位在前和 ASCII 的逐列式不同。混用两种取模方式会导致汉字显示错位。我一般会在字库文件头部注释里写清楚取模设置免得后面自己都忘了。5. 避坑与排查白屏、花屏、闪烁的常见原因5.1 白屏但背光亮现象上电后背光亮屏幕全白没有任何显示内容。原因通常是初始化序列没跑完或者复位失败。先检查复位引脚有没有在初始化开头拉低再拉高延时够不够。然后看 FSMC 时钟使能没有地址偏移对不对。解决用示波器看复位引脚波形确认低电平至少 10ms用调试器单步走LCD_Init看写到哪个命令卡住。如果卡在0x11之后的延时说明延时函数没生效检查 SysTick 配置。5.2 花屏且颜色错乱现象屏幕有显示但内容错位颜色也不对比如红色显示成蓝色。原因一般是像素格式设错或者高低字节反了。RGB565 是 5 位红、6 位绿、5 位蓝如果设成 RGB888 就会颜色错乱。解决检查0x3A寄存器的值是不是0x55然后检查写数据时高低字节有没有交换。FSMC 数据宽度 16 位时一般不用交换但有些屏幕要求先高后低得看手册。5.3 刷屏闪烁严重现象清屏或刷新图片时屏幕明显闪烁肉眼能看到一行一行地刷。原因是逐点写速度太慢或者没有关闭显示就写 GRAM。解决用连续写模式设置好光标后一次性写完整个区域如果还闪可以在写之前发0x28关闭显示写完再发0x29打开。但这样会有短暂黑屏适合对闪烁零容忍的场景。5.4 移植后编译报错缺定义现象换到另一个 STM32 型号后编译报错提示FSMC_NORSRAMInitTypeDef未定义。原因是 F4 系列用的是 FMC 不是 FSMC头文件和库函数都不一样。解决在lcd.h里加条件编译根据STM32F10X_HD或STM32F40_41xxx宏切换包含的头文件。F4 的 FMC 配置结构体叫FMC_NORSRAMInitTypeDef时钟使能是RCC_AHB3PeriphFMC。5.5 屏幕偶尔不亮需多次上电现象第一次上电正常断电再上电就不亮反复几次才亮。原因是电源不稳或者复位时序余量不够。解决在复位引脚加一个 100nF 电容到地延长复位脉冲或者在初始化开头加 200ms 延时等电源完全稳定。这个坑很玄学我遇到过好几次最后都是加电容解决的。6. 进阶技巧用 DMA 和局部刷新把速度提上去如果你已经跑通了基本显示下一步大概率是嫌刷屏慢。这份程序默认是 CPU 轮询写 GRAM刷一屏十几毫秒做简单界面够用但刷视频或者做动画就吃力了。我一般会加 DMA 通道把颜色数据从内存搬到 FSMC 数据地址CPU 腾出来干别的。以 STM32F103 为例DMA1 通道 2 可以映射到 FSMC 写操作。配置步骤先初始化 DMA外设地址设LCD_DATA_ADDR内存地址设颜色缓冲区传输方向是内存到外设数据宽度半字传输完成中断里发下一个命令。注意 DMA 传输期间不能切换 FSMC 片选否则数据会写到错误地址。// DMA 填充屏幕的简化配置 void LCD_DMA_Fill(uint16_t color) { uint32_t i; for (i 0; i LCD_WIDTH * LCD_HEIGHT; i) { dma_buffer[i] color; // 填充缓冲区 } LCD_SetCursor(0, 0); LCD_WriteReg(0x2C); DMA_Cmd(DMA1_Channel2, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel2, LCD_WIDTH * LCD_HEIGHT); DMA_Cmd(DMA1_Channel2, ENABLE); while (DMA_GetFlagStatus(DMA1_FLAG_TC2) RESET); DMA_ClearFlag(DMA1_FLAG_TC2); }dma_buffer是全局数组大小等于屏幕像素数F103 的 RAM 只有 20KB放不下 240x320 的缓冲区所以这个方案只适合小屏或者带外扩 RAM 的型号。F4 系列 RAM 大可以直接开全屏缓冲。局部刷新是另一个思路只刷新变化区域不动整屏。比如显示数字时钟只刷新秒数那几位其他区域不重写。实现方法是记录脏矩形区域每次刷新只写这个区域内的像素。验证 DMA 是否生效可以对比刷屏时间CPU 轮询刷一屏大约 15msDMA 能降到 5ms 以内。如果时间没变检查 DMA 通道有没有被其他外设占用或者 FSMC 的等待周期设得太长。我习惯在刷屏前后翻转一个 GPIO用示波器量脉宽比看代码直观。从那以后我每次拿到新的 TFTLCD 程序都强制先跑一遍全屏填充和字符显示确认底层没问题再往上叠功能。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。