設(shè)計(jì))
做嵌入式和 HVAC 項(xiàng)目的朋友幾乎都會(huì)遇到溫度采集這件事。一類是設(shè)備本地的溫度比如控制板安裝環(huán)境溫度、壓縮機(jī)表面溫度另一類是遠(yuǎn)程的溫度比如風(fēng)道末端、送回風(fēng)口、房間內(nèi)部這些布線距離比較遠(yuǎn)的位置。前陣子我配合某設(shè)備廠商做溫控節(jié)點(diǎn)核心就是標(biāo)題里的兩個(gè)器件PJ85718DM 數(shù)字溫度傳感器加一顆 STM32F205RB 控制芯片把本地溫度直接讀出來(lái)遠(yuǎn)程溫度再通過 RS485/Modbus 匯總上報(bào)。這套方案不算復(fù)雜但把采集、濾波、通信、校準(zhǔn)整個(gè)鏈路走通里面值得記錄的細(xì)節(jié)非常多。這篇文章會(huì)把整個(gè)項(xiàng)目從頭到尾拆開講。不管你是在做 HVAC 溫控器、嵌入式數(shù)據(jù)采集終端還是想學(xué)怎么用 I2C 溫度傳感器加 Modbus 做多點(diǎn)測(cè)溫都能直接照著搭。我會(huì)把選型原因、硬件接線、驅(qū)動(dòng)代碼、濾波處理、遠(yuǎn)程通信、實(shí)測(cè)數(shù)據(jù)、坑點(diǎn)排查全部寫出來(lái)盡量讓沒做過的人也能一次落地。1. 先從需求說(shuō)起嵌入式和 HVAC 到底要測(cè)哪里的溫度1.1 本地溫度和遠(yuǎn)程溫度分別解決什么問題很多剛接觸 HVAC 項(xiàng)目的人會(huì)有個(gè)誤區(qū)溫度采集不就是把傳感器插到設(shè)備里讀個(gè)數(shù)值嗎實(shí)際上本地溫度和遠(yuǎn)程溫度是兩類完全不同的問題。本地溫度通常指控制器自身周圍、壓縮機(jī)殼體、換熱器管壁、配電柜內(nèi)部這些位置。它距離 MCU 很近導(dǎo)線長(zhǎng)度通常不超過幾十厘米用 I2C 接口的數(shù)字傳感器非常合適。好處是傳感器直接輸出數(shù)字量不需要額外搭放大電路或做冷端補(bǔ)償抗干擾能力也強(qiáng)。這類溫度主要用來(lái)做設(shè)備保護(hù)比如壓縮機(jī)排氣溫度超過設(shè)定值就降載甚至停機(jī)或者根據(jù)管壁溫度判斷除霜時(shí)機(jī)。遠(yuǎn)程溫度則是安裝在風(fēng)道、回風(fēng)口、房間溫控面板、室外補(bǔ)償溫度探頭這些需要長(zhǎng)距離布線的位置。這些地方離主控板可能隔著十幾米甚至上百米I2C 在這種距離下根本跑不起來(lái)信號(hào)完整性會(huì)非常差。所以遠(yuǎn)程測(cè)溫的常規(guī)做法是在每個(gè)遠(yuǎn)端位置放一個(gè)小節(jié)點(diǎn)板節(jié)點(diǎn)板上有 MCU 和傳感器采集到溫度后通過 RS485 總線傳回主控。RS485 是差分信號(hào)抗干擾能力強(qiáng)標(biāo)準(zhǔn)應(yīng)用下能到 1200 米這正好覆蓋 HVAC 項(xiàng)目的典型布線距離。所以標(biāo)題里“本地與遠(yuǎn)程溫度”不是一句廢話它代表了兩條完全不同的信號(hào)鏈路。一個(gè)項(xiàng)目里同時(shí)處理好這兩條鏈路才能做出真正能用的溫控系統(tǒng)。1.2 這個(gè)項(xiàng)目適合誰(shuí)參考我建議下面幾類人重點(diǎn)看這篇文章做 HVAC 控制器、樓宇自控、工業(yè)溫控箱的嵌入式工程師這套方案可以直接落地。正在學(xué)習(xí) STM32 和 I2C 傳感器驅(qū)動(dòng)的開發(fā)者可以拿這個(gè)項(xiàng)目當(dāng)完整練手案例。想了解 Modbus RTU 從機(jī)怎么實(shí)現(xiàn)、怎么和觸摸屏或組態(tài)軟件對(duì)接的人第 4 章有代碼拆解。經(jīng)常被溫度數(shù)據(jù)跳變、通信不穩(wěn)定折磨的現(xiàn)場(chǎng)調(diào)試人員第 5 章和第 6 章的實(shí)測(cè)與排查記錄應(yīng)該能幫上忙。我自己當(dāng)年做類似項(xiàng)目時(shí)最頭疼的不是讀溫度而是讀出來(lái)的溫度不可信、傳不出去、或者現(xiàn)場(chǎng)跑幾天就漂了。這些問題大多不是芯片本身的問題而是電路細(xì)節(jié)、濾波策略和通信時(shí)序沒處理好。下面這些內(nèi)容就是我踩過坑之后總結(jié)出來(lái)的。2. 選型與整體方案為什么是 PJ85718DM STM32F205RB2.1 傳感器選型對(duì)比為什么不用 NTC 和 PT100溫度傳感器這塊市面上最常見的是 NTC 熱敏電阻、PT100 鉑電阻、以及各類數(shù)字溫度傳感器。我?guī)涂蛻糇龇桨笗r(shí)通常先問三個(gè)問題測(cè)量范圍多少、精度要求多少、現(xiàn)場(chǎng)有沒有強(qiáng)干擾?;卮鹜赀@三個(gè)問題再?zèng)Q定用哪種。NTC 成本最低很多家電溫控器都在用。但 NTC 是非線性的而且同一批次之間的阻值離散性也不小。要做好更換不標(biāo)定的產(chǎn)品基本必須用電橋加編程校準(zhǔn)表B 值一變整個(gè)曲線就偏了。我并不是否定 NTC在溫度范圍窄、精度要求不高、單點(diǎn)定標(biāo)能接受的產(chǎn)品里它仍然是最經(jīng)濟(jì)的選擇。但如果我們做的是對(duì)精度和一致性有要求的 HVAC 節(jié)點(diǎn)我不想把大量生產(chǎn)校準(zhǔn)成本轉(zhuǎn)移到溫控邏輯里。PT100 精度高能到 ±0.1℃ 甚至更高但需要恒流源或電橋電路、儀表放大器、ADC還要考慮導(dǎo)線電阻補(bǔ)償硬件成本一點(diǎn)都不低。PT100 適合做實(shí)驗(yàn)室級(jí)或者工業(yè)級(jí)標(biāo)準(zhǔn)溫度計(jì)用在分布式測(cè)溫節(jié)點(diǎn)上每個(gè)節(jié)點(diǎn)的物料成本和 PCB 面積都會(huì)失控。PJ85718DM 這種數(shù)字溫度傳感器就適合在這兩者之間找到一個(gè)平衡點(diǎn)。它本身集成了溫度敏感元件和 ADC直接通過 I2C 輸出數(shù)字量。單芯片方案不需要外圍放大精度一般能做到 ±0.5℃ 以內(nèi)分辨率可以達(dá)到 0.0078125℃。對(duì) HVAC 環(huán)境監(jiān)測(cè)來(lái)說(shuō)這個(gè)精度綽綽有余風(fēng)道和房間溫度的控制精度通常在 ±1℃ 到 ±2℃?zhèn)鞲衅鞅旧淼恼`差遠(yuǎn)小于控制回路的動(dòng)作閾值。另外要說(shuō)明的是PJ85718DM 這個(gè)型號(hào)可能不像 NTC 那樣人人熟悉我拿到實(shí)物后的理解是它是一顆 I2C 接口、16 位溫度寄存器輸出的數(shù)字傳感器工作電壓 3.3V 或 5V 都能兼容默認(rèn) 7 位地址是 0x48 那一檔實(shí)際以你手里的數(shù)據(jù)手冊(cè)為準(zhǔn)。如果你拿到的批次寄存器映射稍有差異不影響整體架構(gòu)只需要把驅(qū)動(dòng)里讀寄存器的地址改一下就行。2.2 STM32F205RB 扮演什么角色STM32F205RB 是 Cortex-M3 內(nèi)核主頻最高 120MHzFlash 是 1MBRAM 是 128KB。對(duì)于溫度采集這種任務(wù)來(lái)說(shuō)這個(gè)配置其實(shí)是富余的。那為什么還要選它因?yàn)槲以谶@個(gè)項(xiàng)目里不只是讀一個(gè)溫度還要同時(shí)管理 RS485 通信、看門狗、狀態(tài)指示燈、本地存儲(chǔ)甚至以后可能加 Ethernet 或 USB 轉(zhuǎn)串口升級(jí)。F205 這顆芯片有幾個(gè)點(diǎn)很對(duì)我的胃口。第一它有多個(gè)硬件 I2C 和 USART 外設(shè)I2C1 接傳感器USART2 接 RS485 收發(fā)器外設(shè)之間不沖突代碼也不需要在同一個(gè)中斷里來(lái)回切換。第二它支持 DMAUSART 的收發(fā)可以用 DMA 搬運(yùn)配合 Modbus RTU 的幀收發(fā)可以明顯降低 CPU 占用。第三它有足夠的 IO 和定時(shí)器資源后期哪怕要加兩路 PWM 控制風(fēng)機(jī)轉(zhuǎn)速也不至于重新選型。有人可能會(huì)問用一顆更低端的芯片是不是也行比如 F103 系列。答案是看項(xiàng)目定位。F103 當(dāng)然能跑同樣的邏輯但 F205 在 I2C 外設(shè)的穩(wěn)定性、SRAM 容量、DMA 通道數(shù)量上都要好一些。如果你做的是批量 HVAC 控制器追求極致成本F103 也完全撐得住但如果你做的是高端樓宇自控節(jié)點(diǎn)需要更充裕的資源跑后續(xù)功能F205 是更穩(wěn)妥的選擇。我這次用的正是 F205RB。2.3 系統(tǒng)布局本地采集、遠(yuǎn)程上報(bào)的整體鏈路整個(gè)系統(tǒng)的結(jié)構(gòu)可以理解為三層。第一層是本地采集層。STM32F205RB 通過 I2C 總線直接讀 PJ85718DM 的溫度數(shù)據(jù)。這里 I2C 線的長(zhǎng)度建議控制在 30cm 以內(nèi)傳感器盡量靠近主控板。如果傳感器模塊需要延長(zhǎng)用屏蔽線并在傳感器端加 0.1uF 去耦電容否則在變頻器附近很容易讀到毛刺。第二層是遠(yuǎn)程節(jié)點(diǎn)接入層。每個(gè)遠(yuǎn)端測(cè)溫點(diǎn)放一塊小從機(jī)板板上有 STM32 最小系統(tǒng)、PJ85718DM、RS485 收發(fā)器。從機(jī)板負(fù)責(zé)采集本地溫度并在收到主站請(qǐng)求時(shí)把溫度數(shù)據(jù)返回。這里的“本地”是相對(duì)于從機(jī)板而言的實(shí)際上它也是遠(yuǎn)程節(jié)點(diǎn)。第三層是集中管理上報(bào)層。主控板作為 Modbus 主站通過 RS485 總線輪詢各個(gè)從機(jī)節(jié)點(diǎn)。主站把各點(diǎn)溫度匯總后可以傳給觸摸屏、組態(tài)軟件或者再通過以太網(wǎng)上傳到樓宇管理平臺(tái)。這樣客戶在值班室就能看到每個(gè)風(fēng)道口、每個(gè)房間的溫度變化。說(shuō)句實(shí)在話這種“從機(jī)采集 主站輪詢”的結(jié)構(gòu)是 HVAC 行業(yè)最通用的方案因?yàn)?Modbus RTU 幾乎被所有觸摸屏和組態(tài)軟件原生支持不需要寫私有協(xié)議也不用裝額外驅(qū)動(dòng)。項(xiàng)目驗(yàn)收時(shí)客戶用一根 USB 轉(zhuǎn) 485 線就能看到數(shù)據(jù)說(shuō)服力很強(qiáng)。3. 硬件設(shè)計(jì)從傳感器到 MCU 再到遠(yuǎn)程總線的完整鏈路3.1 傳感器接線與電源設(shè)計(jì)PJ85718DM 的引腳不多通常包括 VDD、GND、SDA、SCL有些封裝還會(huì)引出一根 AD0 地址選擇腳。VDD 直接接 3.3VGND 就近回地SDA 和 SCL 各接一顆上拉電阻到 3.3V。上拉電阻的取值很有講究。我剛開始做的時(shí)候用 10kΩ 上拉結(jié)果 I2C 時(shí)鐘一提高到 400kHz 就經(jīng)常傳輸出錯(cuò)波形上升沿太慢。后來(lái)?yè)Q成 4.7kΩ問題立刻消失。如果你的板子比較緊湊、走線較短1.8kΩ 也可以但不要低于 1kΩ否則灌電流偏大對(duì)芯片 IO 不太友好。電源方面要特別注意傳感器盡量不要直接從 MCU 的 VDD 引腳上再拉一根線供電。MCU 引腳電流變化比較大尤其是驅(qū)動(dòng)繼電器或者指示燈時(shí)電源噪聲會(huì)耦合到溫度數(shù)據(jù)里。我的習(xí)慣是用一個(gè)小功率 LDO 單獨(dú)給傳感器供電或者至少在傳感器 VDD 引腳旁邊并聯(lián)一顆 1uF 陶瓷電容和一顆 0.1uF 高頻去耦電容兩者配合使用。多次實(shí)測(cè)下來(lái)這樣做能把溫度數(shù)據(jù)的隨機(jī)跳變減少一半以上。另外如果傳感器模塊是成品模塊板上一般自帶濾波電容和上拉電阻。自己做板子時(shí)PCB 布線要保證傳感器底下不要鋪大塊地銅避免雜散電容影響 SCL 波形。傳感器盡量靠近 MCU 引腳讓 I2C 走線保持短、直、左右有地線包裹。3.2 RS485 遠(yuǎn)程通信電路要點(diǎn)RS485 收發(fā)器我用的是 SP3485 這類 3.3V 供電的芯片直接和 STM32F205RB 的 USART 引腳連接不需要電平轉(zhuǎn)換。收發(fā)器的 A、B 引腳接到接線端子對(duì)外走雙絞線。這里有幾個(gè)細(xì)節(jié)特別容易翻車我逐個(gè)說(shuō)一下。第一A、B 之間要并接 120Ω 終端電阻。這個(gè)電阻放在總線最遠(yuǎn)端作用是吸收反射信號(hào)。如果總線上只有兩塊板而且距離很短不接也能跑但距離一超過幾十米或節(jié)點(diǎn)一多不接就會(huì)偶發(fā)幀錯(cuò)誤。終端電阻的計(jì)算很簡(jiǎn)單雙絞線的特征阻抗通常在 120Ω 左右終端電阻等于特征阻抗時(shí)反射最小。第二收發(fā)器的 DE 和 RE 引腳要連到一起由 MCU 的一個(gè) GPIO 控制。發(fā)送數(shù)據(jù)前拉高發(fā)送完再拉低切回接收。切換時(shí)機(jī)如果不對(duì)最后幾個(gè)字節(jié)會(huì)出現(xiàn)截?cái)唷N乙话惆l(fā)完最后一個(gè)字節(jié)后延時(shí) 1 個(gè)字符時(shí)間再拉低或者干脆用發(fā)送中斷里加個(gè)延時(shí)。你也可以用硬件自動(dòng)收發(fā)電路但我覺得用 GPIO 控制更直觀、排查問題更方便。第三A、B 線上要加 TVS 管防止現(xiàn)場(chǎng)長(zhǎng)線感應(yīng)雷擊或電機(jī)啟停造成的浪涌損壞收發(fā)器。HVAC 現(xiàn)場(chǎng)經(jīng)常有變頻器、風(fēng)機(jī)、水泵這些設(shè)備啟停瞬間會(huì)產(chǎn)生很強(qiáng)的干擾這個(gè)問題我在第 6 章還會(huì)單獨(dú)說(shuō)。RS485 總線布線建議用屏蔽雙絞線屏蔽層單端接地。如果是多節(jié)點(diǎn)總線要手拉手連接絕對(duì)不能搞成星形星形會(huì)產(chǎn)生大量反射。這是個(gè)老生常談的問題但現(xiàn)場(chǎng)施工時(shí)幾乎每回都要強(qiáng)調(diào)一遍因?yàn)楹芏嗍┕り?duì)習(xí)慣就近接線。3.3 樣機(jī)焊接與布局注意事項(xiàng)如果是自己打樣調(diào)試焊接傳感器之前先確認(rèn)引腳定義最好用萬(wàn)用表量一下 VDD 和 GND防止反接把芯片燒掉。PJ85718DM 這類貼片芯片很小焊接時(shí)焊臺(tái)溫度控制在 350℃ 左右時(shí)間要短。我見過有人焊了太久溫度傳感器被烙鐵烤偏了零點(diǎn)常溫下讀數(shù)比正常高了 2 度后來(lái)怎么軟件校準(zhǔn)都不穩(wěn)定最后換了一顆芯片才好。MCU 核心板的選擇上如果不想自己畫板直接買帶 STM32F205RB 的最小系統(tǒng)板也行但一定要確認(rèn) 3.3V 電源干凈。有些開發(fā)板的 LDO 紋波比較大在做溫度采集時(shí)表現(xiàn)出來(lái)就是讀數(shù)最后一位不停跳動(dòng)換一塊供電穩(wěn)定的板子立刻就好。板子上如果有繼電器、蜂鳴器這些感性負(fù)載它們的電源要和傳感器供電分開走線地線盡量采用單點(diǎn)接地。這樣做能減少地彈噪聲對(duì) ADC 和 I2C 的影響。雖然是“經(jīng)驗(yàn)之談”但在現(xiàn)場(chǎng)調(diào)試時(shí)往往就是這些細(xì)節(jié)決定成敗。4. 軟件實(shí)現(xiàn)驅(qū)動(dòng)、濾波與遠(yuǎn)程通信代碼拆解4.1 I2C 驅(qū)動(dòng)讀取溫度軟件上第一步是把傳感器讀出來(lái)。我用 STM32 的 HAL 庫(kù)打開 I2C1主機(jī)模式時(shí)鐘設(shè)成 100kHz 先跑通后面再提速到 400kHz。讀溫度寄存器的邏輯很簡(jiǎn)單先發(fā)送寄存器地址再讀兩個(gè)字節(jié)把兩個(gè)字節(jié)拼成 16 位整數(shù)最后按分辨率系數(shù)轉(zhuǎn)成溫度值。#define PJ85718DM_ADDR 0x48 #define REG_TEMP 0x00 int16_t read_temp_raw(void) { uint8_t reg REG_TEMP; uint8_t buf[2]; if (HAL_I2C_Master_Transmit(hi2c1, PJ85718DM_ADDR, reg, 1, 100) ! HAL_OK) { return INT16_MIN; // 返回?zé)o效值交給上層處理 } if (HAL_I2C_Master_Receive(hi2c1, PJ85718DM_ADDR, buf, 2, 100) ! HAL_OK) { return INT16_MIN; } return (int16_t)((buf[0] 8) | buf[1]); } float read_temp_celsius(void) { int16_t raw read_temp_raw(); if (raw INT16_MIN) { return -999.0f; // 用明顯異常值表示讀取失敗 } // 這里以 16 位分辨率 0.0078125℃/LSB 為例 return raw * 0.0078125f; }這段代碼有幾個(gè)地方要解釋一下。第一I2C 地址為什么是 0x48 左移位之后的格式HAL 庫(kù)里的地址參數(shù)是 8 位地址也就是包含讀寫位的格式7 位地址 0x48 需要左移一位變成 0x90才能和 HAL 庫(kù)的讀寫位拼接正確。我上面的寫法會(huì)有人直接用宏不同人的寫法可能差一位這點(diǎn)特別容易坑。你寫代碼時(shí)用數(shù)據(jù)手冊(cè)里的地址配合 HAL 庫(kù)時(shí)記得左移或者直接查你手上例程的定義。第二讀到的 raw 值是帶符號(hào)的因?yàn)閷挏囟确秶仑?fù)溫會(huì)出現(xiàn)最高位為 1 的情況。如果我把它定義成 uint16_t零下溫度轉(zhuǎn)換出來(lái)會(huì)變成很大的正數(shù)后臺(tái)顯示一團(tuán)亂麻。所以用 int16_t 接收再轉(zhuǎn) float。第三為什么溫度轉(zhuǎn)換系數(shù)是 0.0078125因?yàn)榉直媛适?1/128 0.0078125也就是 16 位溫度寄存器的最低位表示 0.0078125℃。如果你的芯片數(shù)據(jù)手冊(cè)寫明精度是 12 位、13 位或 14 位轉(zhuǎn)換公式要按實(shí)際有效位調(diào)整不能盲目套。讀取周期方面HVAC 溫度變化慢我把讀溫度放在 1s 定時(shí)中斷里觸發(fā)。如果需要更快響應(yīng)可以縮短到 200ms但對(duì)大多數(shù)風(fēng)道、房間溫度來(lái)說(shuō)沒必要反而會(huì)讓濾波更難做。4.2 溫度數(shù)據(jù)處理均值濾波與防毛刺直接讀出來(lái)的溫度值往往不能直接用因?yàn)楝F(xiàn)場(chǎng)有變頻器、開關(guān)電源這些干擾源原始數(shù)據(jù)偶爾會(huì)冒出一個(gè)明顯偏大的尖峰。我在項(xiàng)目里用了兩重處理第一重是滑動(dòng)平均濾波用來(lái)平滑隨機(jī)噪聲第二重是限幅判斷用來(lái)剔除明顯毛刺。滑動(dòng)平均的代碼可以直接看下面的實(shí)現(xiàn)#define FILTER_SIZE 8 static float temp_hist[FILTER_SIZE]; static uint8_t temp_idx 0; static uint8_t temp_count 0; float temp_sliding_avg(float new_sample) { float sum 0.0f; uint8_t i; temp_hist[temp_idx] new_sample; temp_idx (temp_idx 1) % FILTER_SIZE; if (temp_count FILTER_SIZE) { temp_count; } for (i 0; i temp_count; i) { sum temp_hist[i]; } return sum / temp_count; }用 8 個(gè)樣本做平均1s 讀一次的話就是 8s 平滑窗口。對(duì) HVAC 來(lái)說(shuō)這個(gè)響應(yīng)速度完全足夠而且曲線會(huì)非常漂亮。如果你要做壓縮機(jī)保護(hù)這種對(duì)快速響應(yīng)有要求的場(chǎng)景可以把窗口縮到 4 個(gè)樣本或者干脆不做平均直接用原始值只是再疊加一道限幅濾波。限幅濾波的核心思想是“上次合法值和這次采樣值之差超過一個(gè)閾值就認(rèn)為是毛刺”。比如正常溫度變化在 0.5℃/s那設(shè)置變化閾值 2℃ 已經(jīng)足夠?qū)捤?。一旦檢測(cè)到突變直接丟棄這次采樣用上次值代替。float temp_anti_glitch(float new_sample) { static float last_valid 25.0f; const float max_step 2.0f; if (fabsf(new_sample - last_valid) max_step) { return last_valid; } last_valid new_sample; return new_sample; }實(shí)際調(diào)試時(shí)我先把毛刺日志全部打出來(lái)看是周期性的還是偶發(fā)的。如果是周期性尖峰多半是周圍有開關(guān)動(dòng)作和 I2C 時(shí)鐘重疊如果是偶發(fā)可能是電源抖動(dòng)。命令先抓波形再改參數(shù)“拍腦袋”加濾波參數(shù)只會(huì)掩蓋問題不會(huì)根治問題。4.3 Modbus RTU 從機(jī)通信實(shí)現(xiàn)遠(yuǎn)程節(jié)點(diǎn)上跑的協(xié)議我直接用 Modbus RTU。它是 Modbus 協(xié)議在串行鏈路上的標(biāo)準(zhǔn)實(shí)現(xiàn)幀格式是地址 功能碼 數(shù)據(jù) CRC16。每個(gè)從機(jī)節(jié)點(diǎn)有一個(gè)唯一的地址主站發(fā)請(qǐng)求幀從機(jī)地址匹配后回響應(yīng)幀。我用功能碼 03 讀保持寄存器來(lái)開放溫度數(shù)據(jù)。保持寄存器規(guī)劃如下寄存器地址內(nèi)容說(shuō)明0x0000本地溫度整數(shù)部分例如 23℃0x0001本地溫度小數(shù)部分例如 0.450℃0x0002設(shè)備狀態(tài)bit0 傳感器在線bit1 通信正常0x0003固件版本例如 0x0101 表示 v1.01溫度用整數(shù)加小數(shù)兩部分存是為了避免浮點(diǎn)數(shù)在 Modbus 寄存器里表示時(shí)的字節(jié)序爭(zhēng)議。上位機(jī)拿到兩個(gè)寄存器后拼成 23.450℃。這種方法最直觀也最容易和第三方組態(tài)軟件對(duì)接。Modbus 從機(jī)核心處理邏輯可以寫成這樣uint16_t crc16_modbus(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; uint16_t i; while (len--) { crc ^ *data; for (i 0; i 8; i) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; } void modbus_rtu_poll(void) { uint8_t rxbuf[32]; uint16_t rxlen 0; uint16_t crc_rev, crc_cal; // 這里假設(shè) USART 中斷已經(jīng)按幀接收到 rxbuf并解析出 rxlen if (rxlen 8) { return; } // 檢查從機(jī)地址 if (rxbuf[0] ! SLAVE_ADDR) { return; } // 校驗(yàn) CRC注意 Modbus RTU 的 CRC 低字節(jié)在前 crc_rev (uint16_t)(rxbuf[rxlen - 1] 8) | rxbuf[rxlen - 2]; crc_cal crc16_modbus(rxbuf, rxlen - 2); if (crc_rev ! crc_cal) { return; } // 功能碼 03讀保持寄存器 if (rxbuf[1] 0x03) { uint16_t start_reg (rxbuf[2] 8) | rxbuf[3]; uint16_t reg_count (rxbuf[4] 8) | rxbuf[5]; uint8_t resp[64]; uint8_t pos 0; uint16_t crc_send; resp[pos] SLAVE_ADDR; resp[pos] 0x03; resp[pos] (uint8_t)(reg_count * 2); for (uint16_t i 0; i reg_count; i) { uint16_t reg_val get_holding_register(start_reg i); resp[pos] (uint8_t)(reg_val 8); resp[pos] (uint8_t)(reg_val 0xFF); } crc_send crc16_modbus(resp, pos); resp[pos] (uint8_t)(crc_send 0xFF); resp[pos] (uint8_t)(crc_send 8); // 發(fā)送響應(yīng) usart_send_bytes(resp, pos); } }CRC16 的實(shí)現(xiàn)網(wǎng)上有很多版本我每次都是拿一個(gè)已知幀去驗(yàn)證比如請(qǐng)求幀01 03 00 00 00 02 C4 0B如果算出來(lái)的 CRC 等于 C4 0B說(shuō)明算法正確。這個(gè)習(xí)慣非常重要因?yàn)?CRC 寫錯(cuò)會(huì)導(dǎo)致所有從機(jī)都不響應(yīng)而排查 CRC 問題非常費(fèi)時(shí)間。從機(jī)地址、波特率這些參數(shù)我習(xí)慣用配置項(xiàng)放在程序開頭方便現(xiàn)場(chǎng)更改。波特率固定用 9600 或者 19200。HVAC 項(xiàng)目對(duì)速度要求不高9600 反而抗干擾更好因?yàn)橥瑯右粠瑪?shù)據(jù)占的時(shí)間更長(zhǎng)抗瞬時(shí)干擾能力更強(qiáng)。5. 實(shí)測(cè)過程與數(shù)據(jù)表現(xiàn)5.1 本地測(cè)溫精度測(cè)試硬件焊接完、驅(qū)動(dòng)跑通后要做系統(tǒng)的溫度準(zhǔn)確性驗(yàn)證。我把節(jié)點(diǎn)板和一顆經(jīng)過計(jì)量的標(biāo)準(zhǔn)鉑電阻溫度計(jì)放在同一個(gè)恒溫環(huán)境里分別測(cè)了三個(gè)溫度點(diǎn)冰水混合物 0℃、室溫 25℃、恒溫槽 50℃。實(shí)測(cè)結(jié)果大概是這樣標(biāo)準(zhǔn)溫度℃PJ85718DM 讀數(shù)℃偏差℃0.000.250.2525.0024.80-0.2050.0050.150.15單顆芯片的偏差在 ±0.25℃ 左右滿足 HVAC 環(huán)境監(jiān)測(cè)的 ±1℃ 要求。如果你碰到偏差比較固定且大頭在同一方向的可以先做軟件校準(zhǔn)。校準(zhǔn)方法很簡(jiǎn)單就是兩點(diǎn)校正冰水混合物是天然的 0℃ 標(biāo)準(zhǔn)器再把傳感器放到一個(gè)穩(wěn)定的溫水槽里用高精度溫度計(jì)記錄實(shí)際溫度然后算出增益系數(shù)和偏移量。公式就是標(biāo)準(zhǔn)的線性修正修正值 原始讀數(shù) * 增益 偏移先測(cè)到兩個(gè)點(diǎn)的原始讀數(shù)和標(biāo)準(zhǔn)溫度解二元一次方程組就能算出增益和偏移。校準(zhǔn)參數(shù)存到 STM32 的 Flash 里每次上電讀取這樣傳感器一致性就能拉平。5.2 遠(yuǎn)程讀取驗(yàn)證遠(yuǎn)程通信這塊我用 USART 轉(zhuǎn) USB 線把主站和從機(jī)連起來(lái)先用 PC 上的串口工具直接發(fā) Modbus 幀測(cè)試。給從機(jī) 1 發(fā)讀取幀請(qǐng)求01 03 00 00 00 02 C4 0B從機(jī)響應(yīng)01 03 04 00 17 00 0F CRC解析一下數(shù)據(jù)寄存器 0x0000 是 0x0017十進(jìn)制 23表示 23℃寄存器 0x0001 是 0x000F十進(jìn)制 15表示 0.015℃這里要看你小數(shù)部分是存多少位。如果約定小數(shù)部分存的是千分位那 0x000F 就是 0.015℃不太對(duì)應(yīng)該等于 0.450℃。這里我特意提醒一下兩個(gè)寄存器拼接時(shí)必須確認(rèn)高位在前還是低位在前以及小數(shù)位數(shù)的約定。我在項(xiàng)目里習(xí)慣用“整數(shù)部分 千分位小數(shù)部分”比如 23.450℃ 就存成寄存器 0x0000 23寄存器 0x0001 450。這樣既不會(huì)丟精度上位機(jī)拼接也簡(jiǎn)單。測(cè)試時(shí)如果發(fā)現(xiàn)溫度差得離譜先查是不是這里拼接錯(cuò)了而不是懷疑協(xié)議寫錯(cuò)。5.3 現(xiàn)場(chǎng)安裝位置與熱耦合技巧HVAC 項(xiàng)目里測(cè)溫點(diǎn)位選在哪對(duì)控制效果的影響比傳感器精度還大。我的體會(huì)是“測(cè)得到”不等于“測(cè)得準(zhǔn)”。比如回風(fēng)溫度傳感器如果離送回風(fēng)口太近測(cè)到的其實(shí)是混合后的溫度而不是房間典型溫度風(fēng)道溫度傳感器如果貼在外壁上測(cè)量的其實(shí)是管道壁溫度跟空氣溫度差好幾度。安裝時(shí)一般要把傳感器的感溫部分盡量伸進(jìn)氣流中間或者緊貼管壁并加導(dǎo)熱硅脂。如果傳感器探頭裝在鋁制套管里套管和傳感器之間一定要填充導(dǎo)熱材料空氣層的導(dǎo)熱系數(shù)很低會(huì)導(dǎo)致響應(yīng)變慢、讀數(shù)滯后。另一個(gè)容易忽略的是太陽(yáng)輻射。室外補(bǔ)償溫度探頭如果被陽(yáng)光直射讀數(shù)會(huì)偏高好幾度必須加遮陽(yáng)罩。我見過一個(gè)項(xiàng)目室外探頭裝在屋面中午比實(shí)際溫度高 5℃導(dǎo)致系統(tǒng)不停地調(diào)節(jié)最后排查了半天才發(fā)現(xiàn)是太陽(yáng)直射的問題。6. 常見問題與排查技巧實(shí)錄6.1 讀不到溫度或溫度跳變癥狀是 I2C 讀回的數(shù)值要么一直是 0xFF要么值明顯亂跳。排查順序我建議按電流從外到內(nèi)走。先用示波器或萬(wàn)用表量傳感器 VDD 電壓確認(rèn)是 3.3V 而不是 2.8V 之類。接著量 SCL 和 SDA 的波形看有沒有上下拉電阻。如果沒有上拉I2C 總線根本拉不高讀取必然失敗。如果波形正常但讀值亂跳多半是電源噪聲。在傳感器 VDD 和 GND 之間焊一個(gè) 10uF 電解電容和一個(gè) 0.1uF 陶瓷電容跳變問題通常能消除大半。還要看一下你的 I2C 時(shí)鐘是不是在傳感器允許的范圍內(nèi)。有些傳感器最高只支持 400kHz你把它掛到 1MHz 就覺得“反正都是 I2C 應(yīng)該能跑”這是不行的。6.2 遠(yuǎn)程通信不穩(wěn)定RS485 通信不穩(wěn)定是我在 HVAC 現(xiàn)場(chǎng)遇到最多的問題。典型表現(xiàn)是主站偶爾收不到響應(yīng)或者收到的數(shù)據(jù) CRC 校驗(yàn)失敗。第一步檢查 A、B 兩根線是不是接反了。接反了通常完全不通但如果收發(fā)器內(nèi)置偏置電阻有時(shí)也能通只是信號(hào)余量很差距離一長(zhǎng)就周期性丟包。這種情況我用萬(wàn)用表量 A、B 間的電壓正常空閑時(shí)在 0.2V 到 0.5V 左右如果接近 0V 或者負(fù)值基本判定接反或者缺少終端電阻。第二步檢查終端電阻。掛兩個(gè)節(jié)點(diǎn)以上且距離超過 50m總線兩端必須各并一個(gè) 120Ω 電阻。注意是“兩端”不是每個(gè)節(jié)點(diǎn)都接。如果每個(gè)節(jié)點(diǎn)都接了 120Ω總線上并聯(lián)電阻會(huì)太小收發(fā)器負(fù)載過重信號(hào)幅度被拉低照樣丟包。第三步是檢查接地。RS485 總線最好用屏蔽雙絞線屏蔽層在主機(jī)側(cè)單點(diǎn)接地。如果現(xiàn)場(chǎng)噪聲特別強(qiáng)比如旁邊有變頻器可以考慮在 A、B 之間加 TVS 管同時(shí)串聯(lián)兩個(gè) 10Ω 電阻限制電流尖峰。6.3 校準(zhǔn)方法與系統(tǒng)聯(lián)調(diào)建議校準(zhǔn)出來(lái)的參數(shù)要能覆蓋傳感器本身的分散性。生產(chǎn)線批量校準(zhǔn)時(shí)不能只校單點(diǎn)因?yàn)閱吸c(diǎn)校準(zhǔn)只能修正偏移沒法修正斜率。兩點(diǎn)校準(zhǔn)時(shí)第一點(diǎn)盡量靠近實(shí)際工作范圍的下限第二點(diǎn)靠近上限這樣全量程內(nèi)誤差最小。系統(tǒng)聯(lián)調(diào)時(shí)我建議先做“最小系統(tǒng)調(diào)試”一塊主控板、一塊從機(jī)板、一個(gè)傳感器把從 I2C 讀到最終 RS485 返回的全鏈路跑通再逐步加節(jié)點(diǎn)。一次就把 10 個(gè)節(jié)點(diǎn)全掛上去出了問題很難定位。另外給每塊從機(jī)板刷寫地址時(shí)最好用一個(gè)獨(dú)立的小工具。地址重復(fù)是 Modbus 通信中非常隱蔽的問題兩個(gè)相同地址的從機(jī)同時(shí)響應(yīng)主站收到的數(shù)據(jù)就是亂的而且 CRC 校驗(yàn)時(shí)好時(shí)壞排查起來(lái)極其痛苦。我現(xiàn)在的習(xí)慣是在板子出廠前寫好地址標(biāo)簽并在軟件里做一次地址沖突檢測(cè)每次上電時(shí)從機(jī)在總線上收到比自己地址低的所有節(jié)點(diǎn)都無(wú)響應(yīng)后才允許進(jìn)入工作狀態(tài)。這個(gè)邏輯雖然簡(jiǎn)單但能省掉很多現(xiàn)場(chǎng)問題。還有個(gè)細(xì)節(jié)不要忽視看門狗。HVAC 現(xiàn)場(chǎng)偶爾會(huì)有非常強(qiáng)的電源跌落或電磁干擾導(dǎo)致 MCU 跑飛。沒有獨(dú)立看門狗的話從機(jī)可能一直死在那里主站怎么叫都不應(yīng)。我將獨(dú)立看門狗超時(shí)設(shè)成 3 秒正常工作狀態(tài)下不停喂狗一旦死機(jī)就自動(dòng)復(fù)位。這個(gè)功能看似不起眼但整個(gè)項(xiàng)目在客戶現(xiàn)場(chǎng)運(yùn)行半年后你就會(huì)知道它有多重要。說(shuō)到這我再分享一點(diǎn)自己的體會(huì)這類測(cè)溫項(xiàng)目真正體現(xiàn)功夫的地方往往不在芯片有多高級(jí)而在于把底層細(xì)節(jié)弄扎實(shí)。I2C 上拉、濾波窗口、CRC 校驗(yàn)、終端電阻、安裝位置每一處看起來(lái)都是小事但每一處都可能在現(xiàn)場(chǎng)放大成故障。先保證數(shù)據(jù)采集可信再談通信穩(wěn)定最后做精度修正按這個(gè)順序去做項(xiàng)目的成功率會(huì)高很多。如果你也正在做類似的嵌入式或 HVAC 溫度監(jiān)測(cè)方案建議第一版先按我這套思路搭起來(lái)跑通之后再去優(yōu)化成本和尺寸。后面你還可以在這個(gè)基礎(chǔ)上擴(kuò)展更多功能比如增加兩路 PWM 控制風(fēng)機(jī)、加入在線校準(zhǔn)、對(duì)接以太網(wǎng)網(wǎng)關(guān)等。等這些點(diǎn)都補(bǔ)齊了它就不僅僅是個(gè)溫度采集節(jié)點(diǎn)而是一個(gè)完整的 HVAC 邊緣控制器了。