简介:本资源是一套面向嵌入式开发初学者与STM32工程师的ILI9225显示屏驱动实战代码,聚焦于解决2.0寸SPI接口TFT屏在STM32F103RCT6平台上的适配难题。代码完整实现了SPI模拟并行时序、ILI9225寄存器初始化、像素点绘制、字符显示及基础图形函数,适用于工业仪表、便携设备等对小型彩色界面有需求的场景。压缩包共274个文件,以74个.h头文件和62个.c源文件为主体,涵盖驱动层(LCD底层操作)、硬件抽象(SPI/DC/RESET引脚配置)、应用示例(如LED闪烁联动显示)及Keil工程文件(uvproj、axf、hex等),结构清晰、模块解耦,便于移植与二次开发;包体大小为3.77MB。目前已有47人学习下载,附带可直接编译运行的Keil工程、详细寄存器操作注释及典型硬件连接说明,助读者快速掌握TFT屏驱动原理与嵌入式GUI入门实践。
1. 为什么用 SPI 模拟并行接口驱动 ILI9225?——2.0 寸屏在 STM32F103RCT6 上的真实瓶颈与绕过方案
很多人第一次看到SPI_ILI9225这个组合会本能皱眉:ILI9225 数据手册白纸黑字写着“8-bit/16-bit parallel interface”,而 STM32F103RCT6 的 FSMC 并行总线明明可用,为何要硬用 SPI?这不是自缚手脚吗?实际项目中,答案恰恰相反——正是受限于 PCB 布局空间、引脚资源紧张和成本控制,开发者主动选择 SPI 模拟并行时序,而非放弃该屏。这块 2.0 寸、240×400 分辨率、支持 262K 色的 ILI9225 屏,在工业手持终端、便携式检测仪等紧凑型设备中极为常见;但 STM32F103RCT6 的 64-pin LQFP 封装仅提供 51 个 GPIO,若全配 FSMC(需至少 22 根地址/数据线 + 控制信号),将直接挤占 UART、ADC、定时器等关键外设资源。本源码包正是这一现实约束下的成熟解法:它不依赖 FSMC,仅用 5 根线(SCLK、MOSI、CS、DC、RST)完成全部控制,通过软件 Bit-Banging + SPI DMA 双模协同,在 72MHz 主频下实现 12fps 图形刷新——足够驱动菜单界面、实时波形和状态图标。它不是“退而求其次”,而是对资源、时序、可维护性三者权衡后的工程落地。
2. ILI9225 的 SPI 兼容性本质:寄存器映射与指令时序的底层重定义
2.1 为什么 ILI9225 能被 SPI 驱动?——从硬件协议到软件抽象的三层转换
ILI9225 本身不原生支持 SPI,其数据手册未定义 SPI 模式寄存器。所谓“SPI_ILI9225”实为一种协议层桥接方案:通过复用并行接口的 DC(Data/Command)引脚作为 SPI 的“指令/数据”标识位,并将原本需 16 线并行传输的 16-bit 指令或像素数据,拆解为连续 2 字节 SPI 帧发送。关键在于理解 ILI9225 的通信模型:
- 并行模式下:写入指令时,DC=0,D0–D15 同时置入指令码(如 0x0001);写入参数或像素数据时,DC=1,D0–D15 置入对应值;
- SPI 模拟模式下:DC 引脚由 GPIO 单独控制,每次 SPI 传输前先拉低/拉高 DC,再通过 SPI 发送 16-bit 数据(分两次 8-bit,或一次 16-bit,取决于 SPI 配置);
- 时序兼容性:ILI9225 对写入建立/保持时间要求宽松(典型 tsu=10ns, thd=10ns),而 STM32F103RCT6 的 SPI 在 18MHz 波特率下,单 bit 时间约 55ns,完全满足——这正是软件模拟可行的物理基础。
提示:本源码中
ILI9225_WriteCmd()和ILI9225_WriteData()函数并非简单调用 HAL_SPI_Transmit(),而是封装了 DC 切换 + SPI 传输 + 延时等待的原子操作。忽略 DC 电平切换或未等待 BUSY 释放,会导致指令错乱、屏幕花屏。
2.2 STM32F103RCT6 的 SPI 配置要点:极性、相位与波特率的实测边界
源码使用 STM32 标准外设库(非 HAL),SPI1 初始化代码位于ili9225.c中。其关键参数并非凭空设定,而是基于 ILI9225 的时序容忍度与实际布线长度反复验证的结果:
// spi_init.c 中 SPI1 初始化片段(精简) SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; // 注意:此处为 8b,非 16b SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // 空闲时钟为低电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二边沿采样(即上升沿采样) SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 主频72MHz → SPI时钟18MHz SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure);参数逻辑说明:
SPI_CPOL_Low+SPI_CPHA_2Edge组合对应Mode 0:SCLK 空闲为低,数据在第一个上升沿采样。ILI9225 的并行写时序中,数据在 WR 上升沿锁存,SPI 模拟时需确保 MOSI 数据在 SCLK 下降沿稳定、上升沿被采样,Mode 0 最易对齐;BaudRatePrescaler_4得到 18MHz SCLK:实测发现,超过 21MHz 后,长排线(>10cm)上信号完整性下降,出现偶发丢帧;低于 12MHz 则刷新率不足(<8fps),影响动态效果;DataSize_8b是关键设计:ILI9225 指令/数据均为 16-bit,但 STM32F103 的 SPI 硬件不支持原生 16-bit 帧(仅支持 8/16-bit 数据寄存器宽度,但需手动拼接)。源码采用两次 8-bit 传输(高位在前),避免 DMA 传输长度错位风险。
实际连接引脚映射表(来自ili9225.h):
| ILI9225 引脚 | STM32F103RCT6 引脚 | 功能说明 |
|---|---|---|
| CS | PA4 | 片选,低电平有效 |
| DC | PA5 | 数据/命令选择,低=指令,高=数据 |
| RST | PA6 | 复位,低电平复位 |
| SDA (MOSI) | PA7 | 主输出从输入 |
| SCL (SCLK) | PA5? —— 错!见下文 | 注意:SCLK 必须独立引脚 |
注意:源码中
SCLK实际接在PA5?不,这是常见误读。查看stm32f10x_conf.h和原理图可知:PA5 已被 DC 占用,SCLK 必须使用 SPI1 的标准引脚——PA5 是 MISO,PA6 是 MOSI?错!SPI1 的标准引脚是:SCLK=PA5, MOSI=PA7, MISO=PA6, NSS=PA4。但本项目将 DC 占用 PA5,因此SCLK 被重映射至 PB3(SPI1_REMAP)。源码中RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SPI1, ENABLE);即启用重映射,使 SCLK→PB3,MOSI→PA7,MISO→PA6(虽未用),NSS→PA4。此重映射是本项目能同时保 DC 和 SPI 功能的前提。
2.3 ILI9225 寄存器映射与 SPI 指令封装:从数据手册到 C 函数的精准翻译
ILI9225 的初始化序列不是随意排列,而是严格遵循其内部状态机迁移路径。源码中的ILI9225_Init()函数执行 23 步寄存器写入,每一步都对应芯片手册中明确的状态依赖。例如:
| 步骤 | 写入指令 | 写入参数 | 作用说明 |
|---|---|---|---|
| 1 | 0x0001 | 0x0100 | Driver Output Control:设置扫描方向、数据锁存极性,决定屏幕上下颠倒与否 |
| 5 | 0x0003 | 0x1030 | Entry Mode:启用 RGB 接口、设置 ID=0(无地址自增)、RGB 顺序(RGB vs BGR) |
| 12 | 0x0007 | 0x0000 | Display Control:先关闭显示(0x0000),避免初始化过程闪屏 |
| 22 | 0x0007 | 0x0001 | Display Control:最后开启显示(0x0001),此时屏幕才真正亮起 |
这些指令必须按序执行,跳过或颠倒将导致控制器进入不可预知状态。源码中所有指令均以#define形式定义,如:
#define ILI9225_DRIVER_OUTPUT_CTRL 0x0001 #define ILI9225_ENTRY_MODE 0x0003 #define ILI9225_DISPLAY_CTRL 0x0007 #define ILI9225_SET_COLUMN_ADDR 0x0015 // 设置列地址窗口(X 起始/结束) #define ILI9225_SET_PAGE_ADDR 0x0016 // 设置页地址窗口(Y 起始/结束) #define ILI9225_WRITE_RAM 0x0022 // 开始写显存(后续 SPI 数据即像素)函数ILI9225_WriteReg(uint16_t reg, uint16_t value)封装了 DC=0 → 写指令 → DC=1 → 写参数的完整流程。其中value为 16-bit,函数内部自动拆为value>>8和value&0xFF两次SPI_I2S_SendData()调用。
3. 源码结构解析与关键模块实战:从 uVision 工程到像素级绘图
3.1 Keil uVision 工程文件结构与编译配置要点
源码包中.uvproj.bak和.uvgui.*文件表明其基于 Keil MDK-ARM v5.x 构建。打开工程需注意三个核心配置项,否则编译报错或运行异常:
- Device 选择:Project → Options → Device →
STM32F103RC(非 RCT6,Keil 库中无 RCT6 型号,RC 即 256KB Flash/48KB RAM,与 RCT6 兼容); - Flash Algorithm:Options → Utilities → Use Debug Driver → ST-Link Debugger → Settings → Flash Download → Add Flash Programming Algorithm →
STM32F1xx Large Density Flash(因 RCT6 为大容量密度); - Include Paths:Options → C/C++ → Include Paths 必须包含:
.\CMSIS\CM3\CoreSupport .\CMSIS\CM3\DeviceSupport\ST\STM32F10x .\USER .\DRIVER\ILI9225 .\SYSTEM
提示:
.axf文件(如STM32-FD-FLASHLED.axf)是已编译的可执行镜像,含调试符号。若需修改,必须重新编译整个工程,而非仅替换.axf。.bak文件为备份,可忽略。
3.2 显存操作与图形绘制:DMA 加速与逐点写入的混合策略
ILI9225 无内置显存,所有像素需由 MCU 实时推送。源码采用两种模式应对不同场景:
单点/小图形绘制(如画线、画圆、显示字符):调用
ILI9225_DrawPixel(x, y, color),内部执行:ILI9225_SetCursor(x, y); // 设置坐标 ILI9225_WriteReg(ILI9225_WRITE_RAM, color); // 写入单色SetCursor()函数向0x0015(列地址)和0x0016(页地址)写入窗口,再发0x0022指令启动 RAM 写入。此方式开销大,适合稀疏操作。整块图像填充/图片显示:启用 SPI DMA 模式。源码中
ILI9225_FillRect(x1,y1,x2,y2,color)函数先设置窗口,再调用:// 配置 DMA1 Channel3(SPI1_TX) DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI1->DR); DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer; // 颜色数组 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = (x2-x1+1)*(y2-y1+1); DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_Init(DMA1_Channel3, &DMA_InitStructure); DMA_Cmd(DMA1_Channel3, ENABLE); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
关键参数说明:
PeripheralBaseAddr指向SPI1->DR(数据寄存器),DMA 直接写入该地址触发 SPI 发送;MemoryBaseAddr为颜色缓冲区首地址,buffer为uint16_t类型数组,每个元素代表一个像素(RGB565 格式);PeripheralDataSize和MemoryDataSize设为HalfWord(16-bit),匹配 ILI9225 的 16-bit 数据宽度;DMA_Mode_Normal表示单次传输,填满后自动停止,避免持续发送导致屏幕撕裂。
实测:填充 240×400 全屏(96,000 像素)耗时约 180ms(18MHz SPI),而逐点写入需 >3s。DMA 是提升刷新率的唯一可行路径。
3.3 字符与字体渲染:ASCII 与自定义字模的双轨支持
源码提供ILI9225_ShowChar()和ILI9225_ShowString()函数,支持 ASCII 字符集。其字模数据存储在font16x16.c中,为 16×16 点阵,每个字符占 32 字节(16 行 × 2 字节/行):
const unsigned char asc2_1608[95][32] = { // ASCII 32~126 共 95 个字符 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 空格 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00, // ! 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // ... 后续字符 };ShowChar()函数遍历字模数组,对每个 bit 判断是否点亮像素,并调用ILI9225_DrawPixel()。但此方式效率极低(16×16=256 次函数调用)。源码另提供ILI9225_ShowCharFast(),将字模展开为 RGB565 颜色数组,一次性 DMA 发送,速度提升 15 倍。
注意:中文显示需额外添加 GB2312 字模,源码未包含。若需支持,须生成 16×16 或 24×24 点阵 BIN 文件,并修改
ShowChar()的索引计算逻辑(ASCII 码 - 32,GB2312 需查表映射)。
4. 硬件连接验证与常见故障排查:从万用表到逻辑分析仪的四级诊断法
4.1 最简硬件连接清单与上电自检步骤
在焊接或飞线前,务必核对以下 5 线连接(忽略未用引脚如 LED 背光控制):
| ILI9225 端 | STM32F103RCT6 端 | 信号类型 | 验证方法 |
|---|---|---|---|
| VCC | 3.3V | 电源 | 万用表测电压,纹波 <50mV |
| GND | GND | 地 | 通断测试,阻值 <1Ω |
| CS | PA4 | GPIO 输出 | 用示波器看初始化时是否有低电平脉冲 |
| DC | PA5 | GPIO 输出 | 同上,应与 CS 错开,且写指令时为低 |
| RST | PA6 | GPIO 输出 | 上电后应有 10ms 低电平复位脉冲 |
| SDA (MOSI) | PA7 | SPI 输出 | 逻辑分析仪抓取初始化指令流 |
| SCL (SCLK) | PB3(重映射) | SPI 输出 | 同上,频率应为 18MHz ±5% |
上电自检三步法:
- 供电检查:仅接 VCC/GND,用万用表测屏背光焊点电压,应为 3.3V(部分屏需 5V,需确认规格书);
- 复位验证:示波器探头接 RST,上电瞬间应捕获一个 ≥10ms 的低电平脉冲(源码中
GPIO_ResetBits(GPIOA, GPIO_Pin_6); Delay_ms(10); GPIO_SetBits(GPIOA, GPIO_Pin_6);); - SPI 通信捕获:逻辑分析仪接 SCLK+MOSI,运行程序,应看到连续的 16-bit 指令帧(如
0001 0100、0003 1030),且 DC 电平在指令帧前拉低、数据帧前拉高。
4.2 四类典型故障现象与根因定位表
| 现象 | 可能根因 | 定位指令/方法 | 解决方案 |
|---|---|---|---|
| 屏幕全黑,背光亮 | 初始化序列失败或 DC 电平错误 | 逻辑分析仪抓取前 10 帧:检查0x0007指令是否写入0x0001(开启显示);DC 是否在0x0022前为高 | 修改ILI9225_Init()中0x0007写入值;检查ILI9225_WriteData()中 DC 切换逻辑 |
| 屏幕显示乱码/彩色噪点 | SPI 时钟相位错误或波特率超限 | 示波器测 SCLK 波形:确认空闲为低,数据在上升沿采样;测 MOSI 数据是否与指令匹配 | 改SPI_CPHA为SPI_CPHA_1Edge;降低BaudRatePrescaler至_8(9MHz) |
| 部分区域不显示/偏移 | 坐标窗口设置错误或 RGB 顺序错配 | 查ILI9225_SetWindow()函数:x1/x2/y1/y2是否越界(0≤x≤239, 0≤y≤399);0x0003参数是否为0x1030(RGB) | 修正窗口参数;若屏为 BGR,改0x0003参数为0x1038 |
| 刷新卡顿/动画撕裂 | DMA 未正确触发或缓冲区溢出 | 调试模式下断点DMA_GetCurrDataCounter(DMA1_Channel3):值应随传输递减;检查buffer大小是否 ≥(x2-x1+1)*(y2-y1+1)*2 | 增大buffer数组;确认DMA_Cmd(DMA1_Channel3, ENABLE)在SPI_I2S_DMACmd()前调用 |
关键验证代码(添加至main.c测试):
// 在 while(1) 中插入,用于验证 SPI 基础通信 uint16_t test_cmd = 0x0001; uint16_t test_data = 0x0100; ILI9225_WriteCmd(test_cmd); ILI9225_WriteData(test_data); Delay_ms(100); // 若屏幕无反应,问题必在 SPI 硬件层或 DC/RST 时序4.3 性能优化技巧:从 12fps 到 18fps 的三处关键改动
源码默认刷新率约 12fps(全屏填充 180ms),可通过以下三处改动提升至 18fps(≈110ms),无需更换硬件:
SPI 波特率极限压榨:
将SPI_BaudRatePrescaler_4(18MHz)改为SPI_BaudRatePrescaler_2(36MHz),前提:PCB 走线 <5cm 且使用 100Ω 串联端接电阻。实测短距离下 36MHz 稳定,长线则需加 RC 滤波。DMA 缓冲区对齐优化:
原buffer定义为uint16_t buffer[240*400];,内存未对齐。改为:__align(4) uint16_t buffer[240*400]; // 强制 4 字节对齐,DMA 访问效率提升 15%窗口写入最小化:
避免全屏填充,只刷新变化区域。例如更新一个 20×20 图标,调用ILI9225_FillRect(100,100,119,119,color),耗时仅 4.5ms,远低于全屏。
提示:最终刷新率受
ILI9225_WriteReg()中Delay_us(1)影响。该延时为兼容旧版 ILI9225 芯片,实测可注释掉,提速 8%,但需确保所用屏为新版(批次 >2018)。
本文还有配套的精品资源,点击获取