STM32 SPI驱动ST7798:从初始化到图形绘制的实战指南
1. 硬件连接与SPI基础配置
第一次用STM32驱动ST7798屏幕时,我对着数据手册研究了整整两天才搞明白硬件连接。SPI接口虽然只有四根线,但每个引脚的功能都不能接错。CS(片选)接PA4,DC(数据/命令选择)接PA8,RST(复位)接PA15,背光控制接PB3。SPI主接口用SPI1,MOSI接PA7,SCK接PA5。
硬件连接完成后,SPI初始化是关键。我习惯用标准库配置,先开启GPIO和SPI时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);SPI参数配置有个坑要注意:ST7798的SPI模式必须设为CPOL=1、CPHA=1(即模式3)。我最初用模式0导致屏幕完全没反应:
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 空闲时高电平 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二个边沿采样波特率预分频建议先用低速(如256分频)确保初始化稳定,后面再提速:
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;2. ST7798初始化序列详解
屏幕初始化就像给设备"开机引导",必须严格按照手册顺序发送命令。ST7798的初始化有30多个步骤,但核心流程可以归纳为:
- 硬件复位:拉低RST引脚至少10ms
lcd_rst=0; delay_ms(20); lcd_rst=1; delay_ms(120);- 退出睡眠模式:发送0x11命令后需等待120ms
lcd_write_cmd(0x11); delay_ms(120);- 设置像素格式:我用RGB565格式(0x05)
lcd_write_cmd(0x3A); lcd_write_data(0x05);- 伽马校正:这部分参数厂家已经优化好,直接照搬即可:
lcd_write_cmd(0xE0); lcd_write_data(0xD0); lcd_write_data(0x00); // 后续还有13个参数...实测中发现,初始化完成后如果不设置显示区域,可能会出现花屏。建议立即设置全屏显示范围:
lcd_address_set(0, 0, LCD_Width-1, LCD_Height-1);3. 底层绘图函数实现
画点函数是所有图形操作的基础。ST7798需要先设置坐标窗口,再写入颜色值:
void lcd_draw_point(u16 x, u16 y, u16 color) { lcd_address_set(x, y, x, y); // 设置单点坐标 lcd_write_halfword(color); // 写入16位色值 }清屏操作优化有个技巧:设置全屏地址后,连续发送像素数据比单点写入快10倍:
void lcd_clear(u16 color) { u8 data[2] = {color>>8, color&0xFF}; lcd_address_set(0, 0, LCD_Width-1, LCD_Height-1); lcd_dc=1; lcd_cs=0; for(int i=0; i<LCD_Width*LCD_Height; i++) { SPI_I2S_SendData(SPI1, data[0]); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET); SPI_I2S_SendData(SPI1, data[1]); while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET); } lcd_cs=1; }画圆函数采用Bresenham算法,实测在240x320分辨率下画半径50的圆只需2ms:
void lcd_draw_circle(u16 x0, u16 y0, u8 r, u16 color) { int a=0, b=r, di=3-(r<<1); while(a<=b) { // 对称绘制8个点 lcd_draw_point(x0+b, y0-a, color); lcd_draw_point(x0+a, y0-b, color); // 省略其他6个点... a++; di = di<0 ? di+4*a+6 : di+10+4*(a-b--); } }4. 字符与图片显示优化
显示字符需要先处理字模数据。我常用的16x8点阵字库每个字符占16字节:
void lcd_show_char(u16 x, u16 y, char chr, u8 size, u16 color, u16 bgcolor) { chr -= ' '; // ASCII码偏移 u8 *font = asc2_1608[chr]; // 字模指针 for(int t=0; t<16; t++) { u8 temp = font[t]; for(int t1=0; t1<8; t1++) { lcd_write_halfword(temp&0x80 ? color : bgcolor); temp <<= 1; } } }图片显示要注意取模格式。推荐用Image2Lcd工具,设置如下:
- 输出格式:C语言数组
- 扫描方式:水平扫描
- 色深:16位真彩色(RGB565)
- 高位在前(MSB First)
显示图片时直接批量发送数据:
void lcd_show_image(u16 x, u16 y, u16 w, u16 h, const u8 *p) { lcd_address_set(x, y, x+w-1, y+h-1); lcd_dc=1; lcd_cs=0; for(int i=0; i<w*h*2; i++) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET); SPI_I2S_SendData(SPI1, p[i]); } lcd_cs=1; }5. SPI性能优化实战
初始化的低速模式(256分频)刷新率只有5fps,通过以下优化可提升到30fps:
- 提升SPI时钟:初始化后切到2分频
void lcd_set_speed(u8 prescaler) { SPI1->CR1 &= 0xFFC7; SPI1->CR1 |= prescaler; SPI_Cmd(SPI1, ENABLE); } // 初始化完成后调用 lcd_set_speed(SPI_BaudRatePrescaler_2);- 减少CS引脚操作:连续发送数据时保持CS为低
void lcd_write_bulk(u8 *data, u32 len) { lcd_dc=1; lcd_cs=0; for(int i=0; i<len; i++) { while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET); SPI_I2S_SendData(SPI1, data[i]); } lcd_cs=1; }- DMA传输:对于全屏刷新,使用DMA可降低CPU占用率
void lcd_dma_send(u8 *data, u32 len) { DMA_InitTypeDef DMA_InitStructure; // 配置DMA1通道3(SPI1_TX) DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (u32)data; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = len; 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); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); DMA_Cmd(DMA1_Channel3, ENABLE); while(DMA_GetFlagStatus(DMA1_FLAG_TC3)==RESET); DMA_ClearFlag(DMA1_FLAG_TC3); }