
前陣子做一塊循環水溫度采集板測溫范圍0~100℃、長期穩定性優先。選型的時候對比過好幾個方案最后確定用stm32cubemx 配置硬件SPI配合MAX31865讀取PT100鉑電阻轉換完成信號接外部中斷。整套跑下來讀數穩定在±0.2℃以內夠用且不折騰。想把這套做法從選型到代碼完整寫出來給打算用MAX31865的朋友一個參考。如果你還在用軟件模擬SPI讀這顆芯片或者靠延時輪詢等轉換結果建議看一下這篇。軟件模擬SPI不是不行但MAX31865在連續轉換模式下用外部中斷做“轉換完成通知”明顯更省事CubeMX把SPI和中斷初始化生成好之后剩下的驅動代碼量其實很少。文章按項目推進的順序來先講為什么選這顆芯片再講硬件接線注意什么然后是CubeMX里SPI、外部中斷、串口怎么配最后是寄存器讀寫、溫度換算以及實測過程中遇到過的幾個坑。1. 為什么要用MAX31865而不是自己搭放大電路1.1 PT100測溫方案橫向對比PT100在0℃時阻值100Ω溫度升高阻值變大變化率大約是0.385Ω/℃。這個信號本身的特征就是“阻值變化小、絕對阻值小”所以測溫電路的核心任務有兩個給RTD提供穩定的激勵電流以及把微小的電壓變化放大并轉換成數字量。常見的做法有三種方案外圍元件校準工作量穩定性軟件復雜度運放MCU內部ADC恒流源或精密電橋、儀表放大器、基準源大每一路都要標定中等受溫度漂移影響中等通用高精度ADC如ADS1120激勵電流源仍需外搭寄存器多較大好較高專用RTD轉換芯片MAX31865一顆基準電阻、若干電容很小換電阻即可很好內部集成激勵和參考低我最后選MAX31865核心理由就是它把RTD前端最難搞的部分——激勵電流源、可編程增益、基準電阻采樣、故障檢測——全部做進芯片里了。外部只需要接一顆精密基準電阻SPI直接讀回數字量BOM簡單、布局容易調試壓力小很多。而且它最高支持5MHz的SPI時鐘連續轉換時數據刷新也夠快。1.2 硬件SPI和外部中斷在這套方案里的分工用過軟件模擬SPI的人應該都有體會代碼本身不難但每一bit都要用GPIO翻轉去湊時序數據量小的時候還好一旦系統里還要跑顯示、按鍵、通信、控制算法模擬SPI就會變成一顆“時間黑洞”。MAX31865雖然沒有大塊的數據要傳一次讀操作只涉及兩三個字節但如果測溫頻率高、或者同時掛多個傳感器硬件SPI的優勢就出來了——寄存器配置好之后HAL_SPI_TransmitReceive一發剩下的事情交給外設CPU該干嘛干嘛去。外部中斷解決的是“什么時候該讀”的問題。MAX31865在自動轉換模式下每完成一次轉換會把DRDY引腳拉低這就是一個現成的硬件通知信號。把這個信號接到STM32的外部中斷引腳下降沿觸發在中斷回調里只置一個標志位主循環檢測到標志后去讀SPI數據。這樣就不會出現“讀早了拿舊數據讀晚了又要等下一輪”的尷尬。2. 硬件接線PT100線制和MAX31865外圍電路2.1 引腳和最小系統MAX31865的封裝不難焊主流是TSSOP引腳不算密。和MCU相關的引腳就6個引腳方向連到STM32說明SCLK輸入SPI SCLK引腳SPI時鐘SDI輸入SPI MOSI引腳數據進MAX31865SDO輸出SPI MISO引腳數據出MAX31865CS輸入任意GPIO片選低有效DRDY輸出GPIO外部中斷引腳轉換完成漏極開路低有效FAULT輸出GPIO輸入或外部中斷故障報警漏極開路低有效供電直接3.3V和STM32同電源域SDO電平不需要額外轉換。值得注意的是DRDY和FAULT都是漏極開路輸出內部沒有上拉如果外部不加上拉電阻這兩個引腳永遠讀不到有效的低電平。我建議外部各接一個10kΩ上拉省心。如果板子空間緊張用STM32的內部上拉也能跑但外部上拉在EMC和信號邊沿上更可靠。2.2 PT100兩線、三線、四線怎么選PT100有四線制、三線制、兩線制三種接法不同的接法直接決定測量誤差的量級。兩線制兩根導線既走激勵電流又走電壓檢測導線電阻全部串進測量回路。按每根線0.5Ω算PT100在0℃附近是100Ω0.5Ω直接導致約1.3℃的誤差而且這個誤差隨溫度變化基本是固定的無法自己消除。三線制工業現場最常見的接法。原理是讓一根導線只走激勵電流另一根導線只走電壓檢測并通過芯片內部的差分測量抵消掉一部分線阻影響。能把誤差壓到0.1Ω級別也就是零點幾攝氏度。四線制激勵和檢測完全分開理論上線阻對測量沒有影響精度最高適合實驗室和計量場景。MAX31865引腳里有FORCE、RTDIN、RTDIN-、FORCE-四個RTD相關引腳接線方式是四線制RTD一端接FORCE和RTDIN另一端接RTDIN-和FORCE-。三線制RTD兩端各引一根主信號線到RTDIN和RTDIN-第三根線接到激勵回路做補償具體參考數據手冊的Figure連接示意以手冊為準。兩線制把FORCE和RTDIN做近端短接盡量靠近芯片引腳處短接減少引線誤差。我做這塊板子因為要兼顧精度和現場布線成本用的是三線制實測在30~80℃區間內和四線制參考表的差別在0.1℃以內完全夠用。如果你做的是高精度計量設備直接上四線制別省那兩根線。2.3 基準電阻RREF是精度的關鍵MAX31865的測量原理可以理解成激勵電流流過RTD同時流過一顆外部基準電阻RREF芯片對兩者產生的電壓做比例采樣。最終RTD阻值的計算式是R_RTD (ADC_code × RREF) / 32768從這個公式就能看出來RREF的誤差會被原樣復制到最終結果里沒有任何校正機會。所以RREF的選型必須謹慎PT100對應標準值是430Ω不要用400Ω或470Ω。精度至少0.1%有條件直接上0.05%甚至0.02%。溫度系數TCR盡量低最好15ppm/℃以下。普通貼片電阻的溫度系數動輒100ppm環境溫度變化10℃就能引入0.5%的誤差這個漂移在你長期運行時非常頭疼。我選的是0.1%精度、10ppm/℃的金屬膜貼片電阻放在傳感器接口旁邊遠離發熱器件。做樣機時采購方便效果也穩定。如果你對精度有極致要求可以買精密電阻箱做外部校準把RREF的“等效實際值”標定出來寫進代碼里后面細說。2.4 濾波電容和電源去耦MAX31865數據手冊推薦在VDD和GND之間放0.1μF去耦電容靠近供電引腳。RTD輸入引腳上的RC濾波電容會影響ADC建立時間如果值太大高速轉換時采樣值可能偏小。我做原型的時候在FORCE/RTDIN上各加了一個1nF的電容SPI配置成1MHz讀出來的數據和沒加電容時幾乎一致但抗干擾能力確實好一些。如果是低速慢變溫度測量這個電容不加也能工作。3. CubeMX配置SPI引腳、外部中斷和串口打印一次搞定3.1 SPI參數怎么填才不會翻車打開STM32CubeMX選好MCU型號后在左側Categories里找到SPI勾選Enable。通信速率對溫度采集來說不需要很高我直接配置到1MHz左右穩定第一。具體參數如下參數項設置值ModeTransmit Receive MasterClock Polarity (CPOL)LowClock Phase (CPHA)1 EdgeData Size8 BitFirst BitMSB FirstPrescaler根據系統時鐘調整使SCLK在1MHz左右SPI時鐘極性和相位這里特別容易出問題。MAX31865的時序是SCLK空閑為低數據在時鐘上升沿被采樣對應CubeMX里的CPOLLow、CPHA1Edge也就是大家常說的SPI Mode 0。網上有的帖子和例程寫Mode 1還跑通了多半是代碼或硬件實現有特殊處理。我建議你以手邊這顆芯片數據手冊的時序圖為準配置好后先用邏輯分析儀看一眼SCLK和SDO上的波形比反復猜要快得多。CubeMX生成的初始化代碼里SPI引腳會自動綁定到芯片的硬件外設引腳上比如PA5SCK、PA6MISO、PA7MOSI。如果你這些引腳和別的功能沖突可以在Pinout視圖里手動重新映射。3.2 片選為什么不用硬件NSSMAX31865的片選是CS引腳低有效。每次讀或寫操作都要先把CS拉低發地址字節再收發數據最后拉高CS。用CubeMX的硬件NSSSPI_NSS能不能做能做但沒必要。硬件NSS由SPI外設自動控制看起來方便實際在“讀MAX31865寄存器”這種場景里不夠靈活。比如我讀RTD高字節和低字節是分成兩次SPI傳輸的硬件NSS可能在每次傳輸之間自動釋放片選導致設備認為一次操作結束下一個字節就無效了。而用普通GPIO手動控制CS想拉多久拉多久時序完全透明。MAX31865本身又是單設備掛在SPI總線上片選頻率要求不高完全沒必要讓硬件NSS摻和進來。在CubeMX里做法是把CS引腳配置為GPIO_Output在用戶代碼里手動控制。CubeMX生成的SPI初始化不需要任何額外改動。3.3 DRDY外部中斷和FAULT引腳的處理DRDY是轉換完成信號下降沿有效。在CubeMX里把對應引腳比如PA0配置為GPIO_EXTI0觸發方式選Falling Edge然后在NVIC里使能EXTI0中斷。中斷回調函數只做一件事置一個drdy_flag標志位。FAULT引腳的處理我是建議先不接外部中斷。原因很現實FAULT是漏極開路輸出上電瞬間如果MAX31865還沒進入正常工作狀態這個腳可能是一段低電平會觸發一個虛假的下降沿中斷。你要么在中斷回調里讀到標志后去讀Fault Status寄存器確認要么干脆把FAULT接成普通GPIO輸入在主循環里周期性查詢。我自己的板子上DRDY接外部中斷FAULT接普通輸入引腳主循環每500ms讀一次Fault Status任何故障都逃不掉。3.4 時鐘樹和NVIC優先級的坑CubeMX里SPI的時鐘源來自APB外設時鐘如果系統時鐘沒有配置正確SPI波特率的實際值和你在界面上看到的不一樣。我的習慣是一上來就把RCC設置為HSE外部晶振系統時鐘拉到芯片標稱最高頻率然后再去配置SPI的分頻系數這樣SCLK頻率才計算得準。外部中斷的優先級在NVIC里設置。溫度采集對中斷響應時間要求不高但我習慣把EXTI0放到較高優先級同時保證中斷回調里不調用HAL_Delay、不做浮點運算、不做打印輸出只置標志位。浮點運算和打印留在主循環里做這樣即使測溫中斷頻繁觸發系統調度也不會失控。4. 驅動代碼實現寄存器讀寫到溫度換算4.1 寄存器讀寫基礎函數MAX31865的寄存器操作規則很直接讀操作地址字節最高位置1寫操作地址字節最高位清0。比如要讀RTD值的MSB寄存器地址0x01傳輸的地址字節就是0x81。所有操作在CS拉低后進行結束后拉高?;A讀寫函數如下void max31865_cs_low(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); } void max31865_cs_high(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } uint8_t max31865_read_reg(uint8_t reg) { uint8_t tx reg | 0x80; uint8_t rx 0; max31865_cs_low(); HAL_SPI_Transmit(hspi1, tx, 1, 10); HAL_SPI_Receive(hspi1, rx, 1, 10); max31865_cs_high(); return rx; } void max31865_write_reg(uint8_t reg, uint8_t value) { uint8_t buf[2]; buf[0] reg 0x7F; buf[1] value; max31865_cs_low(); HAL_SPI_Transmit(hspi1, buf, 2, 10); max31865_cs_high(); }讀RTD原始值時要連續讀兩個字節uint16_t max31865_read_rtd_raw(void) { uint8_t msb max31865_read_reg(0x01); uint8_t lsb max31865_read_reg(0x02); return ((uint16_t)msb 8) | lsb; }這里有個細節從寄存器讀回的16位里只有高15位是ADC轉換結果LSB的bit0是FAULT標志位。所以真正的ADC值要用raw 1得到。如果你忘了右移算出來的溫度會整整偏大一倍這是新手最常見的錯誤之一。4.2 配置寄存器逐位說明MAX31865的配置寄存器是0x00工作模式都由這里控制。我用的配置是開啟VBIAS、連續轉換模式、三線制、50Hz濾波、清除故障狀態。void max31865_init(void) { uint8_t config 0x00; config | (1 7); // VBIAS 1使能RTD激勵電流 config | (1 6); // Conversion Mode 1自動連續轉換 config | (1 4); // 3-Wire 1三線制四線/兩線則清0 config | (1 3); // Fault Status Clear 1清除故障標志 config | (1 2); // Filter 150Hz濾波 max31865_write_reg(0x00, config); }逐位解釋一下VBIAS必須是1否則芯片不給RTD供電讀回來的數據永遠是0。連續轉換模式開啟后芯片會周期性地完成一次RTD測量每次完成DRDY就拉低一次。如果不開DRDY永遠不會有反應只能靠單次轉換觸發。50Hz濾波要根據你的工頻選擇。國內電網是50Hz就把Filter位寫1如果你在60Hz地區這一位寫0效果更好。Fault Status Clear位是用來清除故障狀態寄存器的寫1后芯片會自動清除舊的故障標志這個位本身不會被保持。4.3 RTD阻值計算和溫度換算讀取到15位ADC值后先換算成RTD阻值float max31865_read_rtd_resistance(void) { uint16_t raw max31865_read_rtd_raw(); uint16_t adc_code raw 1; float rtd (float)adc_code * 430.0f / 32768.0f; return rtd; }這里的430就是RREF的值32768是15位ADC滿量程對應的碼值??梢运阋幌路直媛?30/32768≈0.0131Ω對應PT100在0℃附近的溫度系數約0.385Ω/℃一個LSB大約代表0.034℃。所以MAX31865本身的量化分辨率很不錯實際誤差更多來自基準電阻和傳感器自身精度。阻值轉溫度我推薦用IEC 60751標準的Callendar-Van Dusen方程反解。正算公式是0~850℃范圍R R0 × (1 A×T B×T2)-200~0℃范圍R R0 × (1 A×T B×T2 C×(T-100)×T3)其中R0取100ΩA3.9083e-3B-5.775e-7C-4.183e-12。反解用二分法最省事迭代24次精度足夠float rtd_to_temp(float resistance) { float low -200.0f; float high 850.0f; float mid 0.0f; for (int i 0; i 24; i) { mid (low high) / 2.0f; float r; if (mid 0.0f) { r 100.0f * (1.0f 3.9083e-3f * mid - 5.775e-7f * mid * mid - 4.183e-12f * (mid - 100.0f) * mid * mid * mid); } else { r 100.0f * (1.0f 3.9083e-3f * mid - 5.775e-7f * mid * mid); } if (r resistance) { low mid; } else { high mid; } } return mid; }如果你的MCU沒有硬件浮點單元比如F103這個二分法會稍微吃點CPU但溫度采集本身頻率低完全可以接受。如果測溫范圍窄、只關心0~100℃也可以用查表法把PT100分度表按1℃間隔做成常量數組二分查找后線性插值速度更快代碼也不復雜。4.4 主循環與中斷回調怎么配合中斷回調里只置標志volatile uint8_t drdy_flag 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin DRDY_Pin) { drdy_flag 1; } }主循環里檢測到標志后讀取數據、換算溫度、打印while (1) { if (drdy_flag) { drdy_flag 0; float resistance max31865_read_rtd_resistance(); float temperature rtd_to_temp(resistance); char msg[64]; snprintf(msg, sizeof(msg), RTD%.2f Ohm, T%.2f C\r\n, resistance, temperature); HAL_UART_Transmit(huart1, (uint8_t *)msg, strlen(msg), 100); } }在主循環里讀數據有一個額外的好處如果某次DRDY中斷到來時主循環正忙標志位會被置起來等主循環空下來再讀不會丟事件。不過要注意讀數之后DRDY引腳的釋放是由硬件自動完成的讀取RTD寄存器后DRDY會被拉高等待下一次轉換完成再拉低。這個時序是MAX31865自動管理的不需要你額外操作。5. 實測翻車記錄從全0xFF到溫度亂跳的排查過程5.1 讀數全0xFF或者全0x00這是最常見的第一坑板子焊完上電讀回來的數據要么全是0xFF要么全是0x00。我把它拆成兩類情況。全0xFF通常是SPI時序不對或CS控制不對。比如CS沒拉低就開始發送單片機和MAX31865根本沒對上話或者SPI的時鐘極性和相位配錯讀回來的全是無效位。排查方法很簡單用邏輯分析儀抓SCLK、SDO、CS三根線看CS低電平期間是否有完整的8個時鐘周期SDO上是否有數據變化。如果CS波形正常但沒有數據檢查SPI是否真的用了正確的引腳。全0x00先檢查VBIAS位。如果配置寄存器的bit7沒有置1MAX31865不會給RTD供電輸入信號為0讀回自然就是0。還有就是電源沒供上、GND沒連接這類基礎問題。我調試時曾遇到過VDD和GND之間電容虛焊上電后芯片半死不活讀出來偶爾0x00偶爾亂碼把電容重新焊好就正常了。5.2 FAULT狀態反復置位如果你在主循環里周期性讀Fault Status寄存器地址0x07會讀到故障標志。MAX31865的故障檢測覆蓋了RTD斷路、RTD短路、參考電阻異常、電源過壓欠壓等場景。故障寄存器的每一位都對應一種故障具體位定義要對著手冊確認我這里不貼表格因為不同版本手冊的位編號容易記混現場調試以手冊為準。我遇到過一次很隱蔽的問題三線制接線時RTDIN-引腳接近懸空導致芯片認為RTD斷路FAULT頻繁拉低。重新對照手冊確認三線制接線方式把第三根補償線接對之后故障標志就消失了。還有一次是低閾值寄存器沒設置默認值導致實際溫度低于某個值時誤報故障。解決方法是初始化時把高閾值寄存器寫成0x7FFF極大值低閾值寄存器寫0x0000極小值先屏蔽掉閾值報警后續再按需求設報警點。5.3 溫度值跳變和DRDY中斷丟失連續轉換模式下DRDY每個轉換周期都會拉低一次。如果你的中斷處理函數里做了一大堆事情下一次轉換完成的下降沿可能在中斷還沒退出時就到了結果表現為“偶爾一次讀數跳變”或者“標志位一直為1”。正確的做法我前面寫過中斷回調里只置標志位真正讀數據、算溫度、打印全放主循環。這樣即便主循環忙得不可開交DRDY信號也只會被置一個標志不會阻塞SPI或UART。如果溫度在小范圍跳得很厲害先不要懷疑芯片用萬用表量一下電源紋波。MAX31865的模擬部分對電源噪聲敏感VDD上如果有高頻毛刺讀數就會抖。我后來在VDD引腳并了一顆4.7μF的陶瓷電容跳變幅度明顯小了。軟件上也可以加濾波連續讀幾次取平均值或者做一階低通濾波實測都能壓住大部分噪聲。5.4 長時間運行后的緩慢漂移設備跑了一天之后發現溫度讀數比剛開始高了0.3℃。這種緩慢漂移十有八九是RREF基準電阻受溫度影響導致。MAX31865用的是比例測量RREF阻值發生變化換算結果就會線性漂移。改善手段有三層換低溫漂的精密電阻把RREF遠離發熱源大功率電阻、穩壓芯片在代碼里記錄RREF的實際溫度做二次補償。前面兩個最直接如果你用的電阻是普通100ppm的貼片環境溫度變化10℃就能給你帶來不止0.3℃的誤差這在長期監測場景里沒法接受。6. 再進一步線阻補償和兩點校準6.1 三線制的補償原理和實際效果三線制之所以能在工業現場用這么廣核心在于它把“激勵電流路徑”和“電壓檢測路徑”分開。激勵電流流過導線會產生壓降但電壓檢測引腳基本上不取電流所以檢測到的電壓不受激勵線電阻影響。MAX31865內部處理邏輯就是圍繞這個思路設計的三線制接法能抵消掉一根導線的電阻效果明顯。實測中我用10米長、0.5mm2的銅導線接三線制PT100在50℃水溫下讀數和短接線相差不到0.1℃。如果換成兩線制同樣導線長度誤差能到1℃以上。所以現場走線距離長的話三線制是底線二線制基本只能用在板級短連接。6.2 用標準電阻箱做兩點校準如果對芯片手冊的精度還不放心或者想消除RREF阻值偏差帶來的固定誤差可以在系統集成階段做一次兩點校準。方法不算復雜準備一個精密電阻箱或者若干個高精度標準電阻替代PT100接到MAX31865的輸入端。在0℃對應100Ω和100℃對應138.51Ω也可以選你實際需要用的兩個溫度點分別讀取芯片計算出來的溫度得到兩個偏差值然后線性插值成一個校正公式T_corrected T_raw × slope offset把算出來的slope和offset寫進Flash或代碼配置區每次測量后應用。實測做不做校準同樣一顆芯片和電阻結果能差0.1~0.2℃。校準之后整個測量鏈路的精度上限基本就取決于PT100傳感器本體了。6.3 長期運行的自檢習慣MAX31865的故障檢測功能不用白不用。我現在的代碼里主循環每隔一段時間讀一次Fault Status寄存器一旦發現RTD斷線、參考電阻異常直接在串口輸出錯誤碼同時置一個“傳感器異?!睜顟B。這樣哪怕現場線纜被老鼠咬斷上位機也能立刻知道是傳感器問題而不是看到一組詭異的溫度值還在照常控制。一個小習慣分享出來每次讀RTD數據之前先看一眼Fault Status有故障就丟棄這次數據不參與控制邏輯。這個習慣幫我提前發現過一次接線端子氧化導致的接觸不良。所謂長期穩定有時候就是靠這種小細節堆出來的。