ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32F7 D-Cache与DMA一致性导致的SPI屏随机杂点排查

STM32F7 D-Cache与DMA一致性导致的SPI屏随机杂点排查 最近帮朋友调一块屏折腾了一晚上问题非常典型STM32F767ZI Waveshare 4.0寸的ILI9486 ShieldSKU13587通过SPI接口刷屏画单个像素、小尺寸矩形都正常一旦画大块填充矩形屏幕上就出现随机分布的彩色杂点每次刷新位置还会变。这个现象很容易让人怀疑是屏坏了、接线不稳或者时序错了但实际排查下来真正的坑来自STM32F7这颗Cortex-M7特有的Cache架构外加SPI像素格式和时钟频率的叠加影响。这篇记录会把完整排查过程、原理分析和最终修复方案写清楚。如果你也在用F7、H7这类带D-Cache的芯片驱动SPI屏或者哪怕只是用SPI DMA发送数据都有必要看一眼。1. 故障现象与软硬件现场1.1 硬件连接与驱动配置先说现场。板子是一块STM32F767ZI开发板主控跑216MHz屏是Waveshare 4.0寸TFT Shield型号SKU13587控制器为ILI9486分辨率为480x320。因为是Shield形态一开始直接叠插在Arduino兼容排针上后来为了排查问题改成杜邦线连接。连线方式很常规SPI1_SCK - PA5SPI1_MOSI - PA7LCD_CS - PD7LCD_DC - PD11LCD_RST - PD12触摸芯片XPT2046与屏幕共用同一个SPI1触摸CS单独接了一个GPIO驱动初始化继承了原有工程里一套“兼容ST7789/ILI9341”的代码主要初始化序列包括0x36MADCTL、0x3A像素格式、0xC0/0xC1电源控制、0x35帧率设置等。这里必须提一个背景问题ILI9486本身是一颗18位色深面板但SPI模式下它可以工作在16位像素格式也可以工作在18位像素格式。工程里一开始0x3A写成0x6618位但后续写像素数据时按每像素2字节的RGB565方式发送这一步就已经埋下了隐患。在F7上启用SPI1的时钟也很常规SPI1挂在APB2总线上APB2时钟为54MHz。当时为了追求刷屏速度SPI分频配成了2分频SPI时钟直接跑到27MHz。现在回头想这个频率对ILI9486来说已经接近甚至超过数据手册标称值信号完整性风险很大。1.2 复现场景大矩形填充时的彩色杂点问题出现得非常稳定。调用一个最普通的fillRect函数例如LCD_FillRect(0, 0, 320, 240, BLUE);全屏刷蓝色时整个矩形内部会出现几十到数百个不等的亮色杂点。杂点不是固定的某一行或某一列每次刷新后位置都会变看起来和“随机噪声”一样。改用纯红色、纯绿色填充时同样出现只是杂点的颜色对比度不同。更明显的是如果填充的是从黑到白的渐变杂点会变成各种彩色像素。继续测试发现几个重要规律画单个像素点完全正常。小尺寸矩形小于64x64基本正常偶尔出现一两个杂点。矩形越大杂点数量越多且随面积近似线性增长。用阻塞方式逐像素发送数据杂点几乎消失。用SPI DMA一次性发送一个大缓冲区杂点高发。这组规律基本排除了“屏幕本身损坏”的可能也排除了“初始化序列完全错误”的可能。因为如果像素格式或者MADCTL配置从一开始就是错的那么显示出来的应该是整体错乱、颜色通道翻转或者位置偏移而不是大面积正常、个别随机像素出错。到这里问题已经指向数据通路要么是SPI硬件外设发送过程出错要么是DMA搬运的数据本身就有问题。现象像是“极少量像素数据在传输中被污染”而不是“所有数据都错”。1.3 先把“伪随机”排除掉在进入具体排查前还有一类“看起来随机”的情况要先排除窗口地址设置错乱导致的GRAM地址跳变。如果fillRect函数中CASET0x2A和RASET0x2B的坐标参数传错或者0x2C命令后发送的数据量超过了当前窗口剩余像素数ILI9486会把后续数据写到下一行、下下行的GRAM地址表现出来就是矩形旁边或内部出现“幽灵像素”。判断方法很简单把fillRect的坐标改成多个不同位置、不同大小如果杂点总是出现在矩形角落、边界附近或者杂点位置和矩形坐标有固定对应关系那基本就是窗口参数问题。但实测下来杂点位置完全随机与矩形坐标没有固定关联可以放心地把这个问题排除掉集中精力查数据链路。2. 第一轮排查从软件SPI到逻辑分析仪2.1 软件SPI与硬件SPI的对照实验排查嵌入式问题最有效的手段就是做对照实验。第一步不是看代码而是先确认“屏本身能不能正常显示”。我把底层发送函数替换成GPIO模拟SPI也就是软件SPI其他初始化、fillRect逻辑完全不动。软件SPI的实现很简单就是拉高拉低SCK逐个bit把数据从MOSI送出去。结果刷屏速度虽然慢到肉眼可见但画面非常干净所有填充矩形都没有杂点。这个结果信息量很大ILI9486硬件本身没问题。初始化序列基本正确。DC引脚切换逻辑基本正确。问题出现在硬件SPI外设或与之配合的DMA路径上。软件SPI和硬件SPI在协议层面上应该完全等价真正不同的是硬件SPI外设的时钟频率、FIFO机制以及和DMA的配合方式。于是接下来把重点转向硬件SPI的配置参数。2.2 供电、接线与板级因素排查按照一般经验SPI屏出现随机杂点首先要怀疑供电和信号完整性。Waveshare 4.0 Shield由开发板的3.3V供电屏幕背光电流不小如果叠加在杜邦线上供电线阻可能导致屏端电压跌落进而造成面板内部逻辑不稳定。我做了两组实验第一组把Shield从叠插改为杜邦线连接尽量缩短线距电源脚就近并了一颗100uF电解电容和一颗0.1uF陶瓷电容。第二组在SCK和MOSI上分别串联33欧姆电阻试图抑制过冲和振铃。两组实验做完杂点现象没有本质改善只是在高频SPI时钟下波形振铃略有减弱。然后尝试降低SPI时钟频率。把SPI1分频从2分频改到8分频也就是从27MHz降到6.75MHz。这个改动效果非常明显杂点数量大幅减少但并没有完全消失。这个现象说明信号完整性是“放大器”而不是“根因”它会把潜在的数据错误放大但即使把时钟频率降到很保守的水平依然有一部分像素数据是错误的。到这里基本可以确定问题不在电源也不完全在信号质量而更可能出在“数据源”本身。2.3 逻辑分析仪下的SPI时序观察为了看清SPI字节流我接了一台逻辑分析仪采样率20MHz抓取CLK、MOSI、DC、CS四路信号。在27MHz SPI时钟下一个时钟周期约37ns20MHz采样率每周期只能采到不到一个点边沿情况完全看不清只能看到MOSI上有严重的振铃和上升沿过冲。降到6.75MHz后波形看着干净多了。用SPI协议解析功能解码MOSI数据流发现了一个非常有意思的现象DMA发送缓冲区里的数据和MOSI上实际发出的数据偶尔会有一个字节不一致。也就是说SPI发出的这个错误像素数据并不是在传输线上被干扰出来的而是DMA从内存读出来的时候数据就已经是错的。这个细节极其关键。它把问题从“信号完整性”直接拉到了“内存数据一致性”上。内存里的buffer明明是CPU刚刚填充好的为什么DMA读出来会有错回看工程代码发现main函数初始化里调用了SCB_EnableDCache()而F7的Cortex-M7是有D-Cache的。问题一下子清晰了。3. 第二轮排查像素格式、SPI参数和显示驱动细节3.1 ILI9486像素格式RGB565还是RGB666在深入Cache问题之前先把第3章里其他几个容易被忽略的细节说清楚。ILI9486是一颗18位色深的面板每个像素真正需要6bit红色、6bit绿色、6bit蓝色。SPI模式下可以通过0x3A寄存器选择16位或18位像素传输格式。0x3A写入0x55时后续像素数据按RGB565每像素2字节发送0x3A写入0x66时按RGB666每像素3字节发送。工程初始化代码写的是0x3A 0x66但fillRect和画点函数发送像素时用的是每像素2字节的RGB565颜色值。这直接导致一个像素的数据量不对面板期望3字节实际只给了2字节。这种情况下GRAM地址不会像坐标参数错乱那样大规模跳变而是会出现“一个像素数据占不满一个存储位宽、下一个像素数据紧接着拼上来”的问题。表现出来就是个别像素颜色错乱看起来像随机杂点。尤其当发送端buffer本身已经被Cache问题污染时两种错误叠加杂点问题更严重。修复很简单把0x3A统一改成0x55发送端固定按RGB565处理。这里想强调一个实践经验不要盲目套用网上针对ST7789或ILI9341的初始化代码ILI9486的像素格式要单独确认。最稳妥的做法是刷一张从左到右的彩色渐变图如果颜色过渡出现明显断层或者红绿蓝顺序错乱基本就是像素格式不匹配。3.2 SPI时钟频率与帧格式匹配STM32F767的SPI1挂在APB2总线上APB2时钟为54MHz。分频器可选2/4/8/16/32/64/128/256分频对应SPI时钟为27MHz、13.5MHz、6.75MHz等。ILI9486数据手册上SPI写时钟上限一般标注为15.15MHz实际工程中要留足余量尤其是使用Shield这种“板载走线加排针引出”的形式信号完整性远不如PCB直连。建议SPI时钟先设到6.75MHz即8分频保证功能正确后再逐步提到13.5MHz。27MHz对这个屏来说太过激进即使波形在短距离下勉强能看长时间运行或环境温度变化后容易出现偶发错误。另一个容易被忽略的是SPI帧格式。STM32 SPI外设可以配置为8位帧或16位帧。如果设置成16位帧那么每次写SPI_DR寄存器的16位数据会作为一个整体在时钟线上输出。对ILI9486来说命令阶段通常按字节处理一旦帧格式切错一个命令会被拆成半个字节或者两个命令被拼成一帧画面会整体错乱。建议统一使用8位帧像素颜色按两个连续字节发送也就是先发高字节、再发低字节。如果在使用16位帧那命令参数也需要补齐到16位对齐但这会让代码逻辑很容易出错没有必要。3.3 DC引脚切换与窗口地址的隐蔽问题DC引脚Data/Command是SPI屏一个特别容易出细节问题的地方。发送命令时DC为低电平发送像素数据时DC为高电平。如果在DMA发送一块像素数据的中途因为某个中断或事件去翻转DC引脚那后续数据会被屏当成命令解析画面直接崩。正确做法是在SetWindow之后把DC拉高然后连续发送整个矩形的像素数据期间绝不切换DC。DMA模式下尤其要注意DMA发送完成中断发生后不要立刻修改DC或CS状态要先确认SPI外设已经完全结束本次传输。窗口地址CASET/RASET也很隐蔽。fillRect函数内部通常这样写LCD_WriteCmd(0x2A); LCD_WriteData(x 8); LCD_WriteData(x 0xFF); LCD_WriteData((x w - 1) 8); LCD_WriteData((x w - 1) 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y 8); LCD_WriteData(y 0xFF); LCD_WriteData((y h - 1) 8); LCD_WriteData((y h - 1) 0xFF); LCD_WriteCmd(0x2C);如果x w - 1超过319或者y h - 1超过239GRAM地址会回卷后续数据写到矩形区域之外。这个现象的典型表现是在屏幕边缘出现“多出来的杂点”位置相对固定。排查时可以先做一次边界检查确保窗口参数合法。4. 锁定元凶STM32F7的D-Cache与DMA的一致性4.1 Cortex-M7的Cache与DMA冲突原理STM32F7系列用的是Cortex-M7内核相比F1、F4最大的变化之一就是增加了L1 I-Cache和D-Cache。D-Cache是一个回写型CacheCPU写内存的时候数据不一定立即写到SRAM物理地址而是先写到Cache里标记为“脏”等Cache行被替换或显式Clean时再写回SRAM。这个机制对普通CPU执行代码来说是透明的但一旦引入DMA问题就来了。DMA外设走的是AXI总线或AHB总线它直接访问物理SRAM地址完全不知道Cache的存在。如果CPU刚刚填充了一个显示缓冲区数据还在D-Cache里DMA立刻去读这块SRAM读出来的可能就是旧数据甚至是随机残留数据。对显示场景来说缓冲区是连续的一大块往里填数据时Cache会按32字节一行的粒度缓存。有的Cache行可能已经因为容量不足被写回了SRAM有的还没有。于是DMA读到的buffer里一部分是CPU写入的新数据一部分是SRAM里的旧垃圾这部分垃圾被当作像素数据发送到屏幕就成了随机杂点。这也是为什么画小矩形基本正常、画大矩形杂点数量明显增加小矩形的数据量小可能刚好落在未命中的Cache行里或者没有触发写回大矩形的数据量远超Cache容量中间必然有一部分数据还没写回SRAM。4.2 三个小实验直接锁定方向在逻辑分析仪发现“内存数据本身就是错的”之后我做了三个验证性实验彻底锁定了Cache一致性问题。实验一在DMA发送前加一行SCB_CleanDCache();也就是把D-Cache所有脏行强制写回SRAM。加了这行之后杂点直接消失。这个实验说明CPU写入缓冲区的数据已经完整同步到SRAM后SPI发出的字节全部正确。实验二把初始化里的SCB_EnableDCache()注释掉彻底关闭D-Cache。结果杂点也消失了显示完全正常。代价是整块SRAM的CPU读写性能下降但证明了问题不是SPI外设硬件缺陷。实验三把缓冲区改成volatile数组并且在填充每个像素后强制写内存比如给同一个颜色值写两遍。这样可以在不关Cache的情况下让更多数据被写穿到SRAM。实测杂点数量明显减少但依然没有完全消失因为Cache写回时机仍然不可控。三个实验交叉验证后结论已经非常清晰随机像素的直接原因是DMA读取了未写回的D-Cache数据。SPI时钟频率过高、像素格式不匹配只是让问题看起来更严重并不是根因。4.3 MPU配置Non-Cacheable缓冲区的完整做法修复方案
返回列表