多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OLED一运行就死机?Bresenham 画线函数隐藏死循环

OLED一运行就死机?Bresenham 画线函数隐藏死循环 前言最近在 WS63OpenHarmony / 星闪平台上基于helloworld_oled样例做一个 SSD1306 OLED 的星空穿越动画。结果屏幕一直全黑程序运行起来没多久就报Oops:NMI崩了。折腾半天最后发现问题出在 OLED 驱动的一个画线函数里——一段从上游样例复制过来的 Bresenham 算法循环变量写错了只要起点和终点不是同一个点就会死循环。这篇博客完整记录排查与修复过程给同样踩坑的朋友提个醒。一、现象OLED 全黑什么都不显示把渲染循环里的清屏ssd1306_Fill(Black)注释掉现象完全一样串口日志显示程序能正常启动然后过一会儿就崩二、先确认程序到底有没有被调用排查第一步加日志确认调用链。在入口、建任务、I2C 初始化、OLED 初始化、主循环分别打printf[starsxun] stars_entry invoked [starsxun] task create returned [starsxun] stars_entry done [starsxun] StarsTask started [starsxun] i2c pin mode set (scl15, sda16) [starsxun] i2c master init ok [starsxun] ssd1306_Init returned [starsxun] stars_init done, entering loop结论很明确入口、创建任务、I2C 初始化、OLED 初始化全部成功卡点在进入主循环之后。关键是——主循环里frame0的日志一行都没打印。而这条日志写在清屏 绘制 刷屏之后说明程序死在第一次渲染里。三、崩溃现场看任务调度快照一眼就能发现问题Name TaskEntryAddr TID Priority Status StackSize ... CPUP 1.0s StarsTask 0x003495de 0xe 17 Running 0x1000 ... 100.0StarsTask的 CPU 占用100%——典型的死循环。紧随其后APP|exception:8000000c APP|Oops:NMI mcause:0x8000000c任务长时间不让出 CPU看门狗喂不上最终触发 NMI 异常复位。四、缩小范围对比正常和崩溃的样例手头有两个相似样例样例绘制方式结果stars只用ssd1306_DrawPixel逐点画星正常starsxun用ssd1306_DrawLine画线段做拖尾崩溃差异直指ssd1306_DrawLine。五、根因Bresenham 画线函数的循环变量用错了出问题的代码有删减voidssd1306_DrawLine(uint8_tx1,uint8_ty1,uint8_tx2,uint8_ty2,SSD1306_COLOR color){uint8_txx1;uint8_tyy1;/* ... */ssd1306_DrawPixel(x2,y2,color);while((x1!x2)||(y1!y2)){/* ← 判断的是函数参数 x1 / y1 */ssd1306_DrawPixel(x1,y1,color);/* ← 画的也是 x1 / y1 *//* ... */xsignX;/* ← 却只更新了局部变量 x *//* ... */ysignY;/* ← 只更新了局部变量 y *//* ... */}}问题一目了然while条件判断的是函数参数x1/y1循环体里更新的是局部变量x/yx1/y1从头到尾从来没变过。所以只要起点和终点不是同一个点while永远为真 →死循环。Bresenham 算法的本意是让一个当前点一步一步挪到终点循环变量应该是那个会移动的点这里是x/y而不是固定的起点。为什么上游样例没事helloworld_oled只调用DrawString显示文字走DrawChar → DrawPixel根本没调用DrawLinestars只用DrawPixel也就是说这个 bug 从一开始就潜伏在ssd1306_DrawLine里只是没人调用它所以从未暴露。一旦有人用它画线立刻中招。还有个细节如果起点和终点恰好相同x1x2 y1y2while条件为假反而不会死循环——这也是它长期潜伏、没人发现的原因之一。六、修复voidssd1306_DrawLine(uint8_tx1,uint8_ty1,uint8_tx2,uint8_ty2,SSD1306_COLOR color){int32_txx1;int32_tyy1;int32_tdeltaXabs((int32_t)x2-(int32_t)x1);int32_tdeltaYabs((int32_t)y2-(int32_t)y1);int32_tsignX((x1x2)?1:-1);int32_tsignY((y1y2)?1:-1);int32_terrordeltaX-deltaY;int32_terror2;while(1){ssd1306_DrawPixel((uint8_t)x,(uint8_t)y,color);if(xx2yy2){/* 到达终点就退出 */break;}error2error*DOUBLE;if(error2-deltaY){error-deltaY;xsignX;}if(error2deltaX){errordeltaX;ysignY;}}}改动点循环变量改为会移动的x/y到达终点xx2 yy2时退出x/y用int32_t避免uint8_t加减时的回绕0 - 1 255deltaX/deltaY显式转int32_t再取绝对值避免无符号提升带来的意外改完后星空动画正常显示。七、经验总结潜伏缺陷 ≠ 没问题。一个函数没人调用不代表它是对的。复制底层驱动 / 算法代码时最好过一眼。调试打点定位法。不确定程序有没有执行、卡在哪逐层打日志最直接。这次日志清晰告诉我们“初始化全过了死在第一次渲染”。善用对比。两个相似样例一个正常一个崩溃diff 一下往往能快速锁定可疑点。CPU 100% 看门狗 / NMI基本等于某个任务陷在死循环里。八、回馈上游追根溯源这份ssd1306.c来自上游仓库的helloworld_oled样例也就是说这个 bug 在上游源头就存在。已经在 GitCode 向上游HiSpark/fbb_ws63提交了修复 PRPR #391https://gitcode.com/HiSpark/fbb_ws63/merge_requests/391改一个while条件救活一块屏。
返回列表