境控制系統(tǒng)實戰(zhàn):溫濕度、粉塵監(jiān)測與ESP8266上云全記錄)
倉庫上周剛出過一次事。東側(cè)貨架第三排紙箱表面凝了一層水珠最底下的幾箱食品包裝已經(jīng)發(fā)霉。查下來就是濕度超標(biāo)——倉庫晚上關(guān)窗之后相對濕度飆到78%排風(fēng)沒開因為沒人值守也沒人知道。這個教訓(xùn)讓我花了三周做了這套基于STM32的倉庫環(huán)境控制系統(tǒng)把溫濕度粉塵監(jiān)測、自動通風(fēng)除濕和ESP8266上云全部打通。這篇記錄從選型、電路、固件、上云到調(diào)試踩坑的完整過程給準(zhǔn)備做類似項目的人一個可以直接抄作業(yè)的參考。1. 倉庫環(huán)境控制這件事為什么值得自己動手做一套1.1 三個參數(shù)到底管什么倉庫環(huán)境不是溫度別太離譜這么簡單。我這次梳理需求時把影響倉庫質(zhì)量的參數(shù)拆成三個維度溫度食品、化工原料、電子元器件對溫度都很敏感。溫度過高加速氧化和微生物繁殖溫度驟降會導(dǎo)致凝露尤其春季晝夜溫差大的時候金屬貨架和墻面上全是水珠。濕度這是最容易翻車的參數(shù)。相對濕度超過70%霉菌會進入快速繁殖期超過80%紙箱吸潮變軟堆垛穩(wěn)定性都受影響。很多倉庫管理者只關(guān)注不漏雨卻忽略了大面積凝露。粉塵倉庫里如果存放面粉、木屑、塑料顆粒粉塵濃度過高不僅影響工人健康還有粉塵爆炸風(fēng)險。即便不是易燃粉塵粉塵沉積在電子設(shè)備表面也會導(dǎo)致接觸不良和散熱惡化。我給自己定的控制目標(biāo)是溫度超過35℃自動通風(fēng)降到32℃停止?jié)穸瘸^65%自動通風(fēng)除濕降到60%停止粉塵濃度超過設(shè)定閾值啟動過濾風(fēng)機。這套閾值不是拍腦袋定的可以參考食品倉庫和一般工業(yè)廠房的環(huán)境標(biāo)準(zhǔn)再結(jié)合當(dāng)?shù)貧夂蚝徒?jīng)驗調(diào)整。1.2 成品方案夠用但總覺得憋屈市面上不是沒有倉庫環(huán)境監(jiān)測設(shè)備。工業(yè)級的環(huán)境監(jiān)控主機加傳感器報價幾千上萬很正常而且存在三個我接受不了的問題數(shù)據(jù)封閉。很多成品方案數(shù)據(jù)只進廠家自己的平臺想導(dǎo)出到本地數(shù)據(jù)庫或者和公司內(nèi)部的倉儲管理系統(tǒng)對接接口要么沒有要么收費。聯(lián)動邏輯寫死。我希望濕度超限時先開排風(fēng)機如果濕度繼續(xù)上升再開除濕機風(fēng)機延遲啟動5分鐘——這種自定義邏輯成品設(shè)備很難改。故障不透明。傳感器壞了、網(wǎng)線斷了設(shè)備只會亮紅燈具體哪一路出問題你還得拿說明書去猜。自己動手做一套成本可以壓到三四百元而且每一行邏輯都看得見、改得動傳感器數(shù)據(jù)想上哪臺服務(wù)器都行。1.3 系統(tǒng)整體數(shù)據(jù)流這套系統(tǒng)的架構(gòu)可以分成四層采集層溫濕度傳感器AHT20和粉塵傳感器GP2Y1010AU0負責(zé)感知環(huán)境??刂茖覵TM32F103C8T6作為主控跑采集、濾波、閾值判斷和繼電器驅(qū)動。傳輸層ESP8266通過串口和STM32通信把數(shù)據(jù)送到云端。展示層云平臺儀表盤看實時數(shù)據(jù)、歷史曲線超限時推送報警。數(shù)據(jù)從傳感器進入MCUMCU處理后一方面驅(qū)動繼電器控制風(fēng)機和除濕機另一方面打包成JSON數(shù)據(jù)通過串口交給ESP8266上報云端。本地自動控制不依賴網(wǎng)絡(luò)——就算WiFi斷了通風(fēng)除濕邏輯照常跑只是云端暫時看不到數(shù)據(jù)而已。這一點很重要倉庫環(huán)境控制必須先保證本地閉環(huán)上云只是錦上添花。2. 硬件選型與引腳規(guī)劃一塊藍板加四個模塊的取舍2.1 主控為什么是STM32F103C8T6STM32F103C8T6這顆芯片在這個項目里屬于降維打擊但我就是故意選它。原因有三資源綽綽有余72MHz主頻、12位ADC、3路USART、2個I2C、4個16位定時器環(huán)境控制這種任務(wù)只用掉不到一半資源。資料多到不用背手冊不管是標(biāo)準(zhǔn)庫還是HAL庫網(wǎng)上隨便搜都是現(xiàn)成工程。價格穩(wěn)定藍板核心板十幾塊錢單獨買芯片散片也就幾塊錢壞了不心疼。有人會問為什么不用Arduino。Arduino做快速原型確實方便但ADC是10位而且串口、定時器資源少如果想擴展風(fēng)機轉(zhuǎn)速測量、多路傳感器引腳就很緊張。STM32雖然上手門檻高一點但后面擴展空間大得多。開發(fā)環(huán)境我用的是Keil5 標(biāo)準(zhǔn)庫。新項目可以選HAL庫但環(huán)境控制邏輯不復(fù)雜標(biāo)準(zhǔn)庫的例程和中文資料最齊全遇到問題好排查。要注意一點Keil5裝好之后第一件事是把STM32F1系列的Device Pack裝上否則新建工程找不到芯片型號。如果你之前裝了C51的Keil再裝STM32版時注意Pack要單獨裝兩者可以共存但Target芯片列表必須選對。2.2 溫濕度傳感器DHT11、AHT20還是SHT30我在設(shè)計時對比過三款主流溫濕度傳感器也實際焊過測試板。直接看表格型號接口精度溫度/濕度價格采樣周期我的評價DHT11單總線±2℃/±5%RH3元左右≥1s夠用但精度一般時序要求嚴(yán)格AHT20I2C±0.3℃/±2%RH6元左右≥80ms精度好、驅(qū)動簡單性價比之王SHT30I2C±0.2℃/±1.5%RH12元左右可配置性能好但倉庫場景沒必要多花錢最終主方案選了AHT20。DHT11也不是不能用但它的單總線時序?qū)χ袛嗵幚硖貏e敏感一被其他任務(wù)打斷就容易讀到錯誤值。AHT20走I2C只要初始化正確讀取穩(wěn)定性高一個檔次。我會在固件部分保留DHT11的備用驅(qū)動代碼方便預(yù)算極限壓縮的讀者參考。2.3 粉塵傳感器GP2Y1010AU0還是PMS5003粉塵監(jiān)測是這個系統(tǒng)里最容易踩坑的部分因為粉塵濃度這個詞太寬泛了。我跑了兩個方案做對比GP2Y1010AU0是夏普的模擬輸出粉塵傳感器價格二十塊左右輸出0.5V到0.9V的電壓對應(yīng)粉塵濃度變化。它需要PWM脈沖驅(qū)動內(nèi)部LED且對采樣時序有要求。優(yōu)點是便宜、能反映相對濃度趨勢缺點是精度不高只能做定性半定量監(jiān)測。PMS5003是激光散射式傳感器串口直接輸出PM2.5和PM10數(shù)值精度高一個數(shù)量級但價格貴很多而且內(nèi)部有個小風(fēng)扇功耗更大風(fēng)道一旦積灰讀數(shù)會漂移。倉庫粉塵監(jiān)測的核心需求是濃度是否越限和變化趨勢不是做空氣質(zhì)量科研所以GP2Y1010AU0完全夠用。如果預(yù)算夠把GP2Y換成PMS5003也只是把ADC采樣換成串口讀幀整體架構(gòu)不用動。2.4 執(zhí)行與聯(lián)網(wǎng)模塊執(zhí)行機構(gòu)用了兩路繼電器一路控制排風(fēng)機一路控制除濕機。繼電器模塊我踩過坑后面詳細說這里先記住兩個原則一是盡量選帶光耦隔離的模塊二是觸發(fā)方式選高電平觸發(fā)不要選低電平觸發(fā)。聯(lián)網(wǎng)模塊選了ESP8266-01S。這個模塊雖然天線性能一般但串口AT指令模式非常成熟功耗也低。如果WiFi信號弱可以換成ESP-12F引腳多、天線好只是接線稍復(fù)雜。注意ESP8266是3.3V供電別直接接5V穩(wěn)壓模塊要單獨考慮。另外加了一個手動/自動切換按鍵還有兩個狀態(tài)LED?,F(xiàn)場調(diào)試和檢修時手動模式可以直接控制風(fēng)機啟停不用拔線。2.5 引腳分配表我的完整引腳規(guī)劃如下僅供抄作業(yè)參考功能引腳說明AHT20 SDAPB7I2C1上拉4.7kAHT20 SCLPB6I2C1上拉4.7k粉塵LED驅(qū)動PA1TIM2_CH2輸出PWM脈沖驅(qū)動GP2Y內(nèi)部LED粉塵電壓采樣PA0ADC1_IN0采樣GP2Y輸出繼電器1排風(fēng)機PB12高電平觸發(fā)光耦隔離繼電器2除濕機PB13高電平觸發(fā)光耦隔離本地按鍵PB14外部下拉長按3秒切換手動/自動狀態(tài)LEDPB1自動模式常亮手動模式閃爍ESP8266 TXPA2USART2_TX接ESP8266 RXESP8266 RXPA3USART2_RX接ESP8266 TX調(diào)試串口PA9/PA10USART1調(diào)試日志輸出備用DHT11PA4預(yù)留單總線引腳規(guī)劃的一個原則把容易受干擾的模擬采樣引腳放在電源引腳附近數(shù)字通信引腳分開走。PA0和PA1相鄰正好GP2Y的采樣和PWM驅(qū)動放在一起走線盡量短后面實測效果明顯比之前亂飛的杜邦線穩(wěn)定。3. 從原理圖到現(xiàn)場電源分配、信號調(diào)理與接線防坑3.1 電源架構(gòu)為什么不能一個降壓芯片走到底倉庫現(xiàn)場通常有220V市電我用一個24V開關(guān)電源給風(fēng)機回路供電再把24V降成5V和3.3V給控制板。沒有直接買220V轉(zhuǎn)5V的模塊原因是風(fēng)機和除濕機的啟動電流沖擊很大控制電路必須和動力回路做好隔離。電源鏈路24V開關(guān)電源 → MP1584降壓模塊 → 5V5V → AMS1117-3.3 → 3.3V給STM325V直接給ESP8266、繼電器模塊、AHT20為什么ESP8266不共用3.3V因為WiFi發(fā)射瞬間電流能沖到300mA以上如果和MCU共用一顆AMS1117會導(dǎo)致3.3V電壓跌落STM32復(fù)位。我一開始圖省事共用結(jié)果ESP8266每次聯(lián)網(wǎng)上報主控就重啟查了半天才定位到是電源問題。功率估算STM32板載功耗約50mAESP8266峰值300mA繼電器每個線圈約70mAGP2Y加上LED驅(qū)動約20mA5V總電流峰值不到600mA。MP1584可以輕松帶但如果以后加PMS5003要把余量留足。3.2 傳感器接口的細節(jié)GP2Y1010AU0的輸出電壓是模擬信號范圍大約0.5V到0.9V直接進STM32的ADC沒問題。但它的輸出阻抗不低現(xiàn)場線一長就容易疊加噪聲。我的做法是在ADC引腳前面加一個簡單的RC低通濾波1k歐姆電阻串聯(lián)加0.1uF電容到地截止頻率約1.6kHz能濾掉大部分高頻干擾又不影響正常的緩慢變化信號。AHT20是I2C接口兩個引腳都需要上拉電阻。芯片數(shù)據(jù)手冊建議4.7k但如果傳感器到主控板的距離超過20厘米我會把上拉電阻改成2.2k否則上升沿太緩I2C容易讀錯。3.3 繼電器開關(guān)的隔離問題繼電器控制風(fēng)機本質(zhì)上是讓弱電控制強電。這里我不建議直接拿STM32 GPIO去灌繼電器模塊不管你用低電平還是高電平觸發(fā)都要選帶光耦隔離的模塊讓主控電路和繼電器觸點電路在電氣上完全分開。低電平觸發(fā)的繼電器模塊有個隱患STM32上電瞬間GPIO處于高阻態(tài)如果模塊內(nèi)部上拉不夠強三極管基極浮空可能導(dǎo)通導(dǎo)致繼電器誤動作。我第一版就用了低電平觸發(fā)模塊結(jié)果每次斷電重啟風(fēng)機會莫名其妙啟動一兩秒。換高電平觸發(fā)模塊后GPIO浮空不會導(dǎo)通三極管問題消失。感性負載打火也不能忽視。風(fēng)機電機是感性負載繼電器觸點斷開瞬間會產(chǎn)生電弧除了觸點壽命下降還會向外輻射電磁干擾。正規(guī)做法是觸點兩端并聯(lián)RC吸收電路一般用100歐姆和0.1uF串聯(lián)外加一個壓敏電阻吸收浪涌。我第一批板子沒加后來發(fā)現(xiàn)ESP8266偶爾離線用示波器看電源線上全是繼電器斷開時的毛刺加上RC后明顯改善。3.4 芯片第一腳怎么確認調(diào)試過程中如果遇到程序燒不進去八成是芯片焊接方向錯了或者焊盤連錫。STM32F103C8T6是LQFP48封裝芯片一角有一個圓形凹點或者斜切角對應(yīng)的就是第一腳。從第一腳開始逆時針數(shù)依次是1到24腳左半側(cè)然后從另一邊數(shù)25到48腳。用風(fēng)槍拆裝之后一定要先用放大鏡檢查有沒有連錫再上電測3.3V對地阻抗確認無短路再燒程序。3.5 現(xiàn)場布線原則倉庫現(xiàn)場不是實驗室桌面布線比焊接更要用心傳感器線和風(fēng)機動力線分開走線槽不要并排。我實測過動力線電流超過2A時靠近它走的單總線溫濕度數(shù)據(jù)會周期性出錯??刂瓢逖b進金屬盒但ESP8266的天線要伸出盒外。金屬盒會屏蔽WiFi信號裝在鐵皮配電箱里信號能掉兩三格。所有外部接線用帶卡扣的端子排防止工人誤拉碰掉。傳感器接頭用防呆插頭按錯方向插不進去。電源入口加一個2A保險絲這個錢不能省一次短路故障能燒掉一堆模塊。4. 固件開發(fā)的三大核心時序讀取、滑動濾波與回差控制4.1 工程建立與基礎(chǔ)外設(shè)初始化固件用標(biāo)準(zhǔn)庫寫的工程結(jié)構(gòu)是經(jīng)典的啟動文件加外設(shè)驅(qū)動。新建工程時記得在Pack Installer里裝好STM32F1系列的Device Pack否則編譯器會報找不到芯片?;A(chǔ)初始化包括SysTick延時這個后面單獨講因為踩了坑USART1調(diào)試串口波特率115200USART2接ESP8266波特率9600I2C1接AHT20ADC1_IN0接粉塵傳感器TIM2_CH2輸出PWM驅(qū)動GP2YGPIO控制繼電器和按鍵4.2 讀取AHT20溫濕度I2C驅(qū)動要點AHT20上電后需要等40ms然后發(fā)0xBE命令初始化之后每讀一次數(shù)據(jù)的流程是發(fā)0xAC命令進入測量等待至少80ms然后連續(xù)讀6個字節(jié)。6個字節(jié)中前兩個半字節(jié)是濕度數(shù)據(jù)接著兩個半字節(jié)是溫度數(shù)據(jù)最后1個字節(jié)是校驗和。濕度和溫度的計算公式濕度 原始濕度值 / 2^20 × 100%溫度 原始溫度值 / 2^20 × 200 - 50代碼里需要注意一個細節(jié)讀取6字節(jié)時I2C主機每接收一個字節(jié)都要回ACK只有最后一個字節(jié)回NACK。如果ACK處理錯了數(shù)據(jù)會錯位讀出來的溫濕度經(jīng)常是離譜值。這個錯誤很難排查我當(dāng)時在邏輯分析儀上看波形才發(fā)現(xiàn)。連續(xù)讀取失敗3次時我直接沿用上一次有效值同時在串口日志里打一個錯誤碼。倉庫環(huán)境變化很慢幾十秒內(nèi)用舊值完全不影響控制。給預(yù)算緊張的讀者說一句如果改用DHT11驅(qū)動核心就是單總線時序。主機拉低總線18ms發(fā)出起始信號然后釋放傳感器回80us低電平響應(yīng)再輸出40位數(shù)據(jù)。每個數(shù)據(jù)位都以50us低電平開頭之后的高電平持續(xù)26到28us表示0持續(xù)70us表示1。判斷位0還是位1要靠延時讀取時等待高電平結(jié)束的時間來區(qū)分。DHT11對中斷延時很敏感所以讀取前最好關(guān)中斷或者用定時器精確捕獲。4.3 粉塵濃度換算從ADC電壓到mg/m3GP2Y1010AU0的驅(qū)動不是通電就行的。它的內(nèi)部LED需要PWM脈沖驅(qū)動典型參數(shù)是周期320us高電平時間10us占空比約3.1%。我用TIM2_CH2輸出PWMARR設(shè)為319CCR設(shè)為9頻率約31.25kHz實測符合數(shù)據(jù)手冊要求。采樣時序有個講究LED點亮之后傳感器輸出要等約0.28ms才穩(wěn)定所以需要在PWM高電平開始后延時0.28ms再打開ADC采樣窗口取電壓。如果隨時亂采讀到的電壓和粉塵濃度對不上。ADC讀到的數(shù)字量要先換算成電壓電壓 ADC值 × 3.3V / 4096然后電壓再換算成粉塵濃度。GP2Y1010的典型靈敏度是0.5V/0.1mg/m3無塵環(huán)境下輸出電壓約0.5V。簡化公式濃度(mg/m3) (電壓 - 0.5) / 0.5這個換算只是半定量精確標(biāo)定需要拿標(biāo)準(zhǔn)粉塵源校準(zhǔn)。倉庫場景更多看相對變化趨勢所以我在代碼里把單位名稱直接寫成PM_INDEX而不是mg/m3避免誤導(dǎo)。ADC抖動是必然的。我做了兩層處理第一層是每次讀取連續(xù)采10次去掉最大最小值后取平均第二層是保留一個長度為20的滑動窗口窗口內(nèi)再做一次平均。兩層下來數(shù)據(jù)平滑度肉眼可見地改善。這里有個現(xiàn)場經(jīng)驗GP2Y用久了內(nèi)部風(fēng)道積灰輸出會整體偏高。我后來在日志里加了一個長期漂移提醒如果連續(xù)12小時粉塵基線比歷史平均值高出0.15V以上就提示清理傳感器。這個功能讓我在系統(tǒng)跑了一個多月后及時清了一次灰。4.4 控制邏輯回差和最小動作間隔控制邏輯是整個系統(tǒng)的靈魂。直接說我的實現(xiàn)思路閾值判斷加回差外加最小動作間隔保護?;夭畹谋匾杂靡粋€例子解釋如果設(shè)定濕度高于65%開排風(fēng)機低于65%關(guān)排風(fēng)機那么傳感器讀數(shù)在64.8%到65.2%之間波動時繼電器會不停吸合釋放一分鐘能跳幾十次。觸點壽命很快耗盡。所以必須設(shè)置兩個閾值開啟閾值和關(guān)閉閾值。我的控制表如下控制對象開啟條件關(guān)閉條件排風(fēng)機高溫溫度≥35℃溫度≤32℃排風(fēng)機高濕濕度≥65%RH濕度≤60%RH除濕機濕度≥70%RH濕度≤62%RH過濾風(fēng)機粉塵粉塵指數(shù)≥4.0粉塵指數(shù)≤2.0代碼用一個簡單的狀態(tài)機構(gòu)實現(xiàn)typedef struct { float on_th; // 開啟閾值 float off_th; // 關(guān)閉閾值 uint8_t state; // 0關(guān)閉, 1開啟 uint32_t last_toggle_time; // 上次切換時間 } CtrlChannel; void ctrl_update(CtrlChannel *ch, float val) { // 最小動作間隔保護5分鐘內(nèi)不允許頻繁切換 if (HAL_GetTick() - ch-last_toggle_time 300000) { return; } if (ch-state 0 val ch-on_th) { ch-state 1; ch-last_toggle_time HAL_GetTick(); relay_on(ch); } else if (ch-state 1 val ch-off_th) { ch-state 0; ch-last_toggle_time HAL_GetTick(); relay_off(ch); } }注意最小動作間隔是下限保護只在正??刂坡窂嚼锷АH绻麥囟瘸^45℃這種危險值我會無視間隔直接開啟風(fēng)機這是安全優(yōu)先的邏輯。本地按鍵切換手動/自動長按3秒切換。自動模式下控制邏輯正常跑手動模式下繼電器由兩個按鍵或者一個按鍵循環(huán)切換控制。每次切換都用狀態(tài)LED反饋自動模式常亮、手動模式500ms閃爍。我還額外用定時器輸入捕獲做了風(fēng)機堵轉(zhuǎn)監(jiān)測。很多風(fēng)機帶FG脈沖輸出轉(zhuǎn)速越高脈沖頻率越高。用PA8定時器輸入捕獲量一下脈沖頻率如果風(fēng)機在已開啟狀態(tài)下頻率低于正常值的30%判定堵轉(zhuǎn)或皮帶脫落立即報警。這個功能在一次皮帶斷裂時真的派上了用場。4.5 串口數(shù)據(jù)幀設(shè)計STM32每隔5秒向ESP8266發(fā)一幀JSON數(shù)據(jù)格式固定為{d:dev01,t:25.6,h:58.2,pm:0.3,fan:1,dry:0}字段含義d是設(shè)備編號t是溫度h是濕度pm是粉塵指數(shù)fan是排風(fēng)機狀態(tài)dry是除濕機狀態(tài)。把執(zhí)行器狀態(tài)也一起上報云端就能知道系統(tǒng)有沒有在干活。拼幀時統(tǒng)一用snprintf緩沖區(qū)開128字節(jié)杜絕用手動拼接導(dǎo)致的內(nèi)存越界。5. ESP8266上云的兩種路徑AT指令透傳與MQTT對接5.1 先想清楚上云要解決什么問題有人把ESP8266上云想得很玄其實就兩件事把數(shù)據(jù)送達服務(wù)器再把服務(wù)器的指令傳回來。路徑不同代價不同只需要把數(shù)據(jù)送到自己的服務(wù)器存庫用AT指令加TCP透傳最直接想用云平臺現(xiàn)成的儀表盤、報警和App刷MQTT固件更省事。我兩條路都跑過下面把各自的關(guān)鍵步驟和坑都寫出來。5.2 路徑AAT指令加TCP透傳ESP8266-01S出廠自帶AT固件串口發(fā)指令即可控制。初始化序列如下ATE0 ATCWMODE1 ATCWJAP你的WiFi名,你的WiFi密碼 ATCIPSTARTTCP,你的服務(wù)器IP,8080 ATCIPMODE1 ATCIPSENDCIPMODE1進入透傳模式之后所有串口數(shù)據(jù)自動轉(zhuǎn)發(fā)到TCP服務(wù)器服務(wù)端下發(fā)的數(shù)據(jù)也能通過串口收到。自己的服務(wù)器可以先用Python寫一個極簡TCP接收腳本測試import socket srv socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.bind((0.0.0.0, 8080)) srv.listen(1) while True: conn, addr srv.accept() while True: data conn.recv(1024) if not data: break print(data.decode(utf-8, errorsignore))AT指令透傳的缺點很明顯不知道ESP8266什么時間掉線透傳模式下串口又被占用重新初始化需要退出透傳模式。我用這套方案做過臨時數(shù)據(jù)驗證穩(wěn)定跑了三天但沒敢作為正式方案。5.3 路徑B刷MQTT固件對接云平臺正式方案我采用了刷MQTT固件的方式對接的是國內(nèi)常見的物聯(lián)網(wǎng)平臺OneNET、巴法云這類。ESP8266刷入MQTT固件后STM32側(cè)發(fā)的AT指令變成ATMQTTCONN平臺服務(wù)器地址,端口,設(shè)備ID,Token ATMQTTSUB主題A ATMQTTPUB主題A, {t:25.6,h:58.2,pm:0.3} 具體地址和Token格式以平臺文檔為準(zhǔn)每家略有差異但流程是一致的。平臺控制臺新建一個產(chǎn)品、一個設(shè)備拿到連接參數(shù)填進代碼就行。MQTT的價值在于消息主題和QoS機制。環(huán)境數(shù)據(jù)用QoS0上報丟了就丟了下一幀5秒后還會來但報警消息我用QoS1確保平臺能收到。設(shè)備上下線狀態(tài)用遺囑消息設(shè)備異常掉線時平臺能第一時間知道。云平臺側(cè)配置也不難創(chuàng)建幾個數(shù)據(jù)流比如temp、humi、pm然后添加儀表盤組件綁定數(shù)據(jù)流曲線和閾值報警就出來了。手機端用平臺配套的App或者小程序隨時隨地看倉庫數(shù)據(jù)。5.4 斷線重連的完整策略上云之后最怕的不是數(shù)據(jù)不準(zhǔn)而是設(shè)備悄悄掉線你卻不知道。我的重連策略分兩級一級重連WiFi斷開。STM32定時發(fā)送ATPING檢查鏈路連續(xù)失敗兩次說明WiFi可能斷了。這時先發(fā)ATCWJAP重新連WiFi再發(fā)ATMQTTCONN重新連MQTT。二級重連MQTT連接超時。連續(xù)3次ATMQTTCONN失敗用GPIO拉低ESP8266的RST引腳強制重啟或者發(fā)ATRESTORE恢復(fù)出廠后重新初始化。所有AT指令的等待都必須帶超時。這是無數(shù)血淚教訓(xùn)換來的下一章專門說。5.5 數(shù)據(jù)兜底方案如果云平臺臨時掛了數(shù)據(jù)也不能丟。我在STM32的Flash里保存最近200條數(shù)據(jù)等MQTT重連成功后按時間戳補發(fā)。補發(fā)時降低頻率每秒最多補發(fā)5條避免擁塞服務(wù)器。后來我還在SD卡上存過一份但Flash的方案對這套系統(tǒng)足夠了因為倉庫環(huán)境數(shù)據(jù)5秒一條200條能覆蓋約17分鐘斷線基本夠用。6. 現(xiàn)場調(diào)試踩過的五個坑從delay卡死到ATRestore死循環(huán)6.1 STM32延時函數(shù)delay卡死原來卡在SysTick系統(tǒng)跑到第二天出現(xiàn)一個詭異現(xiàn)象主循環(huán)偶爾卡死看門狗也喂不上整機燈全滅。仿真器連上后發(fā)現(xiàn)程序停在delay_us函數(shù)里出不來。排查鏈路是這樣的先懷疑晶振頻率配置錯對照啟動文件檢查沒問題再懷疑優(yōu)化等級導(dǎo)致延時函數(shù)被內(nèi)聯(lián)優(yōu)化把優(yōu)化等級從-O2改成-O0沒改善最后用示波器看SysTick引腳發(fā)現(xiàn)SysTick中斷確實在觸發(fā)但中斷里調(diào)用的回調(diào)函數(shù)又等了一個更長的延時——實際上是我的中斷處理函數(shù)里不小心調(diào)用了延時函數(shù)造成SysTick中斷被低優(yōu)先級事件阻塞主循環(huán)的delay永遠等不到掛起的SysTick中斷響應(yīng)。解決方法是三管齊下SysTick中斷優(yōu)先級設(shè)為最低數(shù)值15中斷處理函數(shù)里絕不調(diào)用任何阻塞延時主循環(huán)的延時改用查詢SysTick計數(shù)寄存器的方式而不是依賴中斷。改完之后穩(wěn)如老狗。6.2 粉塵ADC抖動為什么消不掉GP2Y讀數(shù)一開始抖動特別離譜換算成濃度后從0.2跳到4.5。排查過程按順序走先測ADC參考電壓3.29V穩(wěn)定再查PWM驅(qū)動脈沖波形占空比正常然后懷疑RC濾波參數(shù)加大電容到0.47uF效果有限最后拆下傳感器發(fā)現(xiàn)內(nèi)部風(fēng)道積了一層灰探頭表面還有疑似水汽痕跡。清理后看波形紋波立刻小了一半。這個坑說明傳感器本身的污染是任何軟件濾波都救不回來的現(xiàn)場運維必須包含定期清潔傳感器。6.3 ESP8266恢復(fù)出廠設(shè)置時循環(huán)體中檢測不到OK進入死循環(huán)這個問題我覺得很多新手都會遇到必須單獨寫。最初我的代碼是esp8266_send(ATRESTORE\r\n); while (1) { if (strstr(recv_buf, OK)) { break; } }看似正常對吧但ATRESTORE這個指令執(zhí)行時ESP8266會先斷開當(dāng)前連接、恢復(fù)出廠設(shè)置、然后重啟?;貜?fù)OK的時機和模塊重啟輸出混在一起可能先輸出亂碼再輸出OK甚至因為重啟過程沒完成這句OK根本來不及發(fā)到串口。我的循環(huán)沒有超時一旦收不到OK就死等整個控制器直接癱瘓。正確做法是所有AT指令的等待都改成帶超時的輪詢#define AT_TIMEOUT 3000 uint8_t esp8266_wait_response(char *expected, uint32_t timeout_ms) { uint32_t start HAL_GetTick(); while (HAL_GetTick() - start timeout_ms) { if (strstr(recv_buf, expected)) { return 1; } // 繼續(xù)收串口數(shù)據(jù)到緩沖區(qū) } return 0; // 超時 }對ATRESTORE這種特殊指令干脆不等待回復(fù)發(fā)完之后固定延時5秒然后重新發(fā)ATE0、ATCWMODE、ATCWJAP做初始化。重啟會比等待回復(fù)更可靠。6.4 繼電器打火和上電誤動作這個在前面電路部分說過了這里補充一個現(xiàn)場案例。有一次凌晨倉庫系統(tǒng)自動重啟風(fēng)機會立刻轉(zhuǎn)幾秒聲音很大工人以為是鬧鬼。查下來就是低電平觸發(fā)繼承模塊加上GPIO浮空導(dǎo)致的誤觸發(fā)。解決的完整措施換成高電平觸發(fā)的繼電器模塊GPIO外部加10k下拉電阻到地主程序上電第一步先把所有繼電器控制引腳配置為推挽輸出并輸出低電平然后再初始化其他外設(shè)感性負載觸點兩端加RC吸收。處理后上電測試十余次沒有再出現(xiàn)任何誤動作。6.5 長線干擾溫濕度偶爾跳到98%I2C線布了2米長偶爾讀到濕度98%、溫度-18℃一看就是干擾。用示波器抓I2C時鐘線發(fā)現(xiàn)上升沿非常緩雜波疊加嚴(yán)重。解決組合拳線纜換成屏蔽雙絞線屏蔽層單端接地I2C上拉電阻從4.7k改成2.2k軟件加連續(xù)三次一致性校驗三次讀數(shù)差異超過閾值就丟棄本次測量。改完之后用萬用表實測I2C波形干凈很多再沒出現(xiàn)過離譜跳變。6.6 順帶說兩句Keil下載失敗的問題調(diào)試過程中遇到過連接不上仿真器的情況。最常見原因有兩個一是Keil的Pack沒裝對STM32F103C8T6在Keil里選的Target不對二是代碼里禁用了JTAG后標(biāo)準(zhǔn)JTAG接口不能下載但SWD接口仍然可用。如果之前燒過GPIO重映射和JTAG禁用代碼用SWD模式下載就能恢復(fù)。用VSCode加PlatformIO開發(fā)STM32也是一種選擇調(diào)試配置需要手動改launch.json對新手來說門檻比Keil高但代碼編輯體驗確實好。7. 跑了一個月后的實際效果與后續(xù)擴展這套系統(tǒng)在朋友的食品倉庫里跑了一個月效果可以量化濕度從晚上關(guān)窗后的76%-82%降到常年穩(wěn)定在55%-62%溫度控制在26℃到33℃之間排風(fēng)機每天動作約20次繼電器觸點沒有明顯拉弧痕跡ESP8266掉線自動重連了三次最長一次斷線約40分鐘Flash緩存的數(shù)據(jù)在重連后完整補傳沒有丟幀。最值錢的體會是傳感器數(shù)據(jù)要敢信但也要敢懷疑。敢信才能放心地把控制邏輯交給自動程序敢懷疑才能在讀數(shù)異常時想到是傳感器臟了、線松了、還是電源紋波大了。這套系統(tǒng)繼續(xù)擴展的方向我也列一下給后來者參考加一個臭氧發(fā)生器或者紫外燈開關(guān)邏輯和高濕場景聯(lián)動能進一步抑霉再加一路煙霧傳感器和火焰?zhèn)鞲衅鳝h(huán)境控制和消防預(yù)警合到一塊如果倉庫面積大、點位多可以改成LoRa節(jié)點加網(wǎng)關(guān)的模式每個倉庫一個STM32節(jié)點網(wǎng)關(guān)統(tǒng)一上云硬件端把GP2Y換成PMS5003粉塵數(shù)據(jù)會更精確能測量PM2.5和PM10兩個指標(biāo)Flash緩存加一個時間戳隊列斷線時長做到3小時以上不丟數(shù)據(jù)。最后分享一個調(diào)試期的小技巧我在串口命令里加了一條本地指令通過串口助手下發(fā)類似set_humi_on 63.5的文本就能在運行狀態(tài)下修改閾值不用每次改完重新燒固件。倉庫環(huán)境要求變了、季節(jié)變了閾值跟著調(diào)這個功能讓我少燒了至少二十次程序。