
簡介面向STM32開發者的SHT30溫濕度傳感器HAL庫例程包基于STM32F130ZET6平臺適合需要快速實現I2C溫濕度采集的中初級嵌入式開發者也可作為智能家居、環境監測等應用場景的參考方案。SHT30具備±2%濕度與±0.3°C溫度精度搭配主頻72MHz、512KB閃存的STM32F130ZET6可應對多數環境監測需求。例程完整演示了SHT30的驅動配置、數據讀取與解析流程涵蓋HAL庫I2C初始化、主從收發函數調用、溫濕度換算等關鍵環節可直接編譯運行并觀察實時數據。資源包共231個文件、10.22MB以I2C驅動、HAL庫源文件、定時器及串口模塊為主同時包含工程配置文件如uvprojx、ewp與編譯中間文件o、axf等便于在Keil或IAR環境下打開調試。除核心源碼外包內還提供初始化配置和完整參考工程有助于理解I2C通信時序、HAL庫API調用方式以及如何根據傳感器數據驅動后續控制邏輯。同時代碼結構清晰、注釋完整方便直接復用或二次開發。已有1977人下載學習。 講實話我以前做環境監測項目最早用的是DHT11后來換成了SHT30中間還折騰過一陣子AHT20。現在手頭固定板子型號是STM32F103ZET6用的是ST官方HAL庫。如果你正好在找一個能直接跑的SHT30例程又不想被網上那些標準外設庫辦法繞暈那這篇就是給你寫的。我會把SHT30的I2C通信細節、HAL庫工程怎么配、驅動代碼怎么拆解還有我實際調板子時踩過的坑一次性說完。1. 先說說為什么棄用DHT11換到SHT30DHT11在很多入門教程里出現率極高因為它便宜、接線簡單、代碼也好寫。但用過的都知道它的精度實在拿不出手——溫度精度正負2℃濕度精度正負5%有些批次甚至更離譜。放室內看個大概還行一旦要做數據記錄、恒溫控制或者產品和儀器放一起DHT11的數據出來根本不敢用來做決策。SHT30是Sensirion的新一代數字溫濕度傳感器溫度精度能做到正負0.2℃典型值濕度精度正負2%注意是典型值。更關鍵的SHT30出廠就做了個體校準不需要你手里攢一堆溫濕度計去逐點修正。單次測量的平均電流約15μA周期測量模式下平均電流甚至可以到1μA左右電池方案完全扛得住。這個精度和功耗差異就是我換到SHT30的根本原因。另外一個實際考量是通信接口。DHT11用單總線時序要求苛刻一旦中斷被嵌套或者系統忙時序就容易飄讀出來的數據是錯的你都不知道錯在哪只能一遍遍調延時。SHT30用的是標準I2C接口地址固定為0x44或0x45主機只要按I2C協議發命令、收數據就行時序由I2C控制器和內部的晶振保證代碼上不需要精確卡微秒級延時。這在HAL庫環境下尤其省心——ST官方庫把I2C時序細節全封裝好了你只需要調用HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive兩個函數。SHT30的封裝也友好有DFN-8小封裝也有帶PCB焊盤的貼片版本手工焊完全能做。如果你像我一樣用核心板擴展找個SHT30模塊也行模塊上一般自帶上拉電阻和穩壓電路接線就四根VCC、GND、SCL、SDA。板上沒有硬件I2C上拉的話我這里提醒一句SCL和SDA一定要各接一個10kΩ上拉到3.3V否則通信會間歇性失敗后面細講。2. SHT30不能只調包命令、時序與數據格式很多新手拿到SHT30第一反應是找個現成例程復制粘貼。但驅動這類數字傳感器光會調庫函數是不夠的。SHT30手冊里其實就干了幾件事發命令、等測量、讀數據、解算。但每一環都有門道。先說話命令。SHT30的單次測量命令是兩字節0x2C是高字節低字節決定重復性和時鐘拉伸行為。常用低字節有這么幾個命令低字節重復性時鐘拉伸測量時間上限0x06高使能15.5ms0x10高禁用15.5ms0x0D中使能6.5ms0x1E中禁用6.5ms0x13低使能2.5ms0x27低禁用2.5ms時鐘拉伸這里要多解釋一句。所謂時鐘拉伸就是從設備在測量期間主動拉低SCL告訴主機我還沒準備好你別往下走。這在單次測量、主機不等延時直接讀取的場景下很有用。但不巧STM32F103的HAL庫I2C在阻塞模式下對時鐘拉伸的支持并不順暢實際測下來用0x2C 0x06配合HAL_I2C_Master_Receive有時候會卡在等待SCL釋放的地方我后文會專門講這個坑。所以我做例程時默認用0x2C 0x10——禁用時鐘拉伸高重復性然后在發完命令后主動HAL_Delay(20)等它測量完再讀。20ms比15.5ms的上限多了余量哪怕芯片略有批次差異也足夠。你要是做低功耗可以把重復性降到中或低延時隨之縮短。接下來是讀取數據。SHT30會返回6字節結構固定成字節索引內容0溫度數據高位1溫度數據低位2溫度CRC8校驗值3濕度數據高位4濕度數據低位5濕度CRC8校驗值溫度和濕度原始值都是16位無符號數。溫度轉換公式是-45 175 * rawT / 65535濕度轉換公式是100 * rawH / 65535。這個公式手冊上有但你真寫代碼時很容易把除數和被除數寫反我犯過這毛病結果溫度直接飆到一百多度。注意rawT和rawH都是0到65535的整數65535就是滿量程。重點說一下CRC8校驗。兩個數據各跟一個校驗字節已經是很業界良心的做法了。多項式是0x31初始值0xFF和很多I2C傳感器用的CRC算法一致。比如SHT31、SGP30都是這套寫一遍以后還能復用到別的傳感器上。千萬別偷懶跳過校驗數據線稍微長一點或者供電紋波大偶發一個字節錯誤是會很折磨人的有校驗能直接給你報錯重讀比你在串口上看到濕度98%但實際濕度60%強一百倍。還有一個容易忽略的細節SHT30上電后不要立刻發命令建議等100ms以上因為它內部的穩壓器和晶振需要啟動穩定時間。我自己例程里在MCU初始化后先做了一次空讀操作權當喚醒復位避免上電毛刺導致首幀數據異常。3. 搭建STM32F103ZET6的HAL庫工程標題里的F130是筆誤先糾正一個事STM32家族里其實沒有F130ZET6這個型號ST的F1系列里是STM32F103ZET6。舍得查資料就會發現這個F130大概率是筆誤或者輸入法聯想出來的。我這邊板子用的就是F103ZET6動不動512KB Flash、64KB RAM的資源做傳感器采集綽綽有余。下面所有操作按F103ZET6進行。我建工程用的是STM32CubeMX圖形化配置它生成HAL庫框架然后我再往里加驅動。CubeMX版本不用太糾結我用的是6.x系列。打開CubeMX新建工程時芯片型號選STM32F103ZET6調試接口要記得在SYS里勾上Serial Wire不然板子第一次下載完還能跑第二次就提示找不到芯片了這個坑不知道坑過多少人了。時鐘樹部分F103ZET6最高主頻72MHz。我外部晶振用的8MHz無源晶振配置HSE并倍頻到72MHzAPB1總線注意要二分頻到36MHz這不光是為了系統穩定更關鍵的是I2C外設掛在APB1上而I2C想要400kHz快速模式前提就是APB1時鐘36MHz。你要是用默認的72MHz給APB1I2C時序會完全不對通信基本廢掉。外設勾選這邊主要開三樣I2C1我實際用PB6和PB7對應I2C1的SCL和SDA外設使能后選Standard Mode也就是100kHz。實際測試100kHz最穩SHT30數據手冊雖然支持400kHz但線路長了之后400kHz很容易出現CRC校驗失敗。USART1也就是PA9的TX和PA10的RX波特率115200用來把溫濕度打印到串口助手。GPIO留一個LED引腳做運行指示我用的PD2你也可以用板載LED對應的引腳。CubeMX生成完代碼后別忘了檢查一下I2C初始化函數中Timing參數。HAL庫在F1上I2C初始化時不需要像F4那樣手動算I2C_TIMINGR寄存器而是由HAL庫根據APB1時鐘自動計算這算是F1的一個優勢。你只要保證I2C1的Clock Speed填100000就行。工程搭建完成后目錄里會有Inc/Src兩個主要文件夾。驅動文件我會新建sht30.h和sht30.c放在Src和Inc目錄下。這里面有一個隱藏注意點CubeMX生成的代碼不吃手動往main.c里塞上千行工程式代碼尤其每次你重新生成工程main.c會被覆蓋重寫所以我的習慣是全部驅動獨立成文件main.c里只留調用入口這樣重新生成工程時不需要心疼自己寫的代碼丟失。4. 驅動代碼逐段解析從單次測量到CRC校驗下面這段驅動就是我最常用、實測最穩的版本。先說清楚這個驅動只做單次測量模式周期測量我放到最后擴展里講。代碼思路很簡單先發命令再延時再讀6字節最后解算。在sht30.h里這樣聲明#ifndef __SHT30_H #define __SHT30_H #include main.h uint8_t SHT30_Read_TH(float *temperature, float *humidity); #endifsht30.c的完整實現#include sht30.h #define SHT30_ADDR_W (0x44 1) // I2C寫地址7位地址0x44左移 #define SHT30_ADDR_R (0x44 1) | 0x01 // I2C讀地址 #define SHT30_CMD_MS_H 0x2C // 單次測量命令高字節 #define SHT30_CMD_MS_L 0x10 // 高重復性禁用時鐘拉伸 // CRC8校驗多項式0x31初始值0xFF static uint8_t SHT30_CRC8(uint8_t *data, uint8_t len) { uint8_t crc 0xFF; while (len--) { crc ^ *data; for (uint8_t i 0; i 8; i) { crc (crc 0x80) ? (uint8_t)((crc 1) ^ 0x31) : (uint8_t)(crc 1); } } return crc; } uint8_t SHT30_Read_TH(float *temperature, float *humidity) { uint8_t cmd[2] {SHT30_CMD_MS_H, SHT30_CMD_MS_L}; uint8_t buf[6] {0}; if (HAL_I2C_Master_Transmit(hi2c1, SHT30_ADDR_W, cmd, 2, 100) ! HAL_OK) { return 1; } // 高重復性測量上限15.5ms給20ms余量 HAL_Delay(20); if (HAL_I2C_Master_Receive(hi2c1, SHT30_ADDR_R, buf, 6, 100) ! HAL_OK) { return 2; } // 分別校驗溫度和濕度的CRC if (SHT30_CRC8(buf[0], 2) ! buf[2]) { return 3; } if (SHT30_CRC8(buf[3], 2) ! buf[5]) { return 4; } uint16_t rawT ((uint16_t)buf[0] 8) | buf[1]; uint16_t rawH ((uint16_t)buf[3] 8) | buf[4]; *temperature -45.0f 175.0f * (float)rawT / 65535.0f; *humidity 100.0f * (float)rawH / 65535.0f; return 0; }這段代碼有幾個關鍵點要展開講。第一I2C地址為什么要左移。SHT30的7位地址是0x44焊盤ADDR接GND時為0x45。但在HAL庫的HAL_I2C_Master_Transmit里傳入的地址必須包含讀寫位也就是8位地址所以得把0x44左移1位得到0x88作為寫地址或最低位置1得到0x89作為讀地址。很多人剛開始會直接把0x44填進去然后發現I2C總線根本沒有ACK就是卡在這。第二為什么我不把HAL_Delay換成查詢狀態。SHT30支持一種變通發完命令直接進入讀流程依靠時鐘拉伸等傳感器完成。前面提過這個思路在STM32F103 HAL庫下容易踩坑。所以我寧愿犧牲20ms延時換取確定不卡死的操作。在大多數采集場景20ms間隔完全夠用就算你要一秒采一次也沒影響。第三返回錯誤碼代替直接停機。驅動不管讀成功與否都返回一個狀態碼主程序可以根據狀態碼決定是打印串口信息還是亮LED報警。我習慣用1表示發送失敗2表示接收失敗3表示溫度CRC錯誤4表示濕度CRC錯誤。這樣一旦現場出了問題看一眼錯誤碼就能定位到通信的哪個環節。主程序調用就簡單了我在while循環里每秒讀一次float temp 0.0f, humi 0.0f; uint8_t ret 0; char msg[64]; while (1) { ret SHT30_Read_TH(temp, humi); if (ret 0) { sprintf(msg, Temp: %.2f C, Humi: %.2f %%\r\n, temp, humi); HAL_UART_Transmit(huart1, (uint8_t *)msg, strlen(msg), 100); } else { sprintf(msg, SHT30 Error: %d\r\n, ret); HAL_UART_Transmit(huart1, (uint8_t *)msg, strlen(msg), 100); HAL_Delay(300); } HAL_Delay(1000); }這里注意sprintf格式化濕度百分比時我在格式串中用的是兩個百分號%因為sprintf里單個百分號會被識別成格式符不加兩個百分號打印出來數據就亂套了。這種細節調試時最浪費時間。5. 燒進去之后三個實測坑與排查方法代碼寫完了上電實測階段往往才是真正耗時間的地方。我在三輪不同項目的調試中先后遇到這么幾個典型問題每一個都值得拿出來復盤。第一個坑也是最常見的串口一直打Error Code 2或者3接收失敗或者CRC過不去。先別急著懷疑代碼拿出示波器或者邏輯分析儀抓I2C波形。如果沒有示波器就直接看SDA和SCL的對地電壓用萬用表量正常空閑狀態都應該是3.3V。如果是0V說明總線被拉死大概率是模塊上拉電阻缺失。我之前用過一塊很便宜的SHT30模塊板子上沒焊上拉量下來SCL一直低電平。補兩個10k電阻到3.3V問題立刻消失。如果你手頭正好沒有10k4.7k到47k的也可以用只是上拉太小會加重主機負載上拉太大則翻轉速度變慢別太離譜就行。第二個坑發完0x2C 0x06后HAL_I2C_Master_Receive卡死。這個坑我印象極深。我用0x2C 0x06配合HAL庫直接讀取結果程序死在接收函數里HardFault前反復卡在上面的超時查詢。仔細看SHT30手冊才發現0x06是使能時鐘拉伸傳感器測量期間會把SCL拉低HAL庫在阻塞模式下一直等待SCL釋放但SHT30既要拉低SCL又要回數據兩者配合在F1的I2C模塊上就產生了死鎖現象。原理層面的東西不好深究但解決起來很簡單命令改成0x10禁用時鐘拉伸然后自己延時20ms。改完之后這個問題再沒出現過。這也讓我徹底認清硬件I2C固然穩定但必須匹配從設備的時序特性不是所有命令都合適。第三個坑是數據偶發跳變溫度濕度一會兒正常一會兒離譜地大。排查下來發現是我在測試時杜邦線飛了大概20厘米供電又從另一塊板子上取導致地回路不穩。SHT30這類高精度傳感器對供電很敏感我后來在VCC和GND之間加了一個100nF去耦電容并且把I2C線縮短到10厘米以內跳變就消失了。這個現象在DHT11上不會那么明顯因為DHT11本身精度就差跳變兩三個點你根本不在乎但SHT30精度高偶發偏差就會直接暴露出來。除了這三個坑還有幾個邊界情況要處理。硬件I2C偶爾會因從機異常進入總線鎖死狀態表現為SCL/SDA長期為低。我在工程里加了一個I2C總線恢復函數原理是把SCL對應的引腳臨時配置為GPIO輸出手動翻轉至少9個時鐘同時把SDA釋放為高電平這樣可以讓卡在錯誤狀態的從機復位。這個函數平時用不上但傳感器熱插拔或者供電抖動之后它能救你一把。另外關于讀取頻率SHT30單次測量模式下最小間隔可以做到幾十毫秒但我不建議無腦快速循環讀。一方面頻繁讀會增加功耗另一方面傳感器本身有一定的響應時間讀數變化不會瞬時發生你要做數據平滑不如直接讀取間隔設置到500ms或1s以上。做產品尤其要注意這個別把CPU和I2C總線無限壓榨。6. 再往前一步周期測量模式與低功耗設計建議單次測量完全可以覆蓋大多數應用但如果你做的是電池供電設備比如溫濕度記錄儀或者智能家居里的傳感器節點那SHT30的周期測量模式更值得研究。周期測量模式下傳感器內部會以固定頻率自己完成測量兩種常見配置是每秒2次命令0x20 0x32和每秒10次命令0x20 0x22都是高重復性。設置好之后主機不用再發測量命令只要定時去讀6字節數據就行。這樣主控可以在兩次讀取之間進入睡眠由傳感器自己維持測量節奏功耗優勢非常明顯。實測下來2mps配置下SHT30的平均電流在數十微安級別一顆小鋰電池足夠撐很久。但周期模式也有個容易誤解的地方命令發一次后傳感器會一直按這個頻率測量如果你不切換到其他模式它永遠不會自己停。所以代碼里的初始化邏輯要做好管理比如設備上電后發一次0x20 0x32后續循環里直接HAL_I2C_Master_Receive讀數據不要再重復發命令否則每次都會疊加一次配置重置影響讀時序。想退出周期模式時可以發送單次測量命令比如0x2C 0x10傳感器就會回到單次模式。還要留意周期模式下讀到的值并不一定每次都是新的。因為數據緩存更新頻率和讀取頻率可能存在相位差連續讀兩次可能拿到同一個數據。這不算故障做數據記錄時去重或做一下時間戳即可。如果讀數長期不變才需要懷疑傳感器卡在舊狀態可以用讀取狀態寄存器命令0xF3 0x2D檢查Bit15是否為零零表示正常狀態。低功耗設計的另一個細節是SHT30在每次測量完成后需要至少一個I2C停止條件才能進入內部空閑狀態。所以無論用HAL庫還是軟件模擬I2C讀取結束一定要讓總線產生STOP這也是HAL_I2C_Master_Receive天然做好的事情不需要額外處理。還有一個容易被忽略的小功能SHT30內部帶加熱器可以通過命令0x30 0x0D開啟0x30 0x00關閉。這個加熱器不是用來給環境加溫的而是用來在潮濕或者結露環境下烘干傳感器表面保證濕度測量不會因為傳感器罩內積水而失真。如果你把設備用在戶外或者冷庫門口這類濕度高的地方可以在初始化后周期性開啟加熱器十幾秒然后關閉等穩定再讀值。我自己很少用但在一個冷鏈記錄項目里測試過確實能把高濕工況下的數據漂移拉回來。7. 寫在最后的一點實戰體會如果把整個SHT30驅動過程濃縮成一條經驗那就是數字傳感器調試的核心不是把代碼寫出來而是把通信鏈路上每一段電氣行為和時序行為都搞明白。DHT11的軟件時序讓你學會摳延時SHT30的HAL庫例程則讓你學會信協議、查CRC、管理總線狀態。這兩個方向并不沖突經歷過一個再上手另一個上手速度會快很多。我個人在實際項目中的最終配置就是STM32F103ZET6作為主控硬件I2C1速率100kHzSHT30地址0x44命令固定0x2C 0x10加20ms延時讀取后帶CRC校驗串口打印。這套組合我在至少三個不同環境下的設備上連續運行過數月數據穩定沒有一次總線鎖死或者臟數據。你照著搭大概率也能一次跑通。如果后續想擴展建議去看Sensirion官方的嵌入式驅動包里面還有狀態寄存器解析、加熱器控制、傳感器復位等接口邏輯清晰拿過來改改就能嵌套進你的HAL工程。不過官方的抽象層次較高不建議第一版例程就引進去先把單次測量跑通、把原始數據打印出來再逐步增加復雜度這才是最穩妥的路徑。本文還有配套的精品資源點擊獲取