简介:STM32F103VET6驱动2.8英寸TFT彩屏的完整工程包,面向嵌入式开发入门者与进阶者,解决FSMC接口驱动ILI9341控制器时遇到的时序匹配、寄存器配置和代码移植问题。工程基于FSMC的Bank1区域连接LCD,从GPIO复用设置、FSMC时序参数调整,到ILI9341的初始化命令序列、像素格式选择、显示窗口与色彩空间设置均有清晰示例。资源共524个文件,压缩后约29.62MB,以C源码、Keil工程文件、编译产物为核心,附带PDF参考手册、文本说明、HTML文档以及清理脚本等,并同时提供标准库和HAL库两套驱动,方便对照学习。目前已有1581人学习下载。源码结构完整,可直接编译烧录验证;配合手册可系统理解并行接口读写时序和控制器内部寄存器作用,标准库版本适合深入寄存器细节,HAL库版本适合快速集成,为后续驱动其他尺寸TFT屏幕或移植到不同STM32型号打下基础。
1. 一块 2.8 寸 ILI9341 并口屏:为什么建议先把 FSMC 时序定下来
把一块 2.8 寸 TFT 接到 STM32F10x 上,最难受的从来不是初始化序列,而是并口和时序:CS、WR、RD、RS、D0-D15 只要有根线接错,或者 FSMC 参数差一个 HCLK,结果就是白屏或花屏。tft2.8 这套参考工程解决的就是这件事——它按 STM32F10x 手册的 FSMC 要求,把 ILI9341 驱动拆成了标准库和 HAL 库两个版本,并口接法、时序寄存器、初始化序列、画点填充全套给出。适合正在画板想把屏接上、又被 datasheet 里 0x36/0x3A/0x2C 这些寄存器劝退的人:新手照搬能点亮,老手对照时序能调参,省去翻一堆手册的功夫。
2. 先看懂资源包结构:ILI9341 指令、FSMC Bank 映射和两套库工程
2.1 ILI9341 手册里真正要背的寄存器分组
ILI9341 的寄存器数量很多,但驱动 2.8 寸屏真正反复用到的只有三组:基础控制类(软复位 0x01、睡眠开关 0x10/0x11、显示开关 0x28/0x29)、访问控制类(MADCTL 0x36、像素格式 0x3A、窗口地址 0x2A/0x2B、写 GRAM 0x2C、读 GRAM 0x2E),以及 Gamma 调校类(0xE0/0xE1)。第三类在初始化时照抄模组厂给的序列即可,不建议自己改;前两类才是写驱动函数时真正操作的寄存器。
初次上手我的习惯是把控制器手册里的指令表打出来,用荧光笔把 0x36、0x3A、0x2A、0x2B、0x2C 五条划出来。0x36 控制扫描方向和 RGB/BGR 颜色顺序,0x3A 控制像素格式,2.8 寸屏绝大多数固定用 0x55,也就是 16 位 565 格式。0x2A/0x2B 分别是列地址和页地址设置,0x2C 是写入像素数据。屏幕上任意矩形区域要刷新,都逃不开三步:先设列范围,再设行范围,最后向 0x2C 连续写数据。
有了这个基础再看 tft2.8 里的初始化序列,会发现它并不是一长串意义不明的魔法数字。序列前半段的 0xCF、0xED、0xE8 等命令是电源和内部时序调校,后半段的 0x36、0x3A、0xB7、0xE0/0xE1 才决定显示效果。我在移植时保留模组厂提供的序列不动,只手工修改 0x36 一个字节,用来适配横屏、竖屏和镜像方向。这个字节改错,最典型的故障是字体左右镜像或者颜色红蓝互换。
这里补一张我自己整理的寄存器速查表,配合资源包里的初始化代码看会更清楚:
| 寄存器 | 功能 | 典型值 | 用途 |
|---|---|---|---|
| 0x36 MADCTL | 扫描方向与 BGR | 0x08 或 0x00 | 切横竖屏、修正红蓝反 |
| 0x3A PIXFMT | 像素格式 | 0x55 | 固定 16 位 RGB565 |
| 0x2A CASET | 列地址窗口 | 0~239 | 矩形区域左侧和右侧 |
| 0x2B PASET | 行地址窗口 | 0~319 | 矩形区域上侧和下侧 |
| 0x2C RAMWR | 写 GRAM | 连续数据 | 向窗口写入像素 |
| 0x2E RAMRD | 读 GRAM | 连续数据 | 回读屏幕内容做校验 |
2.2 FSMC Bank1 映射与 RS 地址线的换算方法
STM32F10x 手册里把 FSMC 的 Bank1 分成四个片选区,NE1 到 NE4 分别对应 0x60000000、0x64000000、0x68000000、0x6C000000 起始地址。TFT 用 8080 并口时,把屏当作外部存储器,向对应地址写命令或写数据,FSMC 会自动产生 NWE 写信号。这里有个容易忽略的细节:Bank1 的地址线 A[25:0] 在 16 位模式下映射的是外部半字地址,RS/DC 并不一定接 A0,接任何一根地址线都行。
常见做法是把 RS 接到 FSMC_A16。访问 0x60000000 时 A16 为 0,屏认为是命令;访问 0x60020000 时 A16 为 1,屏认为是数据。所以代码里定义基地址后,再定义命令和数据两个宏:
#define LCD_BASE ((uint32_t)0x60000000) #define LCD_CMD (*((volatile uint16_t *) (LCD_BASE))) #define LCD_DATA (*((volatile uint16_t *) (LCD_BASE + 0x20000)))0x20000 这个偏移量的来源是:16 位模式下内部 HADDR 的 bit17 对应外部 FSMC_A16,所以想置高位,偏移就是 1 左移 17 位。用 volatile 修饰是为了防止编译器把连续读写优化合并,在 DMA 刷屏场景里尤其重要。如果你原理图把 RS 接在 A18,偏移就要换成 0x80000(1 左移 19 位)。直接抄网上工程而不核对原理图,是花屏的高发原因。
还有一点值得留意:有些资源包会把 LCD_CMD 写成对地址 0x60000000 的字节访问,LCD_DATA 写成半字访问。在 16 位数据总线配置下,这种写法容易让 FSMC 的字节选通信号产生多余脉冲。稳妥起见,命令和数据都用 16 位访问,返回类型都用 uint16_t,让总线宽度保持一致。这样虽然牺牲一点灵活性,但能减少很多诡异时序问题。
2.3 标准库、HAL 库和寄存器版的选择边界
这类参考包通常给两套工程,标准库和 HAL 库,偶尔带寄存器精简版。三者的差异集中在时钟使能和句柄写法上。标准库用 RCC_APB2PeriphClockCmd、FSMC_NORSRAMInit 这类函数做配置,结构体字段直白,适合想精确掌握每个时序参数的人;HAL 库把大部分初始化收敛到 MX_FSMC_Init 和句柄结构里,代码量少,但出问题时追溯层级更深。
我的观点是:从零开始画板、操作系统还没定,优先用标准库版本,因为 FSMC 时序参数能一行行对照手册校验。在某公司现有 HAL 工程里加一块屏,优先用 HAL 库版本,让一个初始化函数把时钟、GPIO、FSMC 全部搞定。寄存器版主要用来排错——时序怎么调都不对时,用寄存器直接读写 NE1 区域地址,能最快排除库封装带来的干扰。
需要注意边界:标准库和 HAL 库对 FSMC 的 bank 编号、片选枚举命名不一致,同一个 NE1 在标准库是 FSMC_Bank1_NORSRAM1,在 HAL 里是 FSMC_NORSRAM_INSTANCE 加设备号。跨库移植时不要只复制初始化函数,对应的时钟枚举、引脚复用宏也要一起换,否则编译能过,上电白屏。
3. 接线和时序配置:从原理图到 FSMC 寄存器参数的落地过程
3.1 引脚分配:从屏的 FPC 到 FSMC 数据线的完整对应
拿到 tft2.8 包里的原理图,先核对屏的 FPC 引脚定义。2.8 寸 8080 并口屏常见信号包含 DB0-DB15、CS、WR、RD、RS、RESET、TE,部分模组把背光 LED 正负极也引出来。FSMC 侧的对应关系一般是:DB0-DB15 接 FSMC_D0-D15,CS 接 FSMC_NE1,WR 接 FSMC_NWE,RD 接 FSMC_NOE,RS 接 FSMC_A16,RESET 接任意 GPIO,TE 接任意 GPIO。
GPIO 配置上,FSMC 数据线和控制线必须复用推挽输出,引脚模式选 GPIO_Mode_AF_PP。这里有一个常见翻车点:SCK、MOSI、MISO 这类 SPI 引脚名字容易和 FSMC 的 D0-D15 混淆,画板时如果把 D0 接到 SPI1_SCK,FSMC 初始化时又不检查复用映射,运行起来画面就是乱码。我一般会拿示波器在 NWE 和 D0 上对比波形,如果 NWE 有下降沿而 D0 没有随地址变化翻转,优先查引脚复用。
引脚分配还需要考虑 FSMC 与调试口的冲突。比如 PD2 在多数封装上是 FSMC_A25,同时也是 USART2_RX 的可选复用位;PA15 是 JTAG 引脚,如果被复用成 FSMC_A21,JTAG 调试就用不了。正规做法是把 JTAG 释放成 SWD 模式,只保留 PA13/PA14 两根线,再启用 PA15。某次我帮 A 同学调屏,他把 PA15 直接当成普通 GPIO 接 RS,结果初始化 FSMC 后整个调试器都连不上,就是因为没处理 JTAG 释放。
3.2 FSMC 时序寄存器计算:把 ILI9341 手册时序换算成可配置项
FSMC 在 NOR/SRAM 模式下与 8080 接口相关的寄存器主要是 FSMC_BTRx(总线时序寄存器)和 FSMC_BWTRx(写时序寄存器),关键字段有三个:ADDSET 是地址建立时间,DATAST 是数据建立时间,BUSTURN 是总线周转时间。每个字段的单位是 HCLK 周期。STM32F10x 最常用的外部时钟是 72MHz,一个 HCLK 约 13.8ns。
ILI9341 手册给出的并口写周期典型值在 60ns 到 100ns 之间,具体看模组厂标注。我一般会把总周期做到 100ns 以上,留出余量。配置方法是先确定 ADDSET 和 DATAST 的分配:地址建立阶段分配 1 个 HCLK,数据建立阶段分配 6 到 7 个 HCLK,总周期大约在 110ns 到 122ns。这样既不拖慢刷新速度,又能覆盖绝大多数模组的时序要求。
标准库下的配置代码片段:
FSMC_NORSRAMInitTypeDef fsmc; FSMC_NORSRAMTimingInitTypeDef timing; timing.FSMC_AddressSetupTime = 1; timing.FSMC_AddressHoldTime = 0; timing.FSMC_DataSetupTime = 7; timing.FSMC_BusTurnAroundDuration = 0; timing.FSMC_CLKDivision = 0; timing.FSMC_DataLatency = 0; timing.FSMC_AccessMode = FSMC_AccessMode_B; fsmc.FSMC_Bank = FSMC_Bank1_NORSRAM1; fsmc.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; fsmc.FSMC_MemoryType = FSMC_MemoryType_SRAM; fsmc.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; fsmc.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; fsmc.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable; fsmc.FSMC_WriteTimingStruct = &timing; fsmc.FSMC_ReadWriteTimingStruct = &timing; FSMC_NORSRAMInit(&fsmc); FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);AddressHoldTime 写 0 是因为 8080 接口在地址建立后不需要保持,数据直接跟上。DataSetupTime 写 7 对应的数据建立时间约 96ns,再加上地址建立阶段,整周期超过 110ns。AccessMode 用 B 模式,这是 SRAM/并口屏最常用的模式,读写时序对称。如果刷新出花屏,可以把 DataSetupTime 加到 9,这个代价是刷屏速度小幅下降。
3.3 背光、复位和供电顺序:白屏问题的硬件根源
屏的背光通常是几个串联 LED,需要恒流驱动或者限流电阻,不能直接接到 3.3V 电源轨上,电流会过大。最稳妥的做法是在背光正极串一个 10 到 20 欧电阻,或者用一个 NMOS 管做 PWM 调光。复位引脚则需要注意有效电平和脉冲宽度:多数 ILI9341 模组的 RESET 低电平有效,拉低时间要大于 10us,拉高后等待 5ms 以上再发命令。
电源顺序比很多人以为的更严格。理想流程是:先给 VDD 上电,再拉高 RESET,延时一段时间后开背光,最后初始化控制器。如果先把背光点亮再上电复位,屏内部逻辑可能处于未知状态,初始化序列执行完仍然是白屏。我习惯把背光控制放在初始化函数的最后一步,复位放在第一步,这样两次上电之间的状态完全可控。
还有一个容易忽视的点是模组的 I/O 电平。部分 2.8 寸屏模组板载电平转换,可以直接接 3.3V;部分模组没有板载转换,主控 3.3V 输出到屏的 2.8V I/O 一般没问题,但反向读回时要注意屏输出高电平是否超过主控容忍范围。tft2.8 的工程里默认按 3.3V 电平设计,如果你用 5V 单片机,必须加电平转换板,否则长期运行可能损坏屏的驱动 IC。
4. ILI9341 驱动代码拆解:从初始化序列到矩形填充的完整链路
4.1 初始化序列解读:电源命令、像素格式和地址控制
tft2.8 包里的初始化序列通常是几十行连续的命令,看起来像是黑匣子,其实可以按作用分块。第一块是电源和内部稳压,比如 0xC0、0xC1、0xC5、0xC7 这类命令,作用是把内部电压基准调到适合面板背光的范围,模组厂已经调好,不要动。第二块是显示时序,比如 0xB0、0xB1、0xB5,和面板的行扫、列扫周期相关。第三块才是需要关心的:0x36、0x3A、0x2A、0x2B、0x2C。
初始化顺序方面,软复位 0x01 之后必须延时 5ms 以上,退出睡眠 0x11 之后延时 120ms 以上,这两个延时不够会导致后续寄存器写入失败。很多白屏问题不是命令写错,而是延时不够。包里的延时函数通常是基于 SysTick 实现,移植时要注意 HCLK 频率变化后延时倍率是否还准确。
标准库版本下的初始化核心部分:
void ILI9341_Init(void) { LCD_RESET_LOW(); delay_ms(20); LCD_RESET_HIGH(); delay_ms(20); // 硬件复位,拉低时间大于10us即可 LCD_WriteCmd(0x01); // 软件复位 delay_ms(5); LCD_WriteCmd(0x11); // 退出睡眠模式 delay_ms(120); // 必须等到内部振荡器稳定 LCD_WriteCmd(0x3A); LCD_WriteData(0x55); // 16位像素格式 RGB565 LCD_WriteCmd(0x36); LCD_WriteData(0x08); // 竖屏方向,BGR=1,红蓝正常 LCD_WriteCmd(0x29); // 打开显示 delay_ms(20); }这段代码第一组是硬件复位和软件复位配合,确保控制器从初始状态开始执行序列。0x3A 设置 565 格式,后续画点每次写 2 字节。0x36 的 0x08 是 BGR 位为 1 的组合值;如果你显示纯红色却变成蓝色,就把这一字节改为 0x00。0x29 打开显示后,屏如果没有背光控制,会直接亮起来,所以背光建议放最后单独开。
4.2 窗口设置与数据写入:画点、矩形填充和刷图函数
ILI9341 的 GRAM 操作有点类似先圈地再播种:0x2A 写列范围,0x2B 写行范围,0x2C 之后连续写入的点会按行列顺序排列。窗口机制最大的好处是局部刷新时不需要重发全屏数据,比如画一个矩形进度条,只需要把窗口限定在进度条区域。
窗口设置函数:
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCmd(0x2A); LCD_WriteData(x0 >> 8); LCD_WriteData(x0 & 0xFF); LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y0 >> 8); LCD_WriteData(y0 & 0xFF); LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF); LCD_WriteCmd(0x2C); }坐标参数是左闭右闭区间,所以设置 x0=0、x1=239 时,列范围是 240 个点。数据写入采用先高字节后低字节的顺序,这是因为 16 位并口在 565 格式下把高字节放在第一个数据相位。如果高字节和低字节写反,颜色会出现明显的通道互换,后续排查颜色错乱时要第一个检查这里。
区域填充函数就是写一个循环:
void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t count, i; LCD_SetWindow(x0, y0, x1, y1); count = (x1 - x0 + 1) * (y1 - y0 + 1); for (i = 0; i < count; i++) { LCD_WriteData(color); } }count 计算用的是宽乘高,最大区域是 240*320=76800 个像素,每个像素 2 字节。如果直接在循环里调用 LCD_WriteData 函数,函数调用开销和 FSMC 写入速度会叠加。想要提速,可以把 LCD_WriteData 定义成宏,或者用 DMA 连续写入。后者需要把颜色值填到一个连续数组里,然后在数据地址上触发 DMA 传输。
4.3 标准库迁移到 HAL 库的关键点:句柄、延时和寄存器访问
把标准库代码迁到 HAL 库时,最常出问题的地方是延时函数。标准库工程里 delay_ms 通常自己写,HAL 库则依赖 HAL_Delay,它基于 SysTick 中断。如果你在初始化 FSMC 之前调用了 HAL_Delay,而 SysTick 还没有配置,HAL_Delay 会卡死。HAL 库初始化函数本身会配置 SysTick,但顺序一般是在 MX_GPIO_Init 之后、FSMC 初始化之前,所以不要把自写的延时函数和 HAL_Delay 混用。
寄存器访问方面,HAL 库对外设寄存器的封装更薄,底层还是直接操作寄存器地址。读取屏 ID 时,可以绕过库函数直接访问 NE1 地址,比如:
uint32_t lcd_base = 0x60000000; uint16_t id = *(volatile uint16_t *)(lcd_base + 0xD000);这个读操作的风险在于,向屏寄存器发送读命令后,第一个读周期返回的可能是空数据或旧数据,需要连续读两次,第二次才是有效值。ILI9341 的读 GRAM 也有类似行为,读取窗口之外的第一个字节通常是无效的。调试时如果读到固定值或全 FFFF,优先怀疑读命令后的时序偏移,而不是地址线接错。
5. 避坑与排查:白屏、花屏、颜色错乱的定位方法
5.1 白屏:复位时序和供电顺序的问题
现象是背光亮,画面全白,初始化序列执行完没有任何变化。
原因通常是两种:一是复位引脚时序不对,RESET 拉低时间小于 10us,或者复位后延时不够,控制器没进入正常状态;二是背光先于复位点亮,屏内部逻辑在不确定状态。还有一种常见情况是 FSMC 初始化根本没执行成功,因为 GPIO 复用配置遗漏了部分引脚。
解决方法是按固定顺序检查:先用示波器确认 RESET 引脚有先低后高的波形,高电平持续时间大于 5ms;再在初始化函数末尾加一个回读寄存器操作,比如读 0xD3 控制器 ID,如果能读到 0x93,说明控制器和 FSMC 通路正常,问题只出在初始化序列或背光顺序;如果读不到 ID,从 FSMC 初始化开始查。
5.2 花屏:地址线接错和时序余量不足
现象是能点亮,但显示的是乱码,或者字库里每个字符都缺笔画。
花屏最常见的两个来源是 RS 接的地址线和代码里的偏移不匹配。比如原理图把 RS 接在 A17,代码里却按 A16 的 0x20000 偏移写,屏幕把数据当命令、把命令当数据。这时画面往往不是均匀的白屏,而是随机乱码。
第二个来源是 FSMC 时序余量不足。表现为低速时钟下正常,把主频从 36MHz 提到 72MHz 后画面就乱。解决方式是先把 DataSetupTime 调大到 10,再逐次减小直到出现花屏,然后加回 1 到 2 个 HCLK 作为余量。这个方法能一次性排除大部分时序问题。
5.3 颜色错乱或偏色:RGB565 字节序和 BGR 位
现象是图案形状正常,但纯红显示成蓝色,或者渐变图像有明显色块。
原因一般是 0x36 的 BGR 位与模组实际接线方向不一致。ILI9341 的 0x36 字节里 bit3 是 BGR 控制位,为 1 时按 BGR 顺序输出,为 0 时按 RGB 顺序。很多模组默认是 BGR 接线,初始化代码里写的却是 RGB,结果纯色条全部红蓝互换。把 0x36 从 0x08 改成 0x00,或者从 0x00 改成 0x08,通常能解决。
另一种情况是 16 位数据总线上高字节和低字节交叉接反,D0-D7 和 D8-D15 对调。这种问题在纯色测试下看不出来,要刷一个渐变图才能发现。排查时用屏幕显示一个从黑到白的横向渐变,如果颜色出现明显的通道断裂,就检查数据线是否按序排列。
5.4 刷新卡顿和撕裂:轮询写入和 TE 信号
现象是刷全屏时 CPU 占用极高,拖慢其他任务;快速刷新动态画面时屏幕出现一条水平撕裂带。
原因是向 0x2C 连续写入像素时,如果每个像素都用 CPU 循环写入,240*320 个点就是 76800 次函数调用,耗时非常可观。撕裂则是屏幕正在扫描刷新时,GRAM 被同时改写导致上半帧和下半帧来自不同的缓冲内容。
解决方式是使用 DMA 传输像素数据,让 CPU 只负责准备缓冲区地址和长度。撕裂问题则要利用 TE 引脚,屏内部会在进入垂直消隐区时拉高 TE 信号,程序检测到 TE 后再开始 DMA 传输。如果模组没有引出 TE,只能用固定帧率限制刷屏节奏,比如每次刷新间隔控制在 16ms 左右,减少与扫描重叠的概率。
5.5 排查工具:示波器看 NWE 和逻辑分析仪抓数据
现象是上面的方法都试过仍然找不到问题,需要定位到具体信号。
示波器主要看三个信号:NWE 写脉冲是否有下降沿和恢复沿,数据线 D0-D15 是否在 NWE 下降沿前建立,RE 读信号是否在读取操作时出现。如果 NWE 频率和预期时序不符,比如 72MHz 下时序寄存器没生效,直接读 FSMC 配置备份寄存器就能确认。
逻辑分析仪更实用的是抓命令数据流。初始化时在 LCD_WriteCmd 和 LCD_WriteData 各放一个 GPIO 翻转,然后同时抓 GPIO、NWE、数据线,对照命令序列逐条确认。某次排查中我发现第 3 条命令之后的 120ms 延时丢失,导致后续所有寄存器写入无效,就是靠逻辑分析仪看到命令间隔过短定位出来的。
6. 刷新生效再快一步:DMA 传输、双缓冲与验证习惯
屏点亮只是开始,真正决定体验的是刷屏速度。全屏 240*320 个像素,一个像素 2 字节,共 153600 字节。CPU 轮询写入时,加上地址计算和循环开销,每秒只能刷十几帧;换成 DMA 后带宽利用率高得多,帧率能到 30 帧以上。DMA 写入的核心是把外设地址固定为 LCD 数据地址,内存地址按批次递增,数据宽度统一为半字:
DMA_InitTypeDef dma; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel6); dma.DMA_PeripheralBaseAddr = (uint32_t)&LCD_DATA; dma.DMA_MemoryBaseAddr = (uint32_t)frame_buf; dma.DMA_DIR = DMA_DIR_PeripheralDST; dma.DMA_BufferSize = 240 * 320; dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable; dma.DMA_MemoryInc = DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; dma.DMA_Mode = DMA_Mode_Normal; dma.DMA_Priority = DMA_Priority_High; DMA_Init(DMA1_Channel6, &dma); DMA_Cmd(DMA1_Channel6, ENABLE);BufferSize 是像素点个数而不是字节数,因为外设和内存都是半字。DMA_Mode_Normal 表示一次传输完成即停止,适合整帧刷新;如果希望自动循环刷动态画面,可以改成 DMA_Mode_Circular,但注意循环模式下 CPU 无法知道当前刷到哪里,容易产生撕裂。双缓冲的用法是,一个缓冲用于 DMA 传输,另一个缓冲用于 CPU 绘制下一帧,每帧切换一次指针,传输和绘制交错进行,不影响 CPU 计算。
验证驱动是否正常,我有一套固定的三步流程:第一步刷五个纯色色块,确认红绿蓝白黑全部正确;第二步刷横向和纵向渐变图,检查高字节低字节顺序是否有误;第三步快速交替显示两张图,肉眼观察撕裂带,没有明显横纹再上 TE 同步。这套流程每次接入新模组都执行一遍。
从那以后,我每次给新屏写驱动都强制走一遍地址线核对、时序放宽、DMA 刷色条的三件套。先确认 RS 接在哪根地址线上,再检查时序余量,最后才碰初始化序列——顺序反了,白屏和花屏的概率会成倍上升。希望这篇笔记能帮你把 2.8 寸屏的调试时间从几天压缩到几个小时。
本文还有配套的精品资源,点击获取