1. STM32软件IIC驱动AHT20温湿度传感器实战指南
在嵌入式开发中,IIC总线因其简洁的两线制设计(SCL时钟线和SDA数据线)被广泛应用于传感器通信。但硬件IIC有时会受限于引脚分配或时序问题,这时软件模拟IIC(Software IIC)就展现出其灵活性优势。最近我在一个环境监测项目中,使用STM32F103C8T6通过软件IIC成功驱动了AHT20温湿度传感器,实测效果稳定可靠。下面将完整分享从原理到代码的实现过程,包含多个调试阶段积累的实战经验。
2. 硬件选型与核心特性解析
2.1 主控芯片STM32F103C8T6
作为STM32F1系列的经典型号,这款Cortex-M3内核芯片具有:
- 72MHz主频,满足实时性要求
- 64KB Flash + 20KB SRAM
- 多达37个GPIO,可灵活配置为软件IIC引脚
- 内置硬件IIC控制器(但本项目未使用)
提示:虽然我们使用软件模拟IIC,但硬件IIC引脚(PB6/PB7或PB10/PB11)通常仍是最佳选择,因其内部已集成上拉电阻设计。
2.2 AHT20传感器关键参数
这款新一代数字温湿度传感器相比前代DHT11/DHT22有显著提升:
- 测量范围:温度-40~85℃(±0.3℃精度),湿度0~100%RH(±2%RH精度)
- IIC通信地址:0x38(固定不可更改)
- 工作电压:2.2V-5.5V(与3.3V的STM32完美兼容)
- 低功耗特性:测量时1.2mA,待机仅0.2μA
3. 软件IIC底层驱动实现
3.1 GPIO初始化配置
使用PB6(SCL)和PB7(SDA)作为模拟IIC引脚,初始化代码如下:
c复制void IIC_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
// SCL配置为推挽输出
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// SDA初始化为开漏输出
GPIO_InitStruct.Pin = GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// 初始状态置高
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_SET);
}
3.2 关键时序函数实现
软件IIC的核心在于精确控制时序,以下是经过实测稳定的微秒级延时函数:
c复制// 微秒延时函数(基于SysTick)
void IIC_Delay_us(uint32_t us) {
uint32_t ticks = us * (SystemCoreClock / 1000000);
uint32_t start = DWT->CYCCNT;
while((DWT->CYCCNT - start) < ticks);
}
// 起始信号
void IIC_Start(void) {
SDA_OUT();
IIC_SCL_HIGH();
IIC_SDA_HIGH();
IIC_Delay_us(4);
IIC_SDA_LOW(); // 下降沿
IIC_Delay_us(4);
IIC_SCL_LOW(); // 钳住总线
}
// 停止信号
void IIC_Stop(void) {
SDA_OUT();
IIC_SCL_LOW();
IIC_SDA_LOW();
IIC_Delay_us(4);
IIC_SCL_HIGH();
IIC_SDA_HIGH(); // 上升沿
IIC_Delay_us(4);
}
4. AHT20传感器驱动开发
4.1 传感器初始化流程
AHT20上电后需要发送初始化命令(0xBE)进行校准:
c复制uint8_t AHT20_Init(void) {
uint8_t cmd[3] = {0xBE, 0x08, 0x00}; // 初始化命令
HAL_Delay(40); // 上电后至少等待20ms
IIC_Start();
IIC_Send_Byte(0x38<<1); // 写地址
if(!IIC_Wait_Ack()) return 0;
for(int i=0; i<3; i++) {
IIC_Send_Byte(cmd[i]);
if(!IIC_Wait_Ack()) return 0;
}
IIC_Stop();
HAL_Delay(10); // 等待校准完成
return 1;
}
4.2 温湿度数据读取实现
AHT20的测量触发和读取需要遵循特定时序:
c复制void AHT20_Read_Data(float *temperature, float *humidity) {
uint8_t buf[6];
uint32_t temp_raw, humi_raw;
// 触发测量
uint8_t cmd[3] = {0xAC, 0x33, 0x00};
IIC_Start();
IIC_Send_Byte(0x38<<1);
IIC_Wait_Ack();
for(int i=0; i<3; i++) {
IIC_Send_Byte(cmd[i]);
IIC_Wait_Ack();
}
IIC_Stop();
// 等待测量完成(约80ms)
HAL_Delay(100);
// 读取数据
IIC_Start();
IIC_Send_Byte(0x38<<1 | 0x01);
IIC_Wait_Ack();
for(int i=0; i<6; i++) {
buf[i] = IIC_Read_Byte(i==5?0:1);
}
IIC_Stop();
// 数据转换
humi_raw = ((uint32_t)buf[1]<<12) | ((uint32_t)buf[2]<<4) | (buf[3]>>4);
temp_raw = (((uint32_t)buf[3]&0x0F)<<16) | ((uint32_t)buf[4]<<8) | buf[5];
*humidity = (humi_raw * 100.0) / (1<<20);
*temperature = (temp_raw * 200.0) / (1<<20) - 50;
}
5. 调试经验与性能优化
5.1 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 读取全为0xFF | 接线错误/地址不对 | 检查IIC地址是否为0x38,确认SDA/SCL未接反 |
| 数据波动大 | 电源噪声 | 在VCC与GND间加0.1μF去耦电容 |
| 应答失败 | 时序过快 | 增加IIC_Delay_us的延时时间 |
| 温度值异常 | 未校准 | 确保成功执行0xBE初始化命令 |
5.2 软件IIC速度优化技巧
- GPIO速度配置:将GPIO设为最高速模式(GPIO_SPEED_FREQ_HIGH)
- 减少延时冗余:通过示波器实测调整最小稳定延时(本项目实测2μs足够)
- 循环展开:对关键时序函数使用__inline修饰
- DWT计数器:采用CPU周期计数实现精准延时(如上文IIC_Delay_us实现)
6. 完整工程架构建议
code复制AHT20_Project/
├── Core/
│ ├── Src/
│ │ ├── main.c # 主循环和任务调度
│ │ ├── i2c_soft.c # 软件IIC底层驱动
│ │ └── aht20.c # 传感器应用层
├── Drivers/
│ └── STM32F1xx_HAL_Driver/ # HAL库文件
└── Inc/
├── i2c_soft.h # IIC接口声明
└── aht20.h # 传感器API头文件
在main.c中的典型调用示例:
c复制float temp, humi;
if(AHT20_Init()) {
while(1) {
AHT20_Read_Data(&temp, &humi);
printf("Temp: %.1fC Humi: %.1f%%\r\n", temp, humi);
HAL_Delay(2000); // 2秒更新一次
}
}
实际测试中发现,AHT20在连续读取时,每次测量间隔建议至少1秒以保证数据准确性。对于需要快速响应的场景,可以设置为800ms但需注意长期使用可能导致传感器温升影响精度。
