ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于STM32和HX711的四路公路超重检测系统设计与实现

基于STM32和HX711的四路公路超重检测系统设计与实现

简介:面向嵌入式开发学习者与交通监测类课程设计需求,这套基于STM32的公路四路超重检测系统工程资料,围绕多路称重采集、阈值判断、声光报警及联网上传等环节展开,可用于毕业设计、课程实践或原型验证。压缩包共306个文件,约10.31MB,包含57个c源文件和63个h头文件,以及o、crf、d等编译中间文件,另有uvprojx工程、sct分散加载、map与lst映射清单等,便于直接打开Keil工程复现和二次开发。已有333人学习下载。资料保留较完整的工程结构与外设驱动代码,涵盖ADC、定时器、串口通信及物联网协议相关实现,读者可据此梳理四路检测的数据流、报警逻辑和联网监控思路,也能参考工程配置与编译产物进行排错和功能扩展。

1. 去年帮人看一个公路治超点的老设备,问题不是秤不准

去年帮人看一个公路治超点的老设备,问题不是秤不准,而是四条车道的读数互相干扰:一号道刚过完一辆六轴车,三号道的显示还没回零,值班的人只能凭经验猜哪条是真的。这套基于 STM32 的公路四路超重检测系统,要解决的就是这种“多路同时在线”的现场问题——四路称重通道各自独立采集、独立标定、独立判定超限,再由主控统一打包,通过联网模块把车重、时间、车道号一起送到后台。它适合正在做公路超重检测方向毕设或课设、需要一套能从采集跑通到上报的完整方案的人,也适合要把样机装到路边实测的工程场景。下面按需求、选型、架构、实现、避坑、验收的顺序,把这套系统拆开讲。

2. 需求拆解与器件选型:四路通道、量程和采样率怎么定

2.1 先确认“四路”指什么:四条车道,还是同一秤台的四个角

这是动手前必须问清楚的一件事。两种理解对应的软件结构完全不同:四条车道是四个互不相干的独立通道,各自有零点、灵敏度系数和超重阈值;同一秤台四个角则是四个分量,需要先求和得到总重,再用四角差值判断车辆是否偏载。我在方案里把两种模式都留了口子,用一个配置字节ch_mode切换,0 表示独立通道,1 表示四点合成,这样后面换场景不用改代码结构,只改配置。

除了通道定义,还要把性能边界写死,否则答辩现场一问就露怯。公路治超点常见两种工况:一种是引导进站的低速通过,车速 5 到 15 公里每小时;另一种是完全静态停车称重。低速工况下,一辆车压在 1.5 米长的秤板上,有效信号窗口大概 0.3 到 0.4 秒,如果采样率只有 10Hz,窗口里只能拿到三四个点,峰值根本抓不准。所以这套方案定位在低速与静态称重,动态精度按满量程 ±3% 到 ±5% 验收,静态精度可以做到 ±0.5%。想做成高速不停车称重,必须上专用的动态称重传感器和更高的采样率,那是另一个量级的预算,不在本方案范围内。

功能清单我一般按下面几条锁死,后面所有模块都围绕它展开:四路独立采集,单路断线不影响其余三路;车辆到位触发、离开结束,一次通过生成一条完整记录;按轴重和总重双阈值判定超限,本地声光报警并输出一路继电器信号;本地至少存一万条记录,联网上报,断网期间不丢数据;现场可标定,标定参数掉电不丢,还要能防止误改。工作温度按零下 20 到 60 摄氏度设计,传感器接线做防雷和屏蔽。

2.2 主控与 ADC 选型:为什么是四片 HX711,而不是片内 12 位 ADC

先说结论:主控用 STM32F103RCT6,称重前端用四片 HX711,每片对应一路。这个组合不是最先进的,但在四路低速称重场景下是最省心、最容易复现的。

对比项STM32F103RCT6STM32F407VET6
主频72MHz168MHz
Flash / RAM256KB / 48KB512KB / 192KB
串口数量56
片上 ADC12 位12 位
适合场景裸机时间片调度,四路 IO 模拟时序需要网络协议栈、多任务或更强滤波算力

F103 的 48KB 内存跑一个裸机时间片调度器加一个环形缓冲队列绰绰有余,省下来的成本可以贴到传感器和防护上。真正要谨慎的是 ADC 这一环,很多同学图省事直接用片内 12 位 ADC 接应变桥,结果量程 30 吨时最小分辨力大概 7 公斤,空载时噪声就有十几公斤,零点一直跳。这不是代码问题,是分辨率不够。

方案分辨率有效通道输出速率MCU 开销说明
HX711 × 424 位410Hz / 80Hz低,IO 模拟时序通道间物理隔离,无串扰,成本低
ADS125624 位8 路(差分 4 路)最高 30kSPS中,SPI 加 DRDY 中断速率高,但要外加基准和模拟前端
片内 12 位 ADC12 位多路快低分辨力不足,只适合做粗略检测

HX711 的短板是速率,80Hz 输出时内部增益和滤波切换会带来更大噪声,实测零点抖动比 10Hz 时明显。所以我的做法是:静态称重走 10Hz,低速动态走 80Hz,由配置项决定,标定时按实际工况选,不要一套参数打天下。

2.3 触发、执行与联网部件:把外围清单一次列全

触发用一对地感线圈加检测器,检测器输出干接点,经光耦隔离后进 GPIO,这样线圈那边的干扰不会直接打到主控。线圈负责“车来了”,红外对射负责“车走了”,两个信号配合,比单靠重量阈值判断车辆离开靠谱得多——重车停在秤上不动时,光看重量是判断不出它走没走的。

执行部分包括一路继电器输出(可接道闸或外部报警器)、一个有源蜂鸣器和四个车道状态指示灯。本地显示用 0.96 寸 OLED,只显示四路当前重量和状态,不做过多的界面,现场人员看得懂就行。

联网这块有两种常见做法:WiFi 透传模块走 AT 指令,适合有现成热点的实验室和校园场景;4G Cat.1 模块走串口 AT 或 PPP,适合真正装在路边的场景。两者对主控来说都是串口,所以底层驱动写成同一套串口收发加状态机,换模块只改初始化指令表,不动上层协议。存储用 W25Q64 存历史记录,AT24C02 存标定参数做双备份,多花两块钱,能省掉很多“参数丢了要重新标定”的麻烦。

3. 总体架构与通信协议设计:四层链路和数据帧怎么落地

3.1 四层架构与各模块的职责边界

整套系统按感知层、控制层、传输层、应用层划分,这个划分不是为了好看,是为了让每个人接手时知道改哪一层不会牵连别处。

层级主要模块职责对外接口
感知层称重传感器、地感线圈、红外对射把重量和车辆位置变成电信号模拟毫伏信号、开关量
控制层STM32 主控、HX711、OLED、报警输出采集、滤波、标定、判定、本地显示串口、GPIO、SPI、I2C
传输层WiFi / 4G 模块把记录打包上传,接收下行参数UART AT 指令
应用层后台服务与数据库入库、查询、统计、告警推送MQTT / HTTP

控制层内部再分成驱动、业务、应用三段。驱动层只管时序,比如 HX711 读数、Flash 读写、串口收发;业务层管滤波、标定换算、超重判定状态机;应用层管调度,用 SysTick 做 1ms 基准,主循环里按时间片轮询各个任务。这里没有上 RTOS,原因是任务数量少、实时性要求不高,裸机时间片更容易调试,出问题的时候一眼能看出是哪个任务超时。

调度周期我这样分配:HX711 采集任务每 10ms 跑一次,四路轮询;滤波与判定任务每 50ms 跑一次;OLED 刷新 200ms;联网心跳 30 秒;记录落盘在判定完成后立即触发。这套节奏在 F103 上跑下来 CPU 占用不到三成,留足了余量。

3.2 数据帧格式与 CRC16 校验:上下行都用同一套壳

本地模块之间、设备与后台之间,我习惯用同一套帧结构,调试时用串口助手就能看,不用两套解析逻辑。

偏移长度字段说明
02帧头固定 0xAA 0x55
21协议版本当前 0x01,方便以后扩展
31命令字0x01 实时数据,0x02 记录补传,0x10 参数下发,0x11 参数回读
41通道掩码bit0 到 bit3 对应第 1 到第 4 路
52负载长度小端,最大 512 字节
7N负载TLV 结构,重量用 int32 定点,单位 0.01kg
7+N2CRC16MODBUS 多项式 0xA001,低字节在前

重量为什么要用定点而不用 float?因为浮点在串口传输和存储时字节序容易出岔子,int32 定点表示 0.01kg,30 吨就是 3000000,离溢出还远得很,解析也简单。CRC 校验代码如下,初值 0xFFFF,多项式 0xA001,算完低字节先发。

/* MODBUS CRC16:初值 0xFFFF,多项式 0xA001(0x8005 反转) */ static uint16_t crc16_modbus(const uint8_t *p, uint16_t len) { uint16_t crc = 0xFFFF; while (len--) { crc ^= *p++; for (uint8_t i = 0; i < 8; i++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; /* 发送时先发低字节,再发高字节 */ }

这个函数在接收端要再算一遍,和帧尾的两个字节比对,不一致就整帧丢弃并累加错误计数。错误计数超过阈值时,我一般会触发一次模块重连,因为连续 CRC 错通常意味着波特率漂了或者线路接触不良,而不是偶发干扰。

3.3 本地存储分区与掉电保护:参数双备份,记录走环形区

W25Q64 有 8MB,按下面这样分区,边界留出余量,以后加字段不用重排。

起始地址大小用途校验方式
0x0000004KB参数备份 A魔数 0x5A5A + CRC16
0x0010004KB参数备份 B同上
0x002000512KB称重记录环形区,每条 32 字节,共 16384 条每条末尾 CRC8
0x0820004KB运行日志与异常计数无

参数区做双备份的意义在于:写参数时先擦 B 区写入新值并校验,成功后再写 A 区。如果写到一半掉电,下次上电读到 A 区校验失败就回退到 B 区,两个都坏才用默认值。这个逻辑看着简单,但真到了现场,一次意外断电就能把标定系数写成 0xFF,重新标定要封路半小时,代价比多存 4KB 高得多。

记录区用环形写入,写指针存在参数区里,每写一条就更新一次指针。环形区满了就覆盖最老的记录,同时置一个溢出标志。记录结构里带序号和时间戳,补传时后台按序号去重,避免网络重发导致重复入库。

4. 下位机实现:四路采集、动态滤波与超重判定的代码骨架

4.1 四路 HX711 的时序驱动与轮询调度

HX711 的时序不复杂,但有几个参数不能错:DOUT 变低表示数据就绪,SCK 需要给 25 个脉冲,前 24 个移出数据(补码形式),第 25 个脉冲决定下一次的通道和增益。四路就是四组 DOUT 和 SCK,共占用 8 个 IO。

#define HX711_CH_NUM 4 #define HX711_TIMEOUT 30 /* ms,超过判定该路断线 */ typedef struct { GPIO_TypeDef *port_dout; /* 数据脚,输入 */ uint16_t pin_dout; GPIO_TypeDef *port_sck; /* 时钟脚,输出 */ uint16_t pin_sck; int32_t offset; /* 空载零点原始值 */ float scale; /* 每公斤对应的原始计数 */ int32_t raw; /* 最近一次原始读数 */ float weight_kg; /* 换算后的重量 */ uint8_t online; /* 1 在线,0 断线 */ } Hx711Ch_t; /* 读取一路原始值,返回 INT32_MIN 表示超时断线 */ static int32_t hx711_read_raw(Hx711Ch_t *ch) { uint32_t t0 = HAL_GetTick(); /* 等待 DOUT 拉低,说明转换完成 */ while (HAL_GPIO_ReadPin(ch->port_dout, ch->pin_dout) == GPIO_PIN_SET) { if (HAL_GetTick() - t0 > HX711_TIMEOUT) { ch->online = 0; return INT32_MIN; } } int32_t v = 0; for (uint8_t i = 0; i < 24; i++) { HAL_GPIO_WritePin(ch->port_sck, ch->pin_sck, GPIO_PIN_SET); delay_us(1); /* SCK 高电平需保持 0.2~0.5us */ v = (v << 1) | (HAL_GPIO_ReadPin(ch->port_dout, ch->pin_dout) == GPIO_PIN_SET); HAL_GPIO_WritePin(ch->port_sck, ch->pin_sck, GPIO_PIN_RESET); delay_us(1); } /* 第 25 个脉冲:选择下一次为 A 通道增益 128 */ HAL_GPIO_WritePin(ch->port_sck, ch->pin_sck, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(ch->port_sck, ch->pin_sck, GPIO_PIN_RESET); if (v & 0x00800000) { /* 24 位负数补码符号扩展 */ v |= 0xFF000000; } ch->online = 1; return v; }

这段代码有两个地方值得多说一句。一是delay_us(1)不能省成几个__NOP(),72MHz 下一个 NOP 才十几纳秒,凑不够 HX711 要求的 0.2 微秒高电平,读出来的数据会整体偏移甚至全 0,这个坑我在第一版上踩过,示波器一量才发现 SCK 是尖脉冲。二是超时返回必须区分于正常读数,我用 INT32_MIN 做哨兵值,上层看到它就跳过这一路,绝不能让断线的通道参与平均值计算,否则零点会被拉偏。

轮询调度放在 10ms 的时间片里,一次只读一路,四路轮完是 40ms。HX711 在 80Hz 模式下单次转换 12.5ms,40ms 轮一圈刚好跟得上,不会丢数据。如果开 10Hz 模式,单次 100ms,轮询周期要相应拉长到 400ms 以上,这时候HX711_TIMEOUT也得跟着放大,否则会误判断线。

4.2 动态称重的滤波与峰值提取

原始读数不能直接用,空载时会有几十个 counts 的抖动,重车压上去又叠加了车辆振动。我的处理链是三段:先做 5 点中值滤波去掉脉冲干扰,再做一阶低通平滑,最后在触发窗口内取峰值。

#define MED_N 5 #define LPF_ALPHA 0.25f /* 越小越平滑,但响应变慢 */ typedef struct { int32_t med_buf[MED_N]; uint8_t med_idx; float lpf; /* 低通输出 */ int32_t peak; /* 本次窗口峰值 */ uint16_t peak_hold; /* 峰值附近保持计数 */ } Filter_t; /* 中值滤波:5 点排序取中间值,抗脉冲干扰 */ static int32_t median5(int32_t *buf) { int32_t t[MED_N]; for (uint8_t i = 0; i < MED_N; i++) t[i] = buf[i]; for (uint8_t i = 0; i < MED_N - 1; i++) { for (uint8_t j = i + 1; j < MED_N; j++) { if (t[j] < t[i]) { int32_t tmp = t[i]; t[i] = t[j]; t[j] = tmp; } } } return t[MED_N / 2]; } /* 每 50ms 调用一次,输入换算后的公斤值 */ static void filter_update(Filter_t *f, float kg, uint8_t vehicle_in) { f->med_buf[f->med_idx] = (int32_t)(kg * 100.0f); /* 转成 0.01kg 整数 */ f->med_idx = (f->med_idx + 1) % MED_N; float med = median5(f->med_buf) / 100.0f; f->lpf += LPF_ALPHA * (med - f->lpf); if (vehicle_in) { if ((int32_t)(f->lpf * 100) > f->peak) { f->peak = (int32_t)(f->lpf * 100); f->peak_hold = 0; } else if (f->peak_hold < 20) { f->peak_hold++; /* 峰值后 1 秒内不更新,防止抖动误判 */ } } }

中值滤波的窗口取 5 是折中:取 3 点去不掉连续的干扰,取 7 点会把真实的上升沿削平。低通系数 0.25 对应的时间常数大约 3 个采样周期,在 50ms 周期下是 150ms,跟得上车辆压上来的速度,又不至于把振动放大。峰值保持段加了 1 秒的锁定时间,原因是车辆在秤上轻微晃动时,重量会在峰值附近反复波动,不加锁定的话峰值会一直往上爬,判出来的重量比实际偏高。

峰值取的是“轴重”还是“总重”,取决于秤板长度和车辆轴距。秤板短于轴距时,一次只压一根轴,峰值就是单轴重,多轴车要按轴依次累加;秤板长于整车时,峰值直接就是总重。这两种情况的判定逻辑不一样,我在配置里加了weigh_mode,答辩时如果老师问“六轴车怎么算总重”,这就是回答的落点。

4.3 超重判定状态机与报警输出

判定不能只看一个瞬时值,要用状态机把“车来了、正在称、称完了、判超限、上报”这几步串起来,否则一辆车会被重复判定好几次,后台收到的记录数对不上实际车流量。

typedef enum { ST_IDLE = 0, /* 等待车辆触发 */ ST_SAMPLING, /* 车辆在秤,采集峰值 */ ST_SETTLE, /* 车辆离开,等待重量回落 */ ST_JUDGE, /* 判定超限 */ ST_REPORT /* 生成记录并入队 */ } WeighState_e; typedef struct { uint32_t limit_kg; /* 超重阈值,单位 kg */ uint16_t deadband_pct; /* 死区百分比,默认 5,防止临界抖动 */ uint8_t confirm_cnt; /* 连续超限次数确认,默认 3 */ } Threshold_t; static void judge_task(void) { static WeighState_e st = ST_IDLE; static uint8_t over_cnt = 0; Threshold_t *th = &g_threshold; switch (st) { case ST_IDLE: if (g_vehicle_in) { g_filter.peak = 0; st = ST_SAMPLING; } break; case ST_SAMPLING: if (!g_vehicle_in) { st = ST_SETTLE; g_settle_ms = 0; } break; case ST_SETTLE: g_settle_ms += 50; if (g_settle_ms > 500) { st = ST_JUDGE; } break; case ST_JUDGE: { uint32_t peak_kg = g_filter.peak / 100; /* 只有超过阈值加上死区,才累加超限计数 */ if (peak_kg > th->limit_kg * (100 + th->deadband_pct) / 100) { if (++over_cnt >= th->confirm_cnt) { g_alarm_on = 1; /* 声光报警 + 继电器输出 */ g_record.over_flag = 1; } } else { over_cnt = 0; g_record.over_flag = 0; } st = ST_REPORT; break; } case ST_REPORT: record_push(&g_record); /* 写入环形队列 */ over_cnt = 0; st = ST_IDLE; break; default: st = ST_IDLE; break; } }

死区和连续确认这两层是为了对付临界车。一辆限重 30 吨的车,实际称出来 30.2 吨,如果直接判超限,司机和值班员都会来找你。加 5% 死区和连续 3 次确认之后,判出来的超限基本都在 31.5 吨以上,争议少很多。报警输出用g_alarm_on控制,持续 5 秒后自动关闭,防止蜂鸣器一直响。

4.4 上报队列与断网补传

上报队列放在 RAM 里做一个 32 条的环形缓冲,每条记录生成后先入队,联网任务从队头取。发送成功才出队,失败就留在队里等下一轮。队列满的时候,最老的一条转存到 Flash 记录区,保证不丢。

#define QUEUE_SIZE 32 typedef struct { uint32_t seq; /* 记录序号,后台按此去重 */ uint32_t ts; /* Unix 时间戳 */ uint8_t ch; /* 通道号 1~4 */ int32_t weight01kg; /* 重量,单位 0.01kg */ uint8_t over_flag; /* 1 超限 */ } Record_t; static Record_t q_buf[QUEUE_SIZE]; static uint8_t q_head, q_tail, q_count; static uint8_t record_push(const Record_t *r) { if (q_count >= QUEUE_SIZE) { record_save_flash(&q_buf[q_tail]); /* 队列满,落盘保底 */ q_tail = (q_tail + 1) % QUEUE_SIZE; q_count--; } q_buf[q_head] = *r; q_head = (q_head + 1) % QUEUE_SIZE; q_count++; return 1; }

补传逻辑放在联网任务里:连上后台之后,先读 Flash 里未确认的记录,按序号从小到大发,每发一条等一次 ACK,ACK 里的序号对得上才把 Flash 里该条标记为已上传。这里有个细节,标记已上传不要擦整块,用一个 32 位的位图记录哪些条已确认,位图满了再统一整理,这样能少擦很多次 Flash。

5. 常见问题与避坑:五个最容易在现场翻车的点

5.1 零点漂移:上电读数正常,跑两小时重量越飘越高

现象是空载时显示从 0 慢慢涨到几十公斤,重启后又归零。原因通常有两个:一是 HX711 的供电和主控共用一路 3.3V,数字部分电流波动通过地线串到模拟端;二是软件上只在开机时取了零点,之后一直用固定值减。解决的办法是硬件上给 HX711 的 AVDD 单独走 LC 滤波,地线单点汇到电源入口;软件上加自动零点跟踪,车辆离开后如果连续 30 秒重量都在满量程 2% 以内,就缓慢修正零点,每次只修一个很小的步长,避免把真实的小重量也修掉。跟踪步长我一般设成每 10 秒修正 0.5kg,慢慢纠,不激进。

5.2 四路串扰:压一号道,三号道跟着跳数

现象很吓人,以为是软件读了别的通道。实际排查下来,九成是四路传感器的屏蔽线拧在一起走线,或者四片 HX711 共用一根 SCK 时钟。SCK 是方波,频率虽然不高,但走线长、回路大,很容易耦合到相邻通道的模拟输入上。解决分三步:每路传感器用独立屏蔽双绞线,屏蔽层单端接地;四片 HX711 的 SCK 各走各的,绝不在板上并联成一根总线;在 HX711 的输入端并 0.1uF 电容做差模滤波。改完之后再压单路,其余三路的跳数应该在满量程 0.1% 以内。

5.3 联网模块假在线:心跳正常,数据死活发不出去

现象是串口每 30 秒都能收到模块返回的“已连接”,但上报的数据后台一条也收不到。原因是 AT 指令的返回只代表模块和基站或路由建立了链路,不代表上层协议通。这种情况我一般是三层排查:先看模块返回里有没有分配到 IP,再看能不能 ping 通后台地址,最后看协议层有没有收到后台的心跳应答。软件上不能只信模块的“连接成功”提示,必须自己发一条心跳帧并等待后台回包,连续三次没回包就判定链路失效,主动重连。这个超时重连逻辑写进代码里,比在现场盯着串口助手看要省事得多。

5.4 看门狗复位:正好把刚采到的数据写没了

现象是偶尔丢一条记录,日志里能看到复位标志。原因是写 Flash 需要几十毫秒,这段时间喂狗没跟上。解决不是简单地把看门狗时间拉长,而是把写 Flash 的动作拆开:先写 RAM 队列,由低优先级的任务在空闲时批量落盘,落盘前先喂一次狗;参数区写入时用双缓冲,先写 B 区校验通过再写 A 区,任何一步被复位打断,下次上电都有可用的一份。另外,中断里不要做 Flash 操作,F103 在擦写 Flash 时取指会暂停,中断响应会被拖住,反过来又影响喂狗,这是个连环坑。

5.5 标定参数被误改:现场手一抖,整段路的数据全废

现象是换了个操作员之后,所有重量都偏大一倍或偏小一半。原因是标定入口藏在按键长按里,误触就进了标定模式,随便按两下把灵敏度系数改了。解决是三道锁:进入标定必须同时按住两个指定按键并保持 5 秒;进入后要求输入一个四位数口令,口令存在参数区里,默认值在首次上电时强制修改;标定完成写入前,回读一次并算出校验重量,和输入的标准重量偏差超过 2% 就拒绝保存。参数区同时保留上一版参数,我在参数结构里留了个rollback标志,现场出问题时能一键回到上一版。

6. 验收与答辩:怎么证明这套系统真的能用

验收要给出可重复的测试方法,而不是“我试了一下没问题”。我一般按下面这张表走一遍,每一项都要留下数据。

测试项测试方法判定标准
静态精度每路单独加标准砝码,从 20% 到满量程取 5 个点误差不超过满量程 ±0.5%
四路一致性同一载荷依次压四路,比较读数四路之间偏差不超过 ±1%
超重判定用可调载荷逐步逼近阈值死区内不报警,超过死区连续 3 次后报警
断网补传拔掉联网模块,制造 10 条记录后恢复恢复后 60 秒内全部补传,序号无重复
掉电保护记录写入过程中断电,重复 10 次参数不丢,记录最多丢最后 1 条
长时间运行连续通电 72 小时零点漂移不超过满量程 0.3%

答辩现场最容易出问题的环节是演示称重,因为不可能把砝码搬到教室里。我的做法是提前准备一个数据回放模式:把现场采到的一路原始数据存成数组,通过串口注入,系统照常走滤波、判定、上报的完整流程,屏幕上能看到重量曲线跳动、超限报警亮起、后台收到记录。这样演示不依赖实物,也不怕现场环境干扰。要强调的是,回放模式只走业务层,不替代真实采集的验证,验收表里那几项还是得用砝码实测。

还有一个小习惯:把所有可以调的量集中到一个结构体里,阈值、死区、确认次数、上报周期、标定系数全放进去,做成一个参数页,答辩时老师问“这个阈值能不能改”,当场改给他看,比嘴上说“可以改”有说服力得多。

从那以后我每次打样之前,都会先把标定流程和断网补传这两条路在台面上跑通,再焊板子、再装壳。血泪经验是,硬件问题能靠换件解决,流程没走通,到了现场就是干瞪眼。希望这套结构能帮到你,少走几段弯路。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进