十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32硬件SPI+DMA+外部Flash驱动0.99寸圆形TFT屏

STM32硬件SPI+DMA+外部Flash驱动0.99寸圆形TFT屏 简介面向 STM32 进阶开发者的显示方案资源包以 STM32F103RCT6 驱动 0.99 寸 TFT 圆屏为项目载体重点演示硬件 SPI 与 DMA 配合、外部 FLASH W25Q64 存储图片并高速刷屏的实现方法。项目先利用 SPI 接口将图片写入外部 Flash显示时再通过 DMA 搬运至控制器 RAM 并由圆屏呈现可有效降低 CPU 占用适合学习嵌入式外设联动、存储扩展与显示优化的读者。资源共 195 个文件压缩包约 3.65 MB以 h 头文件、c 源码为主另含 uvprojx/uvoptx 工程配置、axf/hex 烧录文件、map/lst 编译输出及 bat 脚本可在 Keil 中直接打开工程对照学习。已有 592 人浏览学习。通过该包可掌握硬件 SPIDMA 降低 CPU 负载的数据搬移思路、W25Q64 外部存储扩展图片容量的流程以及圆屏在紧凑型设备中的接入方式工程结构较完整便于后期改造移植适合边阅读代码边调试作为中高级 STM32 屏显项目及实际产品原型开发的参考资料。 昨天把一个吃灰半年的0.99寸圆形TFT屏从抽屉里翻了出来顺手接到了STM32F103RCT6上。接的时候我想要是还用软件模拟SPI刷图这块屏基本就是一块漂亮的幻灯片——刷一帧能卡掉半条命全程CPU占用拉满。于是这次我没走老路直接把硬件SPI、DMA、外部SPI NOR FLASH三样东西组合在一起做了一套完整的“先存片、后刷片”显示方案。这篇就记录一下整个项目的硬件接线、驱动配置、图片存储和排坑过程给想玩小圆屏的人一个能直接抄作业的参考。1. 项目概述与整体设计思路1.1 硬件型号与资源盘点先说主控。STM32F103RCT6是LQFP64封装的Cortex-M3主频72MHz片上Flash 256KB、SRAM 48KB外设资源对这个小项目来说完全够用。最关键的是它有两个硬件SPISPI1和SPI2SPI1挂在APB2总线上最高可以跑到18Mbit/s在不超频的情况下72MHz/4分频这个速度虽然比不了现在的H7系列但驱动一块0.99寸小圆屏绰绰有余。屏是0.99寸圆形TFT分辨率常见的是240×240驱动IC以GC9107居多也有部分批次是ST7735S。这类屏的接口就是标准的4线SPI加几个控制脚SCK、SDAMOSI、RES、DC、CS、BLK。注意这种小尺寸屏一般只写不读所以MISO引脚通常不接但要是FLASH也要挂在同一条SPI总线上MISO就得留出来。存储用的是W25Q64一颗8Mbit也就是1MB的SPI NOR FLASH。为什么选它便宜、好买、驱动代码网上遍地都是而且SPI NOR支持按字节随机读当作图片资源库非常合适。1.2 为什么非要用硬件SPIDMA外部FLASH这个组合不是炫技是被实际需求逼出来的。软件模拟SPI刷240×240的RGB565图片一帧要传输115200字节GPIO翻转速度撑死几Mbit/s刷一帧画面在200ms以上肉眼可见的撕裂和闪烁CPU还没法干别的。STM32F103RCT6片内Flash只有256KB存代码还好再塞几张全屏图片就捉襟见肘改一张图还得重新刷固件太痛苦。刷长图、做界面切换、放动画帧需要大量连续数据这部分数据放外部FLASH主控只负责“读出来、送出去”不占用宝贵的内存。所以整体思路拆成三层硬件SPI负责和屏幕对话DMA负责搬运数据外部FLASH负责囤图。CPU只是在启动时把图片从FLASH读到内存缓冲再触发一次DMA传输剩下时间可以去处理按键、传感器或者干脆睡大觉。1.3 适合谁参考这个项目适合已经会GPIO点灯、串口打印、基础定时器的人想往“高性能显示”迈一步的玩家。通过这个项目你能顺带搞明白SPI时序怎么配、DMA传输怎么触发、NOR Flash怎么分区块管理这几个知识点以后做任何屏幕项目都用得上。2. 硬件连接与电路设计2.1 引脚分配与连接说明我把TFT和W25Q64挂在同一条SPI1总线上各用各的片选线。这个做法的好处是少占用一组SPI外设坏处是驱动上要小心处理好CS切换时序稍后会说。引脚分配如下功能引脚说明SPI1_SCKPA5时钟线接屏SCK和W25Q64的CLKSPI1_MOSIPA7主出从入接屏SDA和W25Q64的DISPI1_MISOPA6主入从出接W25Q64的DO读FLASH用TFT_CSPA4屏幕片选低有效TFT_DCPB0数据/命令选择高电平数据低电平命令TFT_RSTPB1复位脚低电平复位TFT_BLKPB10背光控制PWM或直接高电平点亮FLASH_CSPB12W25Q64片选低有效连接时要特别注意SCK和MOSI不要接反很多小屏模块上丝印会标成SCL和SDA其实和SPI的SCK、MOSI是一回事。DC和CS不要共用一个引脚虽然某些极限压缩引脚的玩法会这么干但代价是每次读写都要手动翻转DC逻辑复杂且容易出错。2.2 电源与去耦注意事项0.99寸圆屏背光全亮时电流大概在20~40mAW25Q64读写时几mA主控全速跑时几十mA整体功耗不大但从USB取电或者用AMS1117降压供电都要注意稳压后的纹波。我的做法是VCC引脚就近放一个0.1uF陶瓷电容电源入口再放一个10uF钽电容或者电解电容。不要小看这两个电容背光PWM调光时如果电源纹波大屏幕上会出现规律性横条纹排查起来非常折腾。另外所有信号线尽量短杜邦线长度超过15cm后SPI速度就只能往下降这一点在刷屏花屏时是最常见的元凶。2.3 W25Q64的WP和HOLD引脚W25Q64的引脚不算多但有两个人容易忽略WP写保护和HOLD挂起。这两个脚内部虽然都有上拉但稳妥起见我还是建议外部各加一个10K上拉到3.3V。尤其是HOLD脚如果悬空时受到干扰被拉低SPI通信会直接挂起表现就是这个设备莫名其妙不响应重新上电又好了极具迷惑性。3. SPIDMA驱动层原理与配置3.1 SPI时序参数怎么定GC9107这颗驱动IC的写入时序要求不复杂SPI模式0和模式3都支持CPOL0、CPHA0是最常用的。字节顺序MSB先行8位数据宽度这些在ST官方和屏厂给的例程里都是一样的直接照抄即可。时钟分频我选的是4分频也就是72MHz/418MHz。理论上F103的SPI1极限就是18MHz实际跑下来刷纯色块非常稳但接长杜邦线后建议降到8分频9MHz换取稳定性。时钟频率不是越高越好波形边沿变差、信号反射变大都可能造成数据错误。判断办法很简单刷一幅带斜线和文字的图片看边缘有没有毛刺或随机杂色点。3.2 DMA配置传输模式和句柄关联F103的SPI1发送对应DMA1的Channel3接收对应DMA1的Channel2。我这个项目主要用发送方向DMA配置为外设地址固定为SPI1-DR内存地址递增数据宽度8位传输模式用Normal一次性传输方向为内存到外设。这里特别提醒一个HAL库的坑如果用CubeMX生成代码DMA句柄必须和SPI句柄关联起来否则调用HAL_SPI_Transmit_DMA会直接报错。关联代码长这样__HAL_LINKDMA(hspi1, hdmatx, hdma_spi1_tx);这句话的意思是告诉SPI外设“你的发送DMA通道是hdma_spi1_tx”。很多人忘了这一行或者手动写代码时顺序不对导致DMA始终不触发现象就是HAL_SPI_Transmit_DMA返回HAL_BUSY屏幕死活不刷新。3.3 完整DMA发送流程代码void LCD_WriteData_DMA(uint8_t *buf, uint16_t len) { while (HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY); // 等待上一次传输结束 HAL_SPI_Transmit_DMA(hspi1, buf, len); }这段代码看起来简单但有两个关键点每次调用前必须确认SPI状态是READY否则数据会丢失或错位。刷整屏时115200字节一次传完SRAM只有48KB根本放不下整帧所以实际使用时分批传输比如一次传8KB循环15次才算刷完一屏。DMA传输完成可以用HAL_SPI_TxCpltCallback回调通知在回调里置一个标志位就行千万不要在中断回调里做耗时操作比如继续读FLASH、翻转DC脚这些动作放到主循环里做。3.4 实测性能对比软件模拟SPI刷一帧240×240的数据大约在200~300msCPU占用接近100%。硬件SPI不用DMA用阻塞式HAL_SPI_Transmit刷一帧大约60ms但CPU被占满。上了DMA之后传输一帧约50~60msCPU基本放空这个性能差距在界面上是能明显感知的同样做一个按钮按下反色效果软模拟会卡顿DMA方案能做到流畅。4. 外部FLASH的图片存储与管理4.1 为什么选SPI NOR FLASH而不是NAND标题里出现了外部FLASH这里展开说一下选型逻辑。NAND FLASH容量大、单位成本低但有坏块、需要ECC校验、读写以页为单位单片机裸机跑起来麻烦。SPI NOR FLASH虽然容量小但胜在随机读取方便——你随时可以从任意地址读任意长度的字节非常适合存储“图片素材库”这种小颗粒数据。W25Q64的扇区擦除单位是4KB页编程单位是256字节设计分区时按4KB对齐就行。4.2 图片取模与bin文件制作屏幕是RGB565格式每个像素2字节。一副240×240的图片裸数据大小是115200字节也就是约112.5KB。1MB的W25Q64理论上能塞8张整幅图片实际扣除分区和管理信息后放6张比较合理。取模我用的是Image2Lcd这个工具参数设置如下输出格式C数组或Bin文件像素格式RGB565扫描方向水平扫描从左到右从上到下最大宽度和高度240×240包含文件头不勾选这里有个坑Image2Lcd的“灰度/颜色”选项如果选错出来的数据会整幅偏色或反相。RGB565是小端存储也就是16位数据先存低字节再存高字节如果屏幕刷出来颜色错乱优先检查取模时是否勾选了“字节交换”。4.3 图片烧录方案与分区管理图片数据怎么进W25Q64最简单省事的方法把图片bin文件转成C数组编译进固件程序启动时检测FLASH里的“魔法数字”比如前4字节固定为0xAA55A55A如果不是就执行一次全片擦除并写入图片数据写完后再写魔法数字这样以后每次上电只用读FLASH不用重复写入。分区表我建议这样安排起始地址大小用途0x0000004KB头部信息图片数量、启动标志0x001000128KB图片10x021000128KB图片20x041000128KB图片3剩余空间768KB后续可扩展字库、动画帧等4.4 显示一张外部FLASH图片的完整流程void LCD_ShowFlashImage(uint32_t addr, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { uint8_t buf[4096]; uint32_t remain w * h * 2; uint32_t offset 0; while (remain 0) { uint32_t chunk (remain sizeof(buf)) ? sizeof(buf) : remain; W25Q64_Read(addr offset, buf, chunk); LCD_WriteData_DMA(buf, chunk); offset chunk; remain - chunk; } }注意一个关键点W25Q64的读操作和LCD的写操作共用同一个SPI1二者不能并发。所以每个分块要严格串行先读FLASH到内存再把内存通过DMA发给LCD不能一边读一边发。如果追求极致速度可以考虑双缓冲乒乓结构一块缓冲在读FLASH另一块缓冲在DMA发送交替使用能把FLASH读取和LCD刷屏的时间重叠起来实测大概能再提升15%~20%的帧率。5. 圆屏显示的特殊处理5.1 方形像素与圆形屏幕的冲突很多刚拿到圆屏的人会想屏是圆的那我直接在矩形区域刷图不就行了吗其实不然。TFT的显示驱动芯片并不知道玻璃是圆的它把整个240×240的矩形像素区域都拉出来了圆形只是外面的盖板玻璃遮住了四个角。所以如果你在矩形区域刷一张普通图片四个角的内容会被黑色边框遮挡效果非常廉价。要体现出圆屏的价值就得把图片内容限制在圆形区域内或者干脆绘制圆形、圆弧、环形仪表界面。5.2 画圆与填充圆的窗口裁剪法在TFT上画圆不能直接写一个像素就发送一次那样效率太低。正确姿势是“逐行约束窗口”对每一行根据圆方程算出这一行有效像素的左右边界然后只对这一行范围做填充。void LCD_FillCircle(uint16_t cx, uint16_t cy, uint16_t r, uint16_t color) { int16_t y; for (y cy - r; y cy r; y) { if (y 0 || y LCD_HEIGHT) continue; int32_t dy y - cy; int32_t dx (int32_t)sqrtf(r * r - dy * dy); uint16_t x0 cx - dx; uint16_t x1 cx dx; if (x0 0) x0 0; if (x1 LCD_WIDTH) x1 LCD_WIDTH - 1; LCD_SetWindow(x0, y, x1, y); LCD_WriteData_DMA(color, x1 - x0 1); } }这段代码的核心思想是用圆方程约束每行的绘制宽度。每次只开一个高度为1、宽度可变的窗口写入一段连续颜色数据比逐像素点要快一个数量级。画圆环的话只需在外圆范围内再排除内圆的范围同样是在每行算两个区间做差。5.3 圆弧与仪表盘绘制的扩展思路想做仪表盘界面的话推荐在此基础上扩展成角度扫描。根据角度θ算出弧上点的坐标然后填充一个从圆心到弧边的“扇形”或者用圆弧边界做环形高亮。这类绘制在F103上逐行填充完全跑得动240×240的分辨率下一帧满屏填充大约50ms做一个简单的动态仪表指针动画完全没有压力。另外提醒一句不同厂家的圆屏初始化时序和坐标偏移可能不同有的屏幕默认原点在左下角有的屏横竖方向是镜像的。如果画圆画出来是椭圆或者位置偏了多半不是算法问题而是初始化代码里的显示偏移没调好先刷一个边框矩形观察实际显示范围再调整。6. 常见问题与排查技巧实录6.1 屏幕花屏、变色、杂色点这是最高频的问题。排查顺序我总结成一个固定套路现象可能原因处理方法全屏花屏颜色随机SPI时钟太快分频从4改8降到9MHz试试图片颜色反相/偏绿RGB565字节序不对检查Image2Lcd是否勾选字节交换刷到一半错位电源纹波大加电容检查供电线特定区域乱码接线过长缩短杜邦线或改用排线焊接画面整体偏移初始化坐标偏移不对调SetWindow的偏移参数6.2 DMA传输卡死与SPI状态锁死调用HAL_SPI_Transmit_DMA后如果返回HAL_BUSY多半是上一次传输没结束就再次调用。我在项目里加了一个信号量DMA完成回调里置标志位主循环里发送前检查标志位。这样既不会丢数据也不会卡死。还有一个容易被忽略的坑DMA传输完成后SPI的BSY标志可能还没清零如果立刻切换DC引脚发命令偶尔会吞掉命令。稳妥做法是在DMA完成回调里加一个小延时或者等待__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_BSY)为RESET再操作。6.3 W25Q64读不出数据ID全0xFF先确认MISO是不是真的连上了很多屏模块不引出MISO如果FLASH单独接线MISO悬空就会读全1。其次检查WP和HOLD是否上拉第三个常见问题是FLASH_CS没有初始化成高电平导致一上电FLASH就被选中处于未知状态。软件上可以打印JEDEC ID0xEF4017对应W25Q64这是最快判断通信是否正常的办法。6.4 ST-Link下载失败target DLL has been cancelled这个报错很多新手遇到直接懵其实是ST-Link和目标板之间的连接不稳定。常见原因有三类目标板供电不足BOOT0跳线帽没有接地SWDIO/SWCLK线太长或者接触不良。我遇到最邪门的一次是复位引脚上的一颗大电容导致ST-Link握手失败换小电容或直接去掉就正常了。解决办法先确认BOOT0低电平再把SWD速度从4MHz降到1MHz最后检查GND线是否牢靠基本上能解决90%的情况。如果还是不行把ST-Link拔了重插或者给板子单独上电。7. 一点个人的实操体会这个项目做完之后我自己最大的感受是DMA并不是什么高深莫测的东西它就是一个“配置好之后不用管、搬运完成喊你一声”的工具人。难点不在于DMA本身而在于你什么时候触发它、触发前数据准备好了没有、触发后下一个动作要等多久。把这些时序理清楚整个刷屏链路就顺畅了。另外建议入手一块逻辑分析仪几十块钱的8通道就够用抓一下SPI波形SCK频率、CS时序、数据字节对不对一目了然。比拿来示波器盯波形或者瞎猜要高效得多。最后再分享一个可以继续扩展的方向这套“SPIDMA外部FLASH”的组合完全可以升级成简易GUI。在W25Q64剩余空间里放一套字库再把图片替换成图标、按键素材配合一颗陀螺仪或者编码器就能做出一个带旋转菜单的圆形智能手表原型。代码框架不用动只需要加状态机和输入处理剩下的交给DMA去跑就行。本文还有配套的精品资源点击获取
返回列表