关键词: NXP SDK、LPSPI、eDMA、双缓冲、D-Cache 一致性、NVIC 优先级、FreeRTOS 临界区
为什么要做这次重构?
当初系统被极其恶心的隐性 Bug 折磨:
音频爆音/断流: SAI 音频 DMA 也是硬实时,但 OLED 刷屏用的是阻塞式 SPI 发送,疯狂霸占 CPU,导致音频中断响应不及时。
系统假死: 配合 Flash 擦写时,如果在全局临界区里呆太久,FreeRTOS 的心跳直接停跳,系统休克。
最终目标: 让 CPU 只管往内存里画图,画完瞬间切指针,剩下的 SPI 搬运工作全部丢给 eDMA 在后台完成。
避坑指南 1:NXP SDK 的“全双工强迫症”
SDK 里的 LPSPI_MasterTransferEDMA 非常死板,就算 OLED 只需要 TX 疯狂发数据,底层状态机还是会傻傻地等 RX。如果不处理,就会喜提满屏的 400 (Busy) 连环报错和总线死锁。
解法:强行洗胃 + 喂垃圾桶
必须物理开辟一个跟显存一样大的 OLED_DUMMY_RX,塞给 rxData 堵住它的嘴。并且在每次发数据前,暴力清空 FIFO 和状态标志。
/* 扩大废件箱到 1024 字节,装下整屏的 RX 垃圾。必须 32 字节对齐! */
AT_NONCACHEABLE_SECTION_ALIGN(static uint8_t OLED_DUMMY_RX[OLED_VBYTE * OLED_WIDTH], 32);
/* 发送前的暴力洗肠子操作 */
LPSPI_FlushFifo(LPSPI3_PERIPHERAL, true, true);
LPSPI_ClearStatusFlags(LPSPI3_PERIPHERAL, kLPSPI_AllStatusFlag);
txXfer.rxData = OLED_DUMMY_RX; // 必须喂给它,不然 SDK 状态机卡死
txXfer.configFlags = kLPSPI_MasterPcs0;
避坑指南 2:Cortex-M7 的隐形杀手 —— D-Cache
把数据丢给 eDMA 后,屏幕居然撕裂、花屏了。
原因: CPU 画完的图像只留在了 D-Cache(高速缓存)里,还没来得及写回物理 RAM。而 eDMA 是个瞎子,它直接去物理 RAM 搬数据,搬出来的全是上一帧的过期垃圾。
解法:在触发 DMA 前,手动刷 Cache
只要用到 eDMA 读内存,永远别忘了 SCB_CleanDCache_by_Addr
/* 瞬间交换双缓冲指针 */
uint8_t (*temp)[OLED_WIDTH] = DrawBuffer;
DrawBuffer = DisplayBuffer;
DisplayBuffer = temp;
/* 致命遗漏点:确保 CPU 刚画好的 DisplayBuffer 数据真正落入物理内存 */
SCB_CleanDCache_by_Addr((uint32_t *)DisplayBuffer, sizeof(GRAM_Pool[0]));
/* 然后再触发 eDMA 发送 DisplayBuffer... */
避坑指南 3:NVIC 优先级
OLED 的 eDMA 搬运极快,中断触发极其频繁。如果它和 SAI(音频)的优先级一样高,依然会在微观上干扰音频输出。
记住系统资源的食物链: 音频是不可抢占的硬实时生命线,显示只是个后台苦力。
解法:拉开绝对的阶级差距。
/* SAI 音频 DMA 通道:必须给予高优先级特权! */
NVIC_SetPriority(DMA3_DMA19_IRQn, 5); // SAI1 TX
NVIC_SetPriority(DMA4_DMA20_IRQn, 5); // SAI2 RX
/* LPSPI3 OLED DMA 通道:允许被音频无情抢占 */
NVIC_SetPriority(DMA6_DMA22_IRQn, 10);
写给以后的自己:
底层驱动开发没有魔法。当觉得寄存器在“闹鬼”时,多想一想 Cache、DMA、中断嵌套和状态机缓存。