設(shè)計與實現(xiàn))
簡介面向嵌入式開發(fā)學習者與交通監(jiān)測類課程設(shè)計需求這套基于STM32的公路四路超重檢測系統(tǒng)工程資料圍繞多路稱重采集、閾值判斷、聲光報警及聯(lián)網(wǎng)上傳等環(huán)節(jié)展開可用于畢業(yè)設(shè)計、課程實踐或原型驗證。壓縮包共306個文件約10.31MB包含57個c源文件和63個h頭文件以及o、crf、d等編譯中間文件另有uvprojx工程、sct分散加載、map與lst映射清單等便于直接打開Keil工程復現(xiàn)和二次開發(fā)。已有333人學習下載。資料保留較完整的工程結(jié)構(gòu)與外設(shè)驅(qū)動代碼涵蓋ADC、定時器、串口通信及物聯(lián)網(wǎng)協(xié)議相關(guān)實現(xiàn)讀者可據(jù)此梳理四路檢測的數(shù)據(jù)流、報警邏輯和聯(lián)網(wǎng)監(jiān)控思路也能參考工程配置與編譯產(chǎn)物進行排錯和功能擴展。1. 去年幫人看一個公路治超點的老設(shè)備問題不是秤不準去年幫人看一個公路治超點的老設(shè)備問題不是秤不準而是四條車道的讀數(shù)互相干擾一號道剛過完一輛六軸車三號道的顯示還沒回零值班的人只能憑經(jīng)驗猜哪條是真的。這套基于 STM32 的公路四路超重檢測系統(tǒng)要解決的就是這種“多路同時在線”的現(xiàn)場問題——四路稱重通道各自獨立采集、獨立標定、獨立判定超限再由主控統(tǒng)一打包通過聯(lián)網(wǎng)模塊把車重、時間、車道號一起送到后臺。它適合正在做公路超重檢測方向畢設(shè)或課設(shè)、需要一套能從采集跑通到上報的完整方案的人也適合要把樣機裝到路邊實測的工程場景。下面按需求、選型、架構(gòu)、實現(xiàn)、避坑、驗收的順序把這套系統(tǒng)拆開講。2. 需求拆解與器件選型四路通道、量程和采樣率怎么定2.1 先確認“四路”指什么四條車道還是同一秤臺的四個角這是動手前必須問清楚的一件事。兩種理解對應(yīng)的軟件結(jié)構(gòu)完全不同四條車道是四個互不相干的獨立通道各自有零點、靈敏度系數(shù)和超重閾值同一秤臺四個角則是四個分量需要先求和得到總重再用四角差值判斷車輛是否偏載。我在方案里把兩種模式都留了口子用一個配置字節(jié)ch_mode切換0 表示獨立通道1 表示四點合成這樣后面換場景不用改代碼結(jié)構(gòu)只改配置。除了通道定義還要把性能邊界寫死否則答辯現(xiàn)場一問就露怯。公路治超點常見兩種工況一種是引導進站的低速通過車速 5 到 15 公里每小時另一種是完全靜態(tài)停車稱重。低速工況下一輛車壓在 1.5 米長的秤板上有效信號窗口大概 0.3 到 0.4 秒如果采樣率只有 10Hz窗口里只能拿到三四個點峰值根本抓不準。所以這套方案定位在低速與靜態(tài)稱重動態(tài)精度按滿量程 ±3% 到 ±5% 驗收靜態(tài)精度可以做到 ±0.5%。想做成高速不停車稱重必須上專用的動態(tài)稱重傳感器和更高的采樣率那是另一個量級的預算不在本方案范圍內(nèi)。功能清單我一般按下面幾條鎖死后面所有模塊都圍繞它展開四路獨立采集單路斷線不影響其余三路車輛到位觸發(fā)、離開結(jié)束一次通過生成一條完整記錄按軸重和總重雙閾值判定超限本地聲光報警并輸出一路繼電器信號本地至少存一萬條記錄聯(lián)網(wǎng)上報斷網(wǎng)期間不丟數(shù)據(jù)現(xiàn)場可標定標定參數(shù)掉電不丟還要能防止誤改。工作溫度按零下 20 到 60 攝氏度設(shè)計傳感器接線做防雷和屏蔽。2.2 主控與 ADC 選型為什么是四片 HX711而不是片內(nèi) 12 位 ADC先說結(jié)論主控用 STM32F103RCT6稱重前端用四片 HX711每片對應(yīng)一路。這個組合不是最先進的但在四路低速稱重場景下是最省心、最容易復現(xiàn)的。對比項STM32F103RCT6STM32F407VET6主頻72MHz168MHzFlash / RAM256KB / 48KB512KB / 192KB串口數(shù)量56片上 ADC12 位12 位適合場景裸機時間片調(diào)度四路 IO 模擬時序需要網(wǎng)絡(luò)協(xié)議棧、多任務(wù)或更強濾波算力F103 的 48KB 內(nèi)存跑一個裸機時間片調(diào)度器加一個環(huán)形緩沖隊列綽綽有余省下來的成本可以貼到傳感器和防護上。真正要謹慎的是 ADC 這一環(huán)很多同學圖省事直接用片內(nèi) 12 位 ADC 接應(yīng)變橋結(jié)果量程 30 噸時最小分辨力大概 7 公斤空載時噪聲就有十幾公斤零點一直跳。這不是代碼問題是分辨率不夠。方案分辨率有效通道輸出速率MCU 開銷說明HX711 × 424 位410Hz / 80Hz低IO 模擬時序通道間物理隔離無串擾成本低ADS125624 位8 路差分 4 路最高 30kSPS中SPI 加 DRDY 中斷速率高但要外加基準和模擬前端片內(nèi) 12 位 ADC12 位多路快低分辨力不足只適合做粗略檢測HX711 的短板是速率80Hz 輸出時內(nèi)部增益和濾波切換會帶來更大噪聲實測零點抖動比 10Hz 時明顯。所以我的做法是靜態(tài)稱重走 10Hz低速動態(tài)走 80Hz由配置項決定標定時按實際工況選不要一套參數(shù)打天下。2.3 觸發(fā)、執(zhí)行與聯(lián)網(wǎng)部件把外圍清單一次列全觸發(fā)用一對地感線圈加檢測器檢測器輸出干接點經(jīng)光耦隔離后進 GPIO這樣線圈那邊的干擾不會直接打到主控。線圈負責“車來了”紅外對射負責“車走了”兩個信號配合比單靠重量閾值判斷車輛離開靠譜得多——重車停在秤上不動時光看重量是判斷不出它走沒走的。執(zhí)行部分包括一路繼電器輸出可接道閘或外部報警器、一個有源蜂鳴器和四個車道狀態(tài)指示燈。本地顯示用 0.96 寸 OLED只顯示四路當前重量和狀態(tài)不做過多的界面現(xiàn)場人員看得懂就行。聯(lián)網(wǎng)這塊有兩種常見做法WiFi 透傳模塊走 AT 指令適合有現(xiàn)成熱點的實驗室和校園場景4G Cat.1 模塊走串口 AT 或 PPP適合真正裝在路邊的場景。兩者對主控來說都是串口所以底層驅(qū)動寫成同一套串口收發(fā)加狀態(tài)機換模塊只改初始化指令表不動上層協(xié)議。存儲用 W25Q64 存歷史記錄AT24C02 存標定參數(shù)做雙備份多花兩塊錢能省掉很多“參數(shù)丟了要重新標定”的麻煩。3. 總體架構(gòu)與通信協(xié)議設(shè)計四層鏈路和數(shù)據(jù)幀怎么落地3.1 四層架構(gòu)與各模塊的職責邊界整套系統(tǒng)按感知層、控制層、傳輸層、應(yīng)用層劃分這個劃分不是為了好看是為了讓每個人接手時知道改哪一層不會牽連別處。層級主要模塊職責對外接口感知層稱重傳感器、地感線圈、紅外對射把重量和車輛位置變成電信號模擬毫伏信號、開關(guān)量控制層STM32 主控、HX711、OLED、報警輸出采集、濾波、標定、判定、本地顯示串口、GPIO、SPI、I2C傳輸層WiFi / 4G 模塊把記錄打包上傳接收下行參數(shù)UART AT 指令應(yīng)用層后臺服務(wù)與數(shù)據(jù)庫入庫、查詢、統(tǒng)計、告警推送MQTT / HTTP控制層內(nèi)部再分成驅(qū)動、業(yè)務(wù)、應(yīng)用三段。驅(qū)動層只管時序比如 HX711 讀數(shù)、Flash 讀寫、串口收發(fā)業(yè)務(wù)層管濾波、標定換算、超重判定狀態(tài)機應(yīng)用層管調(diào)度用 SysTick 做 1ms 基準主循環(huán)里按時間片輪詢各個任務(wù)。這里沒有上 RTOS原因是任務(wù)數(shù)量少、實時性要求不高裸機時間片更容易調(diào)試出問題的時候一眼能看出是哪個任務(wù)超時。調(diào)度周期我這樣分配HX711 采集任務(wù)每 10ms 跑一次四路輪詢?yōu)V波與判定任務(wù)每 50ms 跑一次OLED 刷新 200ms聯(lián)網(wǎng)心跳 30 秒記錄落盤在判定完成后立即觸發(fā)。這套節(jié)奏在 F103 上跑下來 CPU 占用不到三成留足了余量。3.2 數(shù)據(jù)幀格式與 CRC16 校驗上下行都用同一套殼本地模塊之間、設(shè)備與后臺之間我習慣用同一套幀結(jié)構(gòu)調(diào)試時用串口助手就能看不用兩套解析邏輯。偏移長度字段說明02幀頭固定 0xAA 0x5521協(xié)議版本當前 0x01方便以后擴展31命令字0x01 實時數(shù)據(jù)0x02 記錄補傳0x10 參數(shù)下發(fā)0x11 參數(shù)回讀41通道掩碼bit0 到 bit3 對應(yīng)第 1 到第 4 路52負載長度小端最大 512 字節(jié)7N負載TLV 結(jié)構(gòu)重量用 int32 定點單位 0.01kg7N2CRC16MODBUS 多項式 0xA001低字節(jié)在前重量為什么要用定點而不用 float因為浮點在串口傳輸和存儲時字節(jié)序容易出岔子int32 定點表示 0.01kg30 噸就是 3000000離溢出還遠得很解析也簡單。CRC 校驗代碼如下初值 0xFFFF多項式 0xA001算完低字節(jié)先發(fā)。/* MODBUS CRC16初值 0xFFFF多項式 0xA0010x8005 反轉(zhuǎn) */ 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; /* 發(fā)送時先發(fā)低字節(jié)再發(fā)高字節(jié) */ }這個函數(shù)在接收端要再算一遍和幀尾的兩個字節(jié)比對不一致就整幀丟棄并累加錯誤計數(shù)。錯誤計數(shù)超過閾值時我一般會觸發(fā)一次模塊重連因為連續(xù) CRC 錯通常意味著波特率漂了或者線路接觸不良而不是偶發(fā)干擾。3.3 本地存儲分區(qū)與掉電保護參數(shù)雙備份記錄走環(huán)形區(qū)W25Q64 有 8MB按下面這樣分區(qū)邊界留出余量以后加字段不用重排。起始地址大小用途校驗方式0x0000004KB參數(shù)備份 A魔數(shù) 0x5A5A CRC160x0010004KB參數(shù)備份 B同上0x002000512KB稱重記錄環(huán)形區(qū)每條 32 字節(jié)共 16384 條每條末尾 CRC80x0820004KB運行日志與異常計數(shù)無參數(shù)區(qū)做雙備份的意義在于寫參數(shù)時先擦 B 區(qū)寫入新值并校驗成功后再寫 A 區(qū)。如果寫到一半掉電下次上電讀到 A 區(qū)校驗失敗就回退到 B 區(qū)兩個都壞才用默認值。這個邏輯看著簡單但真到了現(xiàn)場一次意外斷電就能把標定系數(shù)寫成 0xFF重新標定要封路半小時代價比多存 4KB 高得多。記錄區(qū)用環(huán)形寫入寫指針存在參數(shù)區(qū)里每寫一條就更新一次指針。環(huán)形區(qū)滿了就覆蓋最老的記錄同時置一個溢出標志。記錄結(jié)構(gòu)里帶序號和時間戳補傳時后臺按序號去重避免網(wǎng)絡(luò)重發(fā)導致重復入庫。4. 下位機實現(xiàn)四路采集、動態(tài)濾波與超重判定的代碼骨架4.1 四路 HX711 的時序驅(qū)動與輪詢調(diào)度HX711 的時序不復雜但有幾個參數(shù)不能錯DOUT 變低表示數(shù)據(jù)就緒SCK 需要給 25 個脈沖前 24 個移出數(shù)據(jù)補碼形式第 25 個脈沖決定下一次的通道和增益。四路就是四組 DOUT 和 SCK共占用 8 個 IO。#define HX711_CH_NUM 4 #define HX711_TIMEOUT 30 /* ms超過判定該路斷線 */ typedef struct { GPIO_TypeDef *port_dout; /* 數(shù)據(jù)腳輸入 */ uint16_t pin_dout; GPIO_TypeDef *port_sck; /* 時鐘腳輸出 */ uint16_t pin_sck; int32_t offset; /* 空載零點原始值 */ float scale; /* 每公斤對應(yīng)的原始計數(shù) */ int32_t raw; /* 最近一次原始讀數(shù) */ 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 拉低說明轉(zhuǎn)換完成 */ 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 位負數(shù)補碼符號擴展 */ v | 0xFF000000; } ch-online 1; return v; }這段代碼有兩個地方值得多說一句。一是delay_us(1)不能省成幾個__NOP()72MHz 下一個 NOP 才十幾納秒湊不夠 HX711 要求的 0.2 微秒高電平讀出來的數(shù)據(jù)會整體偏移甚至全 0這個坑我在第一版上踩過示波器一量才發(fā)現(xiàn) SCK 是尖脈沖。二是超時返回必須區(qū)分于正常讀數(shù)我用 INT32_MIN 做哨兵值上層看到它就跳過這一路絕不能讓斷線的通道參與平均值計算否則零點會被拉偏。輪詢調(diào)度放在 10ms 的時間片里一次只讀一路四路輪完是 40ms。HX711 在 80Hz 模式下單次轉(zhuǎn)換 12.5ms40ms 輪一圈剛好跟得上不會丟數(shù)據(jù)。如果開 10Hz 模式單次 100ms輪詢周期要相應(yīng)拉長到 400ms 以上這時候HX711_TIMEOUT也得跟著放大否則會誤判斷線。4.2 動態(tài)稱重的濾波與峰值提取原始讀數(shù)不能直接用空載時會有幾十個 counts 的抖動重車壓上去又疊加了車輛振動。我的處理鏈是三段先做 5 點中值濾波去掉脈沖干擾再做一階低通平滑最后在觸發(fā)窗口內(nèi)取峰值。#define MED_N 5 #define LPF_ALPHA 0.25f /* 越小越平滑但響應(yīng)變慢 */ typedef struct { int32_t med_buf[MED_N]; uint8_t med_idx; float lpf; /* 低通輸出 */ int32_t peak; /* 本次窗口峰值 */ uint16_t peak_hold; /* 峰值附近保持計數(shù) */ } 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 調(diào)用一次輸入換算后的公斤值 */ static void filter_update(Filter_t *f, float kg, uint8_t vehicle_in) { f-med_buf[f-med_idx] (int32_t)(kg * 100.0f); /* 轉(zhuǎn)成 0.01kg 整數(shù) */ 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 秒內(nèi)不更新防止抖動誤判 */ } } }中值濾波的窗口取 5 是折中取 3 點去不掉連續(xù)的干擾取 7 點會把真實的上升沿削平。低通系數(shù) 0.25 對應(yīng)的時間常數(shù)大約 3 個采樣周期在 50ms 周期下是 150ms跟得上車輛壓上來的速度又不至于把振動放大。峰值保持段加了 1 秒的鎖定時間原因是車輛在秤上輕微晃動時重量會在峰值附近反復波動不加鎖定的話峰值會一直往上爬判出來的重量比實際偏高。峰值取的是“軸重”還是“總重”取決于秤板長度和車輛軸距。秤板短于軸距時一次只壓一根軸峰值就是單軸重多軸車要按軸依次累加秤板長于整車時峰值直接就是總重。這兩種情況的判定邏輯不一樣我在配置里加了weigh_mode答辯時如果老師問“六軸車怎么算總重”這就是回答的落點。4.3 超重判定狀態(tài)機與報警輸出判定不能只看一個瞬時值要用狀態(tài)機把“車來了、正在稱、稱完了、判超限、上報”這幾步串起來否則一輛車會被重復判定好幾次后臺收到的記錄數(shù)對不上實際車流量。typedef enum { ST_IDLE 0, /* 等待車輛觸發(fā) */ ST_SAMPLING, /* 車輛在秤采集峰值 */ ST_SETTLE, /* 車輛離開等待重量回落 */ ST_JUDGE, /* 判定超限 */ ST_REPORT /* 生成記錄并入隊 */ } WeighState_e; typedef struct { uint32_t limit_kg; /* 超重閾值單位 kg */ uint16_t deadband_pct; /* 死區(qū)百分比默認 5防止臨界抖動 */ uint8_t confirm_cnt; /* 連續(xù)超限次數(shù)確認默認 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; /* 只有超過閾值加上死區(qū)才累加超限計數(shù) */ 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); /* 寫入環(huán)形隊列 */ over_cnt 0; st ST_IDLE; break; default: st ST_IDLE; break; } }死區(qū)和連續(xù)確認這兩層是為了對付臨界車。一輛限重 30 噸的車實際稱出來 30.2 噸如果直接判超限司機和值班員都會來找你。加 5% 死區(qū)和連續(xù) 3 次確認之后判出來的超限基本都在 31.5 噸以上爭議少很多。報警輸出用g_alarm_on控制持續(xù) 5 秒后自動關(guān)閉防止蜂鳴器一直響。4.4 上報隊列與斷網(wǎng)補傳上報隊列放在 RAM 里做一個 32 條的環(huán)形緩沖每條記錄生成后先入隊聯(lián)網(wǎng)任務(wù)從隊頭取。發(fā)送成功才出隊失敗就留在隊里等下一輪。隊列滿的時候最老的一條轉(zhuǎn)存到 Flash 記錄區(qū)保證不丟。#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; }補傳邏輯放在聯(lián)網(wǎng)任務(wù)里連上后臺之后先讀 Flash 里未確認的記錄按序號從小到大發(fā)每發(fā)一條等一次 ACKACK 里的序號對得上才把 Flash 里該條標記為已上傳。這里有個細節(jié)標記已上傳不要擦整塊用一個 32 位的位圖記錄哪些條已確認位圖滿了再統(tǒng)一整理這樣能少擦很多次 Flash。5. 常見問題與避坑五個最容易在現(xiàn)場翻車的點5.1 零點漂移上電讀數(shù)正常跑兩小時重量越飄越高現(xiàn)象是空載時顯示從 0 慢慢漲到幾十公斤重啟后又歸零。原因通常有兩個一是 HX711 的供電和主控共用一路 3.3V數(shù)字部分電流波動通過地線串到模擬端二是軟件上只在開機時取了零點之后一直用固定值減。解決的辦法是硬件上給 HX711 的 AVDD 單獨走 LC 濾波地線單點匯到電源入口軟件上加自動零點跟蹤車輛離開后如果連續(xù) 30 秒重量都在滿量程 2% 以內(nèi)就緩慢修正零點每次只修一個很小的步長避免把真實的小重量也修掉。跟蹤步長我一般設(shè)成每 10 秒修正 0.5kg慢慢糾不激進。5.2 四路串擾壓一號道三號道跟著跳數(shù)現(xiàn)象很嚇人以為是軟件讀了別的通道。實際排查下來九成是四路傳感器的屏蔽線擰在一起走線或者四片 HX711 共用一根 SCK 時鐘。SCK 是方波頻率雖然不高但走線長、回路大很容易耦合到相鄰通道的模擬輸入上。解決分三步每路傳感器用獨立屏蔽雙絞線屏蔽層單端接地四片 HX711 的 SCK 各走各的絕不在板上并聯(lián)成一根總線在 HX711 的輸入端并 0.1uF 電容做差模濾波。改完之后再壓單路其余三路的跳數(shù)應(yīng)該在滿量程 0.1% 以內(nèi)。5.3 聯(lián)網(wǎng)模塊假在線心跳正常數(shù)據(jù)死活發(fā)不出去現(xiàn)象是串口每 30 秒都能收到模塊返回的“已連接”但上報的數(shù)據(jù)后臺一條也收不到。原因是 AT 指令的返回只代表模塊和基站或路由建立了鏈路不代表上層協(xié)議通。這種情況我一般是三層排查先看模塊返回里有沒有分配到 IP再看能不能 ping 通后臺地址最后看協(xié)議層有沒有收到后臺的心跳應(yīng)答。軟件上不能只信模塊的“連接成功”提示必須自己發(fā)一條心跳幀并等待后臺回包連續(xù)三次沒回包就判定鏈路失效主動重連。這個超時重連邏輯寫進代碼里比在現(xiàn)場盯著串口助手看要省事得多。5.4 看門狗復位正好把剛采到的數(shù)據(jù)寫沒了現(xiàn)象是偶爾丟一條記錄日志里能看到復位標志。原因是寫 Flash 需要幾十毫秒這段時間喂狗沒跟上。解決不是簡單地把看門狗時間拉長而是把寫 Flash 的動作拆開先寫 RAM 隊列由低優(yōu)先級的任務(wù)在空閑時批量落盤落盤前先喂一次狗參數(shù)區(qū)寫入時用雙緩沖先寫 B 區(qū)校驗通過再寫 A 區(qū)任何一步被復位打斷下次上電都有可用的一份。另外中斷里不要做 Flash 操作F103 在擦寫 Flash 時取指會暫停中斷響應(yīng)會被拖住反過來又影響喂狗這是個連環(huán)坑。5.5 標定參數(shù)被誤改現(xiàn)場手一抖整段路的數(shù)據(jù)全廢現(xiàn)象是換了個操作員之后所有重量都偏大一倍或偏小一半。原因是標定入口藏在按鍵長按里誤觸就進了標定模式隨便按兩下把靈敏度系數(shù)改了。解決是三道鎖進入標定必須同時按住兩個指定按鍵并保持 5 秒進入后要求輸入一個四位數(shù)口令口令存在參數(shù)區(qū)里默認值在首次上電時強制修改標定完成寫入前回讀一次并算出校驗重量和輸入的標準重量偏差超過 2% 就拒絕保存。參數(shù)區(qū)同時保留上一版參數(shù)我在參數(shù)結(jié)構(gòu)里留了個rollback標志現(xiàn)場出問題時能一鍵回到上一版。6. 驗收與答辯怎么證明這套系統(tǒng)真的能用驗收要給出可重復的測試方法而不是“我試了一下沒問題”。我一般按下面這張表走一遍每一項都要留下數(shù)據(jù)。測試項測試方法判定標準靜態(tài)精度每路單獨加標準砝碼從 20% 到滿量程取 5 個點誤差不超過滿量程 ±0.5%四路一致性同一載荷依次壓四路比較讀數(shù)四路之間偏差不超過 ±1%超重判定用可調(diào)載荷逐步逼近閾值死區(qū)內(nèi)不報警超過死區(qū)連續(xù) 3 次后報警斷網(wǎng)補傳拔掉聯(lián)網(wǎng)模塊制造 10 條記錄后恢復恢復后 60 秒內(nèi)全部補傳序號無重復掉電保護記錄寫入過程中斷電重復 10 次參數(shù)不丟記錄最多丟最后 1 條長時間運行連續(xù)通電 72 小時零點漂移不超過滿量程 0.3%答辯現(xiàn)場最容易出問題的環(huán)節(jié)是演示稱重因為不可能把砝碼搬到教室里。我的做法是提前準備一個數(shù)據(jù)回放模式把現(xiàn)場采到的一路原始數(shù)據(jù)存成數(shù)組通過串口注入系統(tǒng)照常走濾波、判定、上報的完整流程屏幕上能看到重量曲線跳動、超限報警亮起、后臺收到記錄。這樣演示不依賴實物也不怕現(xiàn)場環(huán)境干擾。要強調(diào)的是回放模式只走業(yè)務(wù)層不替代真實采集的驗證驗收表里那幾項還是得用砝碼實測。還有一個小習慣把所有可以調(diào)的量集中到一個結(jié)構(gòu)體里閾值、死區(qū)、確認次數(shù)、上報周期、標定系數(shù)全放進去做成一個參數(shù)頁答辯時老師問“這個閾值能不能改”當場改給他看比嘴上說“可以改”有說服力得多。從那以后我每次打樣之前都會先把標定流程和斷網(wǎng)補傳這兩條路在臺面上跑通再焊板子、再裝殼。血淚經(jīng)驗是硬件問題能靠換件解決流程沒走通到了現(xiàn)場就是干瞪眼。希望這套結(jié)構(gòu)能幫到你少走幾段彎路。本文還有配套的精品資源點擊獲取