STM32标准库函数驱动SHT30温湿度传感器详解
一、SHT30传感器简介
SHT30是Sensirion推出的高精度数字温湿度传感器,支持I²C通信接口,具备以下特性:
-
温度测量范围:-40°C ~ +125°C (±0.2°C精度)
-
湿度测量范围:0% ~ 100% RH (±2% RH精度)
-
工作电压:2.4V ~ 5.5V
-
低功耗特性
二、模式选择说明
SHT30提供两种基本工作模式:
1. 单次测量模式(Single-Shot Mode)
-
发送测量命令后执行单次测量
-
支持不同重复性等级:
-
高重复性:0x2C06 / 0x2400
-
中重复性:0x2C0D / 0x240B
-
低重复性:0x2C10 / 0x2416
-
-
支持时钟拉伸(Clock Stretching)使能/禁用
2. 周期测量模式(Periodic Mode)
-
可配置测量频率:
-
0.5mps:0.5次/秒
-
1mps:1次/秒
-
2mps:2次/秒
-
4mps:4次/秒
-
10mps:10次/秒
-
-
需先发送启动周期测量命令,后通过读取命令获取数据
三、I²C通信时序详解
1. 设备地址
-
7位地址:0x44(默认)或0x45(ADDR引脚接VDD时)
2. 单次测量时序
START → 写地址(0x88) → ACK → 命令高位 → ACK → 命令低位 → ACK → STOP (等待测量完成) START → 读地址(0x89) → ACK → 读取6字节数据 → NACK → STOP
3. 数据格式
[温度高位][温度低位][温度CRC][湿度高位][湿度低位][湿度CRC]
四、硬件连接
SHT30引脚 | STM32引脚 |
---|---|
VDD | 3.3V |
GND | GND |
SDA | PB8 |
SCL | PB9 |
五、完整代码框架
IIC.h
#ifndef __IIC_H
#define __IIC_H
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "Delay.h" // 包含延时函数的头文件
// 定义IIC通信中的应答类型枚举
typedef enum
{
IIC_ACK = 0, // 应答信号
IIC_NACK = 1 // 非应答信号
} IIC_AckType;
// 定义IIC相关的硬件配置
#define RCC_IIC RCC_APB2Periph_GPIOB // IIC使用的GPIO端口时钟(GPIOB)
#define IIC_PORT GPIOB // IIC使用的GPIO端口(GPIOB)
#define SCL_PIN GPIO_Pin_9 // IIC的SCL引脚(PB9)
#define SDA_PIN GPIO_Pin_8 // IIC的SDA引脚(PB8)
// 函数声明
// 初始化IIC接口
void IIC_Init(void);
// 写SCL引脚的电平状态
void IIC_W_SCL(uint8_t BitValue);
// 写SDA引脚的电平状态
void IIC_W_SDA(uint8_t BitValue);
// 读取SDA引脚的电平状态
uint8_t IIC_R_SDA(void);
// 发送IIC起始信号
void IIC_Start(void);
// 发送IIC停止信号
void IIC_Stop(void);
// 发送应答信号
void IIC_SendAck(IIC_AckType State);
// 接收应答信号
IIC_AckType IIC_ReceiveAck(void);
// 发送一个字节数据
void IIC_SendByte(uint8_t Byte);
// 接收一个字节数据
uint8_t IIC_ReceiveByte(IIC_AckType State);
#endif
IIC.c
/**
* @brief 初始化IIC接口
* @param 无
* @return 无
* @note 配置SCL和SDA引脚为开漏输出模式,并拉高引脚电平,使IIC总线处于空闲状态。
*/
void IIC_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_IIC, ENABLE); // 使能GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStructure; // 定义GPIO初始化结构体
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 配置为开漏输出模式
GPIO_InitStructure.GPIO_Pin = SCL_PIN | SDA_PIN; // 选择SCL和SDA引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 配置引脚速度为50MHz
GPIO_Init(IIC_PORT, &GPIO_InitStructure); // 初始化GPIO
GPIO_SetBits(IIC_PORT, SCL_PIN | SDA_PIN); // 将SCL和SDA引脚置高
Delay_us(5); // 延时5微秒
}
/**
* @brief 设置SCL引脚的电平状态
* @param BitValue 电平状态(0:低电平,1:高电平)
* @return 无
* @note 设置SCL引脚的电平,并延时5微秒以确保电平稳定。
*/
void IIC_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(IIC_PORT, SCL_PIN, (BitAction)BitValue); // 设置SCL引脚电平
Delay_us(5); // 延时5微秒
}
/**
* @brief 设置SDA引脚的电平状态
* @param BitValue 电平状态(0:低电平,1:高电平)
* @return 无
* @note 设置SDA引脚的电平,并延时5微秒以确保电平稳定。
*/
void IIC_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(IIC_PORT, SDA_PIN, (BitAction)BitValue); // 设置SDA引脚电平
Delay_us(5); // 延时5微秒
}
/**
* @brief 读取SDA引脚的电平状态
* @param 无
* @return SDA引脚的电平状态(0:低电平,1:高电平)
* @note 读取SDA引脚的电平,并延时5微秒以确保电平稳定。
*/
uint8_t IIC_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(IIC_PORT, SDA_PIN); // 读取SDA引脚电平
Delay_us(5); // 延时5微秒
return BitValue; // 返回读取的电平值
}
/**
* @brief 发送IIC起始信号
* @param 无
* @return 无
* @note 起始信号是IIC通信的开始标志,时序为:SDA从高到低,SCL保持高电平。
*/
void IIC_Start(void)
{
IIC_W_SDA(1); // 拉高SDA
IIC_W_SCL(1); // 拉高SCL
IIC_W_SDA(0); // 拉低SDA,产生起始信号
IIC_W_SCL(0); // 拉低SCL,准备发送数据
}
/**
* @brief 发送IIC停止信号
* @param 无
* @return 无
* @note 停止信号是IIC通信的结束标志,时序为:SDA从低到高,SCL保持高电平。
*/
void IIC_Stop(void)
{
IIC_W_SDA(0); // 拉低SDA
IIC_W_SCL(1); // 拉高SCL
IIC_W_SDA(1); // 拉高SDA,产生停止信号
}
/**
* @brief 发送应答信号
* @param State 应答状态(IIC_ACK:应答,IIC_NACK:非应答)
* @return 无
* @note 根据State参数发送应答或非应答信号,时序为:SDA在SCL高电平期间保持相应状态。
*/
void IIC_SendAck(IIC_AckType State)
{
switch (State)
{
case IIC_ACK :
IIC_W_SDA(0); // 发送应答信号(拉低SDA)
break;
case IIC_NACK :
IIC_W_SDA(1); // 发送非应答信号(拉高SDA)
break;
default:
IIC_W_SDA(1); // 默认发送非应答信号
break;
}
IIC_W_SCL(1); // 拉高SCL,从机读取应答信号
IIC_W_SCL(0); // 拉低SCL,准备后续操作
}
/**
* @brief 接收应答信号
* @param 无
* @return 应答状态(IIC_ACK:应答,IIC_NACK:非应答)
* @note 在SCL高电平期间读取SDA引脚状态,判断从机是否应答。
*/
IIC_AckType IIC_ReceiveAck(void)
{
uint8_t Cnt = 0;
IIC_W_SDA(1); // 释放SDA控制权
IIC_W_SCL(1); // 拉高SCL,从机可发送应答信号
while ((IIC_R_SDA() == 1) && Cnt < 100) // 等待从机应答
{
Cnt++;
if(Cnt == 100) // 超时未收到应答
{
return IIC_NACK;
}
}
IIC_W_SCL(0); // 拉低SCL,结束应答检测
return IIC_ACK; // 返回应答状态
}
/**
* @brief 发送一个字节数据
* @param Byte 要发送的数据(8位)
* @return 无
* @note 从高位到低位依次发送数据的每一位,SCL在每位数据稳定后产生时钟信号。
*/
void IIC_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++) // 逐位发送
{
if((Byte & 0x80) == 0x80) // 判断最高位
{
IIC_W_SDA(1); // 发送1
}
else
{
IIC_W_SDA(0); // 发送0
}
IIC_W_SCL(1); // 拉高SCL,从机读取数据
IIC_W_SCL(0); // 拉低SCL,准备发送下一位
Byte = Byte << 1; // 左移一位,准备发送下一位
}
}
/**
* @brief 接收一个字节数据
* @param State 是否发送应答信号(IIC_ACK:发送应答,IIC_NACK:发送非应答)
* @return 接收到的数据(8位)
* @note 从高位到低位依次读取数据的每一位,SCL在每位数据稳定后产生时钟信号。
*/
uint8_t IIC_ReceiveByte(IIC_AckType State)
{
uint8_t i;
uint8_t Data = 0x00;
IIC_W_SDA(1); // 释放SDA控制权
for (i = 0; i < 8; i ++) // 逐位接收
{
IIC_W_SCL(1); // 拉高SCL,从机发送数据
if (IIC_R_SDA() == 1) // 读取SDA电平
{
Data |= (0x80 >> i); // 将数据位存入Data
}
IIC_W_SCL(0); // 拉低SCL,准备接收下一位
}
IIC_SendAck(State); // 发送应答信号
return Data; // 返回接收到的数据
}
SHT30.h
#ifndef __SHT30_H
#define __SHT30_H
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "IIC.h" // 包含IIC通信的头文件
#include "stdio.h" // 包含标准输入输出头文件
// 定义SHT30传感器数据存储结构体
typedef struct
{
float temperature; // 温度,单位为℃
float humidity; // 湿度,单位为%RH
} SHT30_Data;
// 定义IIC操作类型枚举
typedef enum
{
READ = 0, // 读操作
WRITE // 写操作
} Operation;
// SHT30 IIC 地址(7位地址)
#define SHT30_IIC_ADDRESS (uint8_t)0x44
// CRC校验多项式(X^8 + X^5 + X^4 + 1)
#define POLYNOMIAL (uint8_t)0x31
// SHT30数据长度(温度 + 湿度 + CRC校验)
#define SHT30_DATA_LENGTH 6
// SHT30 测量命令
#define SHT30_CMD_MEASURE_HIGHREP (uint16_t)0x2C06 // 高重复性测量
#define SHT30_CMD_MEASURE_MEDREP (uint16_t)0x240D // 中重复性测量
#define SHT30_CMD_MEASURE_LOWREP (uint16_t)0x2410 // 低重复性测量
// 全局变量声明
extern SHT30_Data SHT30; // 存储SHT30传感器的温度和湿度数据
// 函数声明
void SHT30_SendHeader(Operation op);
void SHT30_SendCmd(uint16_t cmd);
uint8_t SHT30_CRC(uint8_t *data, uint8_t len);
void SHT30_Measure(SHT30_Data *SHT30, uint16_t Cmd);
#endif
SHT30.c
// 包含自定义的SHT30传感器头文件(定义数据结构、操作命令等)
#include "SHT30.h"
// 声明全局传感器数据结构体,用于存储测量结果
SHT30_Data SHT30;
/**
* @brief 发送I2C通信起始头和设备地址
* @param op 操作类型:READ-读取操作 / WRITE-写入操作
* @note 此函数负责I2C通信的起始信号和设备地址发送,
* 根据操作类型构造不同的从机地址字节
*/
void SHT30_SendHeader(Operation op)
{
IIC_Start(); // 产生I2C起始信号
if(op == READ) // 读取操作模式
{
// 构造读地址:(7位地址 << 1) | 0x01(读标志位)
IIC_SendByte((SHT30_IIC_ADDRESS << 1) | 0x01);
// 等待从机应答,若未收到ACK则持续重试
while (IIC_ReceiveAck() == IIC_NACK)
{
printf("SHT30读操作失败!请检查设备连接!\n");
}
}
else if(op == WRITE) // 写入操作模式
{
// 构造写地址:(7位地址 << 1) & 0xFE(写标志位)
IIC_SendByte((SHT30_IIC_ADDRESS << 1) & 0xFE);
// 等待从机应答
while (IIC_ReceiveAck() == IIC_NACK)
{
printf("SHT30写操作失败!请检查设备连接!\n");
}
}
else // 无效操作类型
{
IIC_Stop(); // 发送停止信号保证总线释放
}
}
/**
* @brief 发送16位命令到传感器
* @param cmd 要发送的16位命令(参见数据手册命令列表)
* @note 命令分高8位和低8位两次发送,每次发送后需检查应答
*/
void SHT30_SendCmd(uint16_t cmd)
{
// 发送命令高字节(MSB First)
IIC_SendByte((cmd >> 8) & 0xFF);
// 检查ACK应答
while (IIC_ReceiveAck() == IIC_NACK)
{
printf("SHT30命令高字节发送失败!\n");
}
// 发送命令低字节(LSB)
IIC_SendByte(cmd & 0xFF);
while (IIC_ReceiveAck() == IIC_NACK)
{
printf("SHT30命令低字节发送失败!\n");
}
IIC_Stop(); // 发送停止信号结束本次传输
// 延迟50ms等待传感器处理命令(具体时间需参考数据手册)
Delay_ms(50);
}
/**
* @brief CRC8校验计算(多项式0x31)
* @param data 待校验数据指针
* @param len 数据长度
* @return 计算得到的CRC校验值
* @note 使用SHT30指定的CRC8校验算法,多项式:x^8 + x^5 + x^4 +1 (0x31)
*/
uint8_t SHT30_CRC(uint8_t *data, uint8_t len)
{
uint8_t bit; // 位计数器
uint8_t crc = 0xFF; // 初始值
uint8_t byteCtr; // 字节计数器
// 逐字节处理数据
for(byteCtr = 0; byteCtr < len; byteCtr++)
{
crc ^= data[byteCtr]; // 异或当前字节
// 逐位处理(8次循环)
for(bit = 8; bit > 0; bit--)
{
// 检测最高位是否为1
if((crc & 0x80) == 0x80)
{
crc = (crc << 1) ^ POLYNOMIAL; // 左移并异或多项式
}
else
{
crc = (crc << 1); // 简单左移
}
}
}
return crc; // 返回最终校验值
}
/**
* @brief 执行温湿度测量并处理数据
* @param SHT30 传感器数据结构体指针
* @param Cmd 测量命令(参见数据手册,如0x2C06表示高速测量)
* @note 完整测量流程:发送命令->等待测量->读取数据->校验->转换物理量
*/
void SHT30_Measure(SHT30_Data *SHT30, uint16_t Cmd)
{
uint8_t i;
uint8_t Buff[SHT30_DATA_LENGTH]; // 数据缓冲区(通常为6字节)
// 阶段1:发送测量命令
SHT30_SendHeader(WRITE); // 启动写操作
SHT30_SendCmd(Cmd); // 发送测量命令
// 阶段2:读取测量数据
SHT30_SendHeader(READ); // 切换为读模式
// 读取6字节数据(温度3字节,湿度3字节)
for(i = 0; i < SHT30_DATA_LENGTH; i++)
{
// 最后一个字节发送NACK结束读取
if(i == SHT30_DATA_LENGTH - 1)
{
Buff[i] = IIC_ReceiveByte(IIC_NACK);
}
else
{
Buff[i] = IIC_ReceiveByte(IIC_ACK);
}
}
IIC_Stop(); // 结束本次读取
// 阶段3:数据校验与转换
/* 校验结构:
* 字节0-1:温度数据
* 字节2:温度CRC
* 字节3-4:湿度数据
* 字节5:湿度CRC
*/
if((SHT30_CRC(Buff, 2) == Buff[2]) &&
(SHT30_CRC(Buff + 3, 2) == Buff[5]))
{
// 温度转换公式(数据手册提供)
SHT30->temperature = (-45 + (175.0 *
((Buff[0] << 8) | Buff[1]) / 65535.0));
// 湿度转换公式(数据手册提供)
SHT30->humidity = 100 *
(((Buff[3] << 8) | Buff[4]) / 65535.0);
}
else
{
printf("SHT30数据校验失败!可能数据损坏!\n");
}
}
main.c
#include "stm32f10x.h"
#include "IIC.h"
#include "Usart.h"
#include "SHT30.h"
int main(void)
{
Usart_Init(BAUDRATE);
IIC_Init();
while(1)
{
SHT30_Measure(&SHT30, SHT30_CMD_MEASURE_HIGHREP);
printf("温度:%.2f℃ 相对湿度:%.2f%%RH",SHT30.temperature,SHT30.humidity)
}
}
六、注意事项
-
确保I²C总线时序满足传感器要求(典型100kHz)
-
测量完成后需等待足够时间(单次模式典型12ms)
-
建议始终进行CRC校验确保数据可靠性
七、优化建议
-
使用DMA传输提高效率
-
实现中断驱动模式
-
添加温度补偿算法
-
实现低功耗模式控制
(注:实际使用时需根据具体硬件平台调整引脚配置和延时参数)