
簡(jiǎn)介這份資源是面向嵌入式初學(xué)者與STM32開(kāi)發(fā)者的NTC熱敏電阻溫度采集程序基于STM32F1038T6微控制器實(shí)現(xiàn)解決從模擬電壓采集到溫度換算的完整鏈路問(wèn)題適合用于工業(yè)控制、物聯(lián)網(wǎng)設(shè)備等場(chǎng)景下的溫度監(jiān)測(cè)學(xué)習(xí)與實(shí)踐。壓縮包共4個(gè)文件包含2個(gè)C源文件與2個(gè)頭文件分別承擔(dān)ADC配置、NTC參數(shù)配置及主邏輯實(shí)現(xiàn)整體約5KB結(jié)構(gòu)精簡(jiǎn)便于快速移植到同類(lèi)工程。目前已有6573人學(xué)習(xí)下載熱度較高。程序覆蓋ADC初始化、通道與采樣參數(shù)設(shè)置、輪詢(xún)或中斷方式讀取轉(zhuǎn)換結(jié)果、依據(jù)B值系數(shù)與電阻-溫度關(guān)系完成溫度換算并預(yù)留顯示或串口輸出接口同時(shí)涉及溫度補(bǔ)償、滑動(dòng)平均濾波與任務(wù)調(diào)度優(yōu)化等提升精度與穩(wěn)定性的思路。讀者可借此掌握STM32的ADC操作、中斷處理與NTC傳感技術(shù)是理解硬件接口到軟件算法完整方案的實(shí)用案例。1. NTC 溫度采集從 ADC 原始值到可信溫度的完整鏈路NTC 溫度采集程序在 STM32 項(xiàng)目里出現(xiàn)的頻率極高但真正把它做穩(wěn)的人不多。多數(shù)人第一次寫(xiě)都是 ADC 讀一個(gè)值、套個(gè)公式、串口打印看起來(lái)能跑放到實(shí)際環(huán)境里就開(kāi)始飄同一杯水今天 25.3℃明天 24.1℃換個(gè)板子差兩度線拉長(zhǎng)一點(diǎn)讀數(shù)直接跳變。問(wèn)題不在 NTC 本身而在于從 ADC 原始碼到最終溫度之間有一條被大多數(shù)人忽略的鏈路——參考電壓、分壓電阻、B 值公式、自熱效應(yīng)、濾波策略每一環(huán)都能把精度吃掉。這篇筆記面向的是正在用 STM32 做溫度采集的嵌入式工程師不管你是做魚(yú)缸溫控、智能臺(tái)燈、物聯(lián)網(wǎng)網(wǎng)關(guān)還是畢業(yè)設(shè)計(jì)只要涉及 NTC這套鏈路都通用。我會(huì)按「硬件怎么接 → ADC 怎么配 → 公式怎么算 → 代碼怎么寫(xiě) → 坑怎么避」的順序把每一步的參數(shù)選擇和失敗現(xiàn)象講清楚。讀完你應(yīng)該能獨(dú)立搭出一套誤差可控在 ±0.5℃ 以?xún)?nèi)的采集方案并且知道誤差變大時(shí)該往哪里查。2. 硬件鏈路與 ADC 配置先把信號(hào)源搞干凈2.1 NTC 分壓電路的兩個(gè)關(guān)鍵電阻NTC 采集的本質(zhì)是把電阻變化轉(zhuǎn)成電壓變化STM32 的 ADC 只能測(cè)電壓所以分壓電路是第一步。典型接法是 VCC — 上拉電阻 R1 — ADC 采樣點(diǎn) — NTC — GND采樣點(diǎn)電壓為V_adc VCC × R_ntc / (R1 R_ntc)這里 R1 的選型直接決定靈敏度分布。NTC 的阻值隨溫度指數(shù)下降如果 R1 選得離 NTC 在目標(biāo)溫區(qū)的阻值太遠(yuǎn)ADC 碼值會(huì)擠在一端分辨率浪費(fèi)。常見(jiàn)做法是讓 R1 等于 NTC 在目標(biāo)溫區(qū)中點(diǎn)溫度下的阻值。比如 10K B3950 的 NTC25℃ 時(shí)是 10K如果你主要測(cè) 20~40℃R1 取 10K 就能讓采樣點(diǎn)電壓落在 VCC/2 附近ADC 利用最充分。VCC 的選擇也有講究。很多人直接拿 STM32 的 3.3V 供電當(dāng) VCC但 3.3V 同時(shí)給 MCU 和分壓電路供電時(shí)MCU 電流波動(dòng)會(huì)讓 3.3V 有幾十毫伏的抖動(dòng)直接反映到 ADC 讀數(shù)上。血淚經(jīng)驗(yàn)是分壓電路的 VCC 要么單獨(dú)用一顆 LDO要么至少加 100nF10uF 去耦并且走線遠(yuǎn)離電機(jī)、繼電器這類(lèi)負(fù)載。2.2 STM32 ADC 采樣時(shí)間與參考電壓設(shè)置以 STM32F103 為例ADC 時(shí)鐘最高 14MHz采樣時(shí)間可選 1.5/7.5/13.5/28.5/41.5/55.5/71.5/239.5 個(gè)周期。NTC 分壓點(diǎn)的等效輸出阻抗等于 R1 與 R_ntc 的并聯(lián)值10K 對(duì) 10K 并聯(lián)是 5K這個(gè)阻抗不算低。采樣時(shí)間太短采樣保持電容充不滿(mǎn)讀數(shù)會(huì)偏低且不穩(wěn)定。我一般會(huì)這樣配ADC 時(shí)鐘 12MHz采樣時(shí)間選 55.5 周期總轉(zhuǎn)換時(shí)間約 (55.512.5)/12MHz ≈ 5.7μs。這個(gè)速度對(duì)溫度采集綽綽有余同時(shí)給采樣電容足夠的充電時(shí)間。如果你用的是 STM32F4 或 H7采樣時(shí)間選項(xiàng)不同但原則一樣——源阻抗越大采樣時(shí)間越長(zhǎng)。參考電壓方面STM32F103 的 VREF 通常直接接 VDDA3.3V但 3.3V 本身有誤差和溫漂。如果對(duì)精度要求高可以外接一顆 REF3033 之類(lèi)的基準(zhǔn)源給 VREF這樣 ADC 的滿(mǎn)量程就穩(wěn)定了。沒(méi)有外部基準(zhǔn)時(shí)至少要在代碼里用實(shí)測(cè)的 VDDA 值參與計(jì)算而不是硬編碼 3.3。// STM32F103 ADC1 初始化通道0PA0采樣時(shí)間55.5周期 void NTC_ADC_Init(void) { RCC-APB2ENR | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN; RCC-CFGR ~RCC_CFGR_ADCPRE; RCC-CFGR | RCC_CFGR_ADCPRE_DIV6; // PCLK272MHz, ADC時(shí)鐘12MHz GPIOA-CRL ~GPIO_CRL_MODE0; GPIOA-CRL ~GPIO_CRL_CNF0; // PA0 模擬輸入 ADC1-SQR1 0; // 規(guī)則序列長(zhǎng)度1 ADC1-SQR3 0; // 第一個(gè)轉(zhuǎn)換通道 通道0 ADC1-SMPR2 | ADC_SMPR2_SMP0_2 | ADC_SMPR2_SMP0_1 | ADC_SMPR2_SMP0_0; // SMP0[2:0]111 - 239.5周期源阻抗高時(shí)更穩(wěn) ADC1-CR2 | ADC_CR2_ADON; // 開(kāi)啟ADC for(volatile int i0;i10000;i); // 等待穩(wěn)定 ADC1-CR2 | ADC_CR2_CAL; // 啟動(dòng)校準(zhǔn) while(ADC1-CR2 ADC_CR2_CAL); }這段代碼里SMP0我實(shí)際給了 239.5 周期比前面說(shuō)的 55.5 更保守。原因是很多板子的分壓電阻精度一般源阻抗可能比理論值大采樣時(shí)間拉長(zhǎng)能明顯改善跳變。代價(jià)是轉(zhuǎn)換變慢但溫度采集不需要高速。ADC_CR2_CAL校準(zhǔn)位必須等它自動(dòng)清零否則第一次轉(zhuǎn)換結(jié)果不可信這個(gè)坑后面還會(huì)提。2.3 多通道采集時(shí)的切換順序如果你同時(shí)采 NTC 和其他模擬量比如電池電壓規(guī)則序列里通道順序會(huì)影響結(jié)果。STM32 的 ADC 只有一個(gè)采樣保持電路切換通道后第一個(gè)轉(zhuǎn)換值往往受上一通道殘留影響。常見(jiàn)做法是每個(gè)通道連續(xù)轉(zhuǎn)換兩次丟棄第一次取第二次?;蛘甙?NTC 這種高阻抗通道放在序列最后前面放低阻抗通道讓采樣電容有更多時(shí)間穩(wěn)定。// 雙通道通道0(NTC) 通道1(電池)各轉(zhuǎn)兩次取第二次 u16 NTC_ReadFiltered(void) { u16 v1, v2; ADC1-SQR3 1; // 先轉(zhuǎn)通道1 ADC1-CR2 | ADC_CR2_ADON; while(!(ADC1-SR ADC_SR_EOC)); (void)ADC1-DR; // 丟棄 ADC1-SQR3 0; // 再轉(zhuǎn)通道0 ADC1-CR2 | ADC_CR2_ADON; while(!(ADC1-SR ADC_SR_EOC)); v1 ADC1-DR; ADC1-CR2 | ADC_CR2_ADON; // 通道0再轉(zhuǎn)一次 while(!(ADC1-SR ADC_SR_EOC)); v2 ADC1-DR; return v2; // 取第二次 }邏輯說(shuō)明先轉(zhuǎn)一次通道1把采樣電容“預(yù)熱”到相近電壓再轉(zhuǎn)通道0第一次丟棄、第二次保留。參數(shù)上SQR3的低 5 位是通道號(hào)改通道就改這里。如果你用 HAL 庫(kù)對(duì)應(yīng)的是HAL_ADC_ConfigChannel和HAL_ADC_PollForConversion思路一樣。3. B 值公式與查表法把 ADC 碼變成溫度3.1 B 值公式的推導(dǎo)與參數(shù)含義NTC 的阻值和溫度關(guān)系用 B 值公式描述1/T 1/T0 (1/B) × ln(R/R0)其中 T 是開(kāi)爾文溫度T0 是標(biāo)稱(chēng)溫度通常 298.15K即 25℃R0 是標(biāo)稱(chēng)溫度下的阻值10KB 是材料常數(shù)如 3950。從 ADC 碼反推溫度要先算 R_ntcR_ntc R1 × ADC_code / (ADC_fullscale - ADC_code)再代入 B 值公式求 T。這個(gè)公式在 0~50℃ 范圍內(nèi)誤差可以做到 ±0.2℃但前提是 B 值準(zhǔn)確。市面上 10K B3950 的 B 值公差常見(jiàn) ±1%對(duì)應(yīng)約 ±0.5℃ 誤差。如果買(mǎi)的是 B 值公差 ±5% 的便宜貨算出來(lái)差兩三度很正常這時(shí)候要么換料要么逐顆標(biāo)定。#include math.h #define R1_VALUE 10000.0f // 上拉電阻 10K #define R0_VALUE 10000.0f // NTC 標(biāo)稱(chēng)阻值 10K #define B_VALUE 3950.0f // B 常數(shù) #define T0_KELVIN 298.15f // 25℃ 開(kāi)爾文 #define ADC_FULLSCALE 4095.0f // 12位ADC滿(mǎn)量程 float NTC_CodeToTemp(u16 adc_code) { float r_ntc, temp_k; if (adc_code 0 || adc_code 4095) return -100.0f; // 異常值 r_ntc R1_VALUE * (float)adc_code / (ADC_FULLSCALE - (float)adc_code); temp_k 1.0f / (1.0f/T0_KELVIN (1.0f/B_VALUE) * logf(r_ntc / R0_VALUE)); return temp_k - 273.15f; }參數(shù)說(shuō)明R1_VALUE必須和實(shí)際焊接的電阻一致用萬(wàn)用表量一下再填別信絲印。B_VALUE和R0_VALUE來(lái)自 NTC 規(guī)格書(shū)不同廠家同型號(hào)可能不同。ADC_FULLSCALE是 4095 而不是 4096因?yàn)?12 位 ADC 輸出范圍是 0~4095。logf是單精度對(duì)數(shù)STM32F1 沒(méi)有硬件浮點(diǎn)這個(gè)函數(shù)會(huì)調(diào)用庫(kù)里的軟件浮點(diǎn)一次約幾十微秒1Hz 采集完全夠用。3.2 查表法省掉浮點(diǎn)運(yùn)算的實(shí)用方案如果 MCU 資源緊張或者不想在中斷里跑浮點(diǎn)查表法是更工程化的選擇。做法是預(yù)先算好一張 ADC 碼到溫度的對(duì)應(yīng)表運(yùn)行時(shí)用二分查找或線性插值。表可以按 1℃ 間隔生成覆蓋 -20~100℃共 121 個(gè)點(diǎn)每個(gè)點(diǎn)存 ADC 碼u16占 242 字節(jié)很省。// 由 B 值公式預(yù)生成的 ADC 碼表-20℃ 到 100℃步進(jìn)1℃ const u16 ntc_adc_table[121] { 4020, 4008, 3994, /* ... 中間省略 ... */ 120, 108, 97 }; float NTC_TableLookup(u16 adc_code) { int low 0, high 120, mid; if (adc_code ntc_adc_table[0]) return -20.0f; if (adc_code ntc_adc_table[120]) return 100.0f; while (high - low 1) { mid (low high) / 2; if (adc_code ntc_adc_table[mid]) low mid; else high mid; } // 線性插值 float ratio (float)(ntc_adc_table[low] - adc_code) / (float)(ntc_adc_table[low] - ntc_adc_table[high]); return -20.0f (float)low ratio; }邏輯說(shuō)明表按溫度升序排列ADC 碼是降序溫度越高阻值越小采樣點(diǎn)電壓越低。二分查找定位區(qū)間后做線性插值精度取決于表密度1℃ 間隔插值誤差小于 0.1℃。生成表的方法是用 B 值公式反算每個(gè)溫度對(duì)應(yīng)的 R_ntc再算 ADC 碼用 Python 或 Excel 批量生成即可。注意表要放在const段STM32F1 的 Flash 夠放。3.3 兩種方法的選型對(duì)比對(duì)比項(xiàng)B值公式法查表法代碼體積小但需鏈接 math 庫(kù)表占 242 字節(jié)無(wú) math 依賴(lài)運(yùn)算耗時(shí)約 50~100μs軟件浮點(diǎn)約 5~10μs精度取決于 B 值公差取決于表生成參數(shù)可逐顆標(biāo)定靈活性改參數(shù)即改結(jié)果換 NTC 需重新生成表適用場(chǎng)景采集頻率低、調(diào)試階段高頻采集、量產(chǎn)、無(wú)浮點(diǎn)單元我一般調(diào)試階段用 B 值公式方便改參數(shù)看效果量產(chǎn)固件切查表法省資源也省心。如果 MCU 帶 FPU如 STM32F4公式法的耗時(shí)可以忽略直接用公式更省事。4. 避坑與排查NTC 采集翻車(chē)的五個(gè)現(xiàn)場(chǎng)4.1 讀數(shù)跳變超過(guò) 1℃但萬(wàn)用表測(cè)電壓穩(wěn)定現(xiàn)象串口打印的溫度每隔幾秒跳 1~2℃用萬(wàn)用表量分壓點(diǎn)電壓卻很穩(wěn)。原因ADC 采樣時(shí)間不夠采樣保持電容沒(méi)充滿(mǎn)或者 ADC 時(shí)鐘配置錯(cuò)誤實(shí)際時(shí)鐘超過(guò)規(guī)格導(dǎo)致轉(zhuǎn)換異常。解決把采樣時(shí)間從 7.5 周期加到 55.5 或 239.5 周期檢查RCC_CFGR_ADCPRE分頻確保 ADC 時(shí)鐘不超過(guò) 14MHz。改完還跳在采樣點(diǎn)對(duì)地加 100nF 電容做硬件濾波。4.2 第一次上電讀數(shù)總是偏高或偏低現(xiàn)象每次復(fù)位后第一次讀的溫度不對(duì)第二次開(kāi)始正常。原因ADC 校準(zhǔn)沒(méi)做或者校準(zhǔn)后沒(méi)等穩(wěn)定就開(kāi)始轉(zhuǎn)換。解決初始化里必須執(zhí)行ADC_CR2_CAL校準(zhǔn)并等待其自動(dòng)清零校準(zhǔn)后延時(shí)一小段時(shí)間再啟動(dòng)轉(zhuǎn)換。用 HAL 庫(kù)的話HAL_ADCEx_Calibration_Start之后加個(gè)幾毫秒延時(shí)。4.3 換一塊板子溫度差兩度現(xiàn)象同一顆 NTC同一份固件換板子后讀數(shù)差 1~2℃。原因分壓電阻 R1 的實(shí)際阻值偏差或者 VDDA 實(shí)際不是 3.3V。解決用萬(wàn)用表實(shí)測(cè)每塊板的 R1 和 VDDA把實(shí)測(cè)值寫(xiě)進(jìn)代碼或做成標(biāo)定參數(shù)。量產(chǎn)時(shí)可以在板上留一個(gè)精密電阻通道上電自校準(zhǔn)。4.4 溫度高時(shí)誤差變大現(xiàn)象低溫段挺準(zhǔn)超過(guò) 60℃ 后誤差越來(lái)越大。原因B 值公式在寬溫區(qū)是近似高溫段偏差增大或者 NTC 自熱效應(yīng)明顯測(cè)量電流太大。解決如果只測(cè)窄溫區(qū)用該溫區(qū)的 B 值重新擬合降低測(cè)量電流分壓電阻加大到 47K 或 100K減少自熱。自熱功率 P V2/R10K 分壓時(shí)約 0.27mW一般沒(méi)事但小封裝 NTC 要注意。4.5 電機(jī)一啟動(dòng)溫度就亂跳現(xiàn)象板子上有電機(jī)或繼電器一動(dòng)作溫度讀數(shù)就飛。原因電源波動(dòng)和電磁干擾耦合到 ADC 采樣點(diǎn)。解決分壓電路 VCC 單獨(dú) LDO 供電采樣點(diǎn)加 RC 濾波1K100nF軟件上做中值濾波連續(xù)采 5 次去掉最大最小取平均ADC 采樣避開(kāi)電機(jī)動(dòng)作時(shí)刻。5. 進(jìn)階讓 NTC 采集從能用變成可信5.1 兩點(diǎn)標(biāo)定法把誤差壓到 ±0.3℃B 值公式的誤差主要來(lái)自 B 值和 R0 的公差。如果愿意花十分鐘標(biāo)定精度可以大幅提升。做法是準(zhǔn)備兩個(gè)溫度點(diǎn)冰水混合物0℃和沸水100℃注意海拔修正把 NTC 放進(jìn)去記錄 ADC 碼反算出該顆 NTC 的實(shí)際 B 值和 R0替換代碼里的宏定義。// 兩點(diǎn)標(biāo)定已知 T10℃ 對(duì)應(yīng) adc1T2100℃ 對(duì)應(yīng) adc2 // 反算 R0 和 B float r1 R1_VALUE * adc1 / (ADC_FULLSCALE - adc1); float r2 R1_VALUE * adc2 / (ADC_FULLSCALE - adc2); float t1_k 273.15f, t2_k 373.15f; float b_cal (logf(r2) - logf(r1)) / (1.0f/t2_k - 1.0f/t1_k); float r0_cal r1 * expf(b_cal * (1.0f/t1_k - 1.0f/298.15f));標(biāo)定后把b_cal和r0_cal寫(xiě)回公式同一顆 NTC 在 0~100℃ 內(nèi)誤差通常能到 ±0.3℃。注意沸水溫度隨氣壓變化高原地區(qū)要按當(dāng)?shù)胤悬c(diǎn)修正否則標(biāo)定本身就有偏差。5.2 用 DMA 加定時(shí)器觸發(fā)做無(wú)感采集如果 NTC 采集只是系統(tǒng)里的一小部分不想讓它占用 CPU可以用定時(shí)器觸發(fā) ADC DMA 搬運(yùn)。配置 TIM3 每 100ms 觸發(fā)一次 ADCDMA 把結(jié)果搬到內(nèi)存數(shù)組主循環(huán)直接讀數(shù)組。這樣采集完全在后臺(tái)跑CPU 該干嘛干嘛。// TIM3 觸發(fā) ADC1DMA 搬運(yùn)到 adc_buffer void NTC_DMA_Init(void) { RCC-AHBENR | RCC_AHBENR_DMA1EN; DMA1_Channel1-CPAR (u32)ADC1-DR; DMA1_Channel1-CMAR (u32)adc_buffer; DMA1_Channel1-CNDTR 8; // 8個(gè)采樣點(diǎn) DMA1_Channel1-CCR DMA_CCR_CIRC | DMA_CCR_MINC | DMA_CCR_PSIZE_16BIT; DMA1_Channel1-CCR | DMA_CCR_EN; ADC1-CR2 | ADC_CR2_DMA | ADC_CR2_EXTTRIG; ADC1-CR2 | ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1; // TIM3 TRGO // TIM3 配置為 100ms 更新TRGO 選更新事件 }邏輯說(shuō)明DMA 循環(huán)模式自動(dòng)覆蓋adc_buffer主循環(huán)讀數(shù)組做平均即可。CNDTR8表示緩沖 8 個(gè)點(diǎn)配合 100ms 觸發(fā)就是 800ms 的數(shù)據(jù)窗口。參數(shù)上EXTSEL要查參考手冊(cè)確認(rèn) TIM3 TRGO 對(duì)應(yīng)的編碼不同系列不一樣。這個(gè)方案的好處是采集節(jié)拍由硬件保證不受主循環(huán)任務(wù)影響溫度曲線會(huì)平滑很多。5.3 一個(gè)我常犯的錯(cuò)誤早期做 NTC 采集我總想著一次就把溫度算準(zhǔn)公式參數(shù)反復(fù)調(diào)結(jié)果忽略了硬件本身的一致性。后來(lái)才明白NTC 采集的精度上限在硬件——分壓電阻精度、基準(zhǔn)電壓穩(wěn)定性、PCB 布局這些不解決軟件再優(yōu)化也是白搭?,F(xiàn)在我的習(xí)慣是先拿萬(wàn)用表把分壓點(diǎn)電壓和理論值對(duì)一遍確認(rèn)硬件沒(méi)問(wèn)題再動(dòng)代碼。這個(gè)順序反過(guò)來(lái)能省掉大量玄學(xué)調(diào)試時(shí)間。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取