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