前言
在嵌入式物联网项目中,温湿度采集是非常常见的需求。AHT20是一款高精度、低成本的数字温湿度传感器,广泛应用于智能家居、环境监测等领域。本文将详细介绍AHT20传感器的工作原理,并基于STM32F4平台,手把手教你实现一个完整的AHT20驱动代码。

一、AHT20传感器简介
1.1 产品概述
AHT20,新一代温湿度传感器在尺寸与智能 方面建立了新的标准:它嵌入了适于回流焊 的双列扁平无引脚SMD 封装,底面 3 x 3mm , 高度1.0mm。传感器输出经过标定的数字信 2 号,标准 I2C 格式。 AHT20 配有一个全新设计的 ASIC专用芯片、 一个经过改进的MEMS半导体电容式湿度传感 元件和一个标准的片上温度传感元件,其性 能已经大大提升甚至超出了前一代传感器的 可靠性水平,新一代温湿度传感器,经过改 进使其在恶劣环境下的性能更稳定。每一个传感器都经过校准和测试,在产品表 面印有产品批号。由于对传感器做了改良和 微型化改进,因此它的性价比更高,并且最 终所有设备都将得益于尖端的节能运行模式。其具备以下特点:
-
供电电压:2.2V ~ 5.5V
-
湿度测量范围:0% ~ 100% RH
-
温度测量范围:-40℃ ~ +85℃
-
湿度精度:±2% RH
-
温度精度:±0.3℃
-
I2C通信速率:最高1MHz
-
I2C设备地址:0x38(7位地址)或0x70(写操作地址)
1.2 引脚说明
| 引脚 | 名称 | 功能描述 |
|---|---|---|
| 1 | VDD | 电源正极(2.2V-5.5V) |
| 2 | SDA | I2C数据线 |
| 3 | GND | 电源地 |
| 4 | SCL | I2C时钟线 |
| 5 | NC | 悬空 |
| 6 | NC | 悬空 |
1.3 工作流程
AHT20的典型工作流程如下:
上电 → 初始化(发送初始化命令)→ 等待校准完成 → 触发测量 → 等待测量完成 → 读取数据 → 解析数据
二、I2C通信协议详解
AHT20使用标准I2C协议进行通信,7位设备地址为0x38(写操作地址0x70,读操作地址0x71)。
2.1 命令列表
| 命令 | 操作码 | 说明 |
|---|---|---|
| 初始化 | 0xBE, 0x08, 0x00 | 发送3字节初始化序列 |
| 触发测量 | 0xAC, 0x33, 0x00 | 启动温湿度测量 |
| 读取状态 | 0x71 | 读取传感器状态字 |
| 软复位 | 0xBA | 软件复位传感器 |
2.2 状态字说明
AHT20的状态字占用1字节(8位),各比特位含义如下:
| 位 | 含义 |
|---|---|
| Bit[7] | 忙闲指示位:1=忙,0=空闲 |
| Bit[6:5] | 保留 |
| Bit[4] | 保留 |
| Bit[3] | 校准使能位:1=已校准,0=未校准 |
| Bit[2:0] | 保留 |
2.3 数据格式
测量完成后,传感器返回6字节数据(具体见下面的图片):
字节0:状态字
字节1:湿度数据高8位
字节2:湿度数据中8位
字节3:湿度数据低4位 + 温度数据高4位
字节4:温度数据中8位
字节5:温度数据低8位

三、STM32驱动代码实现
3.1 硬件初始化
首先需要初始化I2C2外设,使用GPIOA的Pin10(SCL)和Pin11(SDA):
cpp
#include <stdbool.h>
#include <stdint.h>
#include "stm32f4xx.h"
#include "cpu_tick.h"
static bool aht20_write(uint8_t data[], uint32_t length);
static bool aht20_read(uint8_t data[], uint32_t length);
static bool aht20_is_ready(void);
bool aht20_init(void)
{
GPIO_InitTypeDef GPIO_InitStructre;
GPIO_StructInit(&GPIO_InitStructre);
GPIO_InitStructre.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructre.GPIO_OType = GPIO_OType_OD; // 开漏输出
GPIO_InitStructre.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上下拉
GPIO_InitStructre.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructre.GPIO_Speed = GPIO_High_Speed;
GPIO_Init(GPIOA, &GPIO_InitStructre);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_I2C2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_I2C2);
I2C_InitTypeDef I2C_InitStructure;
I2C_StructInit(&I2C_InitStructure);
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_ClockSpeed = 100ul * 1000ul; // 100kHz
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_OwnAddress1 = 0x11;
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
cpu_delay_ms(40); // 上电等待
// 检查传感器是否就绪
if(aht20_is_ready())
return true;
// 发送初始化命令
if(!aht20_write((uint8_t []){0xBE, 0x08, 0x00}, 3))
return false;
// 等待初始化完成
for(uint32_t t = 0; t < 100; t ++)
{
cpu_delay_ms(1);
if(aht20_is_ready())
return true;
}
return false;
}
3.2 I2C通信封装
为了提高代码的可读性和可维护性,定义了I2C操作宏:
cpp
#define I2C_CHECK_EVENT(EVENT, TIMEOUT) \
do { \
uint32_t timeout = TIMEOUT; \
while (!I2C_CheckEvent(I2C2, EVENT) && timeout > 0) { \
cpu_delay_us(10); \
timeout -= 10; \
} \
if (timeout <= 0) \
return false; \
} while (0)
static bool aht20_write(uint8_t data[], uint32_t length)
{
I2C_AcknowledgeConfig(I2C2, ENABLE);
I2C_GenerateSTART(I2C2, ENABLE);
I2C_CHECK_EVENT(I2C_EVENT_MASTER_MODE_SELECT, 1000);
I2C_Send7bitAddress(I2C2, 0x70, I2C_Direction_Transmitter);
I2C_CHECK_EVENT(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, 1000);
for(uint32_t i = 0; i < length; i++)
{
I2C_SendData(I2C2, data[i]);
I2C_CHECK_EVENT(I2C_EVENT_MASTER_BYTE_TRANSMITTING, 1000);
}
I2C_GenerateSTOP(I2C2, ENABLE);
return true;
}
static bool aht20_read(uint8_t data[], uint32_t length)
{
I2C_AcknowledgeConfig(I2C2, ENABLE);
I2C_GenerateSTART(I2C2, ENABLE);
I2C_CHECK_EVENT(I2C_EVENT_MASTER_MODE_SELECT, 1000);
I2C_Send7bitAddress(I2C2, 0x70, I2C_Direction_Receiver);
I2C_CHECK_EVENT(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED, 1000);
for(uint32_t i = 0; i < length; i++)
{
// 最后一个字节不需要ACK
if(i == length - 1)
{
I2C_AcknowledgeConfig(I2C2, DISABLE);
}
I2C_CHECK_EVENT(I2C_EVENT_MASTER_BYTE_RECEIVED, 1000);
data[i] = I2C_ReceiveData(I2C2);
}
I2C_GenerateSTOP(I2C2, ENABLE);
return true;
}
3.3 状态读取与判断
cpp
// 读取AHT20的状态字
static bool aht20_read_status(uint8_t *status)
{
uint8_t cmd = 0x71;
if(!aht20_write(&cmd, 1))
return false;
if(!aht20_read(status, 1))
return false;
return true;
}
// 判断传感器是否忙(Bit[7])
static bool aht20_is_busy(void)
{
uint8_t status;
if (!aht20_read_status(&status))
return false;
return (status & 0x80) != 0;
}
// 判断传感器是否校准就绪(Bit[3])
static bool aht20_is_ready(void)
{
uint8_t status;
if (!aht20_read_status(&status))
return false;
return (status & 0x08) != 0;
}
3.4 测量功能实现
cpp
bool aht20_start_measurement(void)
{
return aht20_write((uint8_t[]){0xAC, 0x33, 0x00}, 3);
}
bool aht20_wait_for_measurement(void)
{
for (uint32_t t = 0; t < 200; t++)
{
cpu_delay_ms(1);
if (!aht20_is_busy())
{
return true;
}
}
return false;
}
bool aht20_read_measurement(float *temperature, float *humidity)
{
uint8_t data[6];
if (!aht20_read(data, 6))
return false;
// 解析湿度数据
uint32_t raw_humidity = ((uint32_t)data[1] << 12) |
((uint32_t)data[2] << 4) |
((uint32_t)(data[3] & 0xF0) >> 4);
// 解析温度数据
uint32_t raw_temperature = ((uint32_t)(data[3] & 0x0F) << 16) |
((uint32_t)data[4] << 8) |
((uint32_t)data[5]);
// 转换为实际物理量
*humidity = (float)raw_humidity * 100.0f / (float)0x100000;
*temperature = (float)raw_temperature * 200.0f / (float)0x100000 - 50.0f;
return true;
}
四、使用示例
cpp
#include "aht20.h"
int main(void)
{
float temperature, humidity;
// 系统初始化
cpu_tick_init(); // 假设有滴答定时器初始化
I2C_Cmd(I2C2, ENABLE);
// 初始化AHT20
if (aht20_init())
{
printf("AHT20初始化成功\r\n");
}
else
{
printf("AHT20初始化失败\r\n");
while(1);
}
while(1)
{
// 触发测量
if (aht20_start_measurement())
{
// 等待测量完成
if (aht20_wait_for_measurement())
{
// 读取测量结果
if (aht20_read_measurement(&temperature, &humidity))
{
printf("温度: %.2f℃, 湿度: %.2f%%\r\n", temperature, humidity);
}
}
}
// 每秒采集一次
cpu_delay_ms(1000);
}
}
五、总结
本文详细介绍了AHT20温湿度传感器的特性和使用方法,并给出了完整的STM32驱动代码实现。通过本文的学习,你可以:
-
理解AHT20的工作原理和通信协议
-
掌握I2C通信的编程方法
-
学会状态字解析和数据转换
-
能够在实际项目中快速集成AHT20传感器
希望本文对你有所帮助。如果在实际使用中遇到问题,欢迎在评论区留言交流。