動(dòng)四路MAX6675熱電偶溫度采集與顯示系統(tǒng))
簡(jiǎn)介基于STM32F103的四路MAX6675溫度采集工程面向嵌入式學(xué)習(xí)者與開(kāi)發(fā)者解決多通道K型熱電偶數(shù)據(jù)采集、LCD1602實(shí)時(shí)顯示與串口上傳問(wèn)題。工程完整既可直接燒錄驗(yàn)證也可對(duì)照學(xué)習(xí)STM32的SPI主機(jī)配置、多從機(jī)片選切換、定時(shí)采樣及UART打印等關(guān)鍵環(huán)節(jié)。壓縮包共216個(gè)文件以c/h源碼、uvprojx工程、axf/hex固件及crf/o編譯中間文件為主sct鏈接腳本、map映射表等輔助文件齊全體積7.65MB便于排查鏈接與編譯細(xì)節(jié)。當(dāng)前已有1143人學(xué)習(xí)。內(nèi)容包含標(biāo)準(zhǔn)外設(shè)庫(kù)驅(qū)動(dòng)、四路MAX6675共用SPI總線的采樣子程序、LCD1602顯示邏輯及串口輸出格式配套工程可快速移植到同類多點(diǎn)測(cè)溫項(xiàng)目中。通過(guò)閱讀代碼能掌握從硬件接線、SPI時(shí)序到人機(jī)交互與數(shù)據(jù)上送的完整開(kāi)發(fā)流程適合作為STM32傳感器采集類項(xiàng)目的參考模板。 做過(guò)多路溫度采集項(xiàng)目的朋友應(yīng)該都有體會(huì)單個(gè)傳感器方案好寫一旦擴(kuò)展到四路、八路問(wèn)題就全來(lái)了——通道之間互相干擾、SPI時(shí)序?qū)Σ簧?、串口?shù)據(jù)亂成一團(tuán)、屏幕上顯示錯(cuò)位。我這次把一個(gè)完整可跑的方案整理出來(lái)STM32F103 最小系統(tǒng)板做主機(jī)掛四路 MAX6675 模塊采集熱電偶溫度數(shù)據(jù)同時(shí)輸出到 LCD1602 顯示并通過(guò)串口打印到調(diào)試助手。整個(gè)工程文件打包后命名為“STM32F103 驅(qū)動(dòng)四路MAX6675 采集溫度 LCD1602顯示 串口打印數(shù)據(jù).zip”正好把硬件接線、軟件代碼、調(diào)試方法都包含了。這篇文章把我實(shí)際調(diào)通的思路和踩過(guò)的坑原樣寫出來(lái)供正在做類似項(xiàng)目的朋友參考。這套東西適合誰(shuí)剛接觸 STM32 但已經(jīng)會(huì)用標(biāo)準(zhǔn)庫(kù)跑 GPIO、USART 的人想從單路采集往多路擴(kuò)展的或者需要在設(shè)備上配一個(gè)人機(jī)交互界面但又不想上屏幕方案太復(fù)雜的都可以直接照這個(gè)思路改。我會(huì)把每一條連線、每一段關(guān)鍵代碼、以及為什么這么寫的原因都講清楚保證你照著做能跑通。1. 項(xiàng)目整體思路四路溫度采集系統(tǒng)從需求到選型1.1 需求拆解與方案選型這個(gè)項(xiàng)目核心要解決的事情是用一塊主控芯片同時(shí)讀取四路 K 型熱電偶的溫度值然后一路送到 LCD1602 上顯示一路通過(guò)串口發(fā)送到上位機(jī)。聽(tīng)起來(lái)不復(fù)雜但當(dāng)你真的把四個(gè) MAX6675 掛在同一條 SPI 總線上時(shí)就會(huì)遇到一個(gè)非常實(shí)際的問(wèn)題——MAX6675 不是普通 SPI 從機(jī)它沒(méi)有寄存器地址每次片選拉低之后直接從 SO 引腳吐 16 位數(shù)據(jù)所以多路擴(kuò)展只能靠片選去區(qū)分。選型上我沒(méi)有直接上 STM32F4 或者加外部 ADC而是選擇 STM32F103 四片 MAX6675原因很直接F103 的 SPI 外設(shè)完全夠用72MHz 主頻下跑這幾個(gè)傳感器綽綽有余MAX6675 自帶冷端補(bǔ)償和數(shù)字輸出省掉了熱電偶信號(hào)調(diào)理電路比用運(yùn)放加 ADC 的方案簡(jiǎn)單太多。單路 MAX6675 是 0.25℃ 分辨率測(cè) 0 到 1023.75℃ 范圍K 型熱電偶的典型應(yīng)用場(chǎng)景都覆蓋了。有人會(huì)問(wèn)為什么不用 DS18B20它便宜而且同樣是單總線但測(cè)溫范圍只有 -55 到 125℃工業(yè)環(huán)境測(cè)個(gè)發(fā)熱體、測(cè)個(gè)烤箱溫度就完全無(wú)能為力了。熱電偶加 MAX6675 才是這類場(chǎng)景的正解雖然單路成本稍高但換來(lái)的是量程和抗干擾能力。1.2 系統(tǒng)框架與數(shù)據(jù)流向整體數(shù)據(jù)流是這樣走的四路 MAX6675 的 SO 輸出線并接到同一個(gè) GPIO 輸入引腳我這里用的是軟件模擬 SPI后文會(huì)講原因SCK 共用一根引腳每路各占一根片選 CS。主控輪流拉低不同通道的 CS等一小段時(shí)間后讀出 16 位數(shù)據(jù)換算成實(shí)際溫度值存入一個(gè) float 數(shù)組。然后主循環(huán)里每隔幾百毫秒刷新一次 LCD1602 顯示同時(shí)通過(guò)串口中斷或輪詢方式把格式化好的字符串發(fā)給上位機(jī)。我用的是標(biāo)準(zhǔn)外設(shè)庫(kù)而不是 HAL 庫(kù)因?yàn)檫@種老項(xiàng)目在很多代碼倉(cāng)庫(kù)里都是標(biāo)準(zhǔn)庫(kù)寫的移植起來(lái)方便而且像 F103 這種芯片標(biāo)準(zhǔn)庫(kù)的代碼量更少邏輯更直觀。軟件模擬 SPI 而不是用硬件 SPI也是因?yàn)樗穆?MAX6675 共用時(shí)鐘和數(shù)據(jù)線時(shí)軟件控制片選更靈活不用擔(dān)心硬件 SPI 從機(jī)管理帶來(lái)的片選信號(hào)同步問(wèn)題。實(shí)際跑下來(lái)四路全部讀完一次也就幾毫秒完全不影響實(shí)時(shí)性。2. 硬件設(shè)計(jì)與連接細(xì)節(jié)2.1 STM32F103 與四路 MAX6675 的接線接線是整個(gè)項(xiàng)目里最容易出問(wèn)題的一步別急著上電先把線對(duì)清楚。我用的最小系統(tǒng)板是 F103C8T6MAX6675 模塊是市面上最常見(jiàn)的紅色小板上面自帶 K 型熱電偶插座和濾波電容。四個(gè)模塊的 SO 引腳全部連接到單片機(jī)的 PB14MISO 方向SCK 全部連接到 PB13四路 CS 分別連接到 PA4、PA5、PA6、PA7。這里有個(gè)關(guān)鍵點(diǎn)四個(gè) SO 引腳并在一起相當(dāng)于開(kāi)漏輸出再接一個(gè)公共上拉但 MAX6675 模塊的 SO 本身是推挽輸出所以不需要額外上拉直接并接即可。如果你發(fā)現(xiàn)并接之后某一路讀數(shù)不正常檢查一下是不是某個(gè)模塊的 CS 沒(méi)有完全拉高處于半選通狀態(tài)此時(shí)它會(huì)繼續(xù)往總線上輸出數(shù)據(jù)把其他通道的數(shù)據(jù)給干擾了。模塊供電統(tǒng)一接 3.3V注意不要接 5V。雖然很多 MAX6675 模塊上帶了穩(wěn)壓芯片能接受 5V 輸入但直接接 5V 后SO 輸出高電平會(huì)達(dá)到 5V而 STM32F103 的 GPIO 并不都是 5V 容忍的PB14 如果配置成輸入模式耐壓有限很容易把引腳搞壞。我在這上面燒過(guò)一塊芯片后來(lái)學(xué)乖了全部統(tǒng)一 3.3V 供電。2.2 LCD1602 顯示模塊的連接與電平問(wèn)題LCD1602 是 5V 器件的典型代表而 STM32F103 的 GPIO 輸出是 3.3V直接接邏輯電平理論上能驅(qū)動(dòng)但存在兩個(gè)隱患一是 3.3V 高電平對(duì)于 5V 供電的 LCD1602 來(lái)說(shuō)可能達(dá)不到 VIH 閾值導(dǎo)致命令識(shí)別不穩(wěn)定二是 LCD1602 背光電路如果直接吃 5V控制引腳電平需要串電阻限流否則長(zhǎng)時(shí)間運(yùn)行會(huì)發(fā)熱。我的做法是LCD1602 供電接 5V但數(shù)據(jù)線和控制線通過(guò) 1kΩ 電阻串聯(lián)后接到 STM32 引腳上實(shí)測(cè)非常穩(wěn)定。RS 接 PB0EN 接 PB1D4-D7 接 PB2-PB5。用四線模式而不是八線模式省四個(gè)引腳。對(duì)比度調(diào)節(jié)引腳 V0 接一個(gè) 10kΩ 電位器中間抽頭接到 V0這個(gè)電位器必須接否則屏幕上可能什么都看不到這是很多新手第一次點(diǎn)不亮 LCD 的最大原因不是代碼問(wèn)題是硬件對(duì)比度沒(méi)調(diào)好。2.3 電源、地線與噪聲處理四路 MAX6675 同時(shí)工作時(shí)的功耗并不大主要電流來(lái)自 LCD 背光和芯片本身但有一個(gè)坑要注意熱電偶線本身是一根金屬導(dǎo)線如果走線很長(zhǎng)會(huì)像天線一樣接收環(huán)境噪聲尤其是工業(yè)現(xiàn)場(chǎng)有電機(jī)、開(kāi)關(guān)電源的時(shí)候讀數(shù)可能產(chǎn)生跳動(dòng)。我的處理方式是在 MAX6675 模塊的電源引腳旁邊加一個(gè) 10μF 電解電容和一個(gè) 0.1μF 陶瓷電容做二級(jí)濾波。模塊上本身有小電容但外加電容對(duì)長(zhǎng)電源線的壓降和紋波有明顯的改善作用。另外熱電偶線不要和電源線、電機(jī)驅(qū)動(dòng)線綁在一起走盡量分開(kāi)。如果傳感器離主板很遠(yuǎn)建議用屏蔽線屏蔽層單端接地。這些經(jīng)驗(yàn)都是從實(shí)際測(cè)試?yán)锏脕?lái)的我在實(shí)驗(yàn)室里第一次測(cè)試時(shí)就是因?yàn)闊犭娕季€繞過(guò)了開(kāi)關(guān)電源讀數(shù)一直跳后來(lái)把線重新走位后才穩(wěn)定下來(lái)。3. 軟件實(shí)現(xiàn)與核心代碼解析3.1 SPI 初始化與多路片選管理軟件模擬 SPI 的好處是可以隨便映射引腳不受硬件外設(shè)引腳復(fù)用限制代碼邏輯也直白。初始化時(shí)把 PB13 配成推挽輸出PB14 配成浮空輸入或上拉輸入PA4-PA7 全部推挽輸出并默認(rèn)拉高。這里要特別強(qiáng)調(diào)CS 引腳默認(rèn)必須拉高否則上電瞬間 MAX6675 處于未知狀態(tài)可能在 SO 線上產(chǎn)生毛刺影響第一次讀取。實(shí)際讀取時(shí)先把對(duì)應(yīng)通道的 CS 拉低然后延時(shí) 1-2 微秒再開(kāi)始產(chǎn)生 16 個(gè) SCK 上升沿。為什么要在 CS 拉低后延時(shí)因?yàn)?MAX6675 內(nèi)部在 CS 下降沿后需要一小段時(shí)間準(zhǔn)備數(shù)據(jù)如果立刻給時(shí)鐘讀到的前幾位可能不是有效數(shù)據(jù)。很多移植失敗的例子就是少了這個(gè)延時(shí)。每次讀完把 CS 拉高完成一次通道切換。// 片選引腳宏定義 #define CS1_PORT GPIOA #define CS1_PIN GPIO_Pin_4 #define CS2_PORT GPIOA #define CS2_PIN GPIO_Pin_5 #define CS3_PORT GPIOA #define CS3_PIN GPIO_Pin_6 #define CS4_PORT GPIOA #define CS4_PIN GPIO_Pin_7 #define SCK_PORT GPIOB #define SCK_PIN GPIO_Pin_13 #define SO_PORT GPIOB #define SO_PIN GPIO_Pin_14 uint16_t MAX6675_Read(uint16_t cs_port, uint16_t cs_pin) { uint16_t value 0; GPIO_WriteBit(cs_port, cs_pin, Bit_RESET); delay_us(2); for (uint8_t i 0; i 16; i) { GPIO_WriteBit(SCK_PORT, SCK_PIN, Bit_SET); delay_us(1); value 1; if (GPIO_ReadInputDataBit(SO_PORT, SO_PIN)) { value | 0x0001; } GPIO_WriteBit(SCK_PORT, SCK_PIN, Bit_RESET); delay_us(1); } GPIO_WriteBit(cs_port, cs_pin, Bit_SET); return value; }3.2 MAX6675 數(shù)據(jù)讀取與溫度換算MAX6675 每次輸出 16 位數(shù)據(jù)其中 bit15 始終為 0bit14 到 bit3 是溫度數(shù)據(jù)bit2 是熱電偶開(kāi)路標(biāo)志為 1 時(shí)表示熱電偶斷線或沒(méi)插好。溫度值的換算公式很直接把 bit14 到 bit3 這 12 位數(shù)據(jù)右移 3 位再乘以 0.25得到的就是實(shí)際溫度值單位是攝氏度。舉個(gè)例子讀到的原始值是 0x01B4二進(jìn)制是 0000 0001 1011 0100。右移 3 位得到 0x36也就是十進(jìn)制 54乘以 0.25 等于 13.5℃。這個(gè)換算過(guò)程在代碼里就一行但要注意數(shù)據(jù)類型的處理右移之前不能把 uint16_t 強(qiáng)轉(zhuǎn)成 int8_t否則高位會(huì)被截?cái)嘭?fù)溫度場(chǎng)景下直接就錯(cuò)了。另外MAX6675 有熱電偶開(kāi)路檢測(cè)讀取完成后先判斷 bit2 是否為 1如果是說(shuō)明熱電偶斷開(kāi)或者沒(méi)接好這時(shí)候溫度值沒(méi)有意義我習(xí)慣返回一個(gè)特殊值比如 -1000表示異常狀態(tài)方便上位機(jī)顯示報(bào)警。別忽略這個(gè)位它能幫你快速定位是傳感器問(wèn)題還是程序問(wèn)題。float MAX6675_GetTemp(uint16_t cs_port, uint16_t cs_pin) { uint16_t raw MAX6675_Read(cs_port, cs_pin); if (raw 0x0004) // 熱電偶開(kāi)路檢測(cè)位 { return -1000.0f; } return (float)(raw 3) * 0.25f; }實(shí)際使用時(shí)四個(gè)通道輪流讀取每次讀取間隔建議至少 200 毫秒。因?yàn)?MAX6675 內(nèi)部完成一次溫度轉(zhuǎn)換大約需要 170-220 毫秒如果連續(xù)快速讀取讀到的可能是上一次的舊數(shù)據(jù)或者轉(zhuǎn)換中間的不穩(wěn)定數(shù)據(jù)。我最初為了刷新率把讀取間隔壓到 50 毫秒結(jié)果四路數(shù)據(jù)輪流跳變后來(lái)在循環(huán)里加了一個(gè) 250 毫秒的延時(shí)數(shù)據(jù)就非常穩(wěn)定了。3.3 LCD1602 顯示與串口打印LCD1602 的驅(qū)動(dòng)代碼網(wǎng)上很多但我這兒要提醒幾個(gè)容易忽略的細(xì)節(jié)。初始化時(shí)4 位模式必須按數(shù)據(jù)手冊(cè)規(guī)定的時(shí)序來(lái)先發(fā) 0x03 三次再發(fā) 0x02 切換到 4 位模式然后才是功能設(shè)置、顯示開(kāi)關(guān)、清屏等命令。很多人直接套用網(wǎng)上代碼結(jié)果因?yàn)闀r(shí)序問(wèn)題導(dǎo)致顯示亂碼問(wèn)題就出在初始化階段。顯示邏輯上我用的是比較簡(jiǎn)單的方式把四路溫度分別格式化到兩行顯示第一行顯示 CH1 和 CH2第二行顯示 CH3 和 CH4。每行可以顯示 16 個(gè)字符四路溫度加標(biāo)識(shí)符綽綽有余。顯示溫度時(shí)要處理異常值如果返回值是 -1000就顯示 ERR 而不是一個(gè)怪異的數(shù)字。串口部分我用的 USART1PA9 做 TXPA10 做 RX波特率 1152008 位數(shù)據(jù)位1 位停止位無(wú)校驗(yàn)。調(diào)試助手用常見(jiàn)的串口助手軟件就行注意 CH340 驅(qū)動(dòng)要先裝好設(shè)備管理器里能看到 COM 口才能連上。打印格式我是這樣組織的printf(CH1: %.2f C CH2: %.2f C\r\n, temp[0], temp[1]); printf(CH3: %.2f C CH4: %.2f C\r\n, temp[2], temp[3]);記得在工程里重定向 fputc 函數(shù)到串口發(fā)送不然 printf 輸出到不了串口int fputc(int ch, FILE *f) { USART_SendData(USART1, (uint8_t)ch); while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); return ch; }4. 調(diào)試過(guò)程與常見(jiàn)問(wèn)題排查4.1 溫度讀數(shù)異常問(wèn)題我調(diào)試過(guò)程中遇到的最典型問(wèn)題是某一通道讀數(shù)始終是 0℃ 或者固定跳到某個(gè)數(shù)值。這種情況九成是熱電偶插反或者接觸不良。MAX6675 模塊上的熱電偶插座是有極性的K 型熱電偶的紅色線一般接正極黃色線接負(fù)極插反了溫度顯示會(huì)異常。還有一次是熱電偶插頭氧化導(dǎo)致接觸電阻變大讀數(shù)持續(xù)跳動(dòng)。另外一類問(wèn)題是所有通道都讀到同一個(gè)值而且這個(gè)值不變化。這多半是片選引腳配置問(wèn)題比如四路 CS 全部被拉低了SO 總線同時(shí)被多個(gè)芯片驅(qū)動(dòng)讀出來(lái)的數(shù)據(jù)是拼湊的亂碼。排查方法很簡(jiǎn)單用萬(wàn)用表量 CS 引腳電平確認(rèn)只有當(dāng)前通道是低電平。4.2 LCD1602 不顯示或顯示亂碼LCD1602 不顯示先查三件事對(duì)比度電位器調(diào)了沒(méi)有、背光正不正常、電源是不是 5V。我見(jiàn)過(guò)太多人在這上面折騰半天結(jié)果就是 V0 沒(méi)接電位器或者電位器旋到了最暗的位置。對(duì)比度調(diào)節(jié)的標(biāo)準(zhǔn)方法是慢慢旋轉(zhuǎn)電位器直到屏幕上出現(xiàn)一排黑色方塊。顯示亂碼的原因大多是初始化時(shí)序不對(duì)。我的經(jīng)驗(yàn)是每次寫命令或數(shù)據(jù)之間至少延時(shí) 1-2 毫秒特別是清屏命令需要更長(zhǎng)的延時(shí)。如果 4 位模式下 D4-D7 接反了或者 RS、EN 接錯(cuò)也會(huì)出現(xiàn)亂碼。檢查方法是用示波器看 EN 引腳確認(rèn)每次寫入時(shí) EN 有下降沿。4.3 串口打印數(shù)據(jù)異常串口打印出來(lái)是亂碼優(yōu)先級(jí)最高的是波特率配置。檢查 USART 初始化時(shí) BRR 寄存器計(jì)算是否正確尤其是外部晶振頻率和你代碼里寫的 HSE_VALUE 是否一致。我用的是 8MHz 外部晶振如果代碼誤配成 12MHz波特率就會(huì)偏得離譜上位機(jī)收到全是亂碼。其次檢查 USB 轉(zhuǎn) TTL 模塊的 TX、RX 有沒(méi)有接反。STM32 的 TX 要接模塊的 RXSTM32 的 RX 接模塊的 TX交叉連接這是新手最容易犯的錯(cuò)誤。4.4 多路采集相互干擾的處理四路同時(shí)采集時(shí)如果發(fā)現(xiàn)某一路讀數(shù)會(huì)輕微影響其他路多半是讀取切換時(shí)產(chǎn)生的電氣干擾。我的解決方法是每讀完一路后將 CS 拉高然后延時(shí) 5 毫秒再操作下一路讓 SO 總線上的電平有一個(gè)穩(wěn)定的過(guò)程。如果還不行檢查四路模塊是否共地如果共地點(diǎn)接觸不良也會(huì)產(chǎn)生壓差干擾。還有一種情況是熱電偶引線過(guò)長(zhǎng)感應(yīng)到工頻干擾??梢园巡蓸又底鲆淮魏?jiǎn)單的滑動(dòng)平均濾波比如連續(xù)讀五次去掉最大最小值再取平均雖然 MAX6675 本身已經(jīng)有數(shù)字濾波但工程環(huán)境下加上軟件濾波會(huì)明顯提高穩(wěn)定性。一點(diǎn)實(shí)際體會(huì)最后再說(shuō)一個(gè)我在測(cè)試時(shí)發(fā)現(xiàn)的細(xì)節(jié)MAX6675 手冊(cè)上寫的轉(zhuǎn)換時(shí)間可能因模塊設(shè)計(jì)和供電電壓不同而略有變化。我實(shí)際測(cè)試時(shí)四路通道在 3.3V 供電下200 毫秒的間隔穩(wěn)定可靠但如果我把電壓降到 3.1V 以下讀數(shù)偶爾會(huì)出現(xiàn)跳變說(shuō)明模塊在低壓邊緣工作不穩(wěn)定。如果你的系統(tǒng)供電電壓有波動(dòng)建議在程序里檢測(cè) MAX6675 的異常值位同時(shí)電源部分多留點(diǎn)余量。這個(gè)項(xiàng)目整體調(diào)試下來(lái)最難的不是代碼而是硬件細(xì)節(jié)和耐心排查希望這份經(jīng)驗(yàn)?zāi)軒湍闵僮邚澛?。本文還有配套的精品資源點(diǎn)擊獲取