STM32模拟IIC驱动SHT30温湿度传感器实战指南
1. 准备工作与环境搭建
大家好,今天我来分享一个在STM32上通过模拟IIC驱动SHT30温湿度传感器的完整实战过程。这个方案特别适合那些硬件资源受限或者需要灵活定制IIC时序的场景。我自己在多个项目中都用过这个方案,实测下来非常稳定可靠。
SHT30是一款高精度的数字温湿度传感器,采用IIC接口通信,测量范围广,精度高,非常适合各种环境监测应用。相比于常见的DHT11,SHT30的精度和稳定性都要好很多,当然价格也稍高一些。
在开始之前,你需要准备以下材料:
- 一块STM32开发板(我用的STM32F103系列,其他系列也类似)
- SHT30温湿度传感器模块
- 杜邦线若干
- 两个4.7kΩ的上拉电阻(非常重要!)
- STM32开发环境(我用的Keil MDK,其他IDE也可以)
接线方面,SHT30的VCC接3.3V,GND接地,SCL和SDA分别接STM32的GPIO引脚。这里特别注意:IIC总线需要上拉电阻,否则通信会失败。我选择的是PB14作为SDA,PB15作为SCL,你也可以根据实际情况选择其他引脚。
2. GPIO配置与寄存器设置
配置GPIO是模拟IIC的第一步,也是很多新手容易出错的地方。因为我们要模拟IIC时序,所以需要把使用的GPIO配置为开漏输出模式,并且使能内部上拉或者外接上拉电阻。
我以STM32F103为例,使用PB14和PB15这两个引脚。在标准库中,配置代码如下:
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
// SCL引脚配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// SDA引脚配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 初始状态置高
GPIO_SetBits(GPIOB, GPIO_Pin_14 | GPIO_Pin_15);
}
如果你用的是HAL库,配置方式会有些不同:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
// SCL引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// SDA引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_14;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始状态置高
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET);
}
这里有个重要的知识点:为什么IIC总线要使用开漏输出?因为IIC总线是多个设备共享的,开漏输出可以避免多个设备同时驱动总线时产生冲突。当总线空闲时,上拉电阻确保总线保持高电平。
如果你需要更改到其他引脚,比如PA5和PA6,需要注意寄存器配置的不同。对于GPIOA,引脚0-7由CRL寄存器控制,8-15由CRH寄存器控制。具体配置方法可以参考STM32参考手册中关于GPIO寄存器的说明。
3. IIC时序模拟详解
模拟IIC时序是整个项目的核心,也是最容易出问题的地方。IIC协议虽然简单,但对时序要求很严格。下面我把自己调试好的IIC驱动函数分享给大家,这些函数都是经过实际项目验证的。
首先是起始信号和停止信号:
// 产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); // 设置SDA为输出
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4); // 延时满足时序要求
IIC_SDA = 0; // START信号:当SCL为高时,SDA从高变低
delay_us(4);
IIC_SCL = 0; // 钳住I2C总线,准备发送或接收数据
}
// 产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT(); // 设置SDA为输出
IIC_SCL = 0;
IIC_SDA = 0; // STOP信号:当SCL为高时,SDA从低变高
delay_us(4);
IIC_SCL = 1;
IIC_SDA = 1;
delay_us(4);
}
接下来是发送一个字节的函数:
// IIC发送一个字节
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); // 设置SDA为输出
IIC_SCL = 0; // 拉低时钟开始数据传输
for(t = 0; t < 8; t++)
{
if((txd & 0x80) >> 7)
IIC_SDA = 1;
else
IIC_SDA = 0;
txd <<= 1;
delay_us(2); // 延时满足时序要求
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
delay_us(2);
}
}
读取一个字节的函数:
// 读取一个字节
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i, receive = 0;
SDA_IN(); // 设置SDA为输入
for(i = 0; i < 8; i++)
{
IIC_SCL = 0;
delay_us(2);
IIC_SCL = 1;
receive <<= 1;
if(READ_SDA)
receive++;
delay_us(1);
}
if (!ack)
IIC_NAck(); // 发送nACK
else
IIC_Ack(); // 发送ACK
return receive;
}
等待应答信号的函数:
// 等待应答信号
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime = 0;
SDA_IN(); // SDA设置为输入
IIC_SDA = 1;
delay_us(1);
IIC_SCL = 1;
delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime > 250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL = 0; // 时钟输出0
return 0;
}
在实际调试过程中,我发现延时时间对IIC通信的成功率影响很大。不同的STM32主频需要调整延时时间,我提供的延时参数是针对72MHz主频优化的。如果你的系统主频不同,需要适当调整这些延时参数。
4. SHT30驱动实现
有了IIC基础函数,接下来就可以实现SHT30的具体驱动了。SHT30的通信过程相对简单,主要是发送测量命令,然后读取测量结果。
首先定义一些必要的宏:
#define SHT30_ADDR 0x44 // SHT30的IIC地址
#define IIC_Write 0x00 // 写模式
#define IIC_Read 0x01 // 读模式
// 测量命令
#define CMD_MEASUREMENT 0x2C06
SHT30的初始化函数:
void SHT30_Init(void)
{
GPIO_Configuration(); // 初始化GPIO
delay_init(); // 初始化延时函数
}
读取温湿度的核心函数:
void SHT30_Read_Result(void)
{
u8 buff[6];
u16 temp_raw, hum_raw;
float temperature, humidity;
// 发送测量命令
IIC_Start();
IIC_Send_Byte(SHT30_ADDR << 1 | IIC_Write);
IIC_Wait_Ack();
IIC_Send_Byte(0x2C);
IIC_Wait_Ack();
IIC_Send_Byte(0x06);
IIC_Wait_Ack();
IIC_Stop();
// 等待测量完成
delay_ms(50);
// 读取测量结果
IIC_Start();
IIC_Send_Byte(SHT30_ADDR << 1 | IIC_Read);
if(IIC_Wait_Ack() == 0)
{
buff[0] = IIC_Read_Byte(1); // 温度高8位
buff[1] = IIC_Read_Byte(1); // 温度低8位
buff[2] = IIC_Read_Byte(1); // 温度CRC校验
buff[3] = IIC_Read_Byte(1); // 湿度高8位
buff[4] = IIC_Read_Byte(1); // 湿度低8位
buff[5] = IIC_Read_Byte(0); // 湿度CRC校验
IIC_Stop();
// 数据拼接
temp_raw = (buff[0] << 8) | buff[1];
hum_raw = (buff[3] << 8) | buff[4];
// 转换为实际值
temperature = -45 + 175 * (float)temp_raw / 65535.0;
humidity = 100 * (float)hum_raw / 65535.0;
// 这里可以添加数据处理和输出代码
printf("Temperature: %.2f C, Humidity: %.2f %%\r\n", temperature, humidity);
}
}
在实际使用中,我建议添加CRC校验功能来确保数据的准确性。SHT30每个数据帧都带有CRC校验码,我们可以通过校验来排除传输错误。
CRC校验函数:
u8 SHT30_Check_Crc(u8 *data, u8 nbrOfBytes, u8 checksum)
{
u8 crc = 0xFF;
u8 byteCtr, bit;
for(byteCtr = 0; byteCtr < nbrOfBytes; byteCtr++)
{
crc ^= data[byteCtr];
for(bit = 8; bit > 0; bit--)
{
if(crc & 0x80)
{
crc = (crc << 1) ^ 0x31;
}
else
{
crc = (crc << 1);
}
}
}
return (crc == checksum) ? 0 : 1;
}
5. 常见问题与调试技巧
在调试SHT30的过程中,我遇到过不少问题,这里分享几个常见的坑和解决方法。
第一个常见问题是IIC通信完全没有响应。这时候首先要检查硬件连接:电源是否正常,上拉电阻是否接好,引脚连接是否正确。可以用示波器或者逻辑分析仪观察IIC总线上的波形,看看是否有起始信号和地址传输。
如果没有专业仪器,也可以用简单的办法:用LED指示灯来监控总线状态,或者通过串口打印调试信息。我曾经用这种方法解决过一个棘手的通信问题,发现是因为上拉电阻阻值太大导致上升沿太慢。
第二个问题是能通信但读取的数据全为0。这通常是时序问题,特别是延时时间不准确。不同主频的STM32需要调整延时参数,我建议先用示波器测量实际波形,然后对照SHT30的时序要求调整延时时间。
第三个问题是数据偶尔出错。这时候需要添加CRC校验功能,确保数据的完整性。我在一个工业项目中就遇到过因为电磁干扰导致数据出错的情况,添加CRC校验后问题就解决了。
第四个问题是功耗方面的考虑。如果需要低功耗应用,可以在不测量时将SHT30设置为休眠模式。SHT30的功耗本来就很低,但进一步优化可以延长电池供电设备的续航时间。
调试时我习惯添加详细的日志输出,这样能快速定位问题所在:
#define DEBUG_SHT30 1
#if DEBUG_SHT30
#define SHT30_DEBUG(...) printf(__VA_ARGS__)
#else
#define SHT30_DEBUG(...)
#endif
void SHT30_Read_Result(void)
{
// ... 其他代码不变
#if DEBUG_SHT30
printf("Raw data: ");
for(int i = 0; i < 6; i++)
{
printf("%02X ", buff[i]);
}
printf("\r\n");
#endif
// ... 其他代码不变
}
6. 实际应用与优化建议
在实际项目中使用SHT30时,有几个优化技巧值得分享。首先是测量频率的优化,SHT30支持不同的测量模式,包括单次测量和周期测量。对于大多数应用,单次测量模式就足够了,功耗也更低。
如果需要更高精度的测量,可以在读取数据后进行软件滤波。我常用的方法是通过多次测量取平均值,或者使用滑动平均滤波:
#define FILTER_SIZE 10
float temperature_filter[FILTER_SIZE] = {0};
float humidity_filter[FILTER_SIZE] = {0};
u8 filter_index = 0;
void SHT30_Update_Filter(float temp, float hum)
{
temperature_filter[filter_index] = temp;
humidity_filter[filter_index] = hum;
filter_index = (filter_index + 1) % FILTER_SIZE;
}
float SHT30_Get_Filtered_Temperature(void)
{
float sum = 0;
for(int i = 0; i < FILTER_SIZE; i++)
{
sum += temperature_filter[i];
}
return sum / FILTER_SIZE;
}
另一个优化点是功耗。如果使用电池供电,可以通过间歇测量的方式降低平均功耗:
void SHT30_Low_Power_Mode(void)
{
// 进入低功耗模式
IIC_Start();
IIC_Send_Byte(SHT30_ADDR << 1 | IIC_Write);
IIC_Wait_Ack();
IIC_Send_Byte(0xB0);
IIC_Wait_Ack();
IIC_Send_Byte(0x98);
IIC_Wait_Ack();
IIC_Stop();
}
对于需要长期运行的应用,我还建议添加自检功能,定期检查传感器是否正常工作:
u8 SHT30_Self_Test(void)
{
u8 status[2];
// 读取状态寄存器
IIC_Start();
IIC_Send_Byte(SHT30_ADDR << 1 | IIC_Write);
IIC_Wait_Ack();
IIC_Send_Byte(0xF3);
IIC_Wait_Ack();
IIC_Send_Byte(0x2D);
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(SHT30_ADDR << 1 | IIC_Read);
IIC_Wait_Ack();
status[0] = IIC_Read_Byte(1); // 状态高字节
status[1] = IIC_Read_Byte(0); // 状态低字节
IIC_Stop();
// 检查报警状态和校验位
if((status[1] & 0x02) || (status[1] & 0x04))
{
return 0; // 传感器异常
}
return 1; // 传感器正常
}
最后,在工程实践中,我建议将SHT30的驱动封装成独立的模块,提供清晰的接口函数,这样在不同的项目中可以快速移植和重用。
通过这个完整的实战指南,你应该能够在STM32上成功驱动SHT30温湿度传感器了。在实际项目中,我用的就是这个方案,稳定性很好,精度也满足要求。
更多推荐



所有评论(0)