十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STC8A单片机SHT30温湿度传感器串口显示实战教程

STC8A单片机SHT30温湿度传感器串口显示实战教程 简介本资源是一套面向嵌入式初学者与单片机开发者的STC8A单片机实战例程聚焦SHT30高精度温湿度传感器的I²C通信与数据可视化解决环境参数采集、协议驱动编写及串口调试等典型工程问题。压缩包共18个文件含5个C源文件如i2c.c、sht30.c、main.c实现底层驱动与主控逻辑5个头文件i2c.h、sht30.h、uart.h等定义接口与寄存器以及Keil工程文件.uvproj/.uvopt、编译输出.hex/.m51和配套资料SHT30资料.rar整体1.27MB结构完整、开箱即用。已有241人学习下载涵盖从硬件连接、I²C时序模拟、传感器命令发送与4字节数据解析到UART波特率配置、温湿度数值单位换算℃/RH%及ASCII字符串格式化输出的全流程代码实现特别适合掌握C51编程、外设通信与串口调试能力的进阶实践。 STC8A单片机上手SHT30温湿度传感器串口显示其实不难——这件事我折腾了一下午踩了不少坑今天把完整的过程和思考都整理出来希望能帮你少走弯路。先交代一下背景最近在做一个小型环境监测项目需要一个精度尚可、价格友好的数字温湿度传感器最终选了SHT30。主控用的是STC8A系列单片机开发环境是Keil C51数据通过串口助手在电脑上实时查看。整件事拆开看就三块传感器数据怎么读出来、单片机怎么处理、串口怎么发出去。代码量不大但里面有不少细节决定成败比如I2C时序、SHT30的CRC校验、串口波特率匹配任何一个没处理好显示出来的数据就是乱的。这篇文章适合刚接触STC8A或者SHT30的开发者也适合想把手头传感器模块快速跑通的朋友参考。我会把硬件接线、驱动代码、串口调试流程以及我实际调试时遇到的各种问题全部摊开来讲保证你看完能直接复现。1. 项目整体设计与思路拆解1.1 为什么选STC8A而不是传统51或STM32STC8A系列是宏晶科技推出的增强型8051内核单片机很多人一听到8051就觉得老掉牙但实际上STC8A和上世纪那种AT89C51完全是两个时代的东西。它的指令集兼容传统8051但主频最高能跑到24MHz甚至更高内部集成大容量Flash和SRAM还带硬件I2C、硬件SPI、多路ADC、PWM、专用串口等多种外设模块。这就意味着哪怕你只会传统51的编程套路也能在不太陡的学习曲线下获得接近32位MCU的体验。在SHT30这个项目里我最看重的几点是第一STC8A的工作电压范围宽3.3V和5V都能跑而SHT30是3.3V供电的传感器两者搭配时电平匹配比较容易处理第二STC8A的I2C接口支持硬件模式即使不用硬件模式用GPIO模拟I2C它的GPIO翻转速度也足够快时序不容易出问题第三STC8A的串口模块带独立的波特率发生器配置起来比老51用定时器T1或T2分频要灵活不少调试时也省心。如果你手头正好有STC8A开发板那这个项目基本就是零额外成本起步。1.2 SHT30传感器特性与选型分析温湿度传感器市面上种类非常多入门最常见的自然是DHT11和DHT22但这两者有个共同问题——精度一般DHT11的湿度误差甚至到±5%RH而且它们都是用单总线协议时序要求比较苛刻数据稳定性和一致性也一般。SHT3x系列是Sensirion推出的数字温湿度传感器SHT30是其中性价比最高的一款温度精度典型值±0.3°C湿度精度典型值±2%RH这个精度对于大多数环境监测场景是完全够用的。SHT30和主控之间走的是标准I2C接口默认设备地址是0x44ADDR引脚接地时支持最高1MHz的通信速率内部还有加热器、低功耗模式、周期采集模式等一堆高级功能。在工业级应用里SHT30的长期稳定性和抗污染能力也明显强于DHT系列。对于这个实验项目来说SHT30的驱动难度适中既不像DHT11那样时序脏得一塌糊涂也不像BME280那样带压力传感器导致寄存器配置复杂用来学习I2C通信和传感器驱动是很好的素材。1.3 整体方案架构与数据流向整个系统的信息流非常清晰SHT30传感器通过I2C总线把温湿度原始数据传给STC8A单片机单片机解析校验后经过处理再通过UART串口发送到电脑端的串口调试助手显示出来。如果以后想扩展还可以在单片机里加LCD显示、加WiFi模块上传云端甚至加个OLED屏做本地显示架构都不用大改。这里我画一个简单的逻辑分层物理层SHT30模块与STC8A之间用4根线连接——VCC、GND、SCL、SDA。如果SHT30是3.3V模块VCC接3.3VSTC8A的I2C引脚设置为开漏输出并外加上拉电阻。驱动层编写I2C读写函数或使用STC8A硬件I2C封装SHT30的写命令、读数据、软复位等基础操作。应用层主循环里周期性发起测量请求、读取结果、做CRC校验、把温湿度换算成真实物理值。数据输出层通过串口中断或查询方式将格式化好的字符串发送给串口调试助手。这样做的好处是层次分明调试时能快速定位问题出在哪一层。我实际调试时先用逻辑分析仪看I2C波形确认驱动层没问题然后再用串口助手看数据帧确认应用层换算正确。如果你一上来就堆代码没有分层的概念出了问题往往一头雾水。2. 硬件接线与基础环境搭建2.1 引脚连接与注意事项先说最容易被忽视的电源问题。SHT30的供电范围是2.4V到5.5V虽然它可以兼容5V供电但I2C引脚的电平逻辑是跟随VDD的。如果你用5V给传感器供电那它的SCL和SDA高电平就是5V对STC8A来说5V输入到I/O口是可以接受的但如果你用的是3.3V供电的STM32或其他MCU就必须做电平转换。在这个项目里我统一用了3.3V供电SHT30模块上的I2C上拉电阻一般已经焊好了不需要额外处理。我的具体接线如下表STC8A引脚功能连接目标P1.2I2C时钟SCLSHT30 SCLP1.3I2C数据SDASHT30 SDAVCC3.3V电源SHT30 VCCGND地SHT30 GNDP3.0UART TXUSB转串口模块RXP3.1UART RXUSB转串口模块TX注意STC8A系列不同型号的引脚功能映射不一样比如有些型号串口1默认就是P3.0和P3.1但还有切换功能可以映射到P3.6和P3.7等引脚。你拿到实物后第一件事是查对应型号的数据手册确认引脚功能映射。我用的是STC8A8K64S4A12这颗芯片64K Flash8K SRAM在实验板上跑这个项目绰绰有余。I2C引脚选择P1.2和P1.3是因为这两个引脚支持开漏模式适合I2C总线而且避开了串口下载引脚P3.0/P3.1的冲突。2.2 开发环境配置与工程创建开发环境我用的是Keil C51版本是C51 V9.60以上都行。STC8A不是标准的8051芯片所以在Keil里需要选择正确的芯片型号通常STC官方提供了芯片库文件如果没有对应型号可以先用“STC MCU”或者接近的型号替代但要注意头文件和寄存器定义可能不一样。推荐的做法是去STC官网下载最新的STC-ISP软件这个软件不仅能下载程序还能在“Keil仿真设置”页面里把STC8A系列芯片添加到Keil的设备列表中。添加完成后新建工程时就能直接选择STC8A8K64S4A12Keil会自动带上正确的启动文件和寄存器定义。工程文件结构建议这样安排Project/ |-- main.c // 主函数、初始化调用、主循环 |-- uart.c // 串口初始化与发送函数 |-- uart.h |-- i2c_soft.c // 软件I2C时序 |-- i2c_soft.h |-- sht30.c // SHT30驱动 |-- sht30.h把不同模块拆到独立文件里以后维护和复用都方便。很多初学者喜欢把所有代码堆在一个main.c里几十个函数挤在一起最后自己都找不到逻辑了这个习惯尽早改掉。关于STC8A的I/O模式配置有一点需要特别注意。STC8A的每个端口都可以独立配置为四种模式准双向口、推挽输出、高阻输入、开漏输出。I2C的SDA线因为是双向的必须配置为开漏输出模式并在外部加上拉电阻模块上一般已经有了SCL线是主机输出方向可以配置为推挽输出也可以配置为开漏但从兼容性考虑我习惯把SCL也设为开漏。STC8A配置I/O模式的寄存器是P1M0和P1M1两个寄存器配合控制每个引脚的模式。P1.2和P1.3配置为开漏模式的代码P1M0 | (1 2) | (1 3); // P1.2, P1.3设为开漏 P1M1 ~((1 2) | (1 3));如果你用的是5V供电的SHT30模块并且模块上已经有上拉电阻那I/O配置为开漏也是安全的如果模块上没有上拉你需要在SCL和SDA上各接一个4.7kΩ电阻到VCC。3. SHT30驱动核心细节解析3.1 I2C通信协议基础I2C是一种两线制的串行通信协议由时钟线SCL和数据线SDA组成。主机通过SCL产生时钟SDA在时钟的配合下传输数据。理解I2C的关键就三个动作起始条件、停止条件、字节传输。起始条件是SCL为高电平期间SDA由高变低停止条件是SCL为高电平期间SDA由低变高。数据传输时SDA在SCL高电平期间必须保持稳定数据只能在SCL低电平期间变化。每传输8个bit后接收方需要回一个ACK应答位主机如果想结束读操作可以在最后一个字节后发送NACK。我最初学I2C的时候看了很多文字描述都觉得很抽象后来用逻辑分析仪抓了一次波形瞬间就明白了。强烈建议你手头有逻辑分析仪的话花十分钟抓一下波形看看这比看十遍文字都管用。STC8A本身也带硬件I2C模块可以配置为主机模式让硬件自动产生时序。但在这个例程里我故意用了软件模拟I2C原因是第一软件I2C的移植性好代码拿到别的单片机也能用第二SHT30的通信速率要求不高软件模拟完全够用第三软件I2C能让你真正理解时序而不是只会调寄存器。3.2 SHT30命令集与测量模式SHT30的I2C命令是16位的发送时先发高字节再发低字节。最常用的几个测量命令如下命令功能说明备注0x2C 0x06单次测量高重复性每次测量后进入单次模式0x2C 0x0D单次测量低重复性速度更快精度略低0x27 0x37周期测量10Hz重复性高连续自动测量0x30 0xA2软复位重启传感器0x30 0x41清除状态寄存器清除报警标志在这个实验里我用的是单次测量模式命令0x2C 0x06。所谓“单次测量”就是单片机发送一个测量命令后传感器开始测量等测量完成后数据会保持在输出寄存器里直到主机读取。这种模式的好处是功耗低、控制简单每次测量都是独立的不会出现周期测量模式下的数据重叠问题。发送测量命令后需要等待一段时间让传感器完成测量。SHT30数据手册上写明高重复性单次测量的最大转换时间是15ms我实际测试时留了20ms的延时确保万无一失。3.3 SHT30驱动代码实现与逐行解读先看I2C底层时序这是我软件模拟的核心。起始条件、停止条件、发送字节、读取字节四个函数搞定void I2C_Start(void) { SDA 1; SCL 1; SDA 0; SCL 0; } void I2C_Stop(void) { SDA 0; SCL 1; SDA 1; } void I2C_SendByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SDA (dat 0x80) ? 1 : 0; dat 1; SCL 1; _nop_(); SCL 0; } // 读取从机ACK应答 SDA 1; SCL 1; SCL 0; } unsigned char I2C_RecvByte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { SCL 1; dat 1; if (SDA) dat | 1; SCL 0; } return dat; }注意上面代码里我简化了ACK的处理实际使用时最好把ACK读取结果返回给上层方便调试。我调试时就在ACK检测里加了一个计数如果连续收到非应答信号就说明传感器没响应大概率是地址错了或者接线错了。_nop_()是个空指令用来产生一个机器周期的延时。STC8A在24MHz主频下一个机器周期是1/24MHz约41.6ns这个速度对I2C来说是偏快的所以实际我会在SCL翻转之间塞几个_nop_()把I2C时钟降到几百kHz。如果你发现时序有问题最简单的办法是加延时降速I2C协议本身就是低速协议慢一点更稳定。接下来是SHT30的驱动。SHT30的完整读取流程是这样的先发起始条件再发送传感器地址0x44左移一位后的写命令0x88等ACK后发送测量命令0x2C和0x06然后停止条件延时等待测量完成再发起始条件发送传感器地址加读标志0x89读取6个字节数据最后发停止条件。6个字节的分布是温度高字节、温度低字节、CRC校验、湿度高字节、湿度低字节、CRC校验。SHT30对温度和湿度数据各附带了一个CRC8校验字节用于检测通信过程中数据是否被干扰。数据手册里给出了CRC多项式是0x31初始值是0xFF。CRC校验的C语言实现unsigned char crc8(unsigned char *data, unsigned char len) { unsigned char crc 0xFF; unsigned char i, j; for (i 0; i len; i) { crc ^ data[i]; for (j 0; j 8; j) { if (crc 0x80) crc (crc 1) ^ 0x31; else crc 1; } } return crc; }判断是否校验通过就是比较算出来的CRC和传感器发来的CRC是否一致。如果不一致说明这次读取的数据不可信最好丢弃重新读。我在实际调试中遇到过CRC频繁错误的情况排查下来发现是杜邦线太长导致信号质量差换成短杜邦线后问题消失。温湿度原始数据的换算公式在数据手册里有明确说明温度 -45 175 * (rawTemp / 65535.0) 湿度 100 * (rawHum / 65535.0)其中rawTemp是把温度高字节和温度低字节拼成一个16位无符号整数。我写了个统一的读取函数返回一个结构体typedef struct { float temperature; float humidity; } SHT30_Data; unsigned char SHT30_Read(SHT30_Data *data) { unsigned char buf[6]; unsigned char crc_temp, crc_hum; I2C_Start(); I2C_SendByte(0x88); // 地址写 if (I2C_GetAck() ! 0) return 1; I2C_SendByte(0x2C); I2C_GetAck(); I2C_SendByte(0x06); I2C_GetAck(); I2C_Stop(); delay_ms(20); I2C_Start(); I2C_SendByte(0x89); // 地址读 if (I2C_GetAck() ! 0) return 2; buf[0] I2C_RecvByte(); I2C_SendAck(0); buf[1] I2C_RecvByte(); I2C_SendAck(0); buf[2] I2C_RecvByte(); I2C_SendAck(0); buf[3] I2C_RecvByte(); I2C_SendAck(0); buf[4] I2C_RecvByte(); I2C_SendAck(0); buf[5] I2C_RecvByte(); I2C_SendNack(0); I2C_Stop(); crc_temp crc8(buf, 2); if (crc_temp ! buf[2]) return 3; crc_hum crc8(buf 3, 2); if (crc_hum ! buf[5]) return 4; >void UART1_Init(void) { SCON 0x50; // 0101 0000, 模式1, 允许接收 TMOD 0x0F; TMOD | 0x20; // 定时器1工作在模式2, 8位自动重装 TH1 0xFD; // 波特率115200对应的重装值11.0592MHz晶振 TL1 0xFD; TR1 1; // 启动定时器1 ES 1; // 使能串口1中断 EA 1; // 打开总中断 }这里有个经典问题波特率和晶振频率的匹配。如果晶振是11.0592MHz那定时器初值就是0xFD能产生准确的115200波特率如果晶振是12MHz那115200波特率会存在一定误差可能导致通信不稳定。STC8A内部有可配置的时钟分频而且支持用户通过STC-ISP软件选择“输入用户程序运行时的IRC频率”可以用11.0592MHz的内部IRC频率来避免这个问题。串口发送单个字符和字符串void UART1_SendByte(unsigned char dat) { SBUF dat; while (!TI); TI 0; } void UART1_SendString(char *str) { while (*str) { UART1_SendByte(*str); } }注意while (!TI)和TI 0的顺序很多人在这里容易写反。标准写法是把数据写入SBUF然后等TI置1再清TI。4.2 主循环程序设计思路主循环的逻辑不复杂但要考虑一个实际问题SHT30的一次完整读取需要约20ms的测量时间所以主循环的周期最好控制在200ms以上避免频繁读取导致总线拥堵。我用了一个简单的定时器标志来实现周期读取。主程序框架如下void main(void) { unsigned char res; SHT30_Data sht30_data; unsigned char buf[64]; System_Init(); // 时钟配置、I/O模式配置 UART1_Init(); I2C_Gpio_Init(); UART1_SendString(SHT30 Test, UART Display\r\n); while (1) { res SHT30_Read(sht30_data); if (res 0) { sprintf(buf, Temp: %.2f C Hum: %.2f %%RH\r\n, sht30_data.temperature, sht30_data.humidity); UART1_SendString(buf); } else { sprintf(buf, Read error: %d\r\n, res); UART1_SendString(buf); } delay_ms(500); } }注意Keil C51里的sprintf函数默认支持浮点数格式化但要勾选目标选项里的“使用C51库中的浮点支持”否则编译会报错或者运行时输出异常。另外嵌入式环境里尽量避免频繁使用sprintf和浮点运算因为这会占用大量代码空间和运行时间。我这个实验为了演示方便用了sprintf实际产品开发中更推荐把浮点数分解成整数和小数部分用整数格式化输出这样既快又省Flash。我自己写了一个简单的浮点格式化函数替代sprintfvoid SendFloat(float val, unsigned char decimals) { long integer_part; unsigned long decimal_part; unsigned char i; integer_part (long)val; decimal_part (unsigned long)((val - integer_part) * 100); UART1_SendString(Temp: ); UART1_SendInt(integer_part); UART1_SendByte(.); UART1_SendInt(decimal_part); UART1_SendString( C); }这样发送字符串的效率高很多代码体积也小。4.3 串口调试助手选择与配置要点串口调试助手这个工具做嵌入式的应该人手一份但很多新手不知道不同软件之间的细微差异。常用的有SSCOM、XCOM、友善串口调试助手、STC-ISP自带的串口助手等。我的建议是用STC-ISP自带的串口助手因为它和STC单片机是同一个生态支持自动下载、串口监视等功能界面也简单。如果你需要更专业的串口分析能力XCOM的波形显示功能很适合看传感器数据的动态变化。不管用哪个助手配置要点就三个波特率必须和单片机端一致115200数据位8位、停止位1位、无校验位不要勾选“按十六进制显示”否则你看到的是ASCII码的十六进制形式而不是可读文本我踩过一个坑串口助手收到的数据第一行是乱的后面就正常了。排查后发现是单片机复位时串口还没完全初始化上位机却已经开始接收了。解决办法是在单片机端加一个延时等串口稳定后再发送。或者在上位机端忽略前几个字符。5. 调试过程实录与常见问题排查5.1 I2C通信失败的排查方法与实例I2C通信失败是SHT30项目里最常见的问题现象通常是读不到数据、数据恒为0或者返回值一直报错。我在调试过程中遇到一个很典型的情况SHT30读取函数返回2也就是发送读地址后收不到ACK。排查步骤是这样的第一步检查电压。用万用表量SHT30模块的VCC引脚确认是3.3V还是0V。别笑很多人真的会犯这种低级错误——模块供电没接或者接反了。第二步检查接线。SCL和SDA有没有接反GND有没有共地。我遇到过SCL和SDA两根杜邦线颜色一致接反了导致完全不通的情况。第三步检查地址。SHT30的I2C地址默认是0x44如果模块上的ADDR引脚被拉高地址就变成0x45。如果你的模块是焊好的成品看下背面的ADDR焊盘是不是被短接了。用STC8A读地址是0x88和0x89是根据地址0x44左移一位算出来的如果你用的是0x45地址那读写地址就应该是0x8A和0x8B。第四步用逻辑分析仪或示波器抓波形。这一步最直接。把探头夹在SCL和SDA上观察起始条件是否正常、地址字节是否和预期一致、有没有ACK响应。如果波形完全没有说明代码根本没跑进去或者引脚配置不对如果有波形但看不到ACK说明传感器没收到正确的命令或者响应不了。我那次的问题最终定位到是GPIO模式配置错误。P1.2和P1.3被默认配置成了准双向口准双向口的驱动能力虽然也能拉低电平但和开漏模式在I2C场景下的行为完全不同。准双向口在输出高电平时是两个管子同时驱动输出低电平时是开漏结构这对于I2C的线与逻辑来说是可以工作的但问题是准双向口的输入检测有施密特触发器阈值和开漏模式不同当上拉电阻较大、总线电容较高时信号边沿会变缓容易导致时序不满足。改成开漏模式后问题立刻消失。5.2 温湿度数据跳变的处理技巧SHT30本身是非常稳定的传感器正常情况下读出来的数据不会乱跳。如果你看到温度在25°C和35°C之间乱蹦湿度忽高忽低大概率不是传感器的问题而是数据解析出了bug。最常见的解析错误是字节序搞反。SHT30的高字节在前面低字节在后面组合16位数据时应该是buf[0] 8 | buf[1]如果你写成buf[1] 8 | buf[0]那数据就会错得离谱。还有一个坑如果编译器默认char类型是signed char那么buf[0] 8这个表达式可能会带符号扩展导致移位结果不对。我用的是unsigned char类型就彻底规避了这个问题。另一个问题是浮点数转换。SHT30的原始值是16位无符号整数范围0到65535换算公式里除以65535.0而不是65535。如果你写成了(buf[0] 8 | buf[1]) / 65535那结果会一直是0因为整型除以整型结果还是整型。我在调试中还发现如果测量间隔太短数据偶尔会出现轻微跳变。这是因为SHT30在单次测量模式下两次测量之间需要一定时间让传感器内部稳定。所以我在主循环里把读取间隔设计为500ms实际测试下来数据非常平滑。5.3 串口收不到数据或乱码的排查串口收不到数据首先要确认STC8A程序有没有正常下载。STC单片机的下载方式比较特别是冷启动下载——点击下载按钮后给单片机重新上电ISP引导程序才会进入下载模式。如果你下载后没有复位运行程序可能压根没跑起来。其次确认USB转串口模块的TXD和RXD有没有交叉连接。单片机TX要接模块RX单片机RX要接模块TX如果两头直连数据是收不到的。这个接法经常被新手搞混。如果收到乱码先检查波特率。115200在11.0592MHz晶振下是精准的但如果你的单片机用内部IRC 12MHz波特率会产生约0.3%的误差这种误差短期内不会造成乱码但如果两边误差叠加数据长度一长就可能出错。我用STC-ISP把内部IRC频率设置为11.0592MHz后问题就从未出现过。还有一点需要特别注意STC8A下载程序时选择的“输入用户程序运行时的IRC频率”必须和你代码里期望的主频一致。如果你下载时选24MHz但代码里定时器初值是按11.0592MHz算的那波特率就会完全错误。5.4 常见问题速查表我把调试过程中遇到的高频问题整理成一个表格方便你对照排查现象可能原因排查与解决方法SHT30读取一直返回2地址错误/接线错误确认ADDR引脚状态检查SCL/SDA是否接反确认电源共地返回CRC错误线路过长或信号质量差缩短杜邦线降低I2C速率检查上拉电阻值温度数据显示为负数原始值拼接错误确认字节序使用unsigned char类型湿度显示0%浮点运算写错确保除以65535.0带小数点串口无输出接线错误/波特率不匹配检查TX/RX交叉连接核对波特率设置串口乱码晶振频率与代码不匹配统一IRC频率设置为11.0592MHz数据跳变剧烈测量间隔太短增加主循环延时至少200ms以上发送字符串卡死串口中断标志未清检查TI标志是否在发送完每个字节后清零5.5 用STC-ISP的串口助手配合调试的技巧STC-ISP的串口助手有一个很实用的功能“串口助手”页面可以同时设置多个串口参数方案方便快速切换。我调试时配置了三个方案9600波特率、115200波特率、115200波特率带自动换行这样在不同的测试场景下可以一键切换。还有一个技巧是在串口助手里勾选“时间戳”功能这样每条数据都会带上接收时间。当数据刷新频率很高时靠时间戳能准确看出数据的更新周期是否符合预期。我设置500ms读取一次串口里每条数据的时间戳间隔就应该在500ms左右如果时间戳间隔忽长忽短说明主循环里有阻塞或者延迟不稳定。如果你希望调试体验更好推荐在电脑上装一个虚拟串口工具配合串口监视器分析程序发送的原始字节流。虚拟串口软件可以创建一对互联的虚拟串口一个给单片机程序用一个给调试工具用这样你就能在不接任何硬件的情况下测试你的串口发送代码逻辑是否正确。6. 源码工程结构与扩展方向6.1 完整源码结构说明我把整个工程的源码结构整理一下方便你对照自己的工程检查SHT30_Demo/ |-- main.c // 主循环周期读取并串口输出 |-- uart.c / uart.h // 串口1初始化、字节发送、字符串发送 |-- i2c_soft.c / i2c_soft.h // 软件I2C底层时序 |-- sht30.c / sht30.h // SHT30驱动含CRC校验、数据换算 |-- delay.c / delay.h // 毫秒级延时和微秒级延时main.c里包含系统初始化、IO模式配置、串口初始化以及500ms周期任务调度。uart.c实现串口初始化与发送。i2c_soft.c实现起始、停止、发送、接收、ACK相关函数。sht30.c实现SHT30的测量命令发送、数据读取、CRC校验和物理值换算。delay.c提供基于定时器或空循环的延时函数。这个结构有一个好处如果你想换成SHT31或者SHT35只需要修改sht30.c里的命令字和地址定义上层的串口显示逻辑完全不用动。同理如果你想换成OLED显示也只需要把串口发送字符的部分替换成OLED显示函数驱动层的传感器代码不变。6.2 功能扩展思路与建议跑通这个实验之后继续往下走的方向非常多。我简单列几个我实际用到的扩展方案第一增加OLED屏本地显示。买一个0.96寸的I2C OLEDSSD1306驱动把串口发送的数据同时显示在OLED上这样就不需要一直连着电脑了。OLED可以和SHT30挂在同一条I2C总线上SHT30的地址是0x44OLED的地址是0x3C不冲突。第二多节点采集。如果你有多个SHT30可以通过ADDR引脚设置不同的I2C地址最多可以挂两个SHT30在同一条总线上。如果传感器数量更多建议用I2C总线的多路复用器或者干脆换用其他数据接口。第三数据记录与远程监控。STC8A可以外接一个ESP8266模块通过串口把温湿度数据上传到云平台或者本地服务器。这个方案非常适合农业大棚、机房环境监控等场景。如果不想上云也可以用SD卡模块记录数据隔一段时间把数据导出到电脑上分析。第四低功耗设计。STC8A支持多种低功耗模式SHT30也支持周期测量模式和单次测量模式。如果你的项目需要电池供电可以在空闲时让单片机进入掉电模式每10秒唤醒一次读传感器和发送数据这样可以让电池续航大幅提升。这些扩展方向本质上都是在现有架构上加模块或者改驱动不会推翻原有的代码结构这正说明一开始分层设计的价值所在。6.3 一个更稳的串口输出优化整数定点换算前面提到过嵌入式环境里浮点数开销大这里给出一个我在实际产品代码中常用的定点换算写法。SHT30的原始数据范围是0到65535温度范围为-45°C到130°C湿度范围0到100%。为了在串口输出两个小数位可以把计算结果先放大100倍再拆成整数部分和小数部分。温度定点换算示例long temp_x100; // 温度乘以100后的整数 unsigned int raw_temp; raw_temp (buf[0] 8) | buf[1]; temp_x100 -4500L (17500L * raw_temp) / 65535L;这里思路是-45度对应-4500乘以100175度对应17500乘以100然后用原始值比例算出放大100倍的温度值。输出时UART1_SendInt(temp_x100 / 100); // 整数部分 UART1_SendByte(.); UART1_SendInt(temp_x100 % 100); // 小数部分如果小数部分不足两位比如结果是25.5那temp_x100 % 100是50正常情况下输出50没问题。但如果结果是25.05那temp_x100 % 100是5输出就是5而不是05需要补零处理。这个细节虽然小但产品代码里很关键。这种定点写法比浮点运算快了一个数量级代码体积也小关键是输出的格式完全可控。如果你只是想做个实验用sprintf加浮点也行如果你想把代码用在正式项目里建议尽早改成定点处理。6.4 延时函数的实现细节STC8A的延时函数有个坑用_nop_()空循环延时编译器优化等级不同实际延时时间会差很多。我写延时函数时固定用了一个volatile变量做循环计数防止编译器把循环优化掉。void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 1200; j) ; }这个1200的数值是我在24MHz主频下实测标定出来的不同的主频、不同的优化等级数值需要重新调整。最准确的方法是接一个IO口用示波器或者逻辑分析仪测量实际延时然后微调循环次数。STC-ISP软件里也提供了“软件延时计算器”功能输入主频和延时时间能自动生成延时函数推荐直接用那个。SHT30测量等待时间我用的是20ms这个时间比数据手册标称的最大转换时间15ms多了5ms余量就算传感器在极限条件下工作也能保证数据就绪。7. 写在最后的几点经验这个项目从零到跑通我大概花了半天时间其中大部分时间花在排查I2C的GPIO模式配置和确认地址上。回头总结几点经验供参考。做传感器驱动时数据手册真的值得逐句去读。SHT30的数据手册四十多页核心内容其实就集中在命令表、数据格式和时序图几页里。我第一次读的时候只扫了眼命令表和换算公式觉得足够了结果在读取流程上栽了跟头——手册里明确写了“主机发送测量命令后必须等待测量完成才能读取数据”我忽略了等待时间导致经常读到旧数据。调试串口数据时别只看数据对不对还要注意数据的节拍。如果数据是一帧一帧按固定间隔刷新说明程序主循环运行正常如果数据忽快忽慢说明有阻塞或中断处理不当。我习惯在串口助手里开时间戳这个习惯帮我发现了好几次主循环被延时函数卡住的问题。最后想说的是这种单片机实验项目最值钱的不是代码能跑通而是你能把每一个细节都讲清楚为什么。SHT30为什么地址要左移一位才能用CRC校验为什么用多项式0x31波特率为什么是115200而不是别的值这些问题每一个背后都有一串知识链。等你能把这些问题都讲明白了你会发现再看其他传感器驱动都会觉得豁然开朗。本文还有配套的精品资源点击获取
返回列表