試:4檔開關(guān)ADC采集與Modbus浮點(diǎn)字節(jié)序處理)
最近調(diào)試一塊工控主板碰到一個(gè)典型需求產(chǎn)品面板上要加一個(gè)4檔旋轉(zhuǎn)開關(guān)用來切換工作模式同時(shí)設(shè)備需要把幾個(gè)32位浮點(diǎn)參數(shù)用Modbus RTU傳給上位機(jī)。一開始覺得都是很基礎(chǔ)的事真正動(dòng)手才發(fā)現(xiàn)一個(gè)看似簡單的檔位采集牽涉到IO資源分配、電阻分壓、ADC采樣、消抖濾波一個(gè)看似簡單的float上報(bào)牽涉到IEEE 754格式、寄存器字序、字節(jié)序稍不注意就是一臺(tái)設(shè)備一個(gè)樣排查到懷疑人生。這篇是《嵌入式調(diào)試筆記》的第6篇正好把這兩個(gè)問題放在一起寫。它們一個(gè)在硬件采集層一個(gè)在通信協(xié)議層但本質(zhì)上都指向同一件事在資源受限、協(xié)議有歧義的嵌入式系統(tǒng)里怎么用最少的IO、最穩(wěn)妥的方式把物理量變成數(shù)字把數(shù)字送出去還能讓對(duì)端準(zhǔn)確還原。內(nèi)容不追求高大上全是實(shí)際調(diào)試中驗(yàn)證過的東西。1. 4檔旋轉(zhuǎn)開關(guān)的采集思路為什么別用4個(gè)IO直讀先說結(jié)論如果MCU的IO真的多到用不完4檔開關(guān)當(dāng)然可以用4個(gè)GPIO直讀每檔對(duì)應(yīng)一個(gè)引腳邏輯簡單、代碼也簡單。但從產(chǎn)品設(shè)計(jì)的角度這種方案往往是最貴的選擇因?yàn)镮O永遠(yuǎn)不夠用。一個(gè)稍微復(fù)雜點(diǎn)的工控主板IO都被什么東西占滿了LED指示燈、繼電器控制、按鍵矩陣、傳感器輸入、通信使能腳、撥碼配置腳七七八八加起來一顆STM32F103的IO就吃得差不多了。這時(shí)候?yàn)榱艘粋€(gè)模式選擇開關(guān)再掏出4個(gè)IO代價(jià)太高。更麻煩的是有些MCU封裝只有20腳、28腳IO總數(shù)可能就十幾個(gè)直讀方案直接就不可行。1.1 直讀方案在真實(shí)產(chǎn)品里的代價(jià)4個(gè)IO直讀4檔開關(guān)電路上是省事了但代價(jià)不止是IO數(shù)量。一是外圍上拉電阻成倍增加。開關(guān)公共端接地、檔位端接MCU引腳那么每個(gè)引腳都需要一枚上拉電阻或者啟用內(nèi)部上拉但內(nèi)部上拉一般50k左右抗干擾能力弱工業(yè)場(chǎng)合不建議依賴PCB面積和成本都上去了。二是線束增加。面板上的旋轉(zhuǎn)開關(guān)到主板之間要過4根信號(hào)線加1根公共線線束變粗、接插件引腳變多這對(duì)結(jié)構(gòu)空間和物料成本都是壓力。尤其是一些小型化產(chǎn)品面板后面根本沒有空間塞一捆線。三是診斷能力為零。直讀方案里每根線電平只有高和低兩種狀態(tài)如果某一路因?yàn)榻硬寮蓜?dòng)、線纜斷裂導(dǎo)致信號(hào)丟失系統(tǒng)看到的是一個(gè)固定的錯(cuò)誤檔位而且很難區(qū)分是開關(guān)本身在錯(cuò)誤位置還是信號(hào)鏈路出了問題。1.2 真正省IO的做法一路ADC識(shí)別多檔分壓既然直讀4個(gè)IO不劃算那就換個(gè)思路把檔位信息編碼到模擬電壓里用一個(gè)ADC引腳識(shí)別。具體做法很經(jīng)典旋轉(zhuǎn)開關(guān)的公共端接到MCU的ADC采樣腳每個(gè)檔位串聯(lián)不同阻值的電阻到GND同時(shí)在ADC引腳接一枚上拉電阻到VCC。開關(guān)旋到不同檔位時(shí)分壓比不同ADC引腳上的電壓也不同MCU根據(jù)采樣電壓值反推當(dāng)前檔位。這個(gè)方案的優(yōu)點(diǎn)很突出只占用1個(gè)ADC引腳省下了3個(gè)IO。開關(guān)到主板之間的線束從5根減少到2根信號(hào)線公共地線。因?yàn)槊繖n電壓是連續(xù)模擬量還能順帶實(shí)現(xiàn)簡單的自診斷——如果采樣值落在一個(gè)毫無意義的中值區(qū)可以判定信號(hào)鏈路異常而不是盲目認(rèn)為開關(guān)壞了。電阻分壓的電流很小微安到毫安級(jí)對(duì)功耗敏感的低功耗設(shè)備也友好。當(dāng)然也有代價(jià)。ADC識(shí)別全靠電壓值區(qū)分檔位那么分壓電阻的精度、接觸電阻、線纜壓降、電源波動(dòng)都會(huì)影響結(jié)果必須在電路和固件兩個(gè)層面都做足功夫后面詳細(xì)展開。這里多說一句我見過有人用兩個(gè)IO加2位二進(jìn)制編碼方案也就是用撥碼開關(guān)輸出00/01/10/11的組合只用2個(gè)IO。這個(gè)方案的IO開銷確實(shí)比4路直讀小但需要開關(guān)本身具備編碼輸出能力一般的旋轉(zhuǎn)檔位開關(guān)并沒有這種結(jié)構(gòu)通常是單刀多擲所以實(shí)際應(yīng)用受限。相比之下ADC分壓方案對(duì)開關(guān)結(jié)構(gòu)沒特殊要求通用性更強(qiáng)。2. 電阻分壓的檔位電壓、ADC閾值與誤差裕量電路方案定下來之后最關(guān)鍵的環(huán)節(jié)是分壓電阻選值和閾值劃分。這個(gè)環(huán)節(jié)做得好不好直接決定開關(guān)識(shí)別穩(wěn)不穩(wěn)定。說實(shí)話我早期在這個(gè)問題上吃過虧——隨便選了4個(gè)阻值算了一下理論電壓覺得間隔夠大結(jié)果實(shí)際焊接完一測(cè)某個(gè)檔位在溫度變化之后直接越界后來又回頭重新設(shè)計(jì)。2.1 分壓電路與參數(shù)選擇邏輯我的推薦結(jié)構(gòu)是這樣的ADC引腳通過一枚上拉電阻R_up接到VCC旋轉(zhuǎn)開關(guān)公共端接ADC引腳各檔位分別通過不同阻值的電阻接到GND。為方便講清楚下文的VCC按3.3V、MCU的ADC為12位采樣值0~4095來算。各檔位接入的電阻記為R_sw。開關(guān)旋到某檔時(shí)等效電路就是R_up在上、R_sw在下ADC引腳電壓為V_adc VCC * R_sw / (R_up R_sw)以R_up 10k、檔位電阻分別取1k、2.2k、4.7k、10k為例各檔理論電壓和ADC采樣值如下表檔位R_sw (kΩ)V_adc (V)ADC采樣值12位11.00.30037222.20.59573834.71.1061372410.01.6502048相鄰檔位的ADC采樣值間隔分別是366、634、676從數(shù)值上看已經(jīng)足夠分開。但注意這只是理論值真正的產(chǎn)品還要考慮電阻精度、接觸電阻、電源波動(dòng)、ADC本身的偏移和增益誤差。電阻選型時(shí)建議用1%精度的金屬膜電阻不要把阻值選得太大。超過100k的電阻容易引入PCB漏電流和灰塵污染導(dǎo)致的阻抗變化ADC采樣時(shí)也容易被引腳寄生電容拖慢穩(wěn)定時(shí)間。常用的分壓電阻區(qū)間是1k~100k我一般控制在1k~20k之間。R_up選10k是個(gè)比較平衡的值既能保證電流在安全范圍又不會(huì)因?yàn)樽柚堤≡黾庸摹?.2 閾值劃分與實(shí)測(cè)裕量分壓網(wǎng)絡(luò)設(shè)計(jì)完成后閾值不能簡單取相鄰檔位采樣值的中間數(shù)還要給誤差留出余量。以上表參數(shù)為例相鄰兩個(gè)檔位的采樣值中值如下閾值位置相鄰檔位中值A(chǔ)DC設(shè)定閾值A(chǔ)DCT1檔1/檔2555555T2檔2/檔310551055T3檔3/檔417101710判斷邏輯是采樣值小于等于555判為檔1555~1055判為檔21055~1710判為檔3大于1710判為檔4。實(shí)際測(cè)試時(shí)我用一個(gè)三位半萬用表測(cè)過各檔電壓再換算成ADC采樣值和理論值偏差基本在20~30 LSB以內(nèi)這個(gè)偏差主要來自電阻精度和電源紋波。也就是說每檔的采樣值距離閾值還有300 LSB以上的空間裕量是充足的。有一個(gè)細(xì)節(jié)值得注意如果檔位比較多比如6檔、8檔或者M(jìn)CU的ADC只有10位那么相鄰檔位間隔會(huì)被壓縮閾值設(shè)計(jì)就得更謹(jǐn)慎。一個(gè)實(shí)用技巧是在合法檔位區(qū)域之外再定義非法區(qū)比如采樣值落在某個(gè)檔位的理論值±50 LSB范圍外時(shí)不立即切換檔位而是維持上一次的有效檔位連續(xù)多次采樣都在新檔位理論范圍內(nèi)才確認(rèn)切換。這樣可以有效避免一次偶發(fā)毛刺導(dǎo)致檔位亂跳。2.3 機(jī)械開關(guān)的接觸現(xiàn)象和固件消抖機(jī)械旋轉(zhuǎn)開關(guān)有個(gè)物理特性旋到檔位時(shí)觸點(diǎn)并不是瞬間穩(wěn)定接觸的會(huì)有一個(gè)短暫的通斷抖動(dòng)過程時(shí)間通常在幾毫秒到幾十毫秒。這種抖動(dòng)反映到ADC采樣值上就是檔位剛切換時(shí)采樣值會(huì)在低位和電平跳變之間來回跳動(dòng)。如果固件每個(gè)周期都采樣并立即更新檔位上位機(jī)看到的模式會(huì)在短時(shí)間內(nèi)閃爍變化這在工業(yè)現(xiàn)場(chǎng)很容易造成誤動(dòng)作。處理抖動(dòng)我常用的方式分三層第一層連續(xù)采樣濾波。每次讀取ADC時(shí)連續(xù)采樣8次取平均這個(gè)平均本身就能濾掉大部分高頻噪聲。第二層多次確認(rèn)。把檔位狀態(tài)做成一個(gè)狀態(tài)機(jī)只有連續(xù)N次我一般取5到10次周期10ms采樣都落在同一檔位的判定區(qū)間時(shí)才認(rèn)為檔位切換有效否則維持舊狀態(tài)。第三層遲滯比較。在閾值附近做遲滯處理比如閾值設(shè)為555那么從低檔往高檔切換時(shí)采樣值要大于580才認(rèn)為進(jìn)入高檔從高檔往低檔切換時(shí)采樣值要小于530才認(rèn)為回到低檔。遲滯帶大約等于采樣值波動(dòng)的兩倍以上能有效防止檔位臨界抖動(dòng)。這三層疊加下來我實(shí)測(cè)過正常人手旋開關(guān)的速度再慢從檢測(cè)到檔位變化到狀態(tài)最終確認(rèn)延遲不超過100ms對(duì)模式切換類功能完全夠用。3. Modbus里的float四種字節(jié)序錯(cuò)一個(gè)全亂檔位采集解決之后下一個(gè)問題是這些數(shù)值怎么通過Modbus送出去。如果你的設(shè)備只需要上傳整數(shù)型狀態(tài)比如檔位1~4Modbus的寄存器直接存整型就行沒有任何歧義。但只要涉及到浮點(diǎn)數(shù)比如溫度、壓力、流量事情就變得微妙了。我跟很多工程師聊過大家普遍有一種誤解Modbus協(xié)議是標(biāo)準(zhǔn)協(xié)議float的傳輸順序也應(yīng)該是標(biāo)準(zhǔn)的。實(shí)際上Modbus協(xié)議標(biāo)準(zhǔn)里并沒有對(duì)32位浮點(diǎn)數(shù)在多個(gè)寄存器里的存放順序做強(qiáng)制統(tǒng)一規(guī)定只規(guī)定了每個(gè)寄存器是16位、高位字節(jié)在前也就是寄存器內(nèi)部的字節(jié)序是固定的Big-Endian但兩個(gè)寄存器之間的先后順序是設(shè)備廠商自己定義的。這就埋下了兼容性的大坑。3.1 IEEE 754單精度浮點(diǎn)數(shù)的底層形態(tài)先把基礎(chǔ)說透。一個(gè)32位float也就是單片機(jī)上最常見的float類型在內(nèi)存里占用4個(gè)字節(jié)內(nèi)部結(jié)構(gòu)是符號(hào)位1 bit指數(shù)位8 bit尾數(shù)位23 bit比如數(shù)值1.0用IEEE 754標(biāo)準(zhǔn)表示出來是0x3F800000在內(nèi)存中按大端字節(jié)序看就是3F 80 00 00這四個(gè)字節(jié)。任何一個(gè)支持在線十六進(jìn)制轉(zhuǎn)float的工具都能驗(yàn)證這一點(diǎn)。Modbus RTU的一個(gè)數(shù)據(jù)寄存器是16位也就是2個(gè)字節(jié)所以一個(gè)float需要占用2個(gè)連續(xù)的保持寄存器4個(gè)字節(jié)。問題就出在這4個(gè)字節(jié)如何塞進(jìn)兩個(gè)寄存器里。常見排列方式有下面這幾種格式名稱寄存器1內(nèi)容寄存器2內(nèi)容實(shí)際字節(jié)流大端字節(jié)序大端字序大端字節(jié)序高16位低16位3F 80 00 00小端字序大端字節(jié)序低16位高16位00 00 3F 80大端字序但字內(nèi)字節(jié)交換高16位字節(jié)交換低16位字節(jié)交換80 3F 00 00小端字序且字內(nèi)字節(jié)交換低16位字節(jié)交換高16位字節(jié)交換00 00 80 3F用浮點(diǎn)數(shù)1.0舉例它的原始字節(jié)是3F 80 00 00在不同格式下上位機(jī)讀到的兩個(gè)寄存器值完全不同格式寄存器1寄存器2上位機(jī)還原值如果按AB CD解析AB CD大端字序0x3F800x00001.0CD AB小端字序0x00000x3F805.877E-39 或亂碼BA DC0x803F0x0000亂碼DC BA0x00000x803F亂碼4種格式只有一種和你的上位機(jī)解析方式一致其余3種讀出來的都是毫無意義的小數(shù)或天文數(shù)字。而且這4種格式在現(xiàn)實(shí)中都有設(shè)備在用找不到絕對(duì)的標(biāo)準(zhǔn)。國產(chǎn)儀表、組態(tài)軟件里最常見的是CD AB低字在前、字內(nèi)高字節(jié)在前但西門子、施耐德、以及很多歐美老牌儀表用的是AB CD。所以做Modbus設(shè)備字節(jié)序必須是可配置的不能寫死。3.2 拆分與還原的C實(shí)現(xiàn)兼顧字序和字節(jié)序理解了上面這4種排列實(shí)現(xiàn)一個(gè)通用性強(qiáng)的浮點(diǎn)數(shù)拆分還原函數(shù)就比較清晰了。核心思路是先用memcpy把float變量的4個(gè)字節(jié)取出來然后根據(jù)配置的字序、字節(jié)序模式把這4個(gè)字節(jié)重新排列再拼成兩個(gè)16位寄存器。typedef enum { MODBUS_FLOAT_BIG_ENDIAN 0, /* AB CD */ MODBUS_FLOAT_LITTLE_ENDIAN 1, /* CD AB */ MODBUS_FLOAT_BYTE_SWAP_BIG 2, /* BA DC */ MODBUS_FLOAT_BYTE_SWAP_LITTLE 3 /* DC BA */ } modbus_float_order_t; static uint8_t float_order_map[4][4] { {0, 1, 2, 3}, /* AB CD : 0-0, 1-1, 2-2, 3-3 */ {2, 3, 0, 1}, /* CD AB : 0-2, 1-3, 2-0, 3-1 */ {1, 0, 3, 2}, /* BA DC : 0-1, 1-0, 2-3, 3-2 */ {3, 2, 1, 0} /* DC BA : 0-3, 1-2, 2-1, 3-0 */ }; void float_to_modbus_regs(uint16_t regs[2], float value, modbus_float_order_t order) { uint8_t raw[4]; uint8_t out[4]; memcpy(raw, value, 4); out[0] raw[float_order_map[order][0]]; out[1] raw[float_order_map[order][1]]; out[2] raw[float_order_map[order][2]]; out[3] raw[float_order_map[order][3]]; regs[0] (uint16_t)((out[0] 8) | out[1]); regs[1] (uint16_t)((out[2] 8) | out[3]); } float modbus_regs_to_float(uint16_t regs[2], modbus_float_order_t order) { uint8_t raw[4]; uint8_t in_[4]; float value; in_[0] (regs[0] 8) 0xFF; in_[1] regs[0] 0xFF; in_[2] (regs[1] 8) 0xFF; in_[3] regs[1] 0xFF; raw[float_order_map[order][0]] in_[0]; raw[float_order_map[order][1]] in_[1]; raw[float_order_map[order][2]] in_[2]; raw[float_order_map[order][3]] in_[3]; memcpy(value, raw, 4); return value; }這個(gè)實(shí)現(xiàn)里有個(gè)容易被忽略的細(xì)節(jié)float在MCU本地的內(nèi)存字節(jié)序和Modbus寄存器要求的字節(jié)序是兩回事。上面代碼里memcpy(raw, value, 4)取出來的是MCU內(nèi)存中的實(shí)際字節(jié)序。對(duì)于STM32這類小端MCU數(shù)值1.0在內(nèi)存中是00 00 80 3F而代碼中float_order_map數(shù)組做的事情實(shí)際上是把本地字節(jié)序映射成Modbus標(biāo)準(zhǔn)外部表現(xiàn)。如果換了一顆大端MCU比如一些PowerPC架構(gòu)的處理器raw數(shù)組的內(nèi)容本身就會(huì)變化這時(shí)映射表可能需要重新設(shè)計(jì)。一個(gè)更穩(wěn)妥的做法是先用系統(tǒng)函數(shù)判斷字節(jié)序再?zèng)Q定映射方向不過絕大多數(shù)單片機(jī)應(yīng)用都是小端這個(gè)坑實(shí)際遇到的概率很低。3.3 調(diào)試與驗(yàn)證用一個(gè)Python腳本減少聯(lián)調(diào)時(shí)間寫完固件側(cè)的函數(shù)之后強(qiáng)烈建議寫一個(gè)小工具配合驗(yàn)證尤其是在沒有現(xiàn)成上位機(jī)、只有Modbus Poll這類調(diào)試工具的時(shí)候。我自己習(xí)慣的做法是用Python寫一個(gè)幾十行的腳本把Modbus Poll讀到的原始寄存器值粘貼進(jìn)去腳本自動(dòng)按4種字節(jié)序轉(zhuǎn)換成float打印出來一眼就能看出當(dāng)前設(shè)備用的是哪種順序。比如寄存器1讀到0x0000、寄存器2讀到0x3F80腳本會(huì)輸出regs [0x0000, 0x3F80] orders [ (AB CD, lambda b: b), (CD AB, lambda b: b[2:4] b[0:2]), (BA DC, lambda b: b[1::-1] b[3:1:-1]), (DC BA, lambda b: b[3:1:-1] b[1::-1]), ] for name, func in orders: buf bytes([ (regs[0] 8) 0xFF, regs[0] 0xFF, (regs[1] 8) 0xFF, regs[1] 0xFF, ]) data func(buf) val struct.unpack(f, data)[0] print(f{name}: {val})輸出結(jié)果會(huì)顯示CD AB模式下還原出來是1.0其他模式都是亂碼這樣就能快速確認(rèn)設(shè)備的字節(jié)序配置。這個(gè)腳本也適合在聯(lián)調(diào)現(xiàn)場(chǎng)快速排查上位機(jī)讀出來不對(duì)的爭論到底是誰的問題。4. 聯(lián)調(diào)現(xiàn)場(chǎng)的兩個(gè)真實(shí)坑寄存器錯(cuò)位與ADC臨界值理論部分寫完了但真正的工程問題往往不在理論設(shè)計(jì)階段而在聯(lián)調(diào)現(xiàn)場(chǎng)。下面這兩個(gè)案例是我在這套方案調(diào)試過程中實(shí)際遇到過的一個(gè)出在通信鏈路一個(gè)出在采集鏈路都很有代表性。4.1 案例一上位機(jī)讀溫度變成天文數(shù)字問題不在字節(jié)序而在寄存器錯(cuò)位某個(gè)項(xiàng)目里設(shè)備要上報(bào)兩個(gè)浮點(diǎn)參數(shù)溫度和水位。我按CD AB的模式把溫度放進(jìn)寄存器0、寄存器1把水位放進(jìn)寄存器2、寄存器3。上位機(jī)用的組態(tài)軟件工程師按照他們平臺(tái)的習(xí)慣配置成一次連續(xù)讀8個(gè)寄存器也就是把寄存器0到寄存器7全讀上去了。結(jié)果就是溫度和水位確實(shí)讀出來了但組態(tài)軟件把第4和第5個(gè)寄存器這兩個(gè)寄存器原本是其他設(shè)備狀態(tài)量全是0也按照float解析了導(dǎo)致畫面上出現(xiàn)了一個(gè)5.877E-39的詭異數(shù)值。現(xiàn)場(chǎng)反饋設(shè)備數(shù)據(jù)亂碼。排查過程花了不少時(shí)間因?yàn)橐婚_始都以為是字節(jié)序配置不對(duì)。后來我用Modbus Poll直接讀寄存器0~3的原始值發(fā)現(xiàn)溫度、水位的寄存器值本身完全正確上位機(jī)解析單點(diǎn)也對(duì)問題就出在組態(tài)軟件掃描范圍多讀了幾個(gè)寄存器。這個(gè)教訓(xùn)我寫下來提醒自己Modbus設(shè)備發(fā)布寄存器表時(shí)一定要明確每個(gè)float變量占用的寄存器起始地址和長度同時(shí)把寄存器表中連續(xù)的浮點(diǎn)區(qū)與整數(shù)區(qū)之間預(yù)留空寄存器或做特殊標(biāo)注避免對(duì)端掃描時(shí)把無關(guān)數(shù)據(jù)當(dāng)成浮點(diǎn)解析。如果是給組態(tài)軟件做配置說明最好直接寫清楚讀2個(gè)寄存器而不是讀8個(gè)寄存器。4.2 案例二旋轉(zhuǎn)開關(guān)檔位偶發(fā)跳變ADC閾值臨界是個(gè)隱形炸彈另一塊板子旋轉(zhuǎn)開關(guān)在實(shí)驗(yàn)室測(cè)試一切正常裝到產(chǎn)品外殼里旋了幾下現(xiàn)場(chǎng)反饋偶爾會(huì)出現(xiàn)檔位從2跳到3再跳回2的現(xiàn)象。一開始懷疑開關(guān)質(zhì)量不行換了幾個(gè)開關(guān)還是偶發(fā)。后來把ADC采樣值直接通過調(diào)試串口打印出來才發(fā)現(xiàn)問題不在開關(guān)在電源。這塊板子的3.3V來自一顆低壓差線性穩(wěn)壓器而外殼里還有一個(gè)繼電器每次繼電器吸合的瞬間3.3V會(huì)被拉低幾百毫伏ADC采樣值隨之整體下移幾十個(gè)LSB。檔位2的理論采樣值738本來就離閾值1055比較遠(yuǎn)按理說不至于跳變。但關(guān)鍵是繼電器吸合的瞬間開關(guān)觸點(diǎn)本身也會(huì)因?yàn)闄C(jī)械振動(dòng)產(chǎn)生微小的電平抖動(dòng)采樣值可能出現(xiàn)瞬間跳變到閾值另一側(cè)的毛刺。解決方式就是前面說的三層濾波里的第二層和第三層連續(xù)N次確認(rèn)加閾值遲滯。同時(shí)我還在繼電器的供電回路上加大容量電容、在ADC引腳對(duì)地并聯(lián)一個(gè)0.1uF的濾波電容雙管齊下。改完之后壓力測(cè)試連續(xù)開關(guān)繼電器幾百次檔位狀態(tài)紋絲不動(dòng)。這個(gè)案例給我最大的啟發(fā)是ADC識(shí)別檔位方案的穩(wěn)定性不只取決于分壓電阻和閾值設(shè)計(jì)還取決于系統(tǒng)的整體電氣環(huán)境。采樣電路不是孤立存在的它要和周圍的繼電器、電機(jī)、電源一起工作設(shè)計(jì)時(shí)必須留夠裕量。5. 這套組合的復(fù)用價(jià)值給后續(xù)項(xiàng)目留個(gè)可配置的通用模板旋轉(zhuǎn)開關(guān)的ADC分壓采集加上Modbus浮點(diǎn)數(shù)的通用解析模塊這兩塊功能雖然是圍繞一個(gè)具體項(xiàng)目做的但做完之后我發(fā)現(xiàn)它們的復(fù)用價(jià)值非常高。現(xiàn)在我把這兩部分都整理成了獨(dú)立模塊新項(xiàng)目里再遇到類似需求基本上不用重寫拷貝過去改一下參數(shù)就能用。旋鈕開關(guān)采集模塊我封裝成了檔位表驅(qū)動(dòng)的風(fēng)格每個(gè)檔位的ADC理論值、判定區(qū)間、遲滯帶全部放在一張常量表里要適配新的開關(guān)角度數(shù)或者新的電阻網(wǎng)絡(luò)只改表就行主邏輯不用動(dòng)。Modbus浮點(diǎn)解析模塊則保留字節(jié)序配置項(xiàng)通過一個(gè)結(jié)構(gòu)體傳入設(shè)備實(shí)際使用的字節(jié)序然后在初始化時(shí)確定。不管是做從機(jī)還是做主機(jī)這兩個(gè)模塊都能用。在實(shí)際操作中我還養(yǎng)成了一個(gè)習(xí)慣每做一個(gè)帶Modbus從機(jī)的項(xiàng)目就在聯(lián)調(diào)文檔里明確標(biāo)注浮點(diǎn)的字節(jié)序同時(shí)給對(duì)端工程師提供一個(gè)用Modbus Poll驗(yàn)證的小實(shí)驗(yàn)步驟。這個(gè)小動(dòng)作可以省掉后續(xù)大量的溝通成本——遠(yuǎn)程聯(lián)調(diào)時(shí)雙方爭論字節(jié)序問題往往要花好幾個(gè)小時(shí)但如果一開始就給了驗(yàn)證方法和示例基本幾分鐘就能達(dá)成共識(shí)。如果你手頭也正被這類問題困擾我的建議是先把采集和通信兩個(gè)模塊拆開調(diào)通各自確認(rèn)無誤后再聯(lián)調(diào)。硬件上用可調(diào)電源模擬各種電壓去做ADC識(shí)別的邊界測(cè)試通信上用固定的已知浮點(diǎn)數(shù)比如1.0、3.14去驗(yàn)證對(duì)端解析結(jié)果。這兩個(gè)基礎(chǔ)動(dòng)作做扎實(shí)了聯(lián)調(diào)階段很少會(huì)出現(xiàn)那種兩邊都覺得自己沒錯(cuò)的僵局。