STM32F103 HAL库驱动SHT30温湿度传感器实战与避坑指南

发布时间:2026/10/20 4:04:59
STM32F103 HAL库驱动SHT30温湿度传感器实战与避坑指南 简介这份资源是面向嵌入式开发初学者与工程师的SHT30温湿度传感器驱动例程基于STM32F130ZET6微控制器与ST官方HAL库实现帮助读者快速掌握I2C通信、传感器数据解析与温湿度采集系统的搭建方法。压缩包共231个文件约10.22MB涵盖c源码、h头文件、o目标文件、uvprojx与uvoptx工程配置、icf与sct链接脚本、map映射文件及axf可执行文件等完整保留了从工程配置到编译输出的全套中间产物便于对照理解Keil与IAR双环境的构建流程。资源包内包含初始化配置、I2C读写、数据转换与串口显示等模块读者可据此掌握HAL_I2C_Master_Transmit与Receive的实际用法并参考上拉电阻设置、中断处理与信号完整性等注意事项进行排错。目前已有1981人学习下载适合需要快速跑通SHT30采集链路、积累HAL库外设驱动经验的开发者参考。1. 从一颗 SHT30 说起为什么 STM32F103 的 HAL 库例程值得单独拆一遍前阵子帮一个做环境监测设备的朋友调板子他用的主控是 STM32F103ZET6传感器是 SHT30I2C 接口硬件上拉电阻焊得好好的电源也干净可读出来的湿度值就是会在 60% 到 90% 之间乱跳温度倒是稳。他一开始怀疑是传感器坏了换了两颗新的还是一样最后发现问题出在 HAL 库的HAL_I2C_Mem_Read超时参数给得太小加上没处理 SHT30 的测量等待时间读回来的数据里混进了上一次的旧值。这件事让我意识到SHT30 配 STM32 HAL 库这个组合看起来简单实际上手时踩坑的人真不少。这份例程就是围绕 STM32F103ZET6 和 SHT30 展开的走的是标准 HAL 库路线I2C 通信包含初始化、单次测量、周期测量、温湿度换算和 CRC 校验几个部分。它解决的不是“能不能点亮”的问题而是“读出来的数能不能信”的问题。适合正在用 STM32 HAL 库接 SHT30 的嵌入式开发者尤其是那些已经能读出数据、但对数据稳定性和精度心里没底的人。下面我会按实际调试顺序把这份例程拆开讲清楚包括 I2C 地址怎么定、命令怎么发、CRC 怎么算、周期测量模式怎么配以及几个我亲自翻过车的地方。2. SHT30 的 I2C 通信层地址、命令与时序参数怎么定2.1 7 位地址与 HAL 库的 8 位写法差异SHT30 的 I2C 地址由 ADDR 引脚决定ADDR 接 GND 时 7 位地址是 0x44接 VDD 时是 0x45。很多新手在这里翻车是因为 HAL 库的HAL_I2C_Master_Transmit和HAL_I2C_Mem_Read传的是 8 位地址也就是 7 位地址左移一位。0x44 左移一位变成 0x880x45 变成 0x8A。如果你直接把 0x44 填进去HAL 库会把它当成 8 位地址处理实际发出的地址就变成了 0x22传感器根本不会应答。我一般会在头文件里这样定义/* SHT30 7位地址ADDR接GND时为0x44 */ #define SHT30_I2C_ADDR_7BIT 0x44 /* HAL库使用的8位地址左移一位 */ #define SHT30_I2C_ADDR_8BIT (SHT30_I2C_ADDR_7BIT 1)逻辑说明HAL 库的 I2C 函数内部会把传入的地址直接放到 DR 寄存器硬件自动取高 7 位作为设备地址最低位是读写位。所以必须传左移后的值。参数说明如果你用的是软件模拟 I2C那就要传 7 位地址因为模拟时序里你自己控制读写位。这个区别在换库或换平台时特别容易搞混。2.2 单次测量命令与时钟拉伸处理SHT30 的单次测量命令分两种时钟拉伸使能和时钟拉伸禁用。时钟拉伸使能时传感器在测量期间会把 SCL 拉低主机需要等待禁用时主机发完命令就可以去干别的过一会儿再来读。HAL 库的阻塞式 I2C 函数对时钟拉伸的支持取决于具体实现我一般直接用时钟拉伸禁用模式命令是 0x2C06后面跟两个字节的 CRC 校验。/* 单次测量高重复性时钟拉伸禁用 */ static const uint8_t SHT30_CMD_MEASURE_HIGH[] {0x2C, 0x06}; HAL_StatusTypeDef SHT30_SingleMeasure(float *temp, float *humi) { uint8_t buf[6]; HAL_StatusTypeDef ret; /* 发送测量命令 */ ret HAL_I2C_Master_Transmit(hi2c1, SHT30_I2C_ADDR_8BIT, (uint8_t *)SHT30_CMD_MEASURE_HIGH, 2, 100); if (ret ! HAL_OK) return ret; /* 高重复性测量最大耗时15ms这里等20ms留余量 */ HAL_Delay(20); /* 读6字节温度高、温度低、温度CRC、湿度高、湿度低、湿度CRC */ ret HAL_I2C_Master_Receive(hi2c1, SHT30_I2C_ADDR_8BIT | 0x01, buf, 6, 100); if (ret ! HAL_OK) return ret; /* 后续CRC校验和换算 */ return SHT30_DataConvert(buf, temp, humi); }逻辑说明先发 2 字节命令然后延时等待测量完成再读 6 字节数据。参数说明HAL_I2C_Master_Transmit的超时我设了 100ms实际 I2C 速率 100kHz 时传 2 字节不到 1ms这个值足够。HAL_Delay(20)是关键高重复性模式下测量时间最大 15ms低重复性最大 4ms中重复性最大 6ms。如果你用低重复性延时可以缩短到 5ms但精度会差一些。读的时候地址要或上 0x01表示读操作。2.3 周期测量模式与数据就绪标志如果你需要连续采集单次测量模式效率太低每次都要发命令、等、再读。SHT30 支持周期测量模式发一次命令后传感器会按设定频率自动测量数据放在内部寄存器里主机随时可以读最新值。周期测量命令的高字节是 0x20低字节决定频率和重复性比如 0x2C 是每秒 10 次、高重复性。/* 周期测量每秒10次高重复性 */ static const uint8_t SHT30_CMD_PERIODIC_10HZ[] {0x20, 0x2C}; /* 读取周期测量数据命令0xE000 */ static const uint8_t SHT30_CMD_FETCH_DATA[] {0xE0, 0x00}; HAL_StatusTypeDef SHT30_PeriodicStart(void) { return HAL_I2C_Master_Transmit(hi2c1, SHT30_I2C_ADDR_8BIT, (uint8_t *)SHT30_CMD_PERIODIC_10HZ, 2, 100); } HAL_StatusTypeDef SHT30_FetchData(float *temp, float *humi) { uint8_t buf[6]; HAL_StatusTypeDef ret; ret HAL_I2C_Master_Transmit(hi2c1, SHT30_I2C_ADDR_8BIT, (uint8_t *)SHT30_CMD_FETCH_DATA, 2, 100); if (ret ! HAL_OK) return ret; ret HAL_I2C_Master_Receive(hi2c1, SHT30_I2C_ADDR_8BIT | 0x01, buf, 6, 100); if (ret ! HAL_OK) return ret; return SHT30_DataConvert(buf, temp, humi); }逻辑说明周期测量启动后传感器按 10Hz 更新数据主机用 0xE000 命令去取。参数说明周期测量模式下不需要每次延时但取数据前最好确认数据已经更新过至少一次否则可能读到上电后的默认值。常见做法是启动周期测量后先延时 100ms 再取第一次。另外周期测量模式下传感器功耗会比单次测量高电池供电的设备要权衡。3. 数据换算与 CRC 校验把原始字节变成可信的温湿度3.1 温度湿度的线性换算公式SHT30 读回来的 6 个字节里前 3 个是温度后 3 个是湿度每个量都是 16 位数据加 8 位 CRC。温度换算公式是 -45 175 × (raw / 65535)湿度是 100 × (raw / 65535)。注意 raw 是 16 位无符号数高字节在前。static HAL_StatusTypeDef SHT30_DataConvert(uint8_t *buf, float *temp, float *humi) { uint16_t raw_temp, raw_humi; /* 先校验CRC校验不过直接返回错误 */ if (SHT30_CheckCRC(buf, 2) ! HAL_OK) return HAL_ERROR; if (SHT30_CheckCRC(buf 3, 2) ! HAL_OK) return HAL_ERROR; raw_temp (uint16_t)(buf[0] 8 | buf[1]); raw_humi (uint16_t)(buf[3] 8 | buf[4]); *temp -45.0f 175.0f * ((float)raw_temp / 65535.0f); *humi 100.0f * ((float)raw_humi / 65535.0f); return HAL_OK; }逻辑说明先做 CRC 校验两个数据各校验一次任何一个不过都返回错误避免把错误数据当成有效值。参数说明buf是 6 字节数组buf[0]和buf[1]是温度高字节和低字节buf[2]是温度 CRCbuf[3]和buf[4]是湿度buf[5]是湿度 CRC。换算时用浮点运算STM32F103 没有硬件浮点单元但这点计算量对 72MHz 的 M3 来说不算什么实测一次换算不到 10 微秒。3.2 CRC-8 校验的查表与逐位实现SHT30 用的 CRC 是 CRC-8多项式 0x31初始值 0xFF不反转不异或输出。这个 CRC 和常见的 CRC-8/MAXIM 不一样不能随便找个库来用。我一般用逐位实现代码量小也好移植。static uint8_t SHT30_CRC8(uint8_t *data, uint8_t len) { uint8_t crc 0xFF; uint8_t i, bit; for (i 0; i len; i) { crc ^ data[i]; for (bit 0; bit 8; bit) { if (crc 0x80) { crc (crc 1) ^ 0x31; } else { crc 1; } } } return crc; } static HAL_StatusTypeDef SHT30_CheckCRC(uint8_t *data, uint8_t len) { uint8_t crc SHT30_CRC8(data, len); /* data[len]就是CRC字节 */ if (crc ! data[len]) { return HAL_ERROR; } return HAL_OK; }逻辑说明逐位计算每次处理一个字节先异或进 crc然后循环 8 次最高位为 1 就左移后异或多项式 0x31否则只左移。参数说明data是数据指针len是数据长度SHT30 里温度和湿度各 2 字节所以 len 传 2。SHT30_CheckCRC里data[len]就是紧跟在数据后面的 CRC 字节。这个实现和 SHT30 数据手册里的示例代码一致实测校验通过率 100%。3.3 浮点与定点换算的取舍STM32F103 没有 FPU浮点运算是软件模拟虽然一次换算耗时不长但如果你在中断里频繁调用累积起来也会占 CPU。我一般会在换算时用定点数比如把温度放大 100 倍存成 int16_t湿度同样处理显示或上传时再转成浮点。/* 定点换算温度放大100倍湿度放大100倍 */ static void SHT30_DataConvertFixed(uint8_t *buf, int16_t *temp_x100, uint16_t *humi_x100) { uint16_t raw_temp (uint16_t)(buf[0] 8 | buf[1]); uint16_t raw_humi (uint16_t)(buf[3] 8 | buf[4]); /* -45 175 * raw / 65535放大100倍后取整 */ *temp_x100 (int16_t)(-4500 (int32_t)17500 * raw_temp / 65535); *humi_x100 (uint16_t)((int32_t)10000 * raw_humi / 65535); }逻辑说明用 int32_t 做中间运算避免溢出最后转成 int16_t 和 uint16_t。参数说明温度范围 -45 到 130 度放大 100 倍后是 -4500 到 13000int16_t 范围 -32768 到 32767够用。湿度 0 到 100%放大 100 倍是 0 到 10000uint16_t 够用。这个版本比浮点版快 3 到 5 倍适合在 1ms 定时中断里跑。4. 避坑与排查SHT30 配 HAL 库最容易翻车的五个地方4.1 现象读回来的温湿度全是 0 或 0xFFFF原因I2C 地址写错或者上拉电阻没接。SHT30 的 SDA 和 SCL 都需要 4.7k 到 10k 的上拉有些模块自带有些没有。如果地址写成 7 位而不是 8 位HAL 库会发出错误的地址传感器不应答读回来的缓冲区保持初始值。解决先用逻辑分析仪或示波器看 SDA 和 SCL 上有没有波形地址字节是不是 0x88 或 0x8A。如果没有波形检查 I2C 外设时钟和 GPIO 复用配置如果有波形但地址不对检查地址定义。我一般会在初始化后调用HAL_I2C_IsDeviceReady确认传感器在线。4.2 现象温度正常湿度偶尔跳到 90% 以上原因读数据太快读到了上一次的旧值或者传感器还没完成测量。单次测量模式下发完命令必须等足够时间高重复性要等 15ms中重复性 6ms低重复性 4ms。如果你只延时 5ms 就去读高重复性模式下读到的可能是上一次的数据。解决把延时改成 20ms 留足余量或者改用周期测量模式让传感器自己按节奏更新。另外读回来的数据一定要做 CRC 校验校验不过就丢弃重读不要直接把错误数据换算后输出。4.3 现象CRC 校验一直不过原因CRC 算法用错或者数据字节顺序搞反。SHT30 的 CRC 多项式是 0x31初始值 0xFF不是常见的 0x07 或 0x31 反转。另外温度和湿度的 CRC 是分别计算的不是一起算。解决用数据手册里的示例数据验证你的 CRC 函数。比如温度原始值 0x61 0x9CCRC 应该是 0x5E。如果你算出来不是这个值检查多项式、初始值和字节顺序。我一般会在调试时把原始字节和算出来的 CRC 通过串口打印出来和手册对照。4.4 现象周期测量模式下数据不更新原因启动周期测量后没有等待第一次测量完成就去取数据或者取数据命令发错了。周期测量启动后传感器需要至少一个测量周期才会更新数据寄存器10Hz 模式下就是 100ms。如果你启动后立刻取读到的可能是上电默认值。解决启动周期测量后先延时 100ms 到 200ms再开始取数据。取数据命令是 0xE000不是 0xE0 单字节。另外周期测量模式下如果主机长时间不取数据传感器会继续测量但数据寄存器只保留最新值不会溢出。4.5 现象I2C 总线死锁HAL 库返回 HAL_BUSY原因SHT30 在测量期间如果主机复位或异常可能把 SDA 拉低不放导致总线死锁。HAL 库的 I2C 函数在检测到 BUSY 标志时会一直等超时后返回 HAL_BUSY但总线可能还是死的。解决在 I2C 初始化时加上总线恢复逻辑如果检测到 SDA 被拉低就手动发送 9 个时钟脉冲让从机释放总线。另外HAL 库的 I2C 超时参数不要设太大100ms 足够设太大反而会在死锁时卡住整个系统。我一般会在HAL_I2C_Init之后加一段 GPIO 模拟时序的恢复代码确保总线干净。5. 进阶技巧用 DMA 加空闲中断把 SHT30 的读取彻底非阻塞化前面讲的都是阻塞式读取CPU 要等 I2C 传输完成。如果你的系统里 I2C 总线上挂了多个传感器或者主循环任务很重阻塞式读取会拖慢整体响应。我后来在一个低功耗项目里把 SHT30 的读取改成了 DMA 加空闲中断的方式CPU 占用几乎降到零。具体做法是用HAL_I2C_Master_Transmit_DMA发测量命令发完后启动一个定时器延时 20ms 后触发 DMA 接收。接收完成回调里做 CRC 校验和换算然后通过消息队列把结果发给主任务。这样主循环完全不用等 I2C可以去做别的事。/* DMA发送测量命令 */ HAL_I2C_Master_Transmit_DMA(hi2c1, SHT30_I2C_ADDR_8BIT, (uint8_t *)SHT30_CMD_MEASURE_HIGH, 2); /* 在发送完成回调里启动定时器20ms后触发接收 */ void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) { if (hi2c hi2c1) { __HAL_TIM_SET_COUNTER(htim6, 0); HAL_TIM_Base_Start_IT(htim6); } } /* 定时器中断里启动DMA接收 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim htim6) { HAL_TIM_Base_Stop_IT(htim6); HAL_I2C_Master_Receive_DMA(hi2c1, SHT30_I2C_ADDR_8BIT | 0x01, sht30_buf, 6); } } /* 接收完成回调里做校验和换算 */ void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) { if (hi2c hi2c1) { float temp, humi; if (SHT30_DataConvert(sht30_buf, temp, humi) HAL_OK) { /* 把结果放入队列通知主任务 */ SHT30_ResultQueue_Push(temp, humi); } } }逻辑说明发送用 DMA发送完成回调里启动定时器定时器中断里启动接收 DMA接收完成回调里处理数据。整个过程 CPU 只在回调里执行几个指令其余时间都在休眠或处理其他任务。参数说明定时器 6 我配的是 1ms 周期中断里先停定时器再启动接收避免重复触发。DMA 接收缓冲区sht30_buf要定义成全局或静态不能在回调里被释放。消息队列用 FreeRTOS 的xQueueSendFromISR注意中断优先级要配成 FreeRTOS 可管理的中断优先级。这个方案我在一个电池供电的温湿度记录仪上跑过主控也是 STM32F103ZET6SHT30 每秒采集一次整机平均电流从阻塞式的 12mA 降到了 3mA 左右效果很明显。不过 DMA 方式也有代价代码复杂度上来了调试时如果 I2C 出错错误回调里要记得重启 DMA 和定时器否则整个采集链路就停了。从那以后我每次用 DMA 做 I2C 读取都会在错误回调里加一个状态机复位确保异常后能自动恢复。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询