STM32F103标准库实战:手把手配置SPI1的DMA通道3,搞定LVGL显存刷新

发布时间:2026/5/18 12:45:45

STM32F103标准库实战:手把手配置SPI1的DMA通道3,搞定LVGL显存刷新 STM32F103标准库实战SPI1DMA通道3高效驱动LVGL显存刷新在嵌入式GUI开发中流畅的界面渲染往往受限于屏幕刷新效率。当使用ST7735S这类SPI接口屏幕时传统CPU轮询方式会导致明显的拉窗帘效果。本文将基于STM32F103标准库详细解析如何通过SPI1配合DMA1_Channel3实现LVGL显存的高效刷新让128x128分辨率的屏幕达到60FPS的流畅度。1. 硬件架构与核心原理1.1 SPIDMA的协同工作机制SPISerial Peripheral Interface作为同步串行通信接口其硬件实现相比软件模拟可提升5-10倍的传输速率。当与DMADirect Memory Access结合时数据传输过程完全由硬件自动完成CPU仅在传输前后介入实现真正的零拷贝刷新。关键参数对应关系SPI1_SCK → PA5SPI1_MOSI → PA7DMA1_Channel3 → SPI1_TX专用通道1.2 显存管理策略LVGL默认采用双缓冲机制但针对小内存MCU我们可以优化为单缓冲局部刷新uint8_t lcd_gram[128][256]; // 128x128 RGB565格式内存布局遵循ST7735S的列行式寻址每个像素点对应两个字节高字节R/G低字节G/B。2. 硬件SPI1的精确配置2.1 GPIO与SPI初始化确保时钟使能顺序正确是稳定通信的前提void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; // 1. 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 2. 配置PA5(SCK), PA7(MOSI)为复用推挽输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); // 3. SPI参数配置 SPI_InitStruct.SPI_Direction SPI_Direction_1Line_Tx; // 单线发送 SPI_InitStruct.SPI_Mode SPI_Mode_Master; SPI_InitStruct.SPI_DataSize SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL SPI_CPOL_High; // 时钟极性 SPI_InitStruct.SPI_CPHA SPI_CPHA_2Edge; // 时钟相位 SPI_InitStruct.SPI_NSS SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_2; // 36MHz/218MHz SPI_InitStruct.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStruct); SPI_Cmd(SPI1, ENABLE); }注意SPI时钟相位(CPHA)必须与屏幕驱动IC要求的采样边沿严格匹配ST7735S通常需要上升沿采样。2.2 关键参数解析参数推荐值作用说明SPI_BaudRatePrescalerSPI_BaudRatePrescaler_2在72MHz系统时钟下达到36MHz SPI速率SPI_CPOLHigh空闲时SCK保持高电平SPI_DataSize8b按字节传输而非16位字模式SPI_Direction1Line_Tx仅使用MOSI线进行单向传输3. DMA1通道3的深度配置3.1 DMA初始化代码实现DMA配置需要特别注意内存与外设地址的对齐void DMA1_Channel3_Init(void) { DMA_InitTypeDef DMA_InitStruct; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_DeInit(DMA1_Channel3); DMA_InitStruct.DMA_PeripheralBaseAddr (uint32_t)(SPI1-DR); DMA_InitStruct.DMA_MemoryBaseAddr (uint32_t)lcd_gram; DMA_InitStruct.DMA_DIR DMA_DIR_PeripheralDST; // 内存→外设 DMA_InitStruct.DMA_BufferSize 0; // 初始设为0实际传输前设置 DMA_InitStruct.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode DMA_Mode_Normal; // 非循环模式 DMA_InitStruct.DMA_Priority DMA_Priority_High; DMA_InitStruct.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStruct); DMA_Cmd(DMA1_Channel3, DISABLE); // 初始保持关闭 }3.2 传输过程状态机完整的DMA传输包含以下阶段准备阶段设置显存区域坐标通过ST7735S命令启动阶段清除TC3标志位设置传输数据量128x128x232768字节使能SPI的DMA请求等待阶段轮询DMA_GetFlagStatus(DMA1_FLAG_TC3)收尾阶段禁用DMA通道拉高CS片选4. LVGL驱动层适配要点4.1 显示缓冲区配置在lv_conf.h中修改关键参数#define LV_COLOR_DEPTH 16 #define LV_HOR_RES_MAX 128 #define LV_VER_RES_MAX 128 #define LV_VDB_SIZE 50 // 根据可用RAM调整4.2 刷新函数对接实现LVGL的flush_cb回调函数void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) { uint16_t width area-x2 - area-x1 1; uint16_t height area-y2 - area-y1 1; // 1. 设置刷新区域 Lcd_SetRegion(area-x1, area-y1, area-x2, area-y2); // 2. 启动DMA传输 DMA1_Channel3-CNDTR width * height * 2; DMA_Cmd(DMA1_Channel3, DISABLE); DMA_ClearFlag(DMA1_FLAG_TC3); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); DMA_Cmd(DMA1_Channel3, ENABLE); // 3. 等待传输完成 while(!DMA_GetFlagStatus(DMA1_FLAG_TC3)); // 4. 通知LVGL刷新完成 lv_disp_flush_ready(disp_drv); }4.3 性能优化技巧局部刷新优化仅传输脏区(dirty area)数据而非全屏内存对齐确保lcd_gram地址按4字节对齐添加__align(4)修饰中断优先级若使用RTOS设置DMA中断优先级高于任务调度在正点原子精英板上实测该方案可使ST7735S的刷新率从原始的15FPS提升至62FPSCPU占用率从90%降至不足5%。通过逻辑分析仪抓取波形可见SPI时钟稳定在18MHz每个像素传输时间仅0.44μs。

相关新闻