1. 项目概述
这个项目展示了如何使用N32H473REL7微控制器的硬件SPI接口配合DMA功能,驱动金逸晨2.25寸TFT液晶屏(ST7789控制器)实现基本字母显示功能。对于嵌入式开发者来说,这种小尺寸显示屏在物联网设备、便携式仪器等场景中非常实用。
ST7789是一款广泛应用于中小尺寸TFT液晶屏的控制器芯片,支持262K色彩显示。通过硬件SPI+DMA的方式驱动,可以显著降低CPU负载,提高显示刷新效率。我在多个嵌入式项目中都采用过这种方案,实测效果稳定可靠。
2. 硬件准备与连接
2.1 所需硬件清单
- N32H473REL7开发板(或核心板)
- 金逸晨2.25寸TFT液晶屏(ST7789控制器)
- 杜邦线若干
- 3.3V电源(部分屏幕可能需要背光单独供电)
- 10KΩ电阻(用于部分屏幕的复位电路)
2.2 引脚连接说明
ST7789屏幕通常需要以下连接:
| 屏幕引脚 | N32H473REL7引脚 | 备注 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| SCL | SPI_SCK | SPI时钟线 |
| SDA | SPI_MOSI | SPI数据线 |
| RES | GPIO_PA1 | 复位信号,低电平有效 |
| DC | GPIO_PA2 | 数据/命令选择,高电平为数据,低电平为命令 |
| CS | GPIO_PA3 | 片选信号,低电平有效 |
| BLK | 3.3V或PWM控制 | 背光控制 |
注意:不同批次的屏幕引脚定义可能略有差异,务必以实际屏幕的规格书为准。我曾遇到过同一型号但不同批次的屏幕DC和RES引脚定义相反的情况。
3. 软件环境配置
3.1 开发工具准备
- Keil MDK或IAR Embedded Workbench开发环境
- N32H473REL7的SDK开发包
- ST7789的驱动库(可自行编写或使用开源实现)
3.2 工程配置要点
在开发环境中需要正确配置以下内容:
- SPI外设时钟使能
- GPIO引脚模式配置(SPI引脚设为复用功能,控制引脚设为推挽输出)
- DMA通道配置
- 中断优先级设置(如果使用中断)
c复制// SPI初始化示例代码片段
void SPI_Init(void)
{
SPI_InitType SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
4. ST7789驱动实现
4.1 屏幕初始化序列
ST7789需要按照特定顺序发送初始化命令才能正常工作。以下是典型的初始化流程:
- 硬件复位(拉低RES引脚至少10ms)
- 发送软件复位命令(0x01)
- 设置睡眠模式关闭(0x11)
- 配置颜色模式(0x3A)
- 设置显示方向(0x36)
- 设置显示区域(0x2A和0x2B命令)
- 开启显示(0x29)
c复制void ST7789_Init(void)
{
// 硬件复位
RES_LOW();
DelayMs(20);
RES_HIGH();
DelayMs(120);
// 发送初始化命令序列
ST7789_WriteCommand(0x01); // 软件复位
DelayMs(150);
ST7789_WriteCommand(0x11); // 退出睡眠模式
DelayMs(255);
ST7789_WriteCommand(0x3A); // 设置颜色模式
ST7789_WriteData(0x55); // 16位RGB565格式
DelayMs(10);
// 更多初始化命令...
}
4.2 DMA传输配置
使用DMA可以大幅提高SPI传输效率,特别是在刷新全屏时。以下是DMA配置的关键点:
- 配置DMA源地址(内存中的显示缓冲区)
- 配置DMA目标地址(SPI数据寄存器)
- 设置传输数据长度
- 配置传输完成中断
c复制void DMA_Config(void)
{
DMA_InitType DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel3);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)displayBuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = SCREEN_WIDTH * SCREEN_HEIGHT * 2;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
}
5. 字符显示实现
5.1 字模提取与存储
要在屏幕上显示字母,需要先准备好字模数据。常用的方法有:
- 使用PC端工具生成字模数组(如PCtoLCD2002)
- 直接使用内置的ASCII字模库
- 动态生成矢量字体(资源消耗较大,不推荐用于小屏)
c复制// 典型的8x16 ASCII字模数据结构示例
const uint8_t font8x16[95][16] = {
// 空格
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// !
{0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00},
// 更多字符...
};
5.2 字符显示函数实现
字符显示的基本流程是:
- 设置显示区域(单个字符大小)
- 将字模数据转换为屏幕像素格式(如RGB565)
- 通过SPI+DMA发送像素数据
c复制void ST7789_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bgcolor)
{
uint8_t i, j;
uint8_t line;
// 检查字符是否在字模范围内
if(c < 32 || c > 126) c = ' ';
// 设置显示区域
ST7789_SetWindow(x, y, x + 7, y + 15);
// 准备发送数据
DC_HIGH();
CS_LOW();
// 逐行处理字模数据
for(i = 0; i < 16; i++) {
line = font8x16[c - 32][i];
for(j = 0; j < 8; j++) {
if(line & 0x80) {
SPI_SendData(color >> 8);
SPI_SendData(color & 0xFF);
} else {
SPI_SendData(bgcolor >> 8);
SPI_SendData(bgcolor & 0xFF);
}
line <<= 1;
}
}
CS_HIGH();
}
6. 性能优化技巧
6.1 SPI时钟优化
ST7789的最大SPI时钟频率通常为62.5MHz,但实际使用中需要考虑:
- 导线长度和干扰
- 屏幕内部逻辑处理速度
- 微控制器SPI外设限制
建议从较低频率开始测试(如10MHz),逐步提高直到出现显示异常,然后回退到稳定频率。
6.2 DMA双缓冲技术
对于需要频繁刷新的应用,可以采用双缓冲技术:
- 准备两个显示缓冲区
- DMA传输一个缓冲区时,CPU填充另一个缓冲区
- 交替使用两个缓冲区
c复制// 双缓冲示例
uint16_t buffer1[SCREEN_WIDTH * SCREEN_HEIGHT];
uint16_t buffer2[SCREEN_WIDTH * SCREEN_HEIGHT];
uint16_t *activeBuffer = buffer1;
uint16_t *drawBuffer = buffer2;
void SwapBuffers(void)
{
// 等待前一次DMA传输完成
while(DMA_GetFlagStatus(DMA1_FLAG_TC3) == RESET);
// 交换缓冲区指针
uint16_t *temp = activeBuffer;
activeBuffer = drawBuffer;
drawBuffer = temp;
// 启动新的DMA传输
DMA_SetCurrDataCounter(DMA1_Channel3, SCREEN_WIDTH * SCREEN_HEIGHT * 2);
DMA_SetMemoryAddress(DMA1_Channel3, (uint32_t)activeBuffer);
DMA_Cmd(DMA1_Channel3, ENABLE);
}
6.3 局部刷新优化
全屏刷新消耗资源较大,可以只更新变化的部分:
- 记录需要更新的区域
- 只发送该区域的像素数据
- 特别适合文本显示、仪表指针等应用
7. 常见问题与解决方案
7.1 屏幕无显示或显示异常
可能原因及解决方法:
- 电源问题:检查3.3V电源是否稳定,背光是否开启
- 复位时序问题:确保复位信号满足最小脉宽要求
- SPI模式不匹配:ST7789通常需要CPOL=1, CPHA=1
- 颜色格式错误:确认发送的是RGB565格式数据
7.2 DMA传输不完整或错位
排查步骤:
- 检查DMA缓冲区地址和对齐
- 确认DMA传输长度计算正确(像素数×2字节)
- 检查SPI和DMA时钟是否使能
- 确保在DMA传输完成前不修改缓冲区内容
7.3 显示内容有噪点或闪烁
可能原因:
- 电源噪声:在VCC和GND之间添加滤波电容(如100nF+10μF)
- 信号干扰:缩短SPI线缆长度,或使用双绞线
- 刷新率过高:适当降低SPI时钟频率
- 接地不良:确保所有地线连接可靠
8. 项目扩展思路
8.1 添加中文字符显示
- 制作16×16或更大的中文字模
- 实现GB2312/Unicode编码转换
- 优化存储结构(使用外部Flash或压缩算法)
8.2 实现简单图形界面
- 添加基本绘图函数(线、圆、矩形等)
- 实现按钮、滑块等UI元素
- 设计事件处理机制
8.3 低功耗优化
- 利用ST7789的睡眠模式
- 动态调整刷新率
- 关闭背光或PWM调光
在实际项目中,我发现ST7789屏幕在阳光直射下的可视性是一个挑战。通过调整屏幕对比度和使用高亮度背光可以部分改善,但对于户外应用,可能需要考虑半透半反式屏幕或增加遮光设计。