動(dòng)DS18B20與TM1637及USART溫度上報(bào))
簡(jiǎn)介圍繞STM32F030R8T6平臺(tái)展開的USART通信、DS18B20測(cè)溫與TM1637數(shù)碼管顯示聯(lián)合例程面向已掌握GPIO、定時(shí)器等基礎(chǔ)外設(shè)、希望打通傳感器采集與串口調(diào)試鏈路的嵌入式學(xué)習(xí)者。例程中TM1637負(fù)責(zé)顯示兩位整數(shù)溫度小數(shù)部分通過USART按格式上傳便于觀察DS18B20在0.5℃測(cè)量精度與0.0625℃最高分辨率下的數(shù)據(jù)變化可解決溫度采集、數(shù)碼管驅(qū)動(dòng)和串口格式化輸出之間的聯(lián)調(diào)問題。壓縮包共866個(gè)文件約6.54MB包含.c/.h源碼、Keil工程uvprojx/uvoptx、hex與axf編譯產(chǎn)物以及html、js、png構(gòu)成的說明頁(yè)面和圖片另有pdf、txt文檔輔助查閱目錄按工程、驅(qū)動(dòng)、資料與輸出分類組織。目前已有235人學(xué)習(xí)下載。讀者可據(jù)此梳理STM32F0外設(shè)初始化、單總線時(shí)序、TM1637顯示編碼與串口打印小數(shù)的實(shí)現(xiàn)思路并借助完整工程和編譯輸出快速?gòu)?fù)現(xiàn)實(shí)驗(yàn)、對(duì)照排查通信與顯示異常對(duì)剛接觸STM32F0、單總線與數(shù)碼管掃描的讀者較友好。1. 需求與選型一套溫度采集顯示系統(tǒng)為什么要湊齊 USART、DS18B20 和 TM1637某高校的課程設(shè)計(jì)里常有一個(gè)看起來簡(jiǎn)單、真動(dòng)手卻處處卡殼的需求主控采集環(huán)境溫度本地四位數(shù)字碼管實(shí)時(shí)顯示同時(shí)把數(shù)據(jù)通過串口上報(bào)給上位機(jī)。主控選了 STM32F030傳感器用 DS18B20顯示用 TM1637通信走 USART組合起來是一套典型的「采集—顯示—上報(bào)」閉環(huán)。三者湊在一起不是隨便搭配DS18B20 單總線只占一個(gè) GPIOTM1637 兩線驅(qū)動(dòng)也只要兩個(gè)引腳USART 一收一發(fā)再加兩個(gè)STM32F030 這點(diǎn)引腳預(yù)算完全兜得住。這套方案適合做嵌入式課設(shè)、畢設(shè)的選題也適合需要低成本溫度終端的場(chǎng)景。真正難的不是寫代碼而是單總線微秒時(shí)序、兩線串行時(shí)序和串口幀格式三件事同時(shí)做到穩(wěn)定這篇按可復(fù)現(xiàn)的工程路徑拆開講。2. 總體架構(gòu)設(shè)計(jì)三層結(jié)構(gòu)與四個(gè)模塊的職責(zé)劃分拿到需求后第一件要定下來的不是代碼怎么寫而是資源怎么分。STM32F030 系列常見型號(hào)主頻 48MHz片上 Flash 與 RAM 都不寬裕所以模塊之間要盡量低耦合一個(gè)模塊出問題不能把整條采集鏈路帶崩。工程上我一般把它切成三層硬件抽象層只負(fù)責(zé)引腳電平操作驅(qū)動(dòng)層把時(shí)序打包成函數(shù)應(yīng)用層做采樣調(diào)度、顯示刷新和串口上報(bào)。這樣后面調(diào)試哪個(gè)模塊異常替換或停用都不影響另外兩個(gè)。2.1 硬件資源分配與時(shí)鐘配置引腳分配要提前畫成表寫進(jìn)工程注釋里后期改硬件才不至于亂。一份可用的分配表通常是下面這樣模塊信號(hào)引腳模式備注DS18B20DQPA1開漏輸出/浮空輸入切換外接 4.7k 上拉TM1637CLKPA4推挽輸出與 DIO 不同組TM1637DIOPA5推挽輸出/浮空輸入切換需要讀 ACKUSART1TXPA9復(fù)用推挽接 USB-TTL 的 RXUSART1RXPA10浮空輸入接 USB-TTL 的 TX時(shí)鐘上STM32F030 默認(rèn)走內(nèi)部 8MHz HSI經(jīng) PLL 倍頻到 48MHz 作為系統(tǒng)時(shí)鐘。APB2 上掛 USART1APB1 上掛通用定時(shí)器。之所以強(qiáng)調(diào)時(shí)鐘是因?yàn)?DS18B20 和 TM1637 的延時(shí)精度直接依賴系統(tǒng)主頻主頻改了延時(shí)函數(shù)要重算這是很多人換板子后時(shí)序突然失效的根因。配置通常在system_stm32f0xx.c或者初始化函數(shù)里完成主頻、AHB、APB 分頻都要和延時(shí)參數(shù)對(duì)齊。2.2 軟件分層驅(qū)動(dòng)層與應(yīng)用層的邊界驅(qū)動(dòng)層對(duì)外只暴露四個(gè)函數(shù)ds18b20_init、ds18b20_read_temp、tm1637_display_temp、usart_send_frame。應(yīng)用層完全不知道單總線怎么拉電平也不關(guān)心 TM1637 的段碼怎么排它只調(diào)用這四個(gè)函數(shù)并處理返回值。這條邊界立住了調(diào)試單總線的時(shí)候直接把tm1637_display_temp換成空實(shí)現(xiàn)碼管不動(dòng)但采集照跑問題范圍立刻縮小一半。反過來顯示模塊單獨(dú)調(diào)試時(shí)溫度值可以寫死成常量不用等傳感器。分層的另一個(gè)好處是便于答辯講清楚結(jié)構(gòu)。老師通常會(huì)問「你這個(gè)代碼怎么組織的」能說清三層職責(zé)和四個(gè)接口比堆一堆全局變量要有說服力得多。驅(qū)動(dòng)層的函數(shù)最好帶返回值表示成功或失敗比如ds18b20_read_temp返回 0 表示讀到有效溫度、非 0 表示 CRC 校驗(yàn)失敗或器件無響應(yīng)應(yīng)用層據(jù)此決定是顯示上一次有效值還是顯示----避免病態(tài)數(shù)據(jù)刷屏。2.3 主循環(huán)調(diào)度輪詢、定時(shí)與串口事件的配合主循環(huán)不做復(fù)雜調(diào)度用一個(gè)軟件定時(shí)器節(jié)拍配合狀態(tài)機(jī)就夠了。節(jié)拍源可以是 SysTick 每 1ms 中斷一次主循環(huán)里累加計(jì)數(shù)每到 750ms 觸發(fā)一次溫度轉(zhuǎn)換讀取每到 200ms 刷新一次顯示串口發(fā)送跟隨溫度更新事件一起走。DS18B20 12 位分辨率轉(zhuǎn)換時(shí)間最長(zhǎng) 750ms刷新周期設(shè)成 1s 更穩(wěn)妥顯示刷新可以快一些讓數(shù)字看起來不呆板。// 主循環(huán)骨架節(jié)拍驅(qū)動(dòng)避免阻塞式延時(shí)拖垮響應(yīng) volatile uint32_t g_tick_ms 0; void SysTick_Handler(void) { g_tick_ms; } int main(void) { uint32_t last_sample 0, last_display 0; int16_t temp_x10 0; board_init(); // 時(shí)鐘、GPIO、USART、SysTick 一起初始化 ds18b20_init(); tm1637_init(); while (1) { uint32_t now g_tick_ms; // 每 1s 采樣一次采樣成功才更新顯示緩存 if (now - last_sample 1000) { last_sample now; if (ds18b20_read_temp(temp_x10) 0) { usart_send_temp(temp_x10); // 上報(bào)成功讀到的值 } } // 顯示每 200ms 刷新一次讀失敗時(shí)顯示上次有效值 if (now - last_display 200) { last_display now; tm1637_display_temp(temp_x10); } } }這段骨架的關(guān)鍵點(diǎn)有三個(gè)。g_tick_ms用volatile修飾防止編譯器把循環(huán)里的讀優(yōu)化掉時(shí)間比較用無符號(hào)減法now - last_sample即使計(jì)數(shù)溢出也能正確算出間隔采樣和顯示分開計(jì)時(shí)采樣慢不會(huì)拖慢顯示刷新。實(shí)際項(xiàng)目里如果把delay_ms(750)直接塞進(jìn)主循環(huán)串口接收就會(huì)錯(cuò)過上位機(jī)下發(fā)的命令這是課堂上很常見的翻車點(diǎn)。3. DS18B20 單總線驅(qū)動(dòng)從復(fù)位脈沖到溫度換算的完整時(shí)序DS18B20 用的是 1-Wire 協(xié)議一根線完成供電寄生模式和雙向通信代價(jià)是所有時(shí)序都得主控自己精確控制。整個(gè)交互流程固定復(fù)位 → 存在脈沖 → 跳過 ROM0xCC→ 啟動(dòng)轉(zhuǎn)換0x44→ 等待 → 復(fù)位 → 跳過 ROM → 讀暫存器0xBE→ 讀兩個(gè)字節(jié) → CRC 校驗(yàn)。每一步都卡在微秒級(jí)窗口里延時(shí)函數(shù)不準(zhǔn)后面全亂。3.1 單總線時(shí)序的微秒級(jí)實(shí)現(xiàn)STM32F030 沒有 DWT 周期計(jì)數(shù)器微秒延時(shí)通常用兩種方式SysTick 輪詢或者空循環(huán)校準(zhǔn)??昭h(huán)簡(jiǎn)單但受編譯優(yōu)化等級(jí)影響-O0和-O2出來的延時(shí)能差好幾倍所以正式工程里我更傾向用 SysTick 做基準(zhǔn)。// 基于 SysTick 的微秒延時(shí)主頻 48MHz1us 48 個(gè)周期 void delay_us(uint32_t us) { uint32_t start SysTick-VAL; uint32_t ticks us * (SystemCoreClock / 1000000); uint32_t reload SysTick-LOAD 1; // SysTick 向下計(jì)數(shù)用差值判斷已經(jīng)過去多少 while (1) { uint32_t now SysTick-VAL; uint32_t elapsed (start now) ? (start - now) : (start reload - now); if (elapsed ticks) break; } }參數(shù)說明us是要延時(shí)的微秒數(shù)SystemCoreClock是當(dāng)前系統(tǒng)主頻48MHz 時(shí)每微秒 48 個(gè)計(jì)數(shù)reload是 SysTick 的重裝載值。注意這個(gè)函數(shù)要求 SysTick 已經(jīng)啟動(dòng)并且中斷間隔大于單次延時(shí)否則計(jì)數(shù)會(huì)繞回導(dǎo)致死循環(huán)。實(shí)際用的時(shí)候一次延時(shí)不要超過 SysTick 的重裝周期比如 1ms超過就拆成多次調(diào)用。DS18B20 單步延時(shí)最長(zhǎng)也就 750ms 的轉(zhuǎn)換等待那一步建議用前面主循環(huán)的節(jié)拍去等別用delay_us硬扛。3.2 ROM 命令與功能命令的調(diào)用順序單總線上掛一個(gè)器件時(shí)跳過 ROM 命令0xCC是效率最高的做法省掉 64 位地址匹配。啟動(dòng)轉(zhuǎn)換0x44之后傳感器開始內(nèi)部 ADC12 位分辨率需要 750ms。讀完溫度后如果想驗(yàn)證數(shù)據(jù)可靠可以讀暫存器的第 9 字節(jié)做 CRC8 校驗(yàn)多項(xiàng)式是x^8 x^5 x^4 1反射實(shí)現(xiàn)對(duì)應(yīng)0x8C。// 復(fù)位并檢測(cè)存在脈沖返回 1 表示總線上有器件應(yīng)答 uint8_t ds18b20_reset(void) { uint8_t presence; DQ_OUT(); // 切輸出 DQ_LOW(); // 拉低總線 delay_us(480); // 復(fù)位脈沖 480us DQ_HIGH(); // 釋放由上拉電阻拉高 delay_us(60); // 等 15~60us 后采樣存在脈沖 DQ_IN(); // 切輸入 presence DQ_READ(); delay_us(420); // 等剩余時(shí)隙走完防止下一次操作撞車 return (presence 0) ? 1 : 0; // 低電平表示有器件 }寫字節(jié)和讀字節(jié)是單總線最容易寫錯(cuò)的地方。寫 0 要拉低整個(gè)時(shí)隙至少 60us寫 1 只需拉低 1~2us 后立刻釋放讀的時(shí)候主控先拉低 1~2us 發(fā)起讀時(shí)隙然后在 15us 內(nèi)采樣采樣完再等 45us 左右收尾。下面兩段代碼按這個(gè)窗口寫// 寫一個(gè)字節(jié)LSB 優(yōu)先 void ds18b20_write_byte(uint8_t data) { for (uint8_t i 0; i 8; i) { DQ_OUT(); DQ_LOW(); if (data 0x01) { delay_us(2); // 寫 1短低脈沖后釋放 DQ_HIGH(); delay_us(60); // 補(bǔ)足 60us 時(shí)隙 } else { delay_us(60); // 寫 0拉低整個(gè)時(shí)隙 DQ_HIGH(); delay_us(2); } data 1; } } // 讀一個(gè)字節(jié)LSB 優(yōu)先 uint8_t ds18b20_read_byte(void) { uint8_t data 0; for (uint8_t i 0; i 8; i) { data 1; DQ_OUT(); DQ_LOW(); delay_us(2); // 發(fā)起讀時(shí)隙 DQ_HIGH(); DQ_IN(); delay_us(10); // 15us 內(nèi)采樣 if (DQ_READ()) data | 0x80; delay_us(50); // 收尾湊滿 60us } return data; }這兩個(gè)函數(shù)里的延時(shí)數(shù)字不是隨便寫的都對(duì)應(yīng)數(shù)據(jù)手冊(cè)里的時(shí)序窗口。把delay_us(10)改成 20 就可能在采樣點(diǎn)之后才讀讀出來全是 1把寫 0 的 60us 縮短從機(jī)可能識(shí)別成寫 1。調(diào)不通的時(shí)候第一件事是拿邏輯分析儀抓波形比對(duì)復(fù)位脈沖寬度和讀時(shí)隙采樣點(diǎn)比盲改代碼快得多。3.3 溫度寄存器的讀取、符號(hào)擴(kuò)展與 CRC 校驗(yàn)讀回來的兩個(gè)字節(jié)組成 16 位有符號(hào)數(shù)低 4 位是小數(shù)部分每格 0.0625℃。負(fù)溫度要用補(bǔ)碼處理常見做法是先判斷符號(hào)位再取絕對(duì)值避免直接用整數(shù)除法把負(fù)數(shù)算錯(cuò)。// 返回 0 表示成功temp_x10 輸出單位為 0.1℃ 的溫度 int ds18b20_read_temp(int16_t *temp_x10) { uint8_t buf[9]; if (!ds18b20_reset()) return -1; ds18b20_write_byte(0xCC); // 跳過 ROM ds18b20_write_byte(0x44); // 啟動(dòng)轉(zhuǎn)換 delay_us(750); // 12 位分辨率轉(zhuǎn)換時(shí)間 if (!ds18b20_reset()) return -2; ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 讀暫存器 for (uint8_t i 0; i 9; i) { buf[i] ds18b20_read_byte(); } if (ds18b20_crc8(buf, 8) ! buf[8]) return -3; // CRC 校驗(yàn)失敗 int16_t raw ((int16_t)buf[1] 8) | buf[0]; // 0.0625℃/LSB乘以 10 換成 0.1℃ 便于顯示 *temp_x10 (int16_t)((int32_t)raw * 10 / 16); return 0; }這段代碼里raw * 10 / 16利用了 0.0625 等于 1/16 的整數(shù)關(guān)系避免浮點(diǎn)運(yùn)算對(duì) M0 核很友好。CRC 校驗(yàn)?zāi)且徊讲荒苁慰偩€在長(zhǎng)線或者干擾環(huán)境下偶爾會(huì)讀出錯(cuò)值不校驗(yàn)直接顯示就會(huì)出現(xiàn)「數(shù)字亂跳」的玄學(xué)現(xiàn)象。返回值設(shè)計(jì)成不同的負(fù)數(shù)碼應(yīng)用層能區(qū)分是無應(yīng)答、CRC 錯(cuò)還是別的問題調(diào)試時(shí)看一眼就定位。4. TM1637 顯示與 USART 通信顯存映射與協(xié)議幀設(shè)計(jì)顯示和通信這兩個(gè)模塊本身不難難在細(xì)節(jié)對(duì)齊。TM1637 不是標(biāo)準(zhǔn) I2C起始和停止信號(hào)的定義跟 I2C 不一樣網(wǎng)上抄來的 I2C 代碼直接套會(huì)失敗。USART 雖然外設(shè)成熟但幀格式設(shè)計(jì)不好上位機(jī)解析時(shí)就容易錯(cuò)位。這兩塊要一起講因?yàn)樗鼈児蚕硗粋€(gè)「數(shù)據(jù)表示」問題溫度值怎么變成數(shù)碼管能顯示的段碼又怎么變成串口能傳的字節(jié)流。4.1 TM1637 兩線時(shí)序與段碼表TM1637 的協(xié)議核心是CLK 高電平時(shí) DIO 下降沿為起始CLK 高電平時(shí) DIO 上升沿為停止數(shù)據(jù)在 CLK 上升沿被寫入讀 ACK 時(shí)主控釋放 DIO 由從機(jī)拉低。亮度控制和顯存地址通過命令字設(shè)置顯示數(shù)據(jù)寫入地址0xC0開始的四個(gè)字節(jié)。段碼要按實(shí)際數(shù)碼管共陰共陽(yáng)來定常見共陽(yáng)段碼表是// 共陽(yáng)數(shù)碼管 0~9 段碼位序 dp g f e d c b a static const uint8_t seg_table[10] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; // 顯示帶一位小數(shù)的溫度例如 23.5 顯示為 23.5 void tm1637_display_temp(int16_t temp_x10) { uint8_t buf[4]; uint16_t v (temp_x10 0) ? (uint16_t)(-temp_x10) : (uint16_t)temp_x10; buf[0] (temp_x10 0) ? 0x40 : 0x00; // 負(fù)號(hào)用 g 段 buf[1] seg_table[(v / 100) % 10]; buf[2] seg_table[(v / 10) % 10] | 0x80; // 點(diǎn)亮小數(shù)點(diǎn) buf[3] seg_table[v % 10]; tm1637_write_regs(buf, 4); }seg_table里每一位對(duì)應(yīng)一段 LED位序要和硬件接線一致接反了顯示出來就是亂碼。小數(shù)點(diǎn)位是最高位0x80疊加在對(duì)應(yīng)位的段碼上。溫度超過 99.9℃ 時(shí)最高位會(huì)溢出通常的做法是顯示EEE或者只保留兩位整數(shù)這屬于業(yè)務(wù)規(guī)則要在需求階段就定下來。4.2 四位顯存映射與刷新策略TM1637 內(nèi)部有 6 字節(jié)顯存地址0xC0~0xC5前四個(gè)對(duì)應(yīng)四位數(shù)字。刷新時(shí)整塊寫比逐位寫穩(wěn)因?yàn)橹鹞粚憰?huì)在中間狀態(tài)出現(xiàn)「半截?cái)?shù)字」。起始信號(hào) → 寫地址命令0x40自動(dòng)地址遞增 → 起始 → 寫0xC0→ 連寫四字節(jié) → 停止這是最常用的寫序列。亮度用0x88 | level設(shè)置level 取 0~7。// 起始信號(hào)CLK 高電平期間 DIO 由高變低 void tm1637_start(void) { DIO_HIGH(); CLK_HIGH(); delay_us(2); DIO_LOW(); delay_us(2); CLK_LOW(); } // 寫一個(gè)字節(jié)并讀 ACK返回 0 表示從機(jī)應(yīng)答 uint8_t tm1637_write_byte(uint8_t data) { uint8_t ack; for (uint8_t i 0; i 8; i) { CLK_LOW(); if (data 0x01) DIO_HIGH(); else DIO_LOW(); delay_us(2); CLK_HIGH(); // 上升沿寫入 delay_us(2); data 1; } CLK_LOW(); DIO_HIGH(); // 釋放 DIO 等 ACK delay_us(2); CLK_HIGH(); delay_us(2); ack DIO_READ(); CLK_LOW(); return ack; }刷新頻率不要太高200ms 一次對(duì)肉眼已經(jīng)連續(xù)寫太頻繁反而增加總線占用。主循環(huán)里顯示刷新和溫度采樣分開計(jì)時(shí)正是為了避免采樣卡頓導(dǎo)致顯示閃爍。這段代碼里 ACK 讀回來是 0 才算成功如果一直是 1說明從機(jī)沒響應(yīng)先檢查上拉電阻和電源。4.3 USART 幀格式與中斷收發(fā)串口上報(bào)建議用固定幀頭、命令字、數(shù)據(jù)、CRC、幀尾的結(jié)構(gòu)上位機(jī)按狀態(tài)機(jī)解析比純文本printf更抗干擾。幀長(zhǎng)控制在 8 字節(jié)以內(nèi)波特率 1152008N1。#pragma pack(push, 1) typedef struct { uint8_t head; // 0xA5 幀頭 uint8_t cmd; // 0x01 溫度上報(bào) int16_t temp_x10; // 單位 0.1℃ uint8_t crc8; // 前面所有字節(jié)的 CRC8 uint8_t tail; // 0x5A 幀尾 } temp_frame_t; #pragma pack(pop) // 發(fā)送一幀溫度數(shù)據(jù) void usart_send_temp(int16_t temp_x10) { temp_frame_t f; f.head 0xA5; f.cmd 0x01; f.temp_x10 temp_x10; f.crc8 crc8_calc((uint8_t *)f, 4); f.tail 0x5A; for (uint8_t i 0; i sizeof(f); i) { while (!(USART1-ISR USART_ISR_TXE)); // 等發(fā)送寄存器空 USART1-TDR ((uint8_t *)f)[i]; } }這里用#pragma pack(1)保證結(jié)構(gòu)體不填充幀長(zhǎng)固定 6 字節(jié)。CRC8 覆蓋幀頭到數(shù)據(jù)三字節(jié)上位機(jī)校驗(yàn)不過就丟幀重同步。接收方向如果用中斷要注意在USART1_IRQHandler里讀RDR清標(biāo)志只讀ISR不清標(biāo)志會(huì)導(dǎo)致中斷反復(fù)觸發(fā)這是 USART 調(diào)試中的經(jīng)典坑。發(fā)送用輪詢等TXE就夠數(shù)據(jù)量小不值得上 DMA。5. 避坑與排查單總線、兩線時(shí)序和串口最容易翻車的幾個(gè)點(diǎn)這套組合的難點(diǎn)不在功能本身而在三類模塊各自的邊界條件。調(diào)試階段遇到的「偶爾正常、偶爾不正?!箮缀醵悸湓谶@幾個(gè)地方按現(xiàn)象、原因、解決三步過一遍能省掉大量無頭蒼蠅式的試錯(cuò)。5.1 時(shí)序與延時(shí)類問題現(xiàn)象DS18B20 有時(shí)候能讀到溫度有時(shí)候讀回0x055085℃上電默認(rèn)值或者 CRC 失敗換一塊板子就完全讀不到。原因延時(shí)函數(shù)用空循環(huán)實(shí)現(xiàn)換編譯優(yōu)化等級(jí)或者換主頻后實(shí)際延時(shí)變了復(fù)位脈沖寬度和讀時(shí)隙采樣點(diǎn)全部偏移。解決延時(shí)統(tǒng)一走 SysTick 或定時(shí)器不依賴編譯器把復(fù)位脈沖和讀時(shí)隙的波形用邏輯分析儀抓出來對(duì)齊數(shù)據(jù)手冊(cè)的窗口再調(diào)參數(shù)。TM1637 那邊同理delay_us(2)在某些主頻下實(shí)際可能是 0.5us 或 5usACK 采樣點(diǎn)就會(huì)錯(cuò)。現(xiàn)象碼管顯示的數(shù)字偶爾閃一下變成亂碼過一會(huì)兒又恢復(fù)。原因顯示刷新和溫度采樣共用主循環(huán)采樣里放了 750ms 的硬延時(shí)刷新節(jié)拍被拉長(zhǎng)TM1637 在兩次寫之間時(shí)序被拉歪。解決把轉(zhuǎn)換等待交給節(jié)拍計(jì)數(shù)主循環(huán)不阻塞刷新整塊寫顯存不逐位寫。5.2 電氣與硬件類問題現(xiàn)象DS18B20 單獨(dú)接能讀加上 TM1637 之后單總線頻繁無應(yīng)答。原因單總線 DQ 的上拉電阻阻值偏大或者沒接總線容性負(fù)載一上來上升沿變緩存在脈沖被淹沒另一種情況是 DS18B20 用寄生供電轉(zhuǎn)換期間電流不夠。解決DQ 固定接 4.7k 上拉到 3.3V長(zhǎng)線降到 2.2k 試盡量用外部供電的三線接法VDD 直接接 3.3V寄生模式只在引腳極緊張時(shí)用。TM1637 的 CLK/DIO 也需要上拉很多模塊板上自帶自己搭電路時(shí)別忘了?,F(xiàn)象串口能發(fā)不能收或者收到的數(shù)據(jù)全是 0xFF。原因TX/RX 接反是最常見的另一類是電平不匹配主控 3.3V 直接接 5V 的 USB-TTL 模塊RX 端被拉在高電平。解決交叉接線 TX 接對(duì)方 RX用萬用表量空閑時(shí) TX 是否為高電平USB-TTL 選 3.3V 電平版本或者在 RX 上加電平轉(zhuǎn)換。5.3 通信協(xié)議與中斷類問題現(xiàn)象上位機(jī)收到的幀偶爾錯(cuò)位CRC 一直失敗。原因幀沒有做轉(zhuǎn)義或者長(zhǎng)度保護(hù)數(shù)據(jù)里恰好出現(xiàn)0xA5就被當(dāng)成了新幀頭接收中斷里只讀了一個(gè)字節(jié)就返回剩余字節(jié)把下一幀沖掉。解決接收端用狀態(tài)機(jī)按幀頭、長(zhǎng)度、CRC 逐字節(jié)推進(jìn)CRC 不過就丟棄重同步中斷里一次性把RDR讀空或者用空閑中斷配合 DMA 收整幀?,F(xiàn)象程序跑一段時(shí)間后死機(jī)或者串口中斷再也進(jìn)不去。原因中斷優(yōu)先級(jí)配置沖突SysTick 和 USART 搶占優(yōu)先級(jí)設(shè)成一樣長(zhǎng)延時(shí)里被串口中斷打斷時(shí)序被破壞或者中斷服務(wù)函數(shù)里做了耗時(shí)操作。解決SysTick 優(yōu)先級(jí)設(shè)最低USART 接收中斷設(shè)高一級(jí)中斷里只做標(biāo)志位和緩沖搬運(yùn)解析放到主循環(huán)。這類問題用調(diào)試器看中斷掛起位最快別靠猜。6. 驗(yàn)收與答辯怎么證明三個(gè)模塊都「正?!沟搅蓑?yàn)收環(huán)節(jié)很多人栽在「功能能跑但說不清楚」上。老師或者評(píng)審看的不是你演示一遍溫度變化而是你能不能證明每個(gè)模塊在異常條件下也有可預(yù)期的行為。我一般會(huì)準(zhǔn)備一張驗(yàn)收表把正常路徑和異常路徑都列出來逐條演示或者說明預(yù)期結(jié)果。驗(yàn)收項(xiàng)操作預(yù)期結(jié)果判定依據(jù)單總線識(shí)別上電初始化復(fù)位后檢測(cè)到存在脈沖ds18b20_reset返回 1溫度準(zhǔn)確性冰水混合物、室溫兩點(diǎn)的對(duì)比誤差在 ±0.5℃ 內(nèi)與標(biāo)準(zhǔn)溫度計(jì)比對(duì)CRC 保護(hù)拔掉 DQ 再插回顯示保持上次有效值不出現(xiàn)亂碼返回值非 0 時(shí)走降級(jí)邏輯顯示完整性溫度從 0 變到 99.9四位全亮無缺段小數(shù)點(diǎn)位置正確目視加段碼表核對(duì)串口幀格式上位機(jī)按協(xié)議解析幀頭幀尾對(duì)齊CRC 通過率 100%抓包工具逐幀檢查連續(xù)運(yùn)行通電 2 小時(shí)無死機(jī)、無顯示漂移串口日志時(shí)間戳連續(xù)答辯時(shí)演示順序也有講究。先講架構(gòu)圖三層四模塊一句話說清再演示正常采集讓數(shù)字從室溫跳一下然后主動(dòng)制造一個(gè)異常比如拔掉傳感器展示降級(jí)顯示而不是死機(jī)最后打開串口助手展示幀結(jié)構(gòu)和 CRC 校驗(yàn)。主動(dòng)暴露異常并給出處理方案比只演示成功路徑更能體現(xiàn)工程思維。老師常追問「你這個(gè)延時(shí)怎么保證準(zhǔn)確」答「SysTick 基準(zhǔn)加邏輯分析儀校準(zhǔn)」比答「我試出來的」要穩(wěn)。有個(gè)細(xì)節(jié)值得提前準(zhǔn)備把關(guān)鍵波形截圖存進(jìn)答辯 PPT復(fù)位脈沖、讀時(shí)隙、TM1637 起始信號(hào)各一張配上時(shí)間軸標(biāo)注。評(píng)審看到波形就知道你是真調(diào)過時(shí)序不是抄來能跑就交。我?guī)У捻?xiàng)目里凡是準(zhǔn)備了波形截圖的組答辯時(shí)被追問技術(shù)細(xì)節(jié)的壓力都小很多。從那以后我每次做帶時(shí)序協(xié)議的項(xiàng)目都強(qiáng)制自己先用邏輯分析儀把最基礎(chǔ)的復(fù)位和讀寫時(shí)隙抓一遍確認(rèn)波形對(duì)了再往上堆業(yè)務(wù)邏輯省下的返工時(shí)間遠(yuǎn)比調(diào)試本身多。希望這套拆解能幫到正在搭溫度采集終端的你把三個(gè)模塊一次跑通。本文還有配套的精品資源點(diǎn)擊獲取