在嵌入式开发中,STM32F103系列因其性价比优势广受欢迎,但有限的GPIO资源常成为驱动TFT屏幕时的瓶颈。本文将深入探讨如何通过任意GPIO口模拟8080时序高效驱动ILI9341屏幕,解决PCB布线和IO分配难题。
8080并行接口(又称Intel总线)是驱动TFT屏幕的经典协议,相比6800摩托罗拉总线,它使用独立的读写控制线(RD/WR),在STM32资源受限场景下更具灵活性。ILI9341驱动芯片支持16位RGB565数据格式,典型引脚包括:
硬件设计提示:当使用F103的PB3/PB4引脚时需禁用JTAG功能,可通过
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)实现。
传统方案要求16位数据线集中在一个GPIO端口(如GPIOD),但在实际项目中常遇到:
通过宏定义灵活分配各数据线到不同端口,以下为典型配置示例:
c复制// 数据线分散定义(示例使用GPIOA/B/C)
#define ILI9341_D0_PIN GPIO_Pin_0
#define ILI9341_D0_PORT GPIOA
#define ILI9341_D0_CLK RCC_APB2Periph_GPIOA
#define ILI9341_D1_PIN GPIO_Pin_1
#define ILI9341_D1_PORT GPIOB
/* 其他D2-D15引脚类似定义... */
void ILI9341_GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
// 启用所有涉及到的GPIO时钟
RCC_APB2PeriphClockCmd(ILI9341_D0_CLK | ILI9341_D1_CLK | ... | RCC_APB2Periph_AFIO, ENABLE);
// 配置数据线为推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN;
GPIO_Init(ILI9341_D0_PORT, &GPIO_InitStructure);
/* 其他D1-D15引脚类似初始化... */
}
读取数据时需要临时切换GPIO为输入模式,推荐封装专用函数:
c复制typedef enum {GPIO_Set_Mode_IN, GPIO_Set_Mode_OUT} GPIO_Mode_Type;
void GPIO_SET_DATA_OUT(GPIO_Mode_Type Mode) {
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = (Mode == GPIO_Set_Mode_OUT) ?
GPIO_Mode_Out_PP : GPIO_Mode_IPU;
// 批量配置所有数据线
GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN;
GPIO_Init(ILI9341_D0_PORT, &GPIO_InitStructure);
/* 重复配置D1-D15... */
}
对比三种数据输出方法的性能差异(基于72MHz主频测试):
| 方法 | 执行时间(ms) | 代码体积 | 适用场景 |
|---|---|---|---|
| 位带操作 | 1.2 | 小 | 引脚固定项目 |
| 库函数GPIO_WriteBit | 8.5 | 中 | 需要动态配置 |
| 寄存器直接操作 | 2.1 | 大 | 极致性能需求 |
推荐位带操作实现(需提前定义位带别名):
c复制// 位带宏定义(以D0为例)
#define D0_W BIT_ADDR(ILI9341_D0_PORT->ODR, ILI9341_D0_PIN)
#define D0_R BIT_ADDR(ILI9341_D0_PORT->IDR, ILI9341_D0_PIN)
/* 其他D1-D15类似定义... */
// 高效数据输出函数
void DATAOUT(uint16_t data) {
D0_W = (data>>0)&0x01; D1_W = (data>>1)&0x01;
D2_W = (data>>2)&0x01; D3_W = (data>>3)&0x01;
/* D4-D15位操作类似... */
}
读取时需要组合分散的IO状态:
c复制uint16_t DATAIN(void) {
uint16_t data = 0;
data |= D15_R; data <<= 1;
data |= D14_R; data <<= 1;
/* 组合D13-D0... */
return data;
}
c复制void ILI9341_Write_Cmd(uint16_t cmd) {
ILI9341_CS_CLR; // 片选使能
ILI9341_DC_CLR; // 命令模式
DATAOUT(cmd); // 输出命令
ILI9341_WR_CLR; // 产生写脉冲
ILI9341_WR_SET;
ILI9341_CS_SET; // 片选禁用
}
uint16_t ILI9341_Read_Data(void) {
uint16_t data;
GPIO_SET_DATA_OUT(GPIO_Set_Mode_IN); // 切换输入模式
DATAOUT(0x0000); // 先输出低电平
ILI9341_DC_SET; // 数据模式
ILI9341_CS_CLR;
ILI9341_RD_CLR; // 读脉冲
data = DATAIN();
ILI9341_RD_SET;
ILI9341_CS_SET;
GPIO_SET_DATA_OUT(GPIO_Set_Mode_OUT); // 恢复输出模式
return data;
}
通过示波器测量发现,分散IO方案比集中端口方案慢约800ms(全屏刷新时)。可通过以下手段优化:
基于上述基础函数,可实现高级功能:
c复制// 设置开窗函数优化区域刷新
void ILI9341_OpenWindow(uint16_t x, uint16_t y, uint16_t width, uint16_t height) {
ILI9341_Write_Cmd(0x2A); // 列地址设置
ILI9341_Write_Data(x>>8);
ILI9341_Write_Data(x&0xFF);
ILI9341_Write_Data((x+width-1)>>8);
ILI9341_Write_Data((x+width-1)&0xFF);
ILI9341_Write_Cmd(0x2B); // 行地址设置
/* 类似写入y坐标... */
ILI9341_Write_Cmd(0x2C); // 写入GRAM
}
// 高效清屏函数
void ILI9341_Clear(uint16_t color) {
ILI9341_OpenWindow(0, 0, 240, 320);
for(uint32_t i=0; i<76800; i++) {
ILI9341_Write_Data(color);
}
}
问题1:屏幕显示花屏
问题2:读取ID不正确
问题3:刷新率低
DATAOUT()函数实现通过本文方案,即使在STM32F103C8T6(仅48引脚)上,也能成功驱动320x240分辨率屏幕。实际项目中,建议根据性能需求选择合适方案——对刷新率要求不高的仪器仪表界面可采用本文方法,而视频播放等场景建议选用带FSMC的型号或改用SPI接口屏。