当前位置: 首页 > news >正文

STM32F1 HAL库DMA驱动ST7735屏幕:从零构建高效SPI图形显示系统

1. 为什么选择DMA驱动ST7735屏幕?

在嵌入式开发中,显示驱动往往是资源消耗大户。我刚开始用STM32F1做UI项目时,发现普通SPI方式刷新1.8寸ST7735屏幕时,CPU占用率经常超过60%。这意味着芯片大部分时间都在搬运显示数据,根本无暇处理其他任务。

后来改用DMA(直接内存访问)方案后,实测SPI传输效率提升3倍以上。DMA就像个专职快递员,CPU只需要告诉它"把A地址的数据送到B地址",剩下的搬运工作完全由DMA控制器自动完成。特别是在连续传输场景下:

  • 全屏刷新时,DMA能实现零等待传输
  • 绘制复杂图形时,CPU可并行处理其他逻辑
  • 显示帧率从15FPS提升到45FPS(实测数据)

ST7735这种SPI屏幕特别适合DMA驱动,因为它的显示数据是线性连续存储的。当我们需要刷新屏幕左上角10x10像素区域时,传统方式需要CPU反复操作SPI外设,而DMA只需配置一次传输参数就能自动完成所有像素点的写入。

2. 硬件环境搭建要点

2.1 最小系统连接

我的开发板是STM32F103C8T6最小系统,与ST7735的连接方式如下:

STM32F1 ST7735 PA4(CS) -> CS PA5(SCK) -> SCL PA7(MOSI) -> SDA PA1(DC) -> DC PA2(RESET) -> RES +3.3V -> VCC GND -> GND

这里有个坑要注意:ST7735的背光控制引脚如果直接接VCC,屏幕会常亮但无法调节亮度。我建议通过一个GPIO控制,比如接在PA3引脚,这样代码里可以用PWM调光。

2.2 SPI模式配置

在CubeMX中配置SPI1时,关键参数要这样设置:

hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;

特别注意时钟极性(CPOL)和相位(CPHA)必须与屏幕规格书一致。我遇到过因为相位配置错误导致显示花屏的问题,调试了半天才发现是这里设错了。

3. DMA配置实战技巧

3.1 CubeMX中的DMA设置

在CubeMX的DMA配置页面,需要为SPI_TX添加DMA通道:

  1. 点击"DMA Settings"标签
  2. 添加新配置:SPI1_TX
  3. 选择通道:DMA1 Channel3(不同型号可能不同)
  4. 参数配置:
    • Direction: Memory To Peripheral
    • Priority: Medium
    • Mode: Normal
    • Increment Address: Memory端使能
    • Data Width: Byte

这里有个性能优化技巧:如果使用STM32F1的增强型DMA控制器,可以把FIFO阈值设为1/4,能减少总线冲突。

3.2 关键代码实现

DMA传输的核心代码在st7735.c驱动文件中:

void ST7735_SendDataDMA(uint8_t* buff, size_t buff_size) { HAL_SPI_Transmit_DMA(&hspi1, buff, buff_size); while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY); }

实际使用时发现,连续调用DMA传输时需要检查前一次传输是否完成。我封装了一个安全发送函数:

void ST7735_SendDataSafe(uint8_t* data, uint32_t len) { static uint32_t lastTick = 0; // 防止DMA过载 while(HAL_GetTick() - lastTick < 1); ST7735_SendDataDMA(data, len); lastTick = HAL_GetTick(); }

4. 显示驱动优化策略

4.1 双缓冲机制实现

为了进一步优化显示性能,我实现了简易的双缓冲机制:

uint16_t frameBuffer1[128*160]; uint16_t frameBuffer2[128*160]; uint16_t* activeBuffer = frameBuffer1; void ST7735_Refresh() { ST7735_SetAddressWindow(0, 0, 127, 159); ST7735_SendDataDMA((uint8_t*)activeBuffer, sizeof(frameBuffer1)); activeBuffer = (activeBuffer == frameBuffer1) ? frameBuffer2 : frameBuffer1; }

这样在绘制下一帧时,可以完全避免屏幕撕裂现象。实测显示帧率稳定在60FPS,CPU占用率仅15%。

4.2 局部刷新优化

全屏刷新效率低下,实际项目中更多使用局部刷新。我优化后的区域刷新函数:

void ST7735_UpdateRegion(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { uint16_t width = x2 - x1 + 1; uint16_t height = y2 - y1 + 1; ST7735_SetAddressWindow(x1, y1, x2, y2); uint32_t offset = y1 * 128 + x1; for(uint16_t y = 0; y < height; y++) { ST7735_SendDataDMA((uint8_t*)&activeBuffer[offset], width*2); offset += 128; } }

这个方案比全屏刷新节省80%以上的传输时间,特别适合UI局部更新的场景。

5. 图形API设计实践

5.1 基础绘图函数

基于DMA的像素绘制函数需要特殊处理:

void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if(x >= 128 || y >= 160) return; activeBuffer[y * 128 + x] = color; // 立即模式绘制 ST7735_SetAddressWindow(x, y, x, y); ST7735_SendDataDMA((uint8_t*)&color, 2); }

其他图形基元如直线、圆形等都可以基于这个像素函数实现。不过更高效的做法是直接操作帧缓冲区,最后统一刷新。

5.2 文字显示方案

我移植了轻量级字体渲染引擎:

typedef struct { const uint8_t width; const uint8_t height; const uint16_t *data; } FontDef; void ST7735_DrawChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color, uint16_t bgcolor) { uint32_t i, b, j; for(i = 0; i < font.height; i++) { b = font.data[(ch - 32) * font.height + i]; for(j = 0; j < font.width; j++) { if((b << j) & 0x8000) { ST7735_DrawPixel(x+j, y+i, color); } else if(bgcolor != color) { ST7735_DrawPixel(x+j, y+i, bgcolor); } } } }

配合预先生成的字体数据,可以支持多种字号。实测显示ASCII字符串的速度比传统方案快5倍。

6. 性能调优经验

6.1 SPI时钟优化

ST7735的最大SPI时钟是15MHz,但实际测试发现:

  • 8MHz时稳定性最好
  • 超过12MHz会出现数据丢失
  • 使用DMA时建议设为10MHz

在CubeMX中调整Prescaler参数:

hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; // 9MHz @72MHz主频

6.2 DMA传输中断优化

默认的DMA传输完成中断会有较大延迟,我修改了中断优先级:

HAL_NVIC_SetPriority(DMA1_Channel3_IRQn, 1, 0); HAL_NVIC_EnableIRQ(DMA1_Channel3_IRQn);

同时添加了传输完成回调函数,用于统计实际传输性能:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { static uint32_t lastTime = 0; uint32_t current = HAL_GetTick(); printf("DMA传输耗时:%dms\n", current - lastTime); lastTime = current; }

7. 常见问题解决方案

7.1 显示花屏问题

遇到花屏时建议检查:

  1. SPI相位和极性配置
  2. 电源稳定性(最好加100uF电容)
  3. 复位时序(RESET低电平保持至少10ms)

7.2 DMA传输卡死

DMA卡死通常是因为:

  • 缓冲区地址未对齐(需4字节对齐)
  • 传输过程中被中断打断
  • 内存访问冲突

解决方案是添加超时检测:

HAL_StatusTypeDef ST7735_SendDataDMA(uint8_t* buff, size_t size) { HAL_StatusTypeDef status; status = HAL_SPI_Transmit_DMA(&hspi1, buff, size); uint32_t timeout = HAL_GetTick() + 100; while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY) { if(HAL_GetTick() > timeout) { HAL_SPI_Abort(&hspi1); return HAL_ERROR; } } return status; }

8. 项目实战:天气站UI

最后分享一个实际项目案例 - 迷你天气站的显示实现:

void WeatherStation_UpdateDisplay() { // 绘制背景 ST7735_FillScreen(ST7735_BLUE); // 显示温度 char tempStr[10]; sprintf(tempStr, "%2.1fC", sensorData.temperature); ST7735_DrawString(10, 30, tempStr, Font_16x26, ST7735_WHITE, ST7735_BLUE); // 显示天气图标 if(sensorData.weather == WEATHER_SUNNY) { ST7735_DrawImage(80, 20, 48, 48, sunIcon); } else { ST7735_DrawImage(80, 20, 48, 48, cloudIcon); } // 局部刷新 ST7735_UpdateRegion(10, 30, 60, 56); ST7735_UpdateRegion(80, 20, 128, 68); }

这个实现充分运用了DMA的优势:背景填充用全屏刷新,数据更新用局部刷新,图标显示用DMA加速的图像传输。整个UI刷新耗时仅8ms,系统响应非常流畅。

http://www.jsqmd.com/news/677429/

相关文章:

  • 分数规划学习笔记
  • 2026江西学历提升机构综合实力排行榜:成考+自考全景横评,分析翼程教育为何脱颖而出? - 商业科技观察
  • iOS设备调试支持终极指南:解决Xcode兼容性问题
  • Z变换与数字滤波器设计:原理与应用
  • 终极指南:如何一键恢复B站经典界面,重温小电视播放器的美好时代
  • 2026年4月张家界亲子游/家庭游/品质小团/私人订制旅行社哪家好 - 2026年企业推荐榜
  • 从零构建大模型:Transformer 核心原理详解
  • 基于路阻信息的电动汽车充电需求分布 路网-电网耦合、排队论、温度耗电量、配电网潮流,通过时序蒙...
  • (117页PPT)产品质量先期策划和控制计划(附下载方式)
  • #官方认证|2026年广东十大正规AI智能体搭建 / 管理系统定制开发 / 行业软件开发企业排名,光点科技综合实力遥遥领先 - 十大品牌榜
  • 如何用PKSM成为宝可梦存档管理专家:从备份到跨世代转移全指南
  • 从知网导出到可视化图谱:Citespace 6.2.R4 完整分析CNKI文献的实战流程
  • 广东鸿胜金属设备回收:汕头酒店拆除哪个团队专业 - LYL仔仔
  • UCIe Sideband流控实战:从Spec模糊点到手把手调试避坑指南
  • 别再手算拉普拉斯变换了!用Matlab的laplace/ilaplace函数5分钟搞定信号分析
  • 别再手动描线了!用OpenCV+Steger算法5分钟搞定PCB走线中心提取(附完整C++代码)
  • 告别鼠标!在Ubuntu 22.04上用Touchegg打造MacBook级触控板手势(附详细配置文件)
  • 别再只会看容量了!用Windows自带命令,1分钟精准识别你的内存条型号和频率(附详细解读)
  • 网盘直链下载助手终极指南:八大网盘一键获取真实下载地址
  • Real-Anime-Z效果展示:real-anime-z_19生成的金属质感机甲少女高清图集
  • Element-UI文件上传避坑指南:accept属性设置全解析(含MIME类型对照表)
  • 耐力板工厂选购指南:工程场景怎么选靠谱供应商? - 速递信息
  • Matlab新手避坑指南:用find函数做数据筛选,这3个浮点数比较的坑你踩过吗?
  • **柔性电子驱动下的嵌入式编程新范式:基于Python的可拉伸传感器数据采集系统设计与实现**在柔性电子技术快速发展的今天,传统刚性
  • 搭建智能代账平台收费乱象数据统计分析代码,收集各家平台服务费数据,核算定价差值,识别垄断高价异常区间。
  • KMS_VL_ALL_AIO:Windows与Office激活的终极免费解决方案
  • Bartender/NiceLabel/Codesoft 代理商
  • 2026年山东青岛短视频代运营与广告投流服务商深度横评 - 年度推荐企业名录
  • 中高端汽车内饰源头厂家|广州西到蒙贸易公司一站式批发定制,赋能全渠道商家 - 汽车工厂源头推荐
  • 告别默认SDK!Delphi 11.1 独立配置多版本Android SDK环境实战指南