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