1. 项目概述
最近在做一个嵌入式显示项目,需要驱动一块2.25寸的TFT液晶屏。选用了N32H473REL7这款国产MCU,搭配金逸晨的ST7789驱动芯片的小屏。这种组合在小型嵌入式设备中很常见,成本低、性能稳定。本文将详细介绍如何通过硬件SPI+DMA的方式高效驱动这块屏幕,并实现基本字符显示功能。
2. 硬件设计与引脚配置
2.1 屏幕接口定义
这块2.25寸屏采用SPI接口,主要信号线如下:
- BL (背光控制):PB4
- CS (片选):PA15
- DC (数据/命令选择):PD2
- SDA (MOSI):PB5
- SCL (SCK):PA5
选择硬件SPI1作为通信接口,因为:
- 硬件SPI比软件模拟更稳定高效
- SPI1的引脚与屏幕接口完美匹配
- N32H473的SPI1性能足够驱动这块小屏
2.2 GPIO初始化关键点
在GPIO配置时需要注意:
c复制// 背光控制引脚配置为推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pin = GPIO_PIN_4;
GPIO_InitPeripheral(GPIOB,&GPIO_InitStructure);
// SPI引脚配置为复用功能
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Pin = GPIO_PIN_5; // MOSI
GPIO_InitPeripheral(GPIOB,&GPIO_InitStructure);
特别注意:DC线虽然也是控制信号,但因其切换频率高,建议使用推挽输出而非开漏输出,确保上升沿速度。
3. SPI与DMA配置详解
3.1 SPI主模式配置
SPI1配置为主模式,关键参数如下:
c复制SPI_InitStructure.DataDirection = SPI_DIR_SINGLELINE_TX; // 单线发送
SPI_InitStructure.SpiMode = SPI_MODE_MASTER; // 主模式
SPI_InitStructure.DataLen = SPI_DATA_SIZE_8BITS; // 8位数据
SPI_InitStructure.CLKPOL = SPI_CLKPOL_HIGH; // 时钟极性高
SPI_InitStructure.CLKPHA = SPI_CLKPHA_SECOND_EDGE; // 第二边沿采样
SPI_InitStructure.BaudRatePres = SPI_BR_PRESCALER_8; // 预分频8
选择这些参数的原因是:
- ST7789芯片要求CPOL=1, CPHA=1
- 8位数据宽度是SPI最通用的设置
- 预分频8在72MHz系统时钟下产生9MHz SPI时钟,满足ST7789最大40MHz的要求
3.2 DMA通道配置
使用DMA1通道3作为SPI1_TX通道,关键配置:
c复制DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST; // 内存到外设
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_BYTE; // 外设数据宽度8位
DMA_InitStructure.MemDataSize = DMA_MEM_DATA_WIDTH_BYTE; // 内存数据宽度8位
DMA_InitStructure.Priority = DMA_PRIORITY_HIGH; // 高优先级
DMA传输的优势:
- 解放CPU,提高系统效率
- 批量传输数据时更稳定
- 特别适合屏幕刷新这种大数据量操作
4. ST7789驱动实现
4.1 初始化序列
ST7789需要严格的初始化序列,以下是关键步骤:
c复制// 复位序列
LCD_RES_Set();
delay_ms(20);
LCD_RES_Clr();
delay_ms(200);
LCD_RES_Set();
delay_ms(120);
// 发送初始化命令
LCD_WR_REG(0x11); // 退出睡眠模式
delay_ms(120);
// 配置屏幕参数
LCD_WR_REG(0x3A);
LCD_WR_DATA8(0x05); // 16位RGB565格式
实测发现:复位后的延迟非常关键,过短会导致初始化失败。建议复位低电平保持200ms以上。
4.2 显存操作函数
4.2.1 区域填充函数
c复制void LCD_Fill_DMA(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye, uint16_t color)
{
uint32_t total = (xe-xs+1)*(ye-ys+1)*2; // 计算总字节数
LCD_Address_Set(xs,ys,xe,ye); // 设置操作区域
// 配置DMA
DMA_SetPerMemAddr(DMA1_CH3, (uint32_t)&SPI1->DAT, (uint32_t)buf, DMA_PERIPH_DATA_WIDTH_BYTE);
DMA_SetCurrDataCounter(DMA1_CH3, total);
DMA_EnableChannel(DMA1_CH3, ENABLE);
while(DMA_GetFlagStatus(DMA_FLAG_TC3, DMA1)==RESET); // 等待传输完成
}
4.2.2 字符显示函数
c复制void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t ch, uint16_t fc, uint16_t bc)
{
// 获取字模数据
uint8_t temp = ascii_8x16[ch*16+i];
// 逐像素绘制
for(j=0;j<8;j++){
uint16_t c = (temp & (0x80>>j)) ? fc : bc;
LCD_WR_DATA(c);
}
}
5. 实际应用与优化
5.1 主程序流程
c复制int main(void)
{
// 外设初始化
SPI_Configuration();
DMA_Configuration();
// 屏幕初始化
LCD_Init();
// 测试显示
LCD_Fill(0, 0, LCD_W-1, LCD_H-1, WHITE);
LCD_ShowString(20, 20, "ABCDE", RED, WHITE);
while(1) {
// 主循环
}
}
5.2 性能优化技巧
-
双缓冲技术:在内存中维护两个显示缓冲区,通过DMA交替传输,避免屏幕闪烁。
-
局部刷新:只更新屏幕上变化的部分,减少数据传输量。例如:
c复制// 只刷新变化的字符区域
LCD_Fill(x, y, x+8, y+16, bgColor);
LCD_ShowChar(x, y, newChar, fgColor, bgColor);
- DMA传输优化:
- 使用内存到外设的DMA传输模式
- 设置合适的DMA优先级
- 利用DMA中断通知传输完成
6. 常见问题与解决方法
6.1 屏幕无显示
- 检查背光控制信号是否正常
- 测量SPI时钟信号是否输出
- 确认复位时序是否符合要求
6.2 显示花屏
- 检查SPI相位和极性设置
- 确认数据位数设置(8位/16位)
- 检查DMA传输是否完整
6.3 DMA传输不稳定
- 确保DMA通道优先级设置合理
- 检查内存缓冲区是否对齐
- 避免在DMA传输过程中修改源数据
7. 扩展功能实现
7.1 中文字符显示
扩展字库数据结构:
c复制typedef struct {
uint8_t index[3]; // 汉字内码
uint8_t data[32]; // 16x16点阵数据
} ChineseChar;
7.2 简单图形绘制
实现基本绘图函数:
c复制void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
// Bresenham画线算法实现
int dx = abs(x2-x1), sx = x1<x2 ? 1 : -1;
int dy = -abs(y2-y1), sy = y1<y2 ? 1 : -1;
int err = dx+dy, e2;
while(1){
LCD_DrawPoint(x1,y1,color);
if(x1==x2 && y1==y2) break;
e2 = 2*err;
if(e2 >= dy) { err += dy; x1 += sx; }
if(e2 <= dx) { err += dx; y1 += sy; }
}
}
通过这套驱动,我们成功实现了在N32H473上高效驱动ST7789屏幕。硬件SPI+DMA的方案相比软件模拟SPI,刷新速度提升了3-5倍,CPU占用率大幅降低。这套驱动框架稍作修改即可适配其他SPI接口的显示屏,具有很好的可移植性。