串口跑一晚上丢 3 条日志;按键偶发无响应;电机超调后好几秒才下来。
这三个 bug 加起来,我花了 20 天才定位。而它们的根因是同一个——不是算法写错了,是两个模块之间的约定没定清楚。
这篇文章把这 6 个坑拆开,每个都给你可复现的症状、可验证的修法。
🎯 一、先看全景:6 个坑和它们的共同形状
#mermaid-svg-LzUwsCcFcFboQGR6{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LzUwsCcFcFboQGR6 .error-icon{fill:#552222;}#mermaid-svg-LzUwsCcFcFboQGR6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LzUwsCcFcFboQGR6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LzUwsCcFcFboQGR6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LzUwsCcFcFboQGR6 .marker.cross{stroke:#333333;}#mermaid-svg-LzUwsCcFcFboQGR6 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LzUwsCcFcFboQGR6 p{margin:0;}#mermaid-svg-LzUwsCcFcFboQGR6 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 .cluster-label text{fill:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 .cluster-label span{color:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 .cluster-label span p{background-color:transparent;}#mermaid-svg-LzUwsCcFcFboQGR6 .label text,#mermaid-svg-LzUwsCcFcFboQGR6 span{fill:#333;color:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 .node rect,#mermaid-svg-LzUwsCcFcFboQGR6 .node circle,#mermaid-svg-LzUwsCcFcFboQGR6 .node ellipse,#mermaid-svg-LzUwsCcFcFboQGR6 .node polygon,#mermaid-svg-LzUwsCcFcFboQGR6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LzUwsCcFcFboQGR6 .rough-node .label text,#mermaid-svg-LzUwsCcFcFboQGR6 .node .label text,#mermaid-svg-LzUwsCcFcFboQGR6 .image-shape .label,#mermaid-svg-LzUwsCcFcFboQGR6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-LzUwsCcFcFboQGR6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LzUwsCcFcFboQGR6 .rough-node .label,#mermaid-svg-LzUwsCcFcFboQGR6 .node .label,#mermaid-svg-LzUwsCcFcFboQGR6 .image-shape .label,#mermaid-svg-LzUwsCcFcFboQGR6 .icon-shape .label{text-align:center;}#mermaid-svg-LzUwsCcFcFboQGR6 .node.clickable{cursor:pointer;}#mermaid-svg-LzUwsCcFcFboQGR6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LzUwsCcFcFboQGR6 .arrowheadPath{fill:#333333;}#mermaid-svg-LzUwsCcFcFboQGR6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LzUwsCcFcFboQGR6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LzUwsCcFcFboQGR6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LzUwsCcFcFboQGR6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LzUwsCcFcFboQGR6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LzUwsCcFcFboQGR6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LzUwsCcFcFboQGR6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LzUwsCcFcFboQGR6 .cluster text{fill:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 .cluster span{color:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LzUwsCcFcFboQGR6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LzUwsCcFcFboQGR6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-LzUwsCcFcFboQGR6 .icon-shape,#mermaid-svg-LzUwsCcFcFboQGR6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LzUwsCcFcFboQGR6 .icon-shape p,#mermaid-svg-LzUwsCcFcFboQGR6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LzUwsCcFcFboQGR6 .icon-shape .label rect,#mermaid-svg-LzUwsCcFcFboQGR6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LzUwsCcFcFboQGR6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LzUwsCcFcFboQGR6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LzUwsCcFcFboQGR6 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
串口偶发丢帧
缓冲区归谁管head/tail 发布顺序
按键标志丢了
寄存器读改写谁有权改这一个 bit
状态机卡死
状态由谁改中断还是主循环
传感器读数冲顶
运算符号约定无符号相减回绕
高温段读数乱跳
数值宽度约定乘法溢出
帧校验一直错
参数约定初始值/异或/字节序
本质:模块间契约缺失
这些问题的共同特征是:编译器不报、单测不一定抓到、复现靠运气。因为它们是并发、类型、命名约定层面的,不是逻辑层面的。
而查这类 bug 的代价,可以看这张图:
#mermaid-svg-7eveb3guEBypOAvk{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7eveb3guEBypOAvk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7eveb3guEBypOAvk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7eveb3guEBypOAvk .error-icon{fill:#552222;}#mermaid-svg-7eveb3guEBypOAvk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7eveb3guEBypOAvk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7eveb3guEBypOAvk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7eveb3guEBypOAvk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7eveb3guEBypOAvk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7eveb3guEBypOAvk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7eveb3guEBypOAvk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7eveb3guEBypOAvk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7eveb3guEBypOAvk .marker.cross{stroke:#333333;}#mermaid-svg-7eveb3guEBypOAvk svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7eveb3guEBypOAvk p{margin:0;}#mermaid-svg-7eveb3guEBypOAvk .mermaid-main-font{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-7eveb3guEBypOAvk .exclude-range{fill:#eeeeee;}#mermaid-svg-7eveb3guEBypOAvk .section{stroke:none;opacity:0.2;}#mermaid-svg-7eveb3guEBypOAvk .section0{fill:rgba(102, 102, 255, 0.49);}#mermaid-svg-7eveb3guEBypOAvk .section2{fill:#fff400;}#mermaid-svg-7eveb3guEBypOAvk .section1,#mermaid-svg-7eveb3guEBypOAvk .section3{fill:white;opacity:0.2;}#mermaid-svg-7eveb3guEBypOAvk .sectionTitle0{fill:#333;}#mermaid-svg-7eveb3guEBypOAvk .sectionTitle1{fill:#333;}#mermaid-svg-7eveb3guEBypOAvk .sectionTitle2{fill:#333;}#mermaid-svg-7eveb3guEBypOAvk .sectionTitle3{fill:#333;}#mermaid-svg-7eveb3guEBypOAvk .sectionTitle{text-anchor:start;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-7eveb3guEBypOAvk .grid .tick{stroke:lightgrey;opacity:0.8;shape-rendering:crispEdges;}#mermaid-svg-7eveb3guEBypOAvk .grid .tick text{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;fill:#333;}#mermaid-svg-7eveb3guEBypOAvk .grid path{stroke-width:0;}#mermaid-svg-7eveb3guEBypOAvk .today{fill:none;stroke:red;stroke-width:2px;}#mermaid-svg-7eveb3guEBypOAvk .task{stroke-width:2;}#mermaid-svg-7eveb3guEBypOAvk .taskText{text-anchor:middle;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-7eveb3guEBypOAvk .taskTextOutsideRight{fill:black;text-anchor:start;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-7eveb3guEBypOAvk .taskTextOutsideLeft{fill:black;text-anchor:end;}#mermaid-svg-7eveb3guEBypOAvk .task.clickable{cursor:pointer;}#mermaid-svg-7eveb3guEBypOAvk .taskText.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-7eveb3guEBypOAvk .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-7eveb3guEBypOAvk .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163!important;font-weight:bold;}#mermaid-svg-7eveb3guEBypOAvk .taskText0,#mermaid-svg-7eveb3guEBypOAvk .taskText1,#mermaid-svg-7eveb3guEBypOAvk .taskText2,#mermaid-svg-7eveb3guEBypOAvk .taskText3{fill:white;}#mermaid-svg-7eveb3guEBypOAvk .task0,#mermaid-svg-7eveb3guEBypOAvk .task1,#mermaid-svg-7eveb3guEBypOAvk .task2,#mermaid-svg-7eveb3guEBypOAvk .task3{fill:#8a90dd;stroke:#534fbc;}#mermaid-svg-7eveb3guEBypOAvk .taskTextOutside0,#mermaid-svg-7eveb3guEBypOAvk .taskTextOutside2{fill:black;}#mermaid-svg-7eveb3guEBypOAvk .taskTextOutside1,#mermaid-svg-7eveb3guEBypOAvk .taskTextOutside3{fill:black;}#mermaid-svg-7eveb3guEBypOAvk .active0,#mermaid-svg-7eveb3guEBypOAvk .active1,#mermaid-svg-7eveb3guEBypOAvk .active2,#mermaid-svg-7eveb3guEBypOAvk .active3{fill:#bfc7ff;stroke:#534fbc;}#mermaid-svg-7eveb3guEBypOAvk .activeText0,#mermaid-svg-7eveb3guEBypOAvk .activeText1,#mermaid-svg-7eveb3guEBypOAvk .activeText2,#mermaid-svg-7eveb3guEBypOAvk .activeText3{fill:black!important;}#mermaid-svg-7eveb3guEBypOAvk .done0,#mermaid-svg-7eveb3guEBypOAvk .done1,#mermaid-svg-7eveb3guEBypOAvk .done2,#mermaid-svg-7eveb3guEBypOAvk .done3{stroke:grey;fill:lightgrey;stroke-width:2;}#mermaid-svg-7eveb3guEBypOAvk .doneText0,#mermaid-svg-7eveb3guEBypOAvk .doneText1,#mermaid-svg-7eveb3guEBypOAvk .doneText2,#mermaid-svg-7eveb3guEBypOAvk .doneText3{fill:black!important;}#mermaid-svg-7eveb3guEBypOAvk .doneText0.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneText0.taskTextOutsideRight,#mermaid-svg-7eveb3guEBypOAvk .doneText1.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneText1.taskTextOutsideRight,#mermaid-svg-7eveb3guEBypOAvk .doneText2.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneText2.taskTextOutsideRight,#mermaid-svg-7eveb3guEBypOAvk .doneText3.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneText3.taskTextOutsideRight{fill:black!important;}#mermaid-svg-7eveb3guEBypOAvk .crit0,#mermaid-svg-7eveb3guEBypOAvk .crit1,#mermaid-svg-7eveb3guEBypOAvk .crit2,#mermaid-svg-7eveb3guEBypOAvk .crit3{stroke:#ff8888;fill:red;stroke-width:2;}#mermaid-svg-7eveb3guEBypOAvk .activeCrit0,#mermaid-svg-7eveb3guEBypOAvk .activeCrit1,#mermaid-svg-7eveb3guEBypOAvk .activeCrit2,#mermaid-svg-7eveb3guEBypOAvk .activeCrit3{stroke:#ff8888;fill:#bfc7ff;stroke-width:2;}#mermaid-svg-7eveb3guEBypOAvk .doneCrit0,#mermaid-svg-7eveb3guEBypOAvk .doneCrit1,#mermaid-svg-7eveb3guEBypOAvk .doneCrit2,#mermaid-svg-7eveb3guEBypOAvk .doneCrit3{stroke:#ff8888;fill:lightgrey;stroke-width:2;cursor:pointer;shape-rendering:crispEdges;}#mermaid-svg-7eveb3guEBypOAvk .milestone{transform:rotate(45deg) scale(0.8,0.8);}#mermaid-svg-7eveb3guEBypOAvk .milestoneText{font-style:italic;}#mermaid-svg-7eveb3guEBypOAvk .doneCritText0,#mermaid-svg-7eveb3guEBypOAvk .doneCritText1,#mermaid-svg-7eveb3guEBypOAvk .doneCritText2,#mermaid-svg-7eveb3guEBypOAvk .doneCritText3{fill:black!important;}#mermaid-svg-7eveb3guEBypOAvk .doneCritText0.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneCritText0.taskTextOutsideRight,#mermaid-svg-7eveb3guEBypOAvk .doneCritText1.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneCritText1.taskTextOutsideRight,#mermaid-svg-7eveb3guEBypOAvk .doneCritText2.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneCritText2.taskTextOutsideRight,#mermaid-svg-7eveb3guEBypOAvk .doneCritText3.taskTextOutsideLeft,#mermaid-svg-7eveb3guEBypOAvk .doneCritText3.taskTextOutsideRight{fill:black!important;}#mermaid-svg-7eveb3guEBypOAvk .vert{stroke:navy;}#mermaid-svg-7eveb3guEBypOAvk .vertText{font-size:15px;text-anchor:middle;fill:navy!important;}#mermaid-svg-7eveb3guEBypOAvk .activeCritText0,#mermaid-svg-7eveb3guEBypOAvk .activeCritText1,#mermaid-svg-7eveb3guEBypOAvk .activeCritText2,#mermaid-svg-7eveb3guEBypOAvk .activeCritText3{fill:black!important;}#mermaid-svg-7eveb3guEBypOAvk .titleText{text-anchor:middle;font-size:18px;fill:#333;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-7eveb3guEBypOAvk :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}00:0003:0006:0009:0012:0015:0018:00head 发布顺序 寄存器读改写 状态机竞争 无符号相减 乘法溢出 CRC 参数 定位6 个 bug 的实际排查耗时
前两个占了一半以上,因为它们的症状是"偶发"——偶发 bug 的成本不在修,在复现。
🔧 二、环形缓冲:先写数据还是先动 head
串口收数据、DMA 采样、日志异步落盘——全是"数据来得比处理得快",标准解法都是环形缓冲。
它最常见的错只有一行顺序:
/* ❌ 错的写法 */
buf[head] = data;
head++; // 先发布
/* 消费者此刻可能:看到 head 变了,去读 buf[head-1],
但那一格可能还没真正写进去(编译器重排 / 总线延迟) */
/* ✅ 对的写法 */
buf[head] = data;
__DMB(); // 数据屏障(Cortex-M 上必要)
head++; // 最后才发布
为什么这个 bug 特别难查:它随机。跑一万次碰上一次,现场只有"偶尔丢一条日志",日志里还看不出是哪一条。
更隐蔽的是这四件事,一起犯才致命:
| head/tail 必须 volatile | 编译器把差值缓存在寄存器,中断改了主循环看不见 —— 循环永不退出 |
| 先写 buf 最后加 head | 读到未写入内容,随机丢帧 |
| 单生产单消费才无锁安全 | 多生产者时 head += len 是读改写,两个写者互相覆盖 |
| head 用 uint32_t 不用 uint8_t | 回绕后 head – tail 溢出,算不出真实用量 |
第四条我踩过:uint8_t 的 head/tail 配 256 容量,看着"刚好",但任何一次差值计算都会在回绕点错位。
主循环(消费者)ring bufferUART ISR(生产者)主循环(消费者)ring bufferUART ISR(生产者)#mermaid-svg-YK8QMosD0tAAKXqG{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YK8QMosD0tAAKXqG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YK8QMosD0tAAKXqG .error-icon{fill:#552222;}#mermaid-svg-YK8QMosD0tAAKXqG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YK8QMosD0tAAKXqG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YK8QMosD0tAAKXqG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YK8QMosD0tAAKXqG .marker.cross{stroke:#333333;}#mermaid-svg-YK8QMosD0tAAKXqG svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YK8QMosD0tAAKXqG p{margin:0;}#mermaid-svg-YK8QMosD0tAAKXqG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YK8QMosD0tAAKXqG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-YK8QMosD0tAAKXqG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YK8QMosD0tAAKXqG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-YK8QMosD0tAAKXqG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-YK8QMosD0tAAKXqG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-YK8QMosD0tAAKXqG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-YK8QMosD0tAAKXqG .sequenceNumber{fill:white;}#mermaid-svg-YK8QMosD0tAAKXqG #sequencenumber{fill:#333;}#mermaid-svg-YK8QMosD0tAAKXqG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-YK8QMosD0tAAKXqG .messageText{fill:#333;stroke:none;}#mermaid-svg-YK8QMosD0tAAKXqG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YK8QMosD0tAAKXqG .labelText,#mermaid-svg-YK8QMosD0tAAKXqG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-YK8QMosD0tAAKXqG .loopText,#mermaid-svg-YK8QMosD0tAAKXqG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-YK8QMosD0tAAKXqG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-YK8QMosD0tAAKXqG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-YK8QMosD0tAAKXqG .noteText,#mermaid-svg-YK8QMosD0tAAKXqG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-YK8QMosD0tAAKXqG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YK8QMosD0tAAKXqG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YK8QMosD0tAAKXqG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-YK8QMosD0tAAKXqG .actorPopupMenu{position:absolute;}#mermaid-svg-YK8QMosD0tAAKXqG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-YK8QMosD0tAAKXqG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-YK8QMosD0tAAKXqG .actor-man circle,#mermaid-svg-YK8QMosD0tAAKXqG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-YK8QMosD0tAAKXqG :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}❌ 错误顺序✅ 正确顺序head++ (先发布)buf[old] = data (后写入)读 head,发现变大了读 buf[old] → 拿到脏数据buf[old] = data内存屏障head++ (最后发布)读 head → 一定能看到完整数据
还有一个事务边界问题:字节流不等于报文。推荐长度前缀——先写长度再写内容,读侧先 peek 长度、攒够才提交消费。
/* ⚠️ peek 后长度不够时,tail 绝对不能动 */
uint16_t avail = head – tail;
if (avail < 2) return; // 连长度都不够,等
uint16_t len = *(uint16_t*)&buf[tail];
if (avail < 2 + len) return; // 帧不完整,等下一批
/* 到这里才真正消费 */
tail += 2 + len;
提前推 tail = 帧被截断。现象是"偶尔丢一条日志",但更常见的现象是解析器拿到半截帧后自己乱套,你会先怀疑协议、再怀疑对端,最后才发现是自己的缓冲。
⚙️ 三、寄存器读改写:REG |= MASK 的三步陷阱
REG |= MASK; // 实际是三条指令:读 → 或 → 写
如果中断里也改同一个寄存器(哪怕改的是别的 bit),主循环这三步中间被打断,中断的修改就被主循环那次写回覆盖掉了。
症状非常经典:“丢某一次按键”“丢某一个标志”,复现靠运气。
三种修法,按推荐度排:
| 用 BSRR 这类专用位操作寄存器 | 硬件级原子,一次写完成 | STM32 等有 BSRR 的芯片 ✅ |
| 进临界区(关中断) | 通用但影响实时性 | 没有专用寄存器时 |
| 读改写全部集中在同一处 | 靠架构约定,最脆弱 | 不推荐 |
这条和"模块契约"的关系是:这个 bit 到底谁有权改,必须在设计时就定死。 如果中断和主循环都能改同一个寄存器的不同 bit,那这个设计本身就该改。
🧭 四、状态机:卡死的不是逻辑,是 g_state 竞争
状态机失控的根源通常只有一句话:把"状态间怎么迁移"和"进入状态要做什么"混在一起写了。
正确流程是先列表,再选写法,最后写动作:
状态 × 事件表(– 表示不响应)
上电 启动 故障 复位
待机 — 运行 — 待机
运行 — — 故障 待机
故障 — — — 待机
写这张表的时候,悬着的问题会自动冒出来:故障能不能自恢复?待机收到启动但硬件没就绪,忽略还是转故障?
三种写法怎么选:
| switch 嵌 switch | 3~5 状态、事件少 | 非法事件被静默丢弃(本该报协议问题) |
| 表驱动(函数指针矩阵,NULL = 不响应) | 状态或事件 > 5 | 表即文档,评审对着表改;三四状态时属过度设计 |
| 带 on_enter/on_exit 的状态对象 | 稍大项目 | 推荐 ✅ |
on_enter/on_exit 治的是最易漏的一类 bug:进运行态忘开 PWM、退出忘关、下次进入残留上次状态。它把"状态要用的资源"和"状态本身"绑在一起,不给你忘的机会。
但这里有个致命的竞争:
#mermaid-svg-Ujf5CHGnsEW20waV{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ujf5CHGnsEW20waV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ujf5CHGnsEW20waV .error-icon{fill:#552222;}#mermaid-svg-Ujf5CHGnsEW20waV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ujf5CHGnsEW20waV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ujf5CHGnsEW20waV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ujf5CHGnsEW20waV .marker.cross{stroke:#333333;}#mermaid-svg-Ujf5CHGnsEW20waV svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ujf5CHGnsEW20waV p{margin:0;}#mermaid-svg-Ujf5CHGnsEW20waV defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-Ujf5CHGnsEW20waV g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-Ujf5CHGnsEW20waV g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-Ujf5CHGnsEW20waV g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-Ujf5CHGnsEW20waV g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-Ujf5CHGnsEW20waV g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-Ujf5CHGnsEW20waV .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-Ujf5CHGnsEW20waV .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-Ujf5CHGnsEW20waV .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-Ujf5CHGnsEW20waV .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Ujf5CHGnsEW20waV .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-Ujf5CHGnsEW20waV .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-Ujf5CHGnsEW20waV .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-Ujf5CHGnsEW20waV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ujf5CHGnsEW20waV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ujf5CHGnsEW20waV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ujf5CHGnsEW20waV .edgeLabel .label text{fill:#333;}#mermaid-svg-Ujf5CHGnsEW20waV .label div .edgeLabel{color:#333;}#mermaid-svg-Ujf5CHGnsEW20waV .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-Ujf5CHGnsEW20waV .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-Ujf5CHGnsEW20waV .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-Ujf5CHGnsEW20waV .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-Ujf5CHGnsEW20waV .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-Ujf5CHGnsEW20waV .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ujf5CHGnsEW20waV .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ujf5CHGnsEW20waV #statediagram-barbEnd{fill:#333333;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ujf5CHGnsEW20waV .cluster-label,#mermaid-svg-Ujf5CHGnsEW20waV .nodeLabel{color:#131300;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-Ujf5CHGnsEW20waV .note-edge{stroke-dasharray:5;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-note text{fill:black;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram-note .nodeLabel{color:black;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagram .edgeLabel{color:red;}#mermaid-svg-Ujf5CHGnsEW20waV #dependencyStart,#mermaid-svg-Ujf5CHGnsEW20waV #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-Ujf5CHGnsEW20waV .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ujf5CHGnsEW20waV :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
启动事件
故障事件
复位
心跳
待机
运行
故障
如果中断和主循环都能触发迁移:g_state 读改写竞争症状 = 卡死 / on_enter 跑两次
状态切换必须单任务触碰,或进临界区。 一旦两个上下文都能改 g_state,症状就是"偶发卡死在某个状态"、或者 on_enter 执行两次(导致 PWM 开了两次、计数器初始化两次)。
一个几乎零成本的加固:统计非法事件。
static void report_unhandled(state_t s, event_t e) {
g_unhandled_cnt++;
LOG_WARN("state=%s event=%s unhandled", state_name(s), event_name(e));
}
现场问题经常靠这个直接读出结论——“对面报文在某状态下一直没被处理”。
💾 五、滤波:先搞清楚要滤掉什么
这是选型的唯一判据,也是最常被跳过的一步:
| 随机噪声 | 电源纹波、量化误差,幅度小、每次不同 | 低通 / 滑动平均 |
| 野点 | 继电器动作、通信误码,偶发、幅度大 | 中值 |
套错了后果很明显:用滑动平均滤野点,野点会被"平均"成一个持续几拍的偏移,比原来看起来更糟。
| 中值(3 或 5 点) | 对野点免疫(窗口内单个野点必被丢) | 要比较;3 点三次比较即可,无需排序 |
| 滑动平均 | 滤随机噪声平稳 | 延迟 N/2 个采样周期,电流环/位置环吃相位裕度 |
| 一阶低通 | 单状态变量,内存最小 | 两个坑,见下 |
一阶低通的两个坑,都跟"约定"有关:
坑 1:无符号相减回绕。
/* ❌ uint16_t 相减:s < prev 时会回绕成巨大正数 */
uint16_t s = adc_read();
uint16_t out = prev + ((s – prev) * ALPHA >> 15); // 输出冲顶
/* ✅ 先转有符号 */
int32_t d = (int32_t)s – (int32_t)prev;
int32_t out32 = (int32_t)prev + (d * ALPHA >> 15);
prev = (uint16_t)out32;
症状是"传感器读数偶尔冲顶",而你会先去怀疑传感器坏了、线接触不良、ADC 通道串扰——排查方向从一开始就错了。
坑 2:alpha 与采样周期强绑定。
/* alpha = dt / (dt + RC) */
/* dt=1ms, RC=20ms → alpha ≈ 0.0476 → Q15 定点 = 1560 */
#define ALPHA_Q15 1560
采样频率一变(比如你为了省电把 ADC 从 1kHz 降到 100Hz),同一个 alpha 对应的时间常数直接变 10 倍,滤波器等于换了。所以 alpha 一定要按公式算,别手拍一个 0.1 了事。
比选算法更容易忽略的一条:采样别和干扰源同步。
滤波ADC 采样PWM 载波滤波ADC 采样PWM 载波#mermaid-svg-BB3QGQHIWZioFzo2{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BB3QGQHIWZioFzo2 .error-icon{fill:#552222;}#mermaid-svg-BB3QGQHIWZioFzo2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BB3QGQHIWZioFzo2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BB3QGQHIWZioFzo2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BB3QGQHIWZioFzo2 .marker.cross{stroke:#333333;}#mermaid-svg-BB3QGQHIWZioFzo2 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BB3QGQHIWZioFzo2 p{margin:0;}#mermaid-svg-BB3QGQHIWZioFzo2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BB3QGQHIWZioFzo2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BB3QGQHIWZioFzo2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BB3QGQHIWZioFzo2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BB3QGQHIWZioFzo2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BB3QGQHIWZioFzo2 .sequenceNumber{fill:white;}#mermaid-svg-BB3QGQHIWZioFzo2 #sequencenumber{fill:#333;}#mermaid-svg-BB3QGQHIWZioFzo2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BB3QGQHIWZioFzo2 .messageText{fill:#333;stroke:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BB3QGQHIWZioFzo2 .labelText,#mermaid-svg-BB3QGQHIWZioFzo2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .loopText,#mermaid-svg-BB3QGQHIWZioFzo2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BB3QGQHIWZioFzo2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BB3QGQHIWZioFzo2 .noteText,#mermaid-svg-BB3QGQHIWZioFzo2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BB3QGQHIWZioFzo2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BB3QGQHIWZioFzo2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BB3QGQHIWZioFzo2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BB3QGQHIWZioFzo2 .actorPopupMenu{position:absolute;}#mermaid-svg-BB3QGQHIWZioFzo2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BB3QGQHIWZioFzo2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BB3QGQHIWZioFzo2 .actor-man circle,#mermaid-svg-BB3QGQHIWZioFzo2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BB3QGQHIWZioFzo2 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}❌ 同频同相✅ 错开开关瞬间(纹波峰值)恰好此刻采样每一拍都是同一个偏移纹波变成稳定偏置,滤波当真实信号保留谷点谷点采样(纹波最小)噪声真的变随机了滤波才起作用
电机和电源项目这是必修课:采样点放 PWM 谷点,或者让采样频率与开关频率错开。 否则你的滤波器在滤一个根本不随机的东西。
🕸️ 六、查表插值:乘法溢出和"表怎么存"
非线性关系(热敏电阻、OCV-SOC、传感器原始值转物理量)的标准解法是标定点成表 + 线性插值。
四个实操要点,第一个最坑:
① 乘 dy 前必须先转 int64_t。
/* ❌ x 和 dy 都是 16 位,乘积溢出 */
int16_t y = y0 + (x – x0) * dy / dx;
/* ✅ 先转 64 位再乘 */
int32_t y = y0 + (int32_t)(((int64_t)(x – x0) * dy) / dx);
调试图省事用 int16_t,高温段读数开始乱跳——你会以为传感器坏了,换一颗,还是乱跳。
② 表点按曲率分布,别均匀取。 热敏电阻低温段曲率大,那里要密;接近直线的段可以疏。同样点数下误差最小。
③ const 放 flash 省 RAM,但有两个陷阱:部分 MCU 访问 flash 比 RAM 慢(高频 ISR 里查表要考虑搬 RAM);结构体放 flash 有对齐要求,某些芯片非对齐访问直接触发异常。
④ 反向查表别手写反函数。 给定输出找输入,按 y 排序后二分即可。手推反函数解析式在大范围上必然跑偏,实测表 + 查表准得多。
📋 七、CRC:能不能信对端的底线
CRC 的坑几乎全在"参数约定",不在算法。
/* 逐位实现:200 字节 = 1600 次移位
* 48MHz M0 上几十微秒 —— 放 ISR 会挤掉其他中断 */
uint16_t crc_bitwise(const uint8_t *p, uint16_t n) {
uint16_t crc = 0xFFFF;
while (n—) {
crc ^= *p++;
for (int i = 0; i < 8; i++)
crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
}
return crc;
}
/* 查表法:每字节 2 次查表 + 2 次移位,快 8~10 倍 */
uint16_t crc_table(const uint8_t *p, uint16_t n) {
uint16_t crc = 0xFFFF;
while (n—) crc = (crc >> 8) ^ tbl[(crc ^ *p++) & 0xFF];
return crc;
}
四件事,缺一不可:
① 表不能手抄。 256 项手写必错。用脚本生成,再用标准向量验过再进代码。
② 标准测试向量:"123456789" → 0x4B37(CRC-16/MODBUS)。
对不上,说明这三项里至少一项与对端不一致:
| 初始值 | 0x0000 vs 0xFFFF |
| 输出是否异或 | 0x0000 vs 0xFFFF |
| 字节序 | 高字节先 vs 低字节先 |
现场最典型的一句话:“我们帧校验一直错,但同样数据在 PC 上算是对的”——九成是这三项之一。
③ CRC 不背成帧的锅。 CRC 只发现传输错误,发现不了成帧错误。两条报文拼在一起算,当然错——但错在成帧,不在 CRC。
④ CRC 范围必须涵盖帧头到 CRC 前的全部字节。 只算数据区、漏掉地址/长度/功能码,这些字段被改坏了照样会执行错误动作。
💡 八、共同根因:模块间的"契约"没人写
回头看这六条,全是同一件事的不同表现:
#mermaid-svg-sPkdVnTtwmJCXexj{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sPkdVnTtwmJCXexj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sPkdVnTtwmJCXexj .error-icon{fill:#552222;}#mermaid-svg-sPkdVnTtwmJCXexj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sPkdVnTtwmJCXexj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sPkdVnTtwmJCXexj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sPkdVnTtwmJCXexj .marker.cross{stroke:#333333;}#mermaid-svg-sPkdVnTtwmJCXexj svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sPkdVnTtwmJCXexj p{margin:0;}#mermaid-svg-sPkdVnTtwmJCXexj g.classGroup text{fill:#9370DB;stroke:none;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-sPkdVnTtwmJCXexj g.classGroup text .title{font-weight:bolder;}#mermaid-svg-sPkdVnTtwmJCXexj .cluster-label text{fill:#333;}#mermaid-svg-sPkdVnTtwmJCXexj .cluster-label span{color:#333;}#mermaid-svg-sPkdVnTtwmJCXexj .cluster-label span p{background-color:transparent;}#mermaid-svg-sPkdVnTtwmJCXexj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sPkdVnTtwmJCXexj .cluster text{fill:#333;}#mermaid-svg-sPkdVnTtwmJCXexj .cluster span{color:#333;}#mermaid-svg-sPkdVnTtwmJCXexj .nodeLabel,#mermaid-svg-sPkdVnTtwmJCXexj .edgeLabel{color:#131300;}#mermaid-svg-sPkdVnTtwmJCXexj .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-sPkdVnTtwmJCXexj .label text{fill:#131300;}#mermaid-svg-sPkdVnTtwmJCXexj .labelBkg{background:#ECECFF;}#mermaid-svg-sPkdVnTtwmJCXexj .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-sPkdVnTtwmJCXexj .classTitle{font-weight:bolder;}#mermaid-svg-sPkdVnTtwmJCXexj .node rect,#mermaid-svg-sPkdVnTtwmJCXexj .node circle,#mermaid-svg-sPkdVnTtwmJCXexj .node ellipse,#mermaid-svg-sPkdVnTtwmJCXexj .node polygon,#mermaid-svg-sPkdVnTtwmJCXexj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sPkdVnTtwmJCXexj .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj g.clickable{cursor:pointer;}#mermaid-svg-sPkdVnTtwmJCXexj g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-sPkdVnTtwmJCXexj g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-sPkdVnTtwmJCXexj .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-sPkdVnTtwmJCXexj .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-sPkdVnTtwmJCXexj .dashed-line{stroke-dasharray:3;}#mermaid-svg-sPkdVnTtwmJCXexj .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-sPkdVnTtwmJCXexj #compositionStart,#mermaid-svg-sPkdVnTtwmJCXexj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #compositionEnd,#mermaid-svg-sPkdVnTtwmJCXexj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #dependencyStart,#mermaid-svg-sPkdVnTtwmJCXexj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #dependencyStart,#mermaid-svg-sPkdVnTtwmJCXexj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #extensionStart,#mermaid-svg-sPkdVnTtwmJCXexj .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #extensionEnd,#mermaid-svg-sPkdVnTtwmJCXexj .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #aggregationStart,#mermaid-svg-sPkdVnTtwmJCXexj .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #aggregationEnd,#mermaid-svg-sPkdVnTtwmJCXexj .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #lollipopStart,#mermaid-svg-sPkdVnTtwmJCXexj .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj #lollipopEnd,#mermaid-svg-sPkdVnTtwmJCXexj .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-sPkdVnTtwmJCXexj .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-sPkdVnTtwmJCXexj .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sPkdVnTtwmJCXexj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sPkdVnTtwmJCXexj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sPkdVnTtwmJCXexj :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
缓冲区契约
+谁写 head
+谁读 tail
+发布顺序
+多生产者怎么处理
寄存器契约
+这个 bit 谁有权改
+是否需要原子操作
+临界区边界
状态契约
+哪个上下文能迁状态
+on_enter 的幂等性
+非法事件怎么报
数值契约
+有符号还是无符号
+乘法用多宽
+Q 格式定义
协议契约
+CRC 参数三项
+校验覆盖范围
+字节序
运行时不确定性
静默错误
互通失败
这五份契约,没有一份写在代码里。
它们存在于某个人的脑子里,或者一次口头讨论里。人一走,契约就丢了,然后下一个人重新踩一遍。
#mermaid-svg-yIcHVNaIwAcAZQEL{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yIcHVNaIwAcAZQEL .error-icon{fill:#552222;}#mermaid-svg-yIcHVNaIwAcAZQEL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yIcHVNaIwAcAZQEL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yIcHVNaIwAcAZQEL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yIcHVNaIwAcAZQEL .marker.cross{stroke:#333333;}#mermaid-svg-yIcHVNaIwAcAZQEL svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yIcHVNaIwAcAZQEL p{margin:0;}#mermaid-svg-yIcHVNaIwAcAZQEL .pieCircle{stroke:#000000;stroke-width:2px;opacity:0.7;}#mermaid-svg-yIcHVNaIwAcAZQEL .pieOuterCircle{stroke:#000000;stroke-width:1px;fill:none;}#mermaid-svg-yIcHVNaIwAcAZQEL .pieTitleText{text-anchor:middle;font-size:25px;fill:#000000;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-yIcHVNaIwAcAZQEL .slice{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;fill:#000000;font-size:17px;}#mermaid-svg-yIcHVNaIwAcAZQEL .legend text{fill:#000000;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:17px;}#mermaid-svg-yIcHVNaIwAcAZQEL :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}45%30%15%10%20 天排查时间的真实去向复现偶发问题排除错误方向真正定位与修复写回归测试
只有 15% 花在修复上。 剩下 85% 是在"偶发"和"误判方向"里打转——而无符号相减、乘法溢出这两个,会让你先怀疑硬件,那条弯路特别长。
🔧 九、可以立刻加进自查清单的 5 条
不用改架构,这 5 条今天就能过一遍:
| 1 | 环形缓冲写入顺序 | buf 写在 head++ 之前?有内存屏障? |
| 2 | 寄存器并发修改 | 同一寄存器有多个上下文改?用了 BSRR 还是临界区? |
| 3 | 状态非法事件统计 | 有没有 report_unhandled 计数 + 日志带状态名 |
| 4 | 滤波符号与定点 | 无符号相减前转有符号了?alpha 按公式算的? |
| 5 | CRC 参数与范围 | 标准向量验过?范围含帧头/长度/功能码? |
顺带说一句别花时间的方向:红黑树、AVL、动态规划、Dijkstra、通用哈希表、自己实现密码学。不是不重要,是主场不在你的板子上。
⚠️ 十、最后一句
写了这些代码之后我有个判断:
让固件出问题的,很少是算法不够聪明,多半是它和周围模块的关系没理清。
算法可以抄,可以查,可以让 AI 给你写十个版本。但"这个缓冲区归谁管"“这个 bit 谁有权改”“这个 alpha 对应多长的采样周期”——这些没人能替你定,因为它们是你这个系统的约定。
把这些约定写下来,比学任何一个算法都省时间。
本文由公开技术资料与工程实践整理改写。代码示例为通用 C 写法,不绑定厂商库;Cortex-M 上的内存屏障与原子性细节请对照具体型号的编程手册。



