2026/9/14 5:54:39

STC8A单片机驱动SHT20温湿度传感器:I2C时序与串口显示完整教程

STC8A单片机驱动SHT20温湿度传感器:I2C时序与串口显示完整教程 简介这套基于STC8A单片机的SHT20温湿度传感器实验例程源码面向单片机初学者和嵌入式开发人员以环境温湿度采集与串口助手实时显示为主线演示从传感器读取、数据处理到串口输出的完整流程。资源压缩包共19个文件以C语言源码和Keil工程文件为主体包含5个.c源文件、5个.h头文件、Keil工程配置、编译生成的HEX固件、调试映射文件以及SHT20官方资料手册内容完整整体大小仅1.06MB。源码按功能拆分出初始化、I²C通信、SHT20驱动、UART串口收发和主循环等模块可帮助读者学习传感器时序、C51语法及串口波特率配置配合工程可直接编译运行便于快速搭建环境监测系统。已有141人学习通过该例程可快速上手STC8A外设配置并进一步扩展到其他I²C传感器或更多环境监测应用。1. 不用DHT11、在STC8A上跑SHT20是我认为最稳的温湿度采集组合做环境监测或者物料干燥项目时DHT11虽然便宜但湿度滞回明显、校准缺失连续运行几个月后数据漂移会让你怀疑人生。STC8A是宏晶科技增强型8051单片机1T指令周期内置EEPROM和高精度内部振荡器售价低很多小体积产品都在用。SHT20是Sensirion出品的数字温湿度传感器I²C接口、出厂校准、长期稳定性好和DHT11完全不是一个级别。这个例程源码把STC8A软件模拟I²C读取SHT20再通过串口助手显示温湿度的完整链路做了出来。如果你正在做单片机课程设计、低成本环境监控终端或者想替换掉旧项目的DHT11这份代码可以直接改来用关键是能搞清楚I²C时序和串口调试的关系。2. STC8A与SHT20的I²C硬件接线与时序设计2.1 SHT20寄存器、测量命令与数据换算SHT20的I²C从机地址是0x40写地址为0x80读地址为0x81。触发温度测量命令为0xE3湿度测量命令为0xE5软复位命令为0xFE。测量时主控要等待转换完成温度典型转换时间7ms湿度典型转换时间15ms实际例程里会用延时或等待触发引脚拉低。SHT20原始数据是16位无符号数温度换算公式为T -46.85 175.72 * raw / 65536湿度换算公式为RH -6 125 * raw / 65536。需要注意湿度换算结果可能略微超出0到100%RH需要在代码中做钳位。功能命令字说明温度测量0xE3主模式时钟延展湿度测量0xE5主模式时钟延展读状态寄存器0xE7可检查分辨率、看门狗、校准状态软复位0xFE恢复寄存器默认值2.2 STC8A的I/O配置与软件I²C实现STC8A部分型号有硬件I²C但大多数例程使用普通I/O口模拟I²C因为引脚分配更自由移植起来也更直接。源码中默认将SCL接到P1.1SDA接到P1.0。STC8A的P1口上电后是高阻输入必须先将端口设为准双向口或开漏输出。设置方式如下。// STC8.H 头文件中定义了P1M0、P1M1等寄存器 void sht20_gpio_init(void) { P1M0 0x00; // P1.0、P1.1配置为准双向口 P1M1 0x00; P_SW2 | 0x80; // 开启扩展寄存器访问某些STC8A型号需要 P1 0xFF; // SCL、SDA释放为高电平 }准双向口模式下I/O内部有上拉电阻可以直接驱动SHT20的信号线。如果你把SDA配置成开漏输出还需要外接4.7kΩ上拉电阻。I²C的启动、停止和读写时序例程的i2c.c中用短延时模拟。以下是常见的软件I²C启动和停止函数。sbit IIC_SCL P1^1; sbit IIC_SDA P1^0; void iic_start(void) { IIC_SDA 1; IIC_SCL 1; delay_us(5); IIC_SDA 0; delay_us(5); IIC_SCL 0; } void iic_stop(void) { IIC_SDA 0; IIC_SCL 1; IIC_SDA 1; delay_us(5); IIC_SCL 0; }iic_start先将SCL拉高再将SDA拉低保证在SCL高电平期间SDA出现下降沿这是一个合法的启动条件。iic_stop则在SCL高电平期间让SDA上升沿表示总线释放。模拟I²C的时序核心是保证SCL高电平期间SDA状态稳定所以每步之间都要有微小延时延时长短由单片机的系统时钟决定。2.3 SHT20时序细节与常见问题SHT20在工作时主控发出测量命令后传感器需要一段时间转换。期间从机可以把SDA拉低来表示忙碌这就是时钟延展。软件模拟I²C时如果直接忽略忙信号可能读到不完整的数据。例程中采用了固定延时等待的方式。这里有一个常见的坑STC8A在11.0592MHz晶振下delay_us(5)延时函数的循环次数和12MHz下差不多但若用内部IRC时钟误差可能达到5%以上最好用逻辑分析仪确认SCL频率在100kHz到400kHz之间。如果I²C通信偶尔出错先检查SDA引脚是否配置为双向口了其次检查传感器供电电压是否稳定在3.3VSHT20超过5V会损坏。3. C51驱动源码拆分从SHT2X.c到main.c的模块化实践3.1 压缩包工程结构与Keil编译链路下载解压后工程文件包括SHT20.uvproj、SHT2X.c、SHT2X.h、i2c.c、i2c.h、uart.c、uart.h、delay.c、delay.h、main.c、STC8.H和已编译好的SHT20.hex。用Keil uVision打开SHT20.uvproj就可以直接编译。建议先编译看一遍再打开SHT20 ReadMe.txt确认引脚定义。文件职责关键接口SHT2X.c / SHT2X.hSHT20驱动SHT2X_Init、SHT2X_GetTempHumi2c.c / i2c.h软件I²C时序iic_start、iic_stop、iic_write_byteuart.c / uart.h串口初始化与发送UART1_Init、UART1_SendStringdelay.c / delay.h微秒级延时delay_us、delay_msmain.c主逻辑main()调用上边模块STC8.HSTC8A专属寄存器定义替代Keil自带的reg51.h3.2 SHT2X驱动核心读取温湿度并转换为实际值SHT2X.c中封装了完整的测量流程。我拆开看的时候SHT2X_GetTempHum这个函数把两次测量放在一起返回用指针参数带出温度和湿度值。一个有用的小技巧是在读取数据时先读高字节、再读低字节最后读CRC校验字节。SHT2X在每次测量后都会产生8位CRC丢弃CRC就等于失去了传感器自检能力。例程代码片段如下。unsigned char SHT2X_CheckCrc(unsigned char data[], unsigned char nbrOfBytes) { unsigned char crc 0; unsigned char bit; unsigned char byteCtr; for (byteCtr 0; byteCtr nbrOfBytes; byteCtr) { crc ^ (data[byteCtr]); for (bit 8; bit 0; bit--) { if (crc 0x80) crc (crc 1) ^ 0x31; // 多项式x^8 x^5 x^4 1 else crc (crc 1); } } return crc; }上面这段CRC校验代码在所有SHT2x系列上也通用。校验时按位左移遇到最高位为1就异或多项式0x31。如果你不校验测量数据偶尔出错时很难定位是传感器问题还是I²C时序问题。算完CRC后可以用如下方式换算成实际温湿度。void SHT2X_GetTempHum(float *temp, float *hum) { unsigned int rawTemp 0, rawHum 0; unsigned char temData[2], humData[2]; rawTemp SHT2X_ReadRaw(0xE3); // 读取16位温度原始值 *temp (rawTemp * 175.72 / 65536.0) - 46.85; rawHum SHT2X_ReadRaw(0xE5); // 读取16位湿度原始值 *hum (rawHum * 125.0 / 65536.0) - 6.0; if (*hum 100) *hum 100; if (*hum 0) *hum 0; }主控发送命令后需要等待传感器完成转换。如果使用12位湿度分辨率等待时间建议大于20ms否则连续读取时可能得到前一次测量结果。为了简化例程中直接用delay_ms(30)这也是为什么循环周期看起来不够快的原因。3.3 main.c的主循环与printf重定向main.c中的主循环非常直接先初始化SHT20和UART然后每1秒读一次数据再通过串口发送格式化字符串。SHT20本身支持连续测量但例程使用的是单次测量模式每次读完后传感器会自动进入空闲状态更省电。发送字符串时如果直接用printf记得在uart.c中实现putchar重定向。char putchar(char c) { while (!TI); // 等待发送中断标志置1 TI 0; // 清除发送标志 SBUF c; // 写入串口数据寄存器 return c; } void main(void) { float temp 0, hum 0; SHT2X_Init(); UART1_Init(9600); while (1) { SHT2X_GetTempHum(temp, hum); printf(Temp%.1f C RH%.1f %%\r\n, temp, hum); delay_ms(1000); } }Keil的C51编译器默认不支持浮点printf的长格式必须勾选“Use MicroLIB”或者检查目标选项中的浮点支持。如果没设置好输出会变成乱码或直接卡死。重定向putchar时TI标志在发送完成后由硬件置1这里用查询方式等待也可以改成中断发送但例程是查询方式代码更简单适合教学演示。波特率参数由UART1_Init决定。4. 串口助手显示配置与STC8A串口参数匹配4.1 串口1初始化定时器2作为波特率发生器STC8A的串口1可以工作于方式18位UART波特率可由定时器2或定时器1产生。例程中用的是定时器2因为STC8A定时器2比定时器1多一个16位自动重载模式用起来更省心。下面是初始化9600波特率的核心代码。void UART1_Init(unsigned int baud) { unsigned int reload; reload 65536 - (unsigned long)(FOSC / 4 / baud); // 定时器2工作在波特率发生器模式自动重载 T2L reload 0xFF; T2H (reload 8) 0xFF; AUXR | 0x10; // T2R置1启动定时器2 AUXR | 0x01; // T1x12置1定时器2时钟不分频 SCON 0x50; // 串口方式1REN置1允许接收 TMOD 0x0F; // 保留定时器0配置避免影响其他延时 }上面代码里的FOSC是单片机的系统频率STC8A默认使用内部IRC时钟。若系统频率为11.0592MHz9600波特率下重载值约为0xFFDC。公式来源是:波特率 FOSC / 4 / (65536 - 重载值)。如果是115200计算后要确保误差小于2%。使用STC8A内部24MHz时115200有约-0.8%误差通常能正常工作。关键是把T2R和T1x12这两个位配置正确定时器2时钟选择1T模式。4.2 串口助手软件选型与参数匹配下载本实例后你可以用任意常见的串口调试助手比如SSCOM、XCOM、COMMIX等它们本质上都是打开COM口、设置波特率、显示接收数据。用SSCOM时需要把波特率设为9600数据位8停止位1无校验与STC8A的UART配置保持一致。很多初学者发现串口显示乱码第一反应是传感器坏了其实八成是串口参数或者波特率不匹配。在main.c中发送的字符串以\r\n结尾SSCOM默认显示ASCII字符所以你会看到类似下面的输出Temp25.3 C RH45.0 % Temp25.4 C RH45.2 %为了让显示更清晰也可以在源代码里添加帧头比如[SHT20]但这会多占用几个字节的串口带宽对9600波特率来说不是问题。实际项目中如果通过USB转TTL模块接到PC注意共地否则串口通信不稳定。4.3 浮点格式化输出与串口缓冲区问题C51的printf默认不直接支持%f除非你在Keil工程设置中选中“Use C99”或增加printf的浮点库配置。常见做法是使用专门的格式化函数或者把小数部分拆成整数分别发送。例程里直接使用printf(%.1f)如果你重新创建工程且没有勾选“使用ELF小东西”编译会通过但输出不会带小数点。这时候可以改为手动拆整数与小数。int temp_int (int)temp; int temp_fra (int)(temp * 10) % 10; printf(Temp%d.%d C\r\n, temp_int, temp_fra);%d的格式输出比%f更可靠也更容易在嵌入式环境中控制内存占用。每当串口发送过程中混入杂散字符先检查单片机端的VCC和GND再用示波器看TX引脚是否有正常波形。没有示波器时可以把波特率降到2400再试降低波特率经常能排除信号质量引起的问题。5. 进阶SHT20的CRC-8校验、降低功耗与多路扩展5.1 启用CRC-8校验并丢弃错误数据我在实际工程中不会直接信任传感器返回的每一个字节。SHT20测量后会附带一个8位CRC前述SHT2X_CheckCrc函数可以用于最终数据。把CRC检查逻辑合入读取流程后如果连续多次校验失败可以对传感器做一次软复位然后重新初始化I²C。这样做的好处是系统不会长时间停留在错误数据上。下面给出一个实际可用的带校验的读取示例。unsigned char SHT2X_ReadTempWithCrc(unsigned int *raw) { unsigned char buf[3]; unsigned char crc_expected; iic_start(); iic_write_byte(0x80); // 写地址 iic_write_byte(0xE3); // 温度测量 delay_ms(20); iic_start(); iic_write_byte(0x81); // 读地址 buf[0] iic_read_byte(1); buf[1] iic_read_byte(1); buf[2] iic_read_byte(0); // 最后一个字节后不发ACK crc_expected SHT2X_CheckCrc(buf, 2); iic_stop(); if (crc_expected buf[2]) { *raw (unsigned int)buf[0] 8 | buf[1]; return 1; } return 0; }注意读字节时的ACK在读取最后一个数据字节前主控需要发送NACK告知传感器不再继续读数。有些例程在读完第二个字节后就停了少读CRC这种实现虽然快但从可靠性角度我不建议。5.2 周期性测量与STC8A空闲模式SHT20在每次单次测量后会进入待机状态电流典型值0.3μA这对接电池的系统很重要。STC8A本身也支持IDLE模式把单片机在两次测量间隔中休眠可以显著降低平均功耗。STC8A的IDLE模式用PCON | 0x01进入唤醒后程序从PCON | 0x01后的下一条指令继续执行。通常配合定时器中断唤醒定时器在IDLE模式下不会停止。在主循环中加入以下逻辑即可。void main(void) { float temp, hum; UART1_Init(9600); SHT2X_Init(); Timer0_Init(); while (1) { SHT2X_GetTempHum(temp, hum); printf(Temp%.1f RH%.1f\r\n, temp, hum); PCON | 0x01; // 进入IDLE模式 _nop_(); // 唤醒后先执行这一条 delay_ms(100); } }进入IDLE模式后CPU不再执行指令但I²C和串口外设仍在等待。当你用STC-ISP重新下载程序时需要先断电再上电否则单片机一直在休眠ISP软件无法识别芯片。这也是很多人在低功耗阶段调试卡住的原因。5.3 从单路到多路多组SHT20的扩展方式SHT20的I²C地址是固定的0x40一条总线上不能直接挂两个同地址的SHT20。扩展的办法是用STC8A的另外一组I/O口再软件模拟一路I²C或者使用I²C多路复用芯片例如TCA9548A。如果只是做双室温湿度监控我更推荐直接开两个软件I²C实例在代码里把SCL、SDA的引脚定义通过宏抽象出来。#define IIC1_SCL P1_1 #define IIC1_SDA P1_0 #define IIC2_SCL P2_1 #define IIC2_SDA P2_0然后给每个实例写一套iic_start和iic_stop或者把引脚作为参数传入函数。在实际项目中多路SHT20同时读取时还需要注意SDA线上拉电阻的数量两路I²C共用一条线路会产生竞争因此必须完全分开走线。这样扩展后你可以在一台STC8A上最多接四路SHT20分别监测不同位置的温湿度并统一通过串口输出。若想进一步和上位机联动可以在此基础上把协议改成Modbus RTU每次读取完SHT20数据后把它填入MODBUS保持寄存器这样就能无缝接入组态软件或PLC系统。本文还有配套的精品资源点击获取