溫度采集:DS18B20單總線時(shí)序與ROM搜索代碼解析)
簡(jiǎn)介STM32F030多點(diǎn)溫度采集系統(tǒng)源碼包為物聯(lián)網(wǎng)嵌入式開(kāi)發(fā)人員提供一套基于DHT20傳感器與移遠(yuǎn)BC260Y-CN NB-IoT模塊的完整代碼實(shí)現(xiàn)主要解決低功耗環(huán)境下多點(diǎn)溫度數(shù)據(jù)采集、本地顯示與云平臺(tái)接入問(wèn)題。壓縮包共24個(gè)文件包括10個(gè)C源文件、10個(gè)頭文件、2個(gè)MD說(shuō)明文檔以及gitignore、inscode配置整體約40KB。C源文件覆蓋主程序、DHT20驅(qū)動(dòng)、BC26通信、定時(shí)器、蜂鳴器報(bào)警、硬件初始化等模塊頭文件接口清晰便于閱讀和二次開(kāi)發(fā)。源碼包內(nèi)目錄劃分清楚C文件與頭文件一一對(duì)應(yīng)可快速定位功能模塊兩個(gè)MD文檔對(duì)硬件選型、NB-IoT原理及OneNet平臺(tái)配置等做了梳理降低了上手門(mén)檻。已有39人學(xué)習(xí)。開(kāi)發(fā)者可結(jié)合源碼理解NB-IoT與OneNet平臺(tái)的MQTT接入流程掌握OLED本地顯示、報(bào)警邏輯以及系統(tǒng)時(shí)鐘配置等關(guān)鍵設(shè)計(jì)適合正在學(xué)習(xí)嵌入式物聯(lián)網(wǎng)或準(zhǔn)備相關(guān)比賽、畢業(yè)設(shè)計(jì)的讀者參考借鑒。1. 用 F030 搭多點(diǎn)溫度采集這套代碼包解決的不只是測(cè)溫做某工控項(xiàng)目時(shí)被一個(gè)需求卡過(guò)一條產(chǎn)線上要布十幾個(gè)溫度點(diǎn)預(yù)算只給到一片幾塊錢(qián)的 MCU還要把數(shù)據(jù)一并匯總上傳。STM32F030 就是這個(gè)場(chǎng)景下的典型選擇M0 內(nèi)核、主頻 48MHz、資源剛好夠用而多點(diǎn)溫度采集系統(tǒng)的關(guān)鍵難點(diǎn)不在 ADC而在總線時(shí)序和 ROM 管理——這套代碼包把這兩塊都做完整了。適合正在做溫度采集、傳感器網(wǎng)絡(luò)、低成本改造的嵌入式開(kāi)發(fā)者也適合剛?cè)腴T(mén)想看懂 1-Wire 協(xié)議和器件驅(qū)動(dòng)怎么寫(xiě)的人。它解決的是從“單點(diǎn)測(cè)溫”到“多點(diǎn)組網(wǎng)”之間那段沒(méi)人幫你墊平的落差。2. 方案選型與代碼包結(jié)構(gòu)F030 為什么夠用DS18B20 為什么合適2.1 主控選型邏輯為什么不是 F103 也不是 8 位機(jī)先聊一個(gè)常見(jiàn)選擇誤區(qū)有人做多點(diǎn)測(cè)溫第一反應(yīng)是上 F103 甚至 F407。實(shí)際上溫度傳感器是低速率器件單個(gè) 12 位溫度轉(zhuǎn)換也就 750ms 級(jí)別十幾個(gè)點(diǎn)輪詢一次也不到 2 秒48MHz 的 F030 處理起來(lái)余量非常大而且它的 FLASH 容量從 16KB 起跳跑完整套 1-Wire 協(xié)議棧加協(xié)議解析加命令分發(fā)完全夠用。選 F030 的核心理由是性價(jià)比和封裝TSSOP20 的時(shí)候可以做得很小BOM 成本壓得低這是量產(chǎn)項(xiàng)目里非常實(shí)際的考量。另一個(gè)選擇是 8 位機(jī)如 STM8但 F030 的優(yōu)勢(shì)在于調(diào)試工具鏈統(tǒng)一——用 ST-Link 就能覆蓋整條產(chǎn)品線的調(diào)試不需要額外買(mǎi)燒錄器而且?guī)旌瘮?shù)與 F1/F4 系一脈相承后續(xù)要升級(jí)主控時(shí)遷移成本低。這套代碼包里已經(jīng)包含了標(biāo)準(zhǔn)外設(shè)庫(kù)的初始化文件拿到就是一個(gè)完整的工程不需要去官方例程里翻找再拼裝。2.2 傳感器選型DS18B20 的天然優(yōu)勢(shì)與局限溫度點(diǎn)比較多的時(shí)候常用的傳感器無(wú)非三類(lèi)熱電偶、NTC 熱敏電阻、DS18B20。熱電偶要配冷端補(bǔ)償和運(yùn)放電路成本高NTC 便宜但要每一路單獨(dú)校準(zhǔn)、單獨(dú)接 ADC引線一長(zhǎng)就受干擾。DS18B20 在這個(gè)場(chǎng)景里勝出靠的是三點(diǎn)單總線協(xié)議簡(jiǎn)化了走線數(shù)據(jù)線一拉到底、數(shù)字輸出免去了 ADC 校準(zhǔn)、溫度值直接以 12 位補(bǔ)碼讀出。但這玩意兒有局限代碼包里也做了對(duì)應(yīng)處理單總線上每個(gè)器件有個(gè) 64 位 ROM 序列號(hào)多點(diǎn)采集必須通過(guò)搜索算法枚舉所有在線的器件地址器件對(duì)時(shí)序非常敏感讀時(shí)序和寫(xiě)時(shí)序的延時(shí)窗口以微秒計(jì)這些在代碼里都已經(jīng)調(diào)過(guò)一輪。溫度測(cè)量范圍是 -55°C 到 125°C普通工業(yè)環(huán)境夠用所以這套方案的適用邊界就在“常規(guī)溫度、多點(diǎn)、低成本”這個(gè)三角區(qū)域內(nèi)。2.3 代碼包文件結(jié)構(gòu)每個(gè)文件管什么工程目錄大約是這么組織的拿到手上先對(duì)著確認(rèn)一遍文件/目錄職責(zé)關(guān)鍵內(nèi)容Inc/頭文件集合定時(shí)器配置、GPIO 映射、DS18B20 驅(qū)動(dòng)聲明Src/main.c主流程初始化順序、輪詢調(diào)度、串口打印Src/ds18b20.c單總線驅(qū)動(dòng)復(fù)位、讀寫(xiě)時(shí)序、ROM 操作Src/onewire_search.c多點(diǎn)搜索算法ROM 搜索、CRC 校驗(yàn)Src/delay.c微秒延時(shí)基于 SysTick 的延時(shí)適配MDK-ARM/Keil 工程芯片型號(hào) STM32F030C8T6已配置時(shí)鐘樹(shù)代碼包的區(qū)分度在于它把“單點(diǎn)驅(qū)動(dòng)”和“多點(diǎn)管理”分開(kāi)寫(xiě)了ds18b20.c只管操作一個(gè)器件onewire_search.c負(fù)責(zé)在總線上枚舉所有器件編號(hào)。很多網(wǎng)上代碼把這兩層混在一起改起來(lái)牽一發(fā)動(dòng)全身這套分層在后續(xù)加功能時(shí)會(huì)省心很多。3. 單總線時(shí)序與多點(diǎn)輪詢核心邏輯從物理層往上拆3.1 復(fù)位與存在檢測(cè)時(shí)序的實(shí)現(xiàn)DS18B20 的一切操作從復(fù)位開(kāi)始。主控拉低總線至少 480μs 后釋放器件檢測(cè)到上升沿會(huì)等待 15-60μs然后拉低總線 60-240μs 作為存在脈沖。這里有個(gè)容易翻車(chē)的地方標(biāo)準(zhǔn)里寫(xiě) 480μs 只是下限實(shí)際過(guò)長(zhǎng)反而可能讓器件誤判所以代碼里用的是 600μs 拉低加 40μs 等待。uint8_t ds18b20_reset(void) { uint8_t presence 0; DS18B20_DATA_LOW(); // 拉低總線 delay_us(600); // 復(fù)位脈沖寬度600us 比標(biāo)準(zhǔn)下限留了裕量 DS18B20_DATA_HIGH(); // 釋放總線進(jìn)入接收模式 delay_us(40); // 等器件響應(yīng)窗口 DS18B20_SET_INPUT(); // 切換為輸入模式讀取電平 presence DS18B20_READ_PIN(); // 讀取存在脈沖0 表示器件在位 DS18B20_SET_OUTPUT(); // 恢復(fù)輸出模式避免影響后續(xù)時(shí)序 delay_us(400); // 存在脈沖結(jié)束后等待恢復(fù) return presence; // 返回 0 表示檢測(cè)到器件 }代碼邏輯是按協(xié)議標(biāo)準(zhǔn)拆開(kāi)的四步先拉低產(chǎn)生復(fù)位脈沖再釋放總線拾取器件的存在回應(yīng)然后立刻把引腳切回輸入讀取電平最后切回輸出。DS18B20_SET_INPUT()和DS18B20_SET_OUTPUT()是驅(qū)動(dòng)里封裝好的 GPIO 模式切換這個(gè)細(xì)節(jié)很關(guān)鍵——如果一直保持推挽輸出器件根本拉不低總線你會(huì)看到復(fù)位永遠(yuǎn)失敗。參數(shù)說(shuō)明復(fù)位脈沖 600μs、等待窗口 40μs 是代碼里的默認(rèn)值。F0 系列 GPIO 翻轉(zhuǎn)速度快延時(shí)函數(shù)如果基于 48MHz 主頻跑這兩個(gè)值是不需要?jiǎng)拥?。注意如果你的板子用了外?8MHz 晶振倍頻到 48MHz延時(shí)基準(zhǔn)變了要按新主頻重算delay_us里的循環(huán)次數(shù)。3.2 讀寫(xiě)時(shí)序窗口怎么卡才穩(wěn)單總線通信全靠時(shí)序窗口區(qū)分 0 和 1讀操作尤其容易出問(wèn)題。代碼里寫(xiě)的讀時(shí)序是拉低總線 6μs 后釋放在 15μs 內(nèi)采樣引腳電平。這個(gè)“采樣點(diǎn)”是整個(gè)驅(qū)動(dòng)里最玄學(xué)的地方——采樣太早會(huì)讀到主控自己拉低的電平太晚器件可能已經(jīng)開(kāi)始發(fā)下一位了。uint8_t ds18b20_read_bit(void) { uint8_t bit 0; DS18B20_DATA_LOW(); delay_us(6); // 啟動(dòng)讀時(shí)序的拉低時(shí)間 DS18B20_DATA_HIGH(); DS18B20_SET_INPUT(); // 讀時(shí)序必須切輸入模式否則讀不到器件電平 delay_us(9); // 采樣點(diǎn)在 15us 窗口的中間偏前位置采樣 bit DS18B20_READ_PIN(); // 電平為高讀 1為低讀 0 DS18B20_SET_OUTPUT(); delay_us(50); // 剩余時(shí)間片補(bǔ)滿保證總線恢復(fù) return bit; }這里delay_us(9)是經(jīng)過(guò)實(shí)測(cè)調(diào)出來(lái)的值它決定了采樣點(diǎn)位置。改這段代碼時(shí)有個(gè)經(jīng)驗(yàn)不采樣太早、不采樣太晚兩個(gè)極端都會(huì)導(dǎo)致讀出來(lái)的數(shù)據(jù)出現(xiàn)“全是 0xFF”或者“前幾位漂移”的怪現(xiàn)象。參數(shù)上6μs 拉低對(duì)應(yīng)數(shù)據(jù)手冊(cè)規(guī)范50μs 收尾是為了位時(shí)間內(nèi)讓總線有充分恢復(fù)相鄰位之間不留足夠恢復(fù)時(shí)間連續(xù)讀取時(shí)數(shù)據(jù)會(huì)錯(cuò)位。3.3 ROM 搜索多點(diǎn)采集的第一步是“數(shù)人頭”單總線上掛多個(gè)器件時(shí)必須先做 ROM 搜索把所有器件的 64 位序列號(hào)枚舉出來(lái)。這套代碼采用標(biāo)準(zhǔn)的三字節(jié)搜索流程主控逐位發(fā)“讀 ROM 位”請(qǐng)求再發(fā)“寫(xiě) 0/寫(xiě) 1 選擇位”決定走向哪個(gè)分支。當(dāng)總線上多位器件在某一位上出現(xiàn) 0 和 1 分叉時(shí)就需要記錄分歧點(diǎn)并回溯。核心片段uint8_t ow_search(uint64_t *rom_codes, uint8_t max_devices) { uint8_t count 0; int16_t last_discrepancy -1; uint64_t current_rom 0; int16_t discrepancy -1; while (count max_devices) { uint8_t bit_index; uint8_t dir 0; discrepancy -1; current_rom 0; ds18b20_reset(); ds18b20_write_byte(0xF0); // 搜索 ROM 命令 for (bit_index 0; bit_index 64; bit_index) { uint8_t bitA ds18b20_read_bit(); // 讀原碼 uint8_t bitB ds18b20_read_bit(); // 讀反碼 if (bitA 1 bitB 1) { return count; // 無(wú)器件響應(yīng)搜索結(jié)束 } if (bitA ! bitB) { dir bitA ? 1 : 0; // 該位無(wú)分叉直接確定方向 } else { if (bit_index last_discrepancy) { dir (current_rom bit_index) 0x01; } else if (bit_index last_discrepancy) { dir 1; // 上次分歧位本次走 1 分支 } else { dir 0; // 新分歧先走 0 分支 discrepancy bit_index; } } // 寫(xiě)入選擇位同時(shí)拼裝當(dāng)前 ROM 值 ds18b20_write_bit(dir); if (dir) current_rom | ((uint64_t)1 bit_index); } rom_codes[count] current_rom; last_discrepancy discrepancy; if (discrepancy -1) break; } return count; }邏輯說(shuō)明這個(gè)函數(shù)把標(biāo)準(zhǔn)搜索算法拆成了三個(gè)狀態(tài)判斷。bitA和bitB分別代表總線上所有器件的原碼與反碼采樣結(jié)果兩者都為 1 意味著總線上沒(méi)有器件線路短路或斷開(kāi)兩者不同說(shuō)明該位所有器件一致兩者相同則說(shuō)明該位存在分歧點(diǎn)。通過(guò)維護(hù)last_discrepancy記錄上一次搜索走到的分歧位置搜索完一個(gè)器件后回到最近的分歧點(diǎn)走另一條分支就能窮舉所有 ROM 編號(hào)。參數(shù)說(shuō)明max_devices限制最多枚舉多少個(gè)建議按硬件實(shí)際掛載數(shù)加 2 設(shè)上限避免總線上有雜質(zhì)或虛焊時(shí)搜索死循環(huán)。rom_codes是 64 位數(shù)組每項(xiàng)一個(gè)器件序列號(hào)后續(xù)讀溫度時(shí)用匹配 ROM 命令直接尋址。4. 移植與工程集成改三個(gè)地方就能在自己的板子上跑4.1 引腳映射的修改方式代碼包默認(rèn)把數(shù)據(jù)線放在 PA4這個(gè)選擇是有講究的PA4 在 F030 上沒(méi)有額外的復(fù)用功能沖突方便獨(dú)立控制方向。要改引腳只需要?jiǎng)觗s18b20.c頭文件頂部的宏定義。// 修改引腳映射以 PA6 為例 #define DS18B20_GPIO_PORT GPIOA #define DS18B20_GPIO_PIN GPIO_PIN_6 #define DS18B20_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define DS18B20_DATA_LOW() HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET) #define DS18B20_DATA_HIGH() HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET) #define DS18B20_READ_PIN() HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN)注意改引腳時(shí)需同步調(diào)整DS18B20_SET_INPUT()和DS18B20_SET_OUTPUT()里的 GPIO 模式設(shè)置宏只覆蓋了電平操作和讀取方向切換在驅(qū)動(dòng)函數(shù)里用HAL_GPIO_Init重新配置。這里有個(gè)新手常犯的錯(cuò)只改了端口和引腳號(hào)沒(méi)改DS18B20_GPIO_CLK_ENABLE()結(jié)果引腳永遠(yuǎn)沒(méi)時(shí)鐘讀回來(lái)的數(shù)據(jù)全是 1。4.2 延時(shí)函數(shù)與主頻解耦代碼包自帶一套基于 SysTick 的delay_us它直接讀 SysTick 計(jì)數(shù)器的當(dāng)前值做遞減不依賴中斷。移植到其他主頻時(shí)要改一個(gè)宏定義// 48MHz 主頻下的延時(shí)基準(zhǔn)1us 對(duì)應(yīng) 48 個(gè)計(jì)數(shù) // 如果改到 72MHz 主頻把這里的 48 改成 72 #define SYSTICK_PER_US 48修改完成后最好用邏輯分析儀實(shí)測(cè)一下 10μs 延時(shí)的實(shí)際寬度。以前幫某開(kāi)發(fā)者排查過(guò)一個(gè)問(wèn)題他用了外部 16MHz 晶振然后倍頻到 64MHz但忘了改SYSTICK_PER_US導(dǎo)致所有時(shí)序都偏快約 25%表現(xiàn)是單個(gè)器件能讀到溫度但掛第二個(gè)器件后隨機(jī)丟數(shù)據(jù)——這就是時(shí)序漂移在多點(diǎn)拓?fù)湎卤环糯蟮牡湫桶咐?.3 工程選項(xiàng)上的三個(gè)注意第一編譯優(yōu)化等級(jí)建議從-O0開(kāi)始調(diào)試確認(rèn)邏輯正確后再改-O2。優(yōu)化等級(jí)對(duì)微秒級(jí)延時(shí)的指令周期影響很大delay_us里的空循環(huán)如果被優(yōu)化掉復(fù)位時(shí)序直接失效。第二F030 的 GPIO 輸出速度設(shè)置為GPIO_SPEED_FREQ_LOW單總線速率本身只有幾十 kbps低速檔位能減小過(guò)沖。第三開(kāi)漏輸出模式不是必須的只要在推挽模式下手動(dòng)切換輸入輸出即可但如果你同時(shí)接了外部上拉電阻建議直接用開(kāi)漏模式加 4.7k 上拉總線沖突風(fēng)險(xiǎn)更小。4.4 主循環(huán)調(diào)度寫(xiě)法輪詢與超時(shí)保護(hù)while (1) { // 觸發(fā)一次溫度轉(zhuǎn)換參數(shù) 1 表示非寄生供電 ds18b20_start_conversion(1); delay_ms(5); // 轉(zhuǎn)換啟動(dòng)時(shí)間 for (uint8_t i 0; i device_count; i) { int16_t temp ds18b20_read_temp(rom_codes[i]); if (temp DS18B20_READ_ERROR) { // 打印掉線提示但不阻塞后續(xù)通道 continue; } printf(CH%d: %.2f\r\n, i, temp / 16.0f); } delay_ms(900); // 等待 12 位轉(zhuǎn)換完成750ms 典型值 }主調(diào)度邏輯很簡(jiǎn)單先廣播轉(zhuǎn)換命令讓所有器件同時(shí)開(kāi)始轉(zhuǎn)換等 750ms 后再按 ROM 編號(hào)逐個(gè)讀取。這里有個(gè)小技巧——轉(zhuǎn)換命令是廣播的不需要每個(gè)器件單獨(dú)發(fā)這樣 10 個(gè)點(diǎn)也只消耗一次轉(zhuǎn)換等待時(shí)間。讀溫度時(shí)單點(diǎn)失敗先記錄下來(lái)繼續(xù)走后續(xù)點(diǎn)不要一封掉線就攔停整輪采集工程上這叫“降級(jí)不宕機(jī)”。5. 常見(jiàn)問(wèn)題與避坑記錄多點(diǎn)采集最容易踩的六個(gè)坑5.1 復(fù)位總是不成功示波器看波形是對(duì)的現(xiàn)象復(fù)位函數(shù)返回失敗概率很高特別是溫度變化時(shí)有時(shí)能掃到器件有時(shí)掃不到。原因排查后發(fā)現(xiàn)板子上拉了 2kΩ 上拉電阻。DS18B20 規(guī)范建議上拉電阻 4.7kΩ阻值太小會(huì)導(dǎo)致器件拉低總線時(shí)電流過(guò)大電平被拉到接近 0V 但恢復(fù)時(shí)間變慢。更典型的是阻值太大比如用了 10kΩ總線恢復(fù)沿變緩時(shí)序窗口偏移。解決換成 4.7kΩ 上拉到 3.3V所有異常消失。注意 F030 的 GPIO 輸入閾值是 TTL 電平不是 5V 器件上拉必須接 3.3V 而不是 VDD 5V。5.2 單個(gè)器件正常掛第二個(gè)器件后數(shù)據(jù)隨機(jī)錯(cuò)亂現(xiàn)象第一路溫度穩(wěn)定第二路接上后兩路數(shù)據(jù)偶爾互換或出現(xiàn) -55°C。原因這是典型的 ROM 搜索出錯(cuò)。兩個(gè)器件在 64 位 ROM 上某些位存在分歧搜索算法如果沒(méi)有正確維護(hù)last_discrepancy狀態(tài)第二次搜索就從錯(cuò)誤的分支繼續(xù)導(dǎo)致拿到的 ROM 編號(hào)不匹配實(shí)際器件。解決重新檢查ow_search里分歧位判斷邏輯。一個(gè)實(shí)操經(jīng)驗(yàn)是把搜索到的 ROM 編號(hào)打印出來(lái)比對(duì)如果發(fā)現(xiàn)第二路的編號(hào)是“分叉點(diǎn)沒(méi)回溯”產(chǎn)生的錯(cuò)誤地址確認(rèn)last_discrepancy在每次結(jié)束時(shí)要更新為當(dāng)前循環(huán)的discrepancy而不是沿用初始值。5.3 數(shù)據(jù)連續(xù)讀三遍才穩(wěn)定第一遍總有一路是殘值現(xiàn)象開(kāi)機(jī)第一次輪詢總會(huì)有一個(gè)通道讀到 85°C之后正常。原因這是 DS18B20 的上電默認(rèn)值。器件上電后溫度寄存器復(fù)位為 0x0550對(duì)應(yīng)的就是 85°C這是數(shù)據(jù)手冊(cè)明確寫(xiě)的初始化狀態(tài)不是硬件故障。直接讀溫度而不發(fā)起轉(zhuǎn)換時(shí)讀到這個(gè)值是正常的。解決啟動(dòng)后先發(fā)一次廣播轉(zhuǎn)換命令等待 800ms 再進(jìn)入正常輪詢循環(huán)把“預(yù)熱輪詢”納入初始化流程。或者代碼里把 85°C 當(dāng)作無(wú)效數(shù)據(jù)過(guò)濾掉但這個(gè)方法不嚴(yán)謹(jǐn)——環(huán)境溫度真在 85°C 時(shí)會(huì)被誤濾。5.4 引線超過(guò) 1 米后偶爾誤碼總線掛不住多個(gè)器件現(xiàn)象傳感器線從 30cm 延長(zhǎng)到 2 米原來(lái)穩(wěn)定的一主三從開(kāi)始隨機(jī)丟點(diǎn)。原因總線電容變大沿變緩加上沒(méi)有終端匹配。DS18B20 對(duì)總線上升沿的容忍度有限遠(yuǎn)距離傳輸時(shí)信號(hào)質(zhì)量明顯下降。解決線長(zhǎng)超過(guò) 1 米建議用屏蔽雙絞線屏蔽層單端接地總線兩端各加一個(gè) 4.7kΩ 上拉不是并聯(lián)是分別在物理兩端軟件上把讀取采樣點(diǎn)從 9μs 調(diào)整到 12μs給信號(hào)多留點(diǎn)穩(wěn)定時(shí)間。代碼包里這個(gè)采樣點(diǎn)抽出來(lái)單獨(dú)定義就是為了這種場(chǎng)景能快速調(diào)。5.5 F030 進(jìn)入 STOP 模式后喚醒總線上器件全部丟失現(xiàn)象低功耗模式休眠后喚醒搜索到的器件數(shù)量從 6 變成 1。原因STOP 模式會(huì)把 GPIO 和內(nèi)部時(shí)鐘全部停掉總線電平懸空器件可能進(jìn)入一種異常狀態(tài)。喚醒后沒(méi)有先做一次完整復(fù)位就繼續(xù)輪詢器件狀態(tài)機(jī)已經(jīng)錯(cuò)亂。解決喚醒流程里加一次“總線復(fù)位循環(huán)”——連續(xù)做三次ds18b20_reset()間隔 10ms確??偩€上所有器件狀態(tài)歸零后再搜索。這是從低功耗產(chǎn)品上學(xué)到的經(jīng)驗(yàn)多點(diǎn)場(chǎng)景下喚醒后的復(fù)位次數(shù)比單點(diǎn)要多兩次才保險(xiǎn)。5.6 時(shí)序函數(shù)在優(yōu)化等級(jí)切換后失效現(xiàn)象Debug 模式一切正常Release 模式-O2下復(fù)位和搜索全部失敗。原因delay_us里的空循環(huán)被編譯器優(yōu)化掉一部分函數(shù)整體延時(shí)不達(dá)標(biāo)。另一個(gè)問(wèn)題是編譯器對(duì)寄存器變量的處理影響 GPIO 交替翻轉(zhuǎn)速度。解決delay_us函數(shù)里的延時(shí)變量用volatile修飾防止被優(yōu)化兩個(gè)忙等循環(huán)之間不要做多余內(nèi)存訪問(wèn)如果改優(yōu)化等級(jí)后仍然失敗用示波器測(cè)量實(shí)際延時(shí)并校準(zhǔn)SYSTICK_PER_US的取值。6. 多點(diǎn)采集的進(jìn)階用法掉線自愈與批量數(shù)據(jù)校驗(yàn)這套代碼包再往上走有四個(gè)直接能用的擴(kuò)展方向。第一個(gè)是掉線自愈輪詢。單總線上的器件接觸不良是常態(tài)尤其在振動(dòng)環(huán)境下。我給代碼加了一個(gè)“健康位圖”數(shù)組每一輪搜索成功后更新位圖連續(xù) 3 輪都枚舉不到的器件標(biāo)記為掉線不再參與輪詢掉線器件恢復(fù)后自動(dòng)重新入隊(duì)。這個(gè)機(jī)制讓整個(gè)采集系統(tǒng)在傳感器接觸不良時(shí)不會(huì)丟一路數(shù)據(jù)就停擺。第二個(gè)是批量數(shù)據(jù)校驗(yàn)。一個(gè)輪詢周期里讀完所有溫度后對(duì)所有通道做一個(gè)異或校驗(yàn)和與上次周期比較如果校驗(yàn)和突變但溫度變化率小于 0.5°C 就判定為總線干擾丟棄該輪數(shù)據(jù)再補(bǔ)采一輪。這個(gè)方法能有效濾掉 2 米長(zhǎng)線場(chǎng)景里的偶發(fā)抖動(dòng)比單純?yōu)V波更干凈。第三個(gè)是低功耗定時(shí)采集。單純把主頻降到 8MHz 不夠要讓 MCU 在兩次溫度采集之間進(jìn) STOP 模式。喚醒前用 RTC 定時(shí)器打點(diǎn)喚醒后先做三次總線復(fù)位再搜索我實(shí)測(cè)過(guò)一輪 10 點(diǎn)采集加 5 秒休眠的平均電流在 120μA 左右比一直輪詢省了一個(gè)量級(jí)。第四個(gè)是地址表自動(dòng)錯(cuò)位檢測(cè)。多點(diǎn)系統(tǒng)的隱藏風(fēng)險(xiǎn)是某一路傳感器被換成了同一型號(hào)的備用件后ROM 編號(hào)變了采集程序還按舊地址匹配導(dǎo)致該通道讀回的是另一個(gè)位置的溫度??梢栽诿看嗡阉骱髮?duì)比 ROM 表發(fā)現(xiàn)新增 ROM 時(shí)打印警告并重新映射通道號(hào)。這套代碼包給我最大的教訓(xùn)在“時(shí)序參數(shù)不能用一級(jí)優(yōu)化跑”某次為了趕項(xiàng)目把優(yōu)化等級(jí)全開(kāi)結(jié)果溫度數(shù)據(jù)在 75°C 附近出現(xiàn)固定偏差排查了兩天才發(fā)現(xiàn)是delay_us里的延時(shí)循環(huán)被優(yōu)化掉了一部分。從那以后我每次改完時(shí)序相關(guān)代碼都強(qiáng)制在-O2下完整跑一遍 ROM 搜索驗(yàn)證。多點(diǎn)溫度采集的坑非常分散硬件、協(xié)議、固件三層都有。希望這套拆解能幫你少走一輪冤枉路。本文還有配套的精品資源點(diǎn)擊獲取