STM32 HAL库驱动AHT20温湿度传感器(I2C通信实战)
1. 项目概述与环境准备
大家好,今天我来分享一个非常实用的STM32实战项目——使用HAL库驱动AHT20温湿度传感器。这个项目特别适合刚接触STM32和I2C通信的朋友,我会从最基础的硬件连接开始,一步步带你完成整个开发过程。
AHT20是一款高精度的温湿度传感器,采用I2C接口通信,测量温度范围-40℃~85℃,湿度范围0~100%RH。相比常见的DHT11,它的精度更高,稳定性更好,而且采用数字接口,抗干扰能力更强。在实际项目中,我经常用它来做环境监测,比如智能家居、农业大棚、仓库监控等场景。
需要准备的硬件设备:
- STM32开发板(我用的STM32F103C8T6)
- AHT20温湿度传感器模块
- 杜邦线若干
- USB转串口模块(用于调试输出)
- ST-Link下载器(或其他调试器)
软件环境:
- STM32CubeMX 6.0+
- Keil MDK 5.3+
- 串口调试助手(推荐使用波特律动或者Putty)
我第一次用AHT20的时候,发现它的精度确实比DHT11好很多,温度误差能控制在±0.3℃以内,湿度误差±2%RH。不过要注意的是,AHT20需要正确的初始化流程,否则读数会不准,这个后面我会详细说明。
2. 硬件连接与I2C原理
2.1 硬件连接详解
先来看看硬件怎么连接。AHT20模块一般有4个引脚:VCC、GND、SDA、SCL。接线非常简单:
| AHT20引脚 | STM32引脚 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SDA | PB7 |
| SCL | PB6 |
这里我选择PB6和PB7作为I2C引脚,是因为这两个引脚在STM32F103C8T6上正好是I2C1的备用引脚。在实际焊接时,建议使用4.7K的上拉电阻将SDA和SCL线拉到3.3V,这样能保证信号稳定性。不过很多AHT20模块已经内置了上拉电阻,所以直接连接也可以。
注意: 一定要确保VCC接3.3V,不要接到5V!AHT20是3.3V器件,接5V会烧坏传感器。我之前就犯过这个错误,损失了一个传感器。
2.2 I2C通信原理浅析
I2C是一种同步、半双工的串行通信协议,只需要两根线:SDA(数据线)和SCL(时钟线)。它支持多主多从的架构,每个设备都有唯一的地址,AHT20的默认地址是0x38(7位地址),换算成8位写地址是0x70,读地址是0x71。
I2C的通信过程是这样的:主机(STM32)先发出起始条件,然后发送从机地址和读写位,等待从机应答,之后开始数据传输,每传输一个字节都要等待应答,最后发出停止条件结束通信。
与软件模拟I2C相比,硬件I2C的效率更高,占用CPU资源少,而且稳定性更好。STM32的硬件I2C外设可以自动处理时序,我们只需要配置好参数,调用HAL库函数就可以了。
3. CubeMX工程配置
3.1 引脚配置
打开STM32CubeMX,新建工程,选择你的STM32型号。首先配置时钟树,根据你的晶振频率设置系统时钟,我一般喜欢调到72MHz,这样性能足够用。
在Pinout & Configuration标签页中,找到I2C1,选择I2C模式。将SDA设置为PB7,SCL设置为PB6。模式选择Standard Mode,速度用默认的100kHz就可以,AHT20最高支持400kHz,但100kHz更稳定。
接下来配置串口,我们需要用串口输出温湿度数据。找到USART1(或其他可用的串口),模式选择Asynchronous,波特率设为115200,数据位8位,无校验,停止位1位。
3.2 工程生成设置
在Project Manager页面,设置工程名称和路径,Toolchain选择MDK-ARM。在Code Generator页面,勾选"Generate peripheral initialization as a pair of .c/.h files per peripheral",这样每个外设的初始化代码都会生成单独的文件,方便管理。
还有一个重要的设置:在Project Properties -> C/C++ Build -> Settings -> MCU Settings中,勾选"Use float with printf (-u _printf_float)",这样我们才能在printf中正常输出浮点数。
点击Generate Code生成工程,用Keil打开。第一次生成工程时,我建议先编译一下,确保没有错误。
4. 代码实现详解
4.1 AHT20驱动代码
新建两个文件:aht20.h和aht20.c,添加到工程中。先来看头文件aht20.h:
#ifndef __AHT20_H__
#define __AHT20_H__
#include "main.h"
#include "i2c.h"
#define AHT20_ADDRESS 0x70 // 7位地址左移一位
void AHT20_Init(void);
void AHT20_Read(float *temperature, float *humidity);
#endif
接下来是aht20.c的实现,这是最核心的部分:
#include "aht20.h"
#include "stdio.h"
void AHT20_Init(void)
{
uint8_t cmd[3] = {0xBE, 0x08, 0x00}; // 初始化命令
uint8_t status = 0;
// 读取状态字
HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, &status, 1, 100);
// 检查传感器是否已经初始化
if ((status & 0x08) == 0) {
// 发送初始化命令
HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, cmd, 3, 100);
HAL_Delay(10); // 等待初始化完成
}
}
初始化函数中,我们先读取AHT20的状态字,检查第3位是否为1(表示已经初始化)。如果没有初始化,就发送0xBE命令进行初始化。这里有个坑要注意:发送初始化命令后需要延时10ms左右,等待传感器完成初始化。
4.2 数据读取函数
数据读取函数稍微复杂一些,需要处理数据转换:
void AHT20_Read(float *temperature, float *humidity)
{
uint8_t cmd[3] = {0xAC, 0x33, 0x00}; // 触发测量命令
uint8_t data[6] = {0};
// 发送测量命令
HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, cmd, 3, 100);
HAL_Delay(75); // 等待测量完成,至少75ms
// 读取6字节数据
HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, data, 6, 100);
// 检查状态位
if ((data[0] & 0x80) == 0) {
// 湿度计算
uint32_t hum_raw = ((uint32_t)data[1] << 12) |
((uint32_t)data[2] << 4) |
((uint32_t)data[3] >> 4);
*humidity = (float)hum_raw * 100.0f / (1 << 20);
// 温度计算
uint32_t temp_raw = (((uint32_t)data[3] & 0x0F) << 16) |
((uint32_t)data[4] << 8) |
(uint32_t)data[5];
*temperature = (float)temp_raw * 200.0f / (1 << 20) - 50.0f;
} else {
// 读取失败
*temperature = 0;
*humidity = 0;
}
}
这个函数先发送测量命令0xAC,然后等待75ms让传感器完成测量。读取到的6字节数据中,前4位是状态字,后面20位是湿度数据,再后面20位是温度数据。需要经过一系列位移和转换才能得到真实的温湿度值。
我第一次写这个函数的时候,位移操作老是搞错,导致读数不对。后来仔细看了数据手册才明白,AHT20的数据格式是20位的原始数据,需要按公式转换成实际值。
5. 主程序与串口输出
5.1 main函数实现
在主函数中,我们需要初始化外设,然后循环读取温湿度数据:
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "aht20.h"
#include "stdio.h"
// 重定义printf,方便串口输出
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART2_UART_Init();
AHT20_Init(); // 初始化AHT20
float temperature, humidity;
char buffer[50];
while (1) {
AHT20_Read(&temperature, &humidity);
// 格式化输出
sprintf(buffer, "温度: %.1f ℃, 湿度: %.1f %%\r\n", temperature, humidity);
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY);
HAL_Delay(2000); // 每2秒读取一次
}
}
这里我重定义了printf函数,这样可以直接用printf通过串口输出数据,比用HAL_UART_Transmit方便很多。每2秒读取一次温湿度数据,这个间隔可以根据需要调整,但最好不要小于1秒,因为AHT20需要时间完成测量。
5.2 数据处理与优化
在实际使用中,我发现AHT20的读数偶尔会有跳动,可以通过软件滤波来平滑数据:
#define SAMPLE_SIZE 5
float filter_data(float *values, uint8_t count)
{
float sum = 0;
for (uint8_t i = 0; i < count; i++) {
sum += values[i];
}
return sum / count;
}
// 在主循环中使用
float temp_samples[SAMPLE_SIZE];
float hum_samples[SAMPLE_SIZE];
uint8_t sample_index = 0;
while (1) {
AHT20_Read(&temperature, &humidity);
temp_samples[sample_index] = temperature;
hum_samples[sample_index] = humidity;
sample_index = (sample_index + 1) % SAMPLE_SIZE;
float avg_temp = filter_data(temp_samples, SAMPLE_SIZE);
float avg_hum = filter_data(hum_samples, SAMPLE_SIZE);
// 输出平滑后的数据
sprintf(buffer, "温度: %.1f ℃, 湿度: %.1f %%\r\n", avg_temp, avg_hum);
HAL_UART_Transmit(&huart2, (uint8_t*)buffer, strlen(buffer), HAL_MAX_DELAY);
HAL_Delay(1000);
}
这种简单的移动平均滤波能有效减少读数跳动,使数据显示更稳定。在我的实际测试中,滤波后的数据波动明显减小,更适合长期监测应用。
6. 常见问题与调试技巧
6.1 I2C通信失败排查
I2C通信最容易出问题,下面是我总结的排查步骤:
-
检查硬件连接:用万用表测量SDA和SCL线的电压,空闲时应该是高电平(3.3V)。如果电压偏低,可能是上拉电阻太大或者漏接了上拉电阻。
-
检查地址是否正确:AHT20的地址是0x38(7位),但HAL库要求左移一位,所以写地址是0x70,读地址是0x71。可以用I2C扫描程序检查设备是否在线。
-
时序问题:AHT20的测量需要时间,发送测量命令后必须等待至少75ms才能读取数据。如果等待时间不够,读到的会是无效数据。
-
电源噪声:AHT20对电源噪声比较敏感,建议在VCC和GND之间加一个100nF的滤波电容。
我遇到过最奇怪的问题是读数偶尔为0,后来发现是电源噪声导致的,加上滤波电容后就稳定了。
6.2 数据精度优化
要提高测量精度,可以注意以下几点:
-
避免传感器自加热:尽量降低采样频率,不要过于频繁地读取数据。AHT20在测量时会产生少量热量,频繁测量会导致温度读数偏高。
-
正确安装位置:不要让传感器直接暴露在阳光下或靠近热源,避免空气不流通的死角。
-
校准:虽然AHT20出厂时已经校准,但如果对精度要求极高,可以用标准温湿度计进行对比,然后在代码中加入校准偏移量。
在我的项目中,通常温度精度能到±0.5℃,湿度±3%以内,完全满足大多数应用需求。
7. 项目扩展与应用
7.1 添加OLED显示
除了串口输出,还可以添加OLED显示屏实时显示温湿度。SSD1306 OLED屏也是I2C接口,可以和AHT20共用I2C总线:
// 初始化OLED
OLED_Init();
// 在主循环中显示数据
char temp_str[10], hum_str[10];
sprintf(temp_str, "%.1fC", temperature);
sprintf(hum_str, "%.1f%%", humidity);
OLED_ShowString(0, 0, "Temperature:", 16);
OLED_ShowString(0, 16, temp_str, 16);
OLED_ShowString(0, 32, "Humidity:", 16);
OLED_ShowString(0, 48, hum_str, 16);
这样就能在OLED屏上实时显示温湿度了,做成一个独立的温湿度监测仪。
7.2 数据上传与远程监控
还可以通过ESP8266 WiFi模块将数据上传到云平台,实现远程监控:
// 连接WiFi
ESP8266_Connect("SSID", "password");
// 上传数据到云平台
sprintf(post_data, "temp=%.1f&hum=%.1f", temperature, humidity);
ESP8266_HTTPPost("api.thingspeak.com", "80", "/update", post_data);
我做过一个农业大棚监控系统,就是用STM32+AHT20采集数据,通过ESP8266上传到云平台,农民在手机上就能随时查看大棚内的温湿度。
7.3 报警功能
添加报警功能,当温湿度超过设定范围时触发报警:
#define TEMP_MAX 30.0
#define TEMP_MIN 10.0
#define HUM_MAX 80.0
#define HUM_MIN 20.0
if (temperature > TEMP_MAX || temperature < TEMP_MIN) {
// 温度异常,触发报警
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 点亮LED
sprintf(alarm_msg, "温度异常: %.1f ℃", temperature);
HAL_UART_Transmit(&huart2, (uint8_t*)alarm_msg, strlen(alarm_msg), HAL_MAX_DELAY);
}
if (humidity > HUM_MAX || humidity < HUM_MIN) {
// 湿度异常,触发报警
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET); // 点亮另一个LED
sprintf(alarm_msg, "湿度异常: %.1f %%", humidity);
HAL_UART_Transmit(&huart2, (uint8_t*)alarm_msg, strlen(alarm_msg), HAL_MAX_DELAY);
}
这个功能在仓库监控、实验室等场景非常有用,可以及时发现环境异常。
在实际项目中,我发现AHT20的长期稳定性很好,连续运行几个月读数都不会漂移。不过要注意防尘防潮,灰尘积累会影响湿度测量的准确性。
更多推荐



所有评论(0)