STM8S驱动SHTC3温湿度传感器:I2C模拟与避坑指南
发布时间:2026/10/15 16:15:47 锦皓数字建站

简介这份资源面向嵌入式开发初学者与物联网方向工程师围绕STM8微控制器驱动SHTC3温湿度传感器展开解决I2C通信、定时采集与UART数据上传的完整实现问题。包内共156个文件以36个h头文件、34个c源文件为核心配合27个xcl、23个o及18个pbi等工程与编译中间文件另有hex固件、ewp/eww工程配置和bat脚本压缩包约5.18MB覆盖STM8L15x系列外设驱动库与工程结构。资源重点呈现每秒采集一次温湿度并通过串口发送的完整流程涉及I2C初始化与数据解析、定时器溢出触发、UART波特率与数据位配置以及ST-LINK调试与寄存器观察等排错思路。已有1718人学习下载适合希望掌握微控制器与传感器接口、通信协议及时间同步的开发者参考复用。1. 从一份 stm8SHTC3.zip 说起为什么还有人死磕 8 位机跑温湿度前阵子帮一个做小家电控制板的朋友看代码他丢过来一个压缩包名字就叫 stm8SHTC3.zip。打开一看里面是 STM8S 系列单片机驱动 SHTC3 温湿度传感器的完整工程IAR 和 STVD 两套工程文件都在源码、头文件、延时函数、I2C 模拟时序一应俱全。他遇到的问题很典型传感器读出来的温湿度偶尔跳变有时候直接卡死在等待 ACK 的循环里。这不是个例STM8 配 SHTC3 这个组合在低成本温湿度采集场景里非常常见小到加湿器、除湿机大到冷链记录仪、农业大棚节点都能见到它的影子。SHTC3 是 Sensirion 出的数字温湿度传感器I2C 接口典型精度 ±2%RH 和 ±0.2℃价格便宜供货稳定。STM8S 则是 ST 家的 8 位机主频 16MHz资源紧巴巴但胜在便宜、皮实、外设够用。两者凑一起成本能压到很低适合量产型产品。这份资源能解决的核心问题就一个让你不用从零啃数据手册直接拿到一套能跑的驱动框架把温湿度读出来。适合谁适合手头有 STM8 平台、需要快速验证 SHTC3 的嵌入式工程师也适合刚接触 I2C 时序、想找个真实项目练手的新手。但别指望它是个开箱即用的库里面有些坑我后面会一个个拆。2. SHTC3 的 I2C 时序与 STM8 的适配从数据手册到能跑的代码2.1 先搞清楚 SHTC3 的通信协议到底特殊在哪SHTC3 用的是标准 I2C 协议但有几个地方和常见传感器不一样不注意就会翻车。第一它的从机地址是 0x707 位地址写操作时左移一位变成 0xE0读操作是 0xE1。很多新手直接拿 0x70 去发结果自然没反应。第二SHTC3 的命令格式是 16 位比如测量命令 0x7CA2 是「时钟拉伸使能」模式0x7866 是「时钟拉伸禁用」模式。这两个命令的区别很关键使能时钟拉伸时传感器在测量期间会拉低 SCL 线主机必须等待禁用时主机需要自己延时等待测量完成。STM8 的硬件 I2C 外设不算好用很多人直接用 GPIO 模拟这时候时钟拉伸处理不好就会卡死。第三SHTC3 上电后需要至少 1ms 的稳定时间然后发送唤醒命令 0x3517否则它一直在睡眠状态你发什么它都不理。我见过太多人栽在唤醒这一步代码里翻来覆去找不到问题其实就是少了一句唤醒。2.2 用 GPIO 模拟 I2C 的完整实现STM8S 的硬件 I2C 在有些型号上存在已知的锁死问题所以这份资源里用的是 GPIO 模拟。下面这段代码是核心的 I2C 起始、停止和字节收发函数我加了详细注释。// 定义 I2C 引脚根据实际板子修改 #define SHTC3_SCL_PORT GPIOC #define SHTC3_SCL_PIN GPIO_PIN_5 #define SHTC3_SDA_PORT GPIOC #define SHTC3_SDA_PIN GPIO_PIN_6 // SCL 和 SDA 方向切换宏 #define SCL_OUT() {GPIO_Init(SHTC3_SCL_PORT, SHTC3_SCL_PIN, GPIO_MODE_OUT_PP_HIGH_FAST);} #define SCL_IN() {GPIO_Init(SHTC3_SCL_PORT, SHTC3_SCL_PIN, GPIO_MODE_IN_PU_NO_IT);} #define SDA_OUT() {GPIO_Init(SHTC3_SDA_PORT, SHTC3_SDA_PIN, GPIO_MODE_OUT_PP_HIGH_FAST);} #define SDA_IN() {GPIO_Init(SHTC3_SDA_PORT, SHTC3_SDA_PIN, GPIO_MODE_IN_PU_NO_IT);} #define SCL_H() GPIO_WriteHigh(SHTC3_SCL_PORT, SHTC3_SCL_PIN) #define SCL_L() GPIO_WriteLow(SHTC3_SCL_PORT, SHTC3_SCL_PIN) #define SDA_H() GPIO_WriteHigh(SHTC3_SDA_PORT, SHTC3_SDA_PIN) #define SDA_L() GPIO_WriteLow(SHTC3_SDA_PORT, SHTC3_SDA_PIN) #define SDA_READ() GPIO_ReadInputPin(SHTC3_SDA_PORT, SHTC3_SDA_PIN) // 微秒级延时STM8S 16MHz 下约 1us void i2c_delay(void) { asm(nop); asm(nop); asm(nop); asm(nop); } // 起始条件SCL 高时 SDA 由高变低 void i2c_start(void) { SDA_OUT(); SDA_H(); SCL_H(); i2c_delay(); SDA_L(); i2c_delay(); SCL_L(); i2c_delay(); } // 停止条件SCL 高时 SDA 由低变高 void i2c_stop(void) { SDA_OUT(); SDA_L(); SCL_H(); i2c_delay(); SDA_H(); i2c_delay(); } // 发送一个字节返回从机 ACK 状态0 表示应答1 表示非应答 uint8_t i2c_write_byte(uint8_t data) { uint8_t i, ack; SDA_OUT(); for (i 0; i 8; i) { if (data 0x80) SDA_H(); else SDA_L(); data 1; i2c_delay(); SCL_H(); i2c_delay(); SCL_L(); i2c_delay(); } SDA_IN(); // 释放 SDA准备读取 ACK i2c_delay(); SCL_H(); i2c_delay(); ack SDA_READ() ? 1 : 0; SCL_L(); i2c_delay(); SDA_OUT(); return ack; } // 读取一个字节ack0 发送应答ack1 发送非应答 uint8_t i2c_read_byte(uint8_t ack) { uint8_t i, data 0; SDA_IN(); for (i 0; i 8; i) { data 1; SCL_H(); i2c_delay(); if (SDA_READ()) data | 0x01; SCL_L(); i2c_delay(); } SDA_OUT(); if (ack) SDA_H(); else SDA_L(); i2c_delay(); SCL_H(); i2c_delay(); SCL_L(); i2c_delay(); return data; }这段代码的逻辑很直白起始条件拉低 SDA 再拉低 SCL停止条件反过来。写字节时高位先出每个 bit 在 SCL 高电平期间保持稳定。读字节时释放 SDA在 SCL 高电平期间采样。关键参数是i2c_delay()里的空指令数量STM8S 在 16MHz 下一条 nop 约 62.5ns四个 nop 约 250ns对应 I2C 的 100kHz 到 400kHz 之间的速率。如果你超频到 16MHz 以上需要增加 nop 数量如果降频到 8MHz可以减少。这个延时不够精确没关系SHTC3 对时序容忍度还可以但别差太多否则 ACK 会采错。2.3 SHTC3 的唤醒、测量与数据转换有了 I2C 底层接下来就是 SHTC3 的专用命令。下面这段是完整的读取流程。#define SHTC3_ADDR_W 0xE0 #define SHTC3_ADDR_R 0xE1 #define SHTC3_WAKEUP 0x3517 #define SHTC3_SLEEP 0xB098 #define SHTC3_MEAS_T_FIRST_STRETCH 0x7CA2 // 温度在前时钟拉伸使能 #define SHTC3_MEAS_RH_FIRST_STRETCH 0x5C24 // 湿度在前时钟拉伸使能 // 发送 16 位命令 static uint8_t shtc3_send_cmd(uint16_t cmd) { i2c_start(); if (i2c_write_byte(SHTC3_ADDR_W)) { i2c_stop(); return 1; } if (i2c_write_byte(cmd 8)) { i2c_stop(); return 1; } if (i2c_write_byte(cmd 0xFF)) { i2c_stop(); return 1; } i2c_stop(); return 0; } // 唤醒传感器上电后必须调用 uint8_t shtc3_wakeup(void) { uint8_t ret shtc3_send_cmd(SHTC3_WAKEUP); if (ret) return ret; delay_ms(1); // 唤醒后需要至少 1ms 稳定 return 0; } // 读取温湿度temp 和 humi 为输出单位分别是 0.01℃ 和 0.01%RH uint8_t shtc3_read(float *temp, float *humi) { uint8_t buf[6]; uint16_t raw_t, raw_h; uint8_t crc_t, crc_h; uint8_t i; if (shtc3_send_cmd(SHTC3_MEAS_T_FIRST_STRETCH)) return 1; delay_ms(15); // 时钟拉伸模式下等待测量完成典型 12.1ms i2c_start(); if (i2c_write_byte(SHTC3_ADDR_R)) { i2c_stop(); return 2; } for (i 0; i 6; i) { buf[i] i2c_read_byte(i 5 ? 1 : 0); } i2c_stop(); raw_t (buf[0] 8) | buf[1]; crc_t buf[2]; raw_h (buf[3] 8) | buf[4]; crc_h buf[5]; // 这里省略 CRC 校验实际产品建议加上 *temp -45.0f 175.0f * raw_t / 65535.0f; *humi 100.0f * raw_h / 65535.0f; return 0; }逻辑说明先发测量命令然后等待测量完成。这里用的是时钟拉伸使能模式理论上传感器会拉低 SCL 告诉主机「我还没好」但 STM8 的 GPIO 模拟 I2C 没有处理这个信号所以直接用delay_ms(15)硬等。这是这份资源里一个偷懒但能跑的做法。参数上温度转换公式是-45 175 * raw / 65535湿度是100 * raw / 65535这是 SHTC3 数据手册里的标准公式。CRC 校验被省略了量产产品建议补上否则遇到干扰数据会出错。测量命令 0x7CA2 是温度在前如果你需要湿度在前改成 0x5C24。3. 工程结构与移植要点IAR 和 STVD 双工程怎么选3.1 压缩包里的文件到底怎么组织解压 stm8SHTC3.zip 后目录结构大致是这样的根目录下有两个文件夹一个是IAR一个是STVD分别对应两套开发环境。每个文件夹里都有User目录放main.c、shtc3.c、shtc3.h还有Driver目录放 STM8 的标准外设库文件比如stm8s_gpio.c、stm8s_clk.c。另外有一个Delay目录里面是delay.c和delay.h用 TIM4 做毫秒延时。这种结构很典型把用户代码和库代码分开移植的时候只需要动User和Delay里的引脚定义。我一般会先看main.c里的初始化流程确认时钟配置和 GPIO 初始化有没有问题。常见的情况是别人给的工程用的是内部 16MHz RC 时钟而你的板子用的是外部晶振这时候CLK_Init()里的配置要改否则延时全乱I2C 时序也跟着崩。3.2 移植到自己的板子上要改哪几个地方移植步骤不复杂但每一步都要确认。第一步改引脚定义。在shtc3.h里找到SHTC3_SCL_PORT、SHTC3_SCL_PIN、SHTC3_SDA_PORT、SHTC3_SDA_PIN这四个宏改成你实际连接的 GPIO。注意 STM8S 的 GPIO 模式要设成开漏输出还是推挽输出这份资源用的是推挽输出因为 SHTC3 的 SDA 线上没有外部上拉靠推挽驱动。如果你的板子上有 4.7k 上拉电阻可以改成开漏输出更符合 I2C 规范。第二步改时钟配置。在main.c里找到CLK_Config()函数确认系统时钟是 16MHz。如果不是要同步修改delay.c里的延时参数和i2c_delay()里的 nop 数量。第三步确认延时函数可用。delay_ms()依赖 TIM4 中断要确保中断向量表里 TIM4 的中断处理函数指向正确。第四步编译下载用串口打印温湿度值观察是否稳定。如果读数跳变严重先检查电源是否干净SHTC3 对电源纹波比较敏感建议在 VDD 和 GND 之间加一个 100nF 电容。3.3 IAR 和 STVD 的工程配置差异IAR 的工程文件是.eww和.ewpSTVD 的是.stw和.stp。两者最大的区别在链接文件和启动代码。IAR 用的是lnkstm8s103f3.icf之类的链接脚本STVD 用的是.lkf文件。如果你换芯片型号比如从 STM8S103F3 换到 STM8S003F3Flash 和 RAM 大小不一样链接文件要跟着换。另外IAR 的优化等级默认是 High有时候会把i2c_delay()里的空指令优化掉导致延时失效。我一般会把i2c_delay()所在的文件优化等级设为 Low或者给函数加__no_init之类的修饰。STVD 用的是 Cosmic 编译器优化选项在工程设置里默认不优化反而省心。还有一点STVD 的库函数版本可能和 IAR 不一样如果编译报错说找不到某个寄存器定义检查一下stm8s.h里的芯片型号宏有没有打开。4. 避坑与排查SHTC3 读不出数据的五个血泪经验4.1 现象上电后第一次读总是失败后面就正常原因SHTC3 上电后需要至少 1ms 的稳定时间而且必须发送唤醒命令。很多代码在初始化时直接发测量命令传感器还在睡眠状态自然不响应。解决在main()里初始化完 GPIO 后先调用shtc3_wakeup()然后延时 1ms 再开始测量。如果还不行把延时加到 5ms有些批次的传感器需要更长时间。4.2 现象读出的温度值固定不变或者一直是 0原因I2C 读时序有问题最常见的是 ACK 位处理错误。在i2c_read_byte()里读完 8 个 bit 后主机要发送 ACK 或 NACK。如果最后一个字节发送了 ACK传感器会继续输出下一个字节导致数据错位。解决确认最后一个字节用 NACK也就是i2c_read_byte(1)。另外检查SDA_IN()和SDA_OUT()的切换时机读之前一定要释放 SDA否则总线被主机拉死传感器无法输出数据。4.3 现象偶尔卡死在while等待 ACK 的循环里原因STM8 的 GPIO 模拟 I2C 没有超时机制如果传感器没响应i2c_write_byte()会一直等 ACK。常见触发条件是电源波动或者总线干扰。解决在i2c_write_byte()里加超时计数比如循环 1000 次还没读到 ACK 就返回错误。另外在i2c_start()之前检查 SDA 和 SCL 是否都是高电平如果不是说明总线被拉死需要发送 9 个时钟脉冲解锁。4.4 现象温湿度值跳变很大比如温度突然跳到 80℃原因CRC 校验没做干扰数据被当成有效值。SHTC3 的输出数据里带了 CRC 校验字节但这份资源里省略了校验。解决补上 CRC-8 校验多项式是x^8 x^5 x^4 1初始值 0xFF。校验不通过就丢弃这次数据重新测量。另外电源纹波也会导致读数跳变在 VDD 引脚附近加 100nF 和 1uF 电容。4.5 现象移植到新板子后I2C 完全没反应原因引脚定义改了但 GPIO 初始化模式不对。STM8S 的 GPIO 有输入、输出、上拉、开漏等多种模式如果设成浮空输入SDA 和 SCL 都没有驱动能力。解决确认SCL_OUT()和SDA_OUT()宏里用的是GPIO_MODE_OUT_PP_HIGH_FAST或GPIO_MODE_OUT_OD_HIGH_FAST。如果板子上有上拉电阻用开漏没有就用推挽。另外检查CLK_Config()里有没有使能对应 GPIO 的时钟STM8S 的外设时钟默认是关闭的不开时钟写寄存器无效。5. 进阶技巧用 CRC 校验和低功耗模式把 SHTC3 用到量产级别5.1 补上 CRC-8 校验让数据可信前面提到这份资源省略了 CRC 校验量产产品不能这么干。SHTC3 的每个测量结果后面跟一个 CRC 字节校验算法是 CRC-8多项式 0x31初始值 0xFF。下面是一个查表法的实现速度快适合 STM8 这种资源紧张的芯片。// CRC-8 查表多项式 0x31初始值 0xFF static const uint8_t crc8_table[256] { 0x00, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, 0xB9, 0x88, 0xDB, 0xEA, 0x7D, 0x4C, 0x1F, 0x2E, // ... 省略中间数据完整表在工程里 }; uint8_t shtc3_crc8(const uint8_t *data, uint8_t len) { uint8_t crc 0xFF; while (len--) { crc crc8_table[crc ^ *data]; } return crc; } // 在 shtc3_read 里加上校验 if (shtc3_crc8(buf[0], 2) ! buf[2]) return 3; // 温度 CRC 错误 if (shtc3_crc8(buf[3], 2) ! buf[5]) return 4; // 湿度 CRC 错误逻辑说明CRC 计算覆盖两个数据字节结果和第三个字节比较。如果不等说明数据传输过程中出错了直接返回错误码上层重新测量。参数上多项式 0x31 是 SHTC3 数据手册规定的初始值 0xFF 也是固定的。查表法比逐位计算快很多但占用 256 字节 FlashSTM8S003F3 只有 8KB Flash要权衡一下。如果 Flash 紧张可以用逐位计算代码量小但速度慢。5.2 低功耗模式让电池供电的节点跑得更久SHTC3 本身支持睡眠模式测量完发0xB098命令就能让它进入睡眠电流降到 0.1uA 级别。STM8S 也有低功耗模式比如 Halt 模式电流可以降到几个 uA。两者结合电池供电的温湿度节点可以跑好几年。具体做法是测量完温湿度后发睡眠命令让 SHTC3 睡觉然后 STM8S 进入 Halt 模式用 TIM4 或者外部中断定时唤醒。唤醒后先唤醒 SHTC3延时 1ms再测量。注意 Halt 模式下 I2C 引脚的状态要保持否则漏电流会增大。我一般会把 SCL 和 SDA 设成推挽输出高电平或者开漏输出加上拉具体看板子设计。5.3 一个验证技巧用串口打印原始数据和转换结果调试的时候别只看转换后的温湿度值把原始数据也打印出来。比如raw_t 0x8000对应温度是-45 175 * 32768 / 65535 ≈ 42.5℃。如果原始数据一直在变但转换结果不变说明转换公式写错了。如果原始数据不变说明 I2C 读取有问题。我习惯在shtc3_read()里加一句printf(raw_t%04X raw_h%04X\r\n, raw_t, raw_h)用串口助手看。STM8S 的串口配置简单波特率 9600 或 115200 都行。注意printf会占用不少 Flash调试完记得关掉。从那以后我每次拿到新的传感器驱动都强制走一遍「唤醒-测量-读原始数据-校验-转换」的完整流程不跳过任何一步。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。