简介:本资源是正点原子推出的ILI9325/ILI9341 TFT LCD并口驱动工程,面向嵌入式初学者与STM32开发工程师,解决TFT液晶屏在裸机环境下基于并行接口的稳定驱动与显示适配问题。工程基于STM32F10x平台,完整包含LCD底层驱动(lcd.c/h)、系统时钟与GPIO初始化、ILI9325寄存器配置、16位RGB色彩映射及帧缓冲机制实现,同时兼容ILI9341控制器,显著提升代码复用性与硬件适配灵活性。压缩包共151个文件,以38个头文件(.h)和37个源文件(.c)构成核心驱动逻辑,辅以编译中间文件(.o/.d)、Keil工程配置(.uvproj/.uvopt)、调试脚本(.bat)及生成镜像(.hex/.axf),总大小2.05MB,结构规范,便于理解驱动分层设计与工程构建流程。目前已有755人学习下载,读者可直接导入Keil MDK使用,掌握并口时序控制、LCD初始化流程、写命令/数据同步机制及跨型号移植要点。
1. 正點原子TFT_LCD(ILI9325驱动)不是“老古董”,而是嵌入式LCD并口驱动的典型教学锚点
很多刚接触STM32或ARM Cortex-M开发的工程师看到“ILI9325”“20141228”这些字样,第一反应是:这驱动过时了,现在都用SPI的ILI9341、ST7789,谁还搞8位/16位并口?但现实恰恰相反——正點原子这套TFT_LCD并口驱动代码,至今仍是理解LCD底层时序、GPIO模拟总线、FSMC外设映射和寄存器级初始化逻辑最扎实的入门范本。它不依赖HAL库抽象,不封装成黑盒API,所有写指令、送数据、时序延时、读写使能控制全部裸露在C文件里。你在树莓派上调试ILI9341屏幕时遇到的“花屏”“偏色”“初始化失败”,根源往往就藏在ILI9325驱动里那几行看似冗余的LCD_WR_DATA宏定义和LCD_WriteReg函数中。这套代码适合两类人:一是想真正吃透LCD控制器通信本质的嵌入式初学者;二是需要在资源受限MCU(如STM32F103C8T6)上复用并口带宽、避免SPI瓶颈的量产项目工程师。
2. 从ILI9325到ILI9341:为什么并口驱动逻辑可复用,而寄存器配置必须重写
2.1 ILI9325与ILI9341的核心差异不在接口,而在初始化流程与寄存器映射
ILI9325和ILI9341同属ILITEK公司推出的TFT LCD控制器系列,均支持8/9/16位并行接口(Intel 8080模式),物理引脚定义高度兼容:RS(寄存器/数据选择)、WR(写使能)、RD(读使能)、CS(片选)、D0-D15(数据线)。但二者关键区别在于:
- 初始化序列长度:ILI9325需约20条寄存器写入指令完成基本配置;ILI9341则需超过50条,且包含多段延时(如
0xB1伽马校准前需10ms等待) - 关键寄存器地址不同:例如“设置行列地址”的指令,ILI9325用
0x2A(水平地址)+0x2B(垂直地址),ILI9341沿用相同地址,但参数格式不同(ILI9341需高位在前,ILI9325低位在前) - 读状态机制差异:ILI9325可通过
0x0F寄存器直接读取状态字;ILI9341无此寄存器,需通过0x0A(电源模式)+0x0B(帧率)组合判断是否就绪
提示:不要试图直接替换ILI9325的
.c文件中的寄存器值来“适配”ILI9341。必须重写LCD_Init()函数,按ILI9341 datasheet第12章《Initialization Sequence》逐条实现。正點原子原版代码中LCD_WriteReg(0x01, 0x0000)这类调用,在ILI9341中对应的是LCD_WriteReg(0xCF, 0x0000)(电源控制A),地址和值均不可混用。
2.2 并口硬件连接决定驱动层架构:GPIO模拟 vs FSMC外设
正點原子该版本驱动默认采用GPIO模拟并口(Software GPIO Bit-Banging),即用普通IO口模拟WR、RS、CS等控制信号,数据线D0-D15接MCU的16个GPIO。这种方式无需配置复杂外设,移植性强,但速度受限于GPIO翻转频率(STM32F103下实测约2MHz总线速率)。
若要提升刷屏性能,必须切换至FSMC(Flexible Static Memory Controller)模式。以STM32F103ZET6为例,需将LCD数据线映射到FSMC的D0-D15(对应GPIOE0–GPIOE15),控制线映射为:
RS→FSMC_NOR_A0(地址线A0,自动区分指令/数据)WR→FSMC_NOR_NWE(写使能)RD→FSMC_NOR_NOE(读使能)CS→FSMC_NOR_NE1(片选)
此时驱动层不再手动置位/清零GPIO,而是通过FSMC地址空间访问:
// FSMC模式下写指令(RS=0) *(volatile uint16_t*)(0x60000000) = 0x0001; // 向基地址0x60000000写入指令0x0001 // FSMC模式下写数据(RS=1) *(volatile uint16_t*)(0x60000002) = 0xFFFF; // 向0x60000002写入数据0xFFFF其中0x60000000为FSMC Bank1 NOR/SRAM区域1的起始地址,0x60000002因A0=1自动触发数据写入。
| 驱动方式 | 优点 | 缺点 | 典型刷屏速率 |
|---|---|---|---|
| GPIO模拟 | 移植简单,任意MCU可用 | CPU占用高,速率低 | ~1.5 MHz |
| FSMC硬件 | CPU零干预,速率可达36MHz | 引脚固定,需查MCU手册匹配 | ~25 MHz |
2.3 正點原子驱动中LCD_WR_DATA宏的底层逻辑拆解
正點原子源码中关键宏定义如下(以GPIO模拟为例):
#define LCD_WR_DATA(data) { \ LCD_RS_SET; /* RS=1, 选择数据端口 */ \ LCD_CS_CLR; /* CS=0, 片选有效 */ \ LCD_D0_15_OUT(data); /* 将data写入D0-D15 GPIO */ \ LCD_WR_CLR; /* WR下降沿锁存 */ \ LCD_WR_SET; /* 恢复WR高电平 */ \ LCD_CS_SET; /* CS=1, 取消片选 */ \ }这段代码暴露了并口通信的最小原子操作:
LCD_RS_SET:通过GPIO_SetBits(GPIOx, GPIO_Pin_x)置位RS引脚,告诉LCD“接下来是数据”;LCD_D0_15_OUT(data):将16位data值通过GPIO_Write(GPIOx, data)一次性输出到16个数据线;LCD_WR_CLR+LCD_WR_SET:制造一个宽度≥10ns的WR脉冲(实际由GPIO翻转速度决定),LCD在WR下降沿采样数据线;LCD_CS_CLR/LCD_CS_SET:确保每次操作都在片选有效窗口内完成,避免总线冲突。
注意:
LCD_D0_15_OUT(data)必须保证16个GPIO在同一时钟周期内同步更新。若使用GPIO_Write()函数,需确认其底层是否调用BSRR寄存器(批量置位/复位)而非逐bit操作。STM32标准外设库中GPIO_Write()默认使用BSRR,安全;但若自行用GPIO_SetBits()+GPIO_ResetBits()组合,则可能造成数据线高低电平不同步,导致LCD误读。
3. 在STM32F103上复用正點原子ILI9325驱动,适配ILI9341屏幕的实操步骤
3.1 硬件连接验证:确认并口引脚与ILI9341规格书完全对齐
ILI9341支持8/16位并口,正點原子开发板默认采用16位模式。务必核对以下引脚映射(以STM32F103ZET6为例):
| LCD信号 | MCU引脚 | 备注 |
|---|---|---|
| D0-D7 | GPIOE0–GPIOE7 | 数据线低位 |
| D8-D15 | GPIOE8–GPIOE15 | 数据线高位 |
| RS | GPIOE11 | 必须接E11(FSMC_A0)或任意GPIO(GPIO模拟) |
| WR | GPIOE7 | 若用FSMC,必须接E7(FSMC_NWE);GPIO模拟可任意选 |
| RD | GPIOE6 | FSMC模式下接E6(FSMC_NOE) |
| CS | GPIOE10 | FSMC模式下接E10(FSMC_NE1) |
| RST | GPIOE9 | 复位引脚,需上拉电阻 |
提示:ILI9341的
RST引脚必须严格遵循datasheet要求——上电后保持低电平≥10ms,再拉高≥120ms才能进入初始化状态。正點原子原版代码中LCD_Rst()函数仅做一次GPIO_ResetBits()+Delay_ms(10),对ILI9341不够可靠。应改为:void LCD_Rst(void) { GPIO_ResetBits(GPIOE, GPIO_Pin_9); // RST=0 Delay_ms(20); // 保持低电平20ms GPIO_SetBits(GPIOE, GPIO_Pin_9); // RST=1 Delay_ms(150); // 等待150ms确保稳定 }
3.2 替换初始化序列:从ILI9325到ILI9341的寄存器重写清单
正點原子LCD_Init()函数中原始ILI9325初始化段需整体替换。以下是ILI9341最小可行初始化序列(基于ILI9341 datasheet Rev1.3):
void LCD_Init(void) { LCD_Rst(); // 先复位 // 1. 电源控制(Power Control) LCD_WriteReg(0xCF, 0x0000); // Power Control A LCD_WriteReg(0xED, 0x6400); // Power Control B LCD_WriteReg(0xE8, 0x0000); // Driver Timing Control A LCD_WriteReg(0xCB, 0x3908); // Power Control 1 LCD_WriteReg(0xF7, 0x0000); // Pump Ratio Control // 2. VCOM控制(VCOM Control) LCD_WriteReg(0xC0, 0x2300); // VCOM Control 1 LCD_WriteReg(0xC1, 0x1000); // VCOM Control 2 // 3. 像素格式(Pixel Format) LCD_WriteReg(0x3A, 0x0055); // Interface Pixel Format: 16-bit (RGB565) // 4. 帧率(Frame Rate) LCD_WriteReg(0xB1, 0x0000); // Frame Rate Control: 70Hz LCD_WriteReg(0xB4, 0x0000); // Display Inversion Control // 5. Gamma校准(Gamma Set) LCD_WriteReg(0xB6, 0x0808); // Display Function Control // 6. 退出睡眠(Exit Sleep) LCD_WriteReg(0x11, 0x0000); // Sleep Out Delay_ms(120); // 等待120ms // 7. 开启显示(Display On) LCD_WriteReg(0x29, 0x0000); // Display On }每条LCD_WriteReg(reg, value)调用中,reg为16位寄存器地址,value为16位参数值。注意:ILI9341所有寄存器写入均为16位,即使寄存器本身只用低8位(如0x11),也必须传入0x0000而非0x0011。
3.3 调整时序参数:解决ILI9341常见的“白屏”与“横纹”问题
ILI9341对WR脉冲宽度和建立/保持时间更敏感。正點原子原版LCD_WR_DATA宏中LCD_WR_CLR与LCD_WR_SET之间无延时,在STM32F103@72MHz下可能不足10ns。需插入NOP延时:
#define LCD_WR_DATA(data) { \ LCD_RS_SET; \ LCD_CS_CLR; \ LCD_D0_15_OUT(data); \ LCD_WR_CLR; \ __nop(); __nop(); __nop(); /* 插入3个NOP,约42ns @72MHz */ \ LCD_WR_SET; \ __nop(); __nop(); /* WR高电平保持时间 */ \ LCD_CS_SET; \ }若仍出现白屏,说明WR脉冲过短,可增加至5个__nop();若出现横纹(部分行错位),则是RS建立时间不足,需在LCD_RS_SET后加2个__nop()。
4. 树莓派官方驱动不适用ILI9341并口屏?用BCM2837 GPIO Bit-Banging实现兼容
4.1 为什么树莓派官方驱动(fbtft)默认不支持并口ILI9341
树莓派官方Linux内核驱动fbtft(位于drivers/staging/fbtft)主要面向SPI接口TFT屏(如ILI9341-SPI、ST7735),其设计假设是:
- 总线为SPI,通过
spi_write()发送指令/数据; - 屏幕尺寸、刷新率由Framebuffer设备节点(
/dev/fb1)统一管理; - GPIO仅用于
DC(数据/指令选择)、RESET、BACKLIGHT等辅助控制。
而并口ILI9341需要同时操控16根数据线+4根控制线,远超SPI驱动的抽象能力。因此,树莓派官方驱动无法直接驱动并口ILI9341——这不是bug,而是架构设计使然。
4.2 在树莓派4B上用Python+RPi.GPIO实现并口驱动原型
虽不能用fbtft,但可借助RPi.GPIO库模拟并口时序。以下为最小可行性代码(需root权限):
import RPi.GPIO as GPIO import time # GPIO引脚定义(BCM编号) DATA_PINS = [2,3,4,14,15,17,18,27,22,23,24,10,9,25,11,8] # D0-D15 RS_PIN = 7 # RS WR_PIN = 5 # WR CS_PIN = 6 # CS GPIO.setmode(GPIO.BCM) GPIO.setup(DATA_PINS + [RS_PIN, WR_PIN, CS_PIN], GPIO.OUT) def lcd_wr_data(data): # 设置RS=1(数据模式) GPIO.output(RS_PIN, GPIO.HIGH) # 片选有效 GPIO.output(CS_PIN, GPIO.LOW) # 输出16位数据(需拆分为高低8位分两次写,因RPi.GPIO不支持16位并行) for i in range(16): GPIO.output(DATA_PINS[i], (data >> i) & 0x01) # WR下降沿 GPIO.output(WR_PIN, GPIO.LOW) time.sleep(0.000001) # 1us延时 GPIO.output(WR_PIN, GPIO.HIGH) GPIO.output(CS_PIN, GPIO.HIGH) # 写入ILI9341指令0x29(Display On) lcd_wr_data(0x2900) # 高8位0x29为指令,低8位0x00为参数注意:树莓派GPIO无法真正16位并行输出,上述代码实际是逐bit模拟,速率极低(<10kHz),仅用于验证通信链路。量产需用C语言+
/dev/gpiomem直接操作寄存器,或选用带FSMC的MCU(如STM32H7)替代。
4.3 关键参数表:ILI9341并口时序约束与树莓派GPIO实测值
| 参数 | ILI9341 Spec要求 | 树莓派4B GPIO实测 | 是否满足 | 解决方案 |
|---|---|---|---|---|
| WR脉冲宽度(tWP) | ≥10ns | ≈80ns(含time.sleep(1e-6)) | ✅ | 保留1us延时 |
| WR建立时间(tWDS) | ≥10ns | ≈50ns | ✅ | 无需额外延时 |
| RS建立时间(tRSD) | ≥10ns | ≈40ns | ✅ | 同上 |
| 数据保持时间(tDH) | ≥10ns | ≈60ns | ✅ | 同上 |
| 最大写入速率 | 10MHz | ≈100kHz(Python) | ❌ | 改用C语言+内存映射 |
5. 验证驱动是否生效:三步定位法排查ILI9341并口通信故障
5.1 第一步:用万用表测量WR/RS/CS电平跳变
将万用表调至直流电压档(2V量程),红表笔接WR引脚,黑表笔接地。运行初始化代码后,应观察到:
WR引脚在LCD_WR_DATA()执行时,出现规律性低电平脉冲(每次持续约1μs,间隔数μs);RS引脚在写指令时为低电平(0V),写数据时为高电平(3.3V);CS引脚在每次操作期间保持低电平,操作结束后恢复高电平。
若WR无跳变,检查LCD_WR_CLR/LCD_WR_SET对应的GPIO是否配置为输出模式;若RS始终为高电平,检查LCD_RS_SET/LCD_RS_CLR宏定义是否反了逻辑(常见错误:GPIO_ResetBits()误用于置位)。
5.2 第二步:用逻辑分析仪捕获前10条初始化指令
连接Saleae Logic 8等逻辑分析仪,采集WR、RS、CS及D0-D7(先测低8位)信号。正常ILI9341初始化应捕获到如下序列:
| 时间点 | WR | RS | CS | D0-D7 | 含义 |
|---|---|---|---|---|---|
| t0 | ↓ | 0 | 0 | 0xCF | 写寄存器0xCF |
| t1 | ↓ | 0 | 0 | 0x00 | 参数高8位 |
| t2 | ↓ | 0 | 0 | 0x00 | 参数低8位 |
| t3 | ↓ | 0 | 0 | 0xED | 写寄存器0xED |
若发现D0-D7全为0xFF或0x00,说明LCD_D0_15_OUT(data)未正确写入数据,需检查GPIO初始化顺序(是否先GPIO_Init()再GPIO_Write())。
5.3 第三步:强制写入纯色画面,绕过初始化直接验证显存
若初始化后屏幕仍黑屏,可跳过LCD_Init(),直接向GRAM(显存)写入纯色数据,验证硬件连通性:
// 初始化后立即执行(假设GRAM起始地址为0x20000000) void LCD_Fill(uint16_t color) { LCD_WriteReg(0x002A, 0x0000); // SET_COLUMN_ADDR, XSTART=0 LCD_WriteReg(0x002B, 0x0000); // SET_PAGE_ADDR, YSTART=0 LCD_WriteReg(0x002A, 0x00EF); // XEND=239 LCD_WriteReg(0x002B, 0x013F); // YEND=319 LCD_WriteReg(0x002C, 0x0000); // WRITE_MEMORY_START for(uint32_t i = 0; i < 240*320; i++) { LCD_WR_DATA(color); // 连续写入color值 } } // 调用:LCD_Fill(0xF800); // 红色若屏幕显示均匀红色,证明并口数据通路正常,问题必在初始化序列;若仍黑屏或花屏,则聚焦LCD_WriteReg()函数中寄存器地址/值是否匹配ILI9341 datasheet。
本文还有配套的精品资源,点击获取