1. STM32F103C8T6驱动AHT20+BMP280传感器全解析
作为一名嵌入式开发工程师,我最近在做一个环境监测项目,需要同时采集温湿度和气压数据。经过对比选型,最终选择了AHT20温湿度传感器和BMP280气压传感器的组合。这两个传感器都是I2C接口,正好可以用STM32F103C8T6的硬件I2C或者软件模拟I2C来驱动。下面我就详细分享一下整个开发过程,包括硬件连接、驱动编写、数据处理等关键环节。
1.1 传感器选型与特性分析
AHT20温湿度传感器是新一代数字式环境传感器,相比前代产品有显著提升:
- 采用3x3mm SMD封装,高度仅1.0mm,非常适合空间受限的应用
- 标准I2C接口,地址固定为0x38
- 温度测量范围-40~85℃,精度±0.3℃
- 湿度测量范围0~100%RH,精度±2%RH
- 响应时间快,湿度8秒,温度5~30秒
- 功耗低,测量模式下仅0.25mA
BMP280气压传感器则是博世公司的经典产品:
- 同样支持I2C接口(地址0x77或0x76)
- 气压测量范围300-1100hPa,相对精度±0.12hPa
- 温度测量范围-40~85℃,精度±1.0℃
- 内置温度补偿,可直接输出校准后的数据
- 功耗极低,标准模式下仅2.7μA
这两个传感器都支持3.3V供电,与STM32F103C8T6的供电电压完美匹配,不需要额外的电平转换电路。
1.2 硬件连接设计
硬件连接非常简单,只需要4根线:
code复制STM32F103C8T6 AHT20/BMP280
PB6(SCL) SCL
PB7(SDA) SDA
3.3V VCC
GND GND
实际项目中我使用了软件模拟I2C,所以任意两个GPIO都可以用作SCL和SDA。这里选择PB6和PB7是为了后续可以方便切换到硬件I2C。
注意:如果同时连接多个I2C设备,需要确保地址不冲突。AHT20的地址固定为0x38,BMP280可以通过SDO引脚选择0x76或0x77。
2. 软件驱动实现
2.1 I2C驱动基础
由于STM32标准库的硬件I2C使用较为复杂,我选择先用软件模拟实现I2C协议。这样调试更方便,也更容易移植到其他平台。
2.1.1 GPIO初始化
首先配置GPIO为开漏输出模式:
c复制void I2C_Bus_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7); // 初始高电平
}
开漏输出配合上拉电阻可以实现真正的I2C总线特性,当输出低电平时强下拉,输出高电平时由外部上拉电阻拉高。
2.1.2 I2C基本时序
软件I2C需要实现以下几个基本时序:
- 起始条件:SCL高电平时,SDA从高变低
c复制uint8_t Soft_I2C_START(void)
{
Soft_I2C_SDA_1;
Soft_I2C_NOP;
Soft_I2C_SCL_1;
Soft_I2C_NOP;
if(!Soft_I2C_SDA_STATE) return Soft_I2C_BUS_BUSY;
Soft_I2C_SDA_0;
Soft_I2C_NOP;
Soft_I2C_SCL_0;
Soft_I2C_NOP;
if(Soft_I2C_SDA_STATE) return Soft_I2C_BUS_ERROR;
return Soft_I2C_READY;
}
- 停止条件:SCL高电平时,SDA从低变高
c复制void Soft_I2C_STOP(void)
{
Soft_I2C_SDA_0;
Soft_I2C_NOP;
Soft_I2C_SCL_1;
Soft_I2C_NOP;
Soft_I2C_SDA_1;
Soft_I2C_NOP;
}
- 发送字节:每个时钟周期发送1bit,MSB先发
c复制static uint8_t Soft_I2C_SendByte(uint8_t data)
{
uint8_t i;
Soft_I2C_SCL_0;
for(i=0; i<8; i++) {
if(data & 0x80) Soft_I2C_SDA_1;
else Soft_I2C_SDA_0;
data <<= 1;
Soft_I2C_NOP;
Soft_I2C_SCL_1;
Soft_I2C_NOP;
Soft_I2C_SCL_0;
Soft_I2C_NOP;
}
return Soft_I2C_Wait_Ack();
}
- 接收字节:每个时钟周期读取1bit
c复制static uint8_t Soft_I2C_ReceiveByte(void)
{
uint8_t i, data = 0;
Soft_I2C_SDA_1;
Soft_I2C_SCL_0;
for(i=0; i<8; i++) {
Soft_I2C_SCL_1;
Soft_I2C_NOP;
data <<= 1;
if(Soft_I2C_SDA_STATE) data |= 0x01;
Soft_I2C_SCL_0;
Soft_I2C_NOP;
}
Soft_I2C_SendNACK();
return data;
}
2.2 AHT20驱动实现
2.2.1 初始化流程
AHT20的初始化需要特别注意校准状态检测:
c复制uint8_t ATH20_Init(void)
{
uint8_t tmp[2];
uint8_t count = 0;
delay_ms(40); // 上电后等待40ms
// 发送初始化命令 0xBE
tmp[0] = 0x08;
tmp[1] = 0x00;
Sensors_I2C_WriteRegister(ATH20_SLAVE_ADDRESS, 0xBE, 2, tmp);
delay_ms(500); // 等待校准完成
// 检查校准状态
while(ATH20_Read_Cal_Enable() == 0) {
// 校准未完成,发送软复位
Sensors_I2C_WriteRegister(ATH20_SLAVE_ADDRESS, 0xBA, 0, tmp);
delay_ms(200);
// 重新初始化
Sensors_I2C_WriteRegister(ATH20_SLAVE_ADDRESS, 0xBE, 2, tmp);
if(count++ >= 10) return 0; // 重试10次失败
delay_ms(500);
}
return 1;
}
关键点:
- 上电后必须等待至少40ms才能通信
- 初始化命令0xBE需要两个参数:0x08和0x00
- 必须检查状态寄存器的Bit[3]是否为1,表示校准完成
- 如果校准未完成,需要先软复位再重新初始化
2.2.2 数据读取与计算
AHT20的数据读取流程:
c复制void ATH20_Read_CTdata(uint32_t *ct)
{
uint8_t Data[6];
uint32_t RetuData = 0;
uint16_t cnt = 0;
// 1. 触发测量命令 0xAC
uint8_t tmp[2] = {0x33, 0x00};
Sensors_I2C_WriteRegister(ATH20_SLAVE_ADDRESS, 0xAC, 2, tmp);
// 2. 等待测量完成(最多100ms)
delay_ms(75); // 典型值75ms
while((ATH20_Read_Status() & 0x80) == 0x80) {
delay_ms(1);
if(cnt++ >= 100) break;
}
// 3. 读取6个字节数据
Sensors_I2C_ReadRegister(ATH20_SLAVE_ADDRESS, 0x00, 6, Data);
// 4. 计算湿度值(20bit)
RetuData = ((uint32_t)Data[1] << 12) | ((uint32_t)Data[2] << 4) | ((Data[3] & 0xF0) >> 4);
ct[0] = RetuData;
// 5. 计算温度值(20bit)
RetuData = ((uint32_t)(Data[3] & 0x0F) << 16) | ((uint32_t)Data[4] << 8) | Data[5];
ct[1] = RetuData;
}
温湿度计算公式:
c复制// 湿度计算(放大了10倍)
humi = CT_data[0] * 1000 / 1024 / 1024;
// 温度计算(放大了10倍)
temp = CT_data[1] * 200 * 10 / 1024 / 1024 - 500;
2.3 BMP280驱动实现
2.3.1 初始化与校准数据读取
BMP280的初始化流程:
c复制uint8_t BMP280_Init(void)
{
uint8_t bmp280_id;
uint8_t tmp[1];
// 1. 读取芯片ID(应该是0x58)
Sensors_I2C_ReadRegister(BMP280_SLAVE_ADDRESS, 0xD0, 1, &bmp280_id);
// 2. 读取24字节校准数据
Sensors_I2C_ReadRegister(BMP280_SLAVE_ADDRESS, 0x88, 24, (u8 *)&bmp280Cal);
// 3. 配置测量模式和过采样率
tmp[0] = (BMP280_OVERSAMP_8X << 2) | (BMP280_OVERSAMP_16X << 5) | BMP280_NORMAL_MODE;
Sensors_I2C_WriteRegister(BMP280_SLAVE_ADDRESS, 0xF4, 1, tmp);
// 4. 配置滤波系数(这里设为5)
tmp[0] = (5 << 2);
Sensors_I2C_WriteRegister(BMP280_SLAVE_ADDRESS, 0xF5, 1, tmp);
return bmp280_id;
}
关键参数说明:
- 气压过采样率设为8x(BMP280_OVERSAMP_8X)
- 温度过采样率设为16x(BMP280_OVERSAMP_16X)
- 工作模式设为正常模式(BMP280_NORMAL_MODE)
- IIR滤波系数设为5,可以有效平滑数据
2.3.2 数据读取与补偿计算
BMP280的数据读取和补偿计算较为复杂:
c复制void BMP280GetData(float* pressure, float* temperature, float* asl)
{
// 1. 读取原始数据
BMP280GetPressure();
// 2. 温度补偿计算
*temperature = BMP280CompensateT(bmp280RawTemperature) / 100.0f;
// 3. 气压补偿计算
float p = BMP280CompensateP(bmp280RawPressure) / 256.0f;
// 4. 滤波处理
presssureFilter(&p, pressure);
// 5. 计算海拔高度
*asl = BMP280PressureToAltitude(pressure);
}
温度补偿算法:
c复制static s32 BMP280CompensateT(s32 adcT)
{
s32 var1, var2, T;
var1 = ((((adcT >> 3) - ((s32)bmp280Cal.dig_T1 << 1))) * ((s32)bmp280Cal.dig_T2)) >> 11;
var2 = (((((adcT >> 4) - ((s32)bmp280Cal.dig_T1)) * ((adcT >> 4) - ((s32)bmp280Cal.dig_T1))) >> 12)
* ((s32)bmp280Cal.dig_T3)) >> 14;
bmp280Cal.t_fine = var1 + var2;
T = (bmp280Cal.t_fine * 5 + 128) >> 8;
return T;
}
气压补偿算法更为复杂,涉及64位运算:
c复制static uint32_t BMP280CompensateP(s32 adcP)
{
int64_t var1, var2, p;
var1 = ((int64_t)bmp280Cal.t_fine) - 128000;
var2 = var1 * var1 * (int64_t)bmp280Cal.dig_P6;
var2 = var2 + ((var1 * (int64_t)bmp280Cal.dig_P5) << 17);
var2 = var2 + (((int64_t)bmp280Cal.dig_P4) << 35);
var1 = ((var1 * var1 * (int64_t)bmp280Cal.dig_P3) >> 8) + ((var1 * (int64_t)bmp280Cal.dig_P2) << 12);
var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)bmp280Cal.dig_P1) >> 33;
if (var1 == 0) return 0;
p = 1048576 - adcP;
p = (((p << 31) - var2) * 3125) / var1;
var1 = (((int64_t)bmp280Cal.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
var2 = (((int64_t)bmp280Cal.dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((int64_t)bmp280Cal.dig_P7) << 4);
return (uint32_t)p;
}
海拔高度计算:
c复制static float BMP280PressureToAltitude(float* pressure)
{
if (*pressure > 0) {
return ((powf((1015.7f / *pressure), 0.1902630958f) - 1.0f) * 298.15f) / 0.0065f;
}
return 0;
}
3. 系统集成与优化
3.1 主程序框架
主程序采用轮询方式读取传感器数据:
c复制int main()
{
// 1. 系统初始化
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
Usart1_Init(9600);
I2C_Bus_Init();
// 2. 传感器初始化
if(ATH20_Init() == 0) {
printf("ATH20 Init Failed!\n");
while(1);
}
if(BMP280_Init() != 0x58) {
printf("BMP280 Init Failed!\n");
while(1);
}
// 3. 主循环
while(1) {
// 每1秒读取一次数据
if(tim_v.DataGet_Ct > 1000) {
tim_v.DataGet_Ct = 0;
// 读取AHT20数据
while(ATH20_Read_Cal_Enable() == 0) {
ATH20_Init();
delay_ms(30);
}
ATH20_Read_CTdata(CT_data);
humi = CT_data[0] * 1000 / 1024 / 1024;
temp = CT_data[1] * 200 * 10 / 1024 / 1024 - 500;
// 读取BMP280数据
BMP280GetData(&Bmp_p, &Bmp_t, &Bmp_h);
// 打印数据
printf("Temp:%.1fC, Humi:%.1f%%, Press:%.2fhPa, ASL:%.2fm\n",
temp/10.0, humi/10.0, Bmp_p, Bmp_h);
}
delay_ms(10);
}
}
3.2 数据滤波处理
为了消除传感器数据的抖动,我实现了限幅平均滤波算法:
c复制#define FILTER_NUM 5
#define FILTER_A 0.1f
static void presssureFilter(float* in, float* out)
{
static u8 i = 0;
static float filter_buf[FILTER_NUM] = {0.0};
double filter_sum = 0.0;
u8 cnt = 0;
float deta;
if(filter_buf[i] == 0.0f) {
filter_buf[i] = *in;
*out = *in;
if(++i >= FILTER_NUM) i=0;
}
else {
if(i) deta = *in-filter_buf[i-1];
else deta = *in-filter_buf[FILTER_NUM-1];
if(fabs(deta) < FILTER_A) {
filter_buf[i] = *in;
if(++i >= FILTER_NUM) i = 0;
}
for(cnt=0; cnt<FILTER_NUM; cnt++) {
filter_sum += filter_buf[cnt];
}
*out = filter_sum / FILTER_NUM;
}
}
这种滤波算法结合了限幅和平均滤波的优点:
- 只有当新数据与历史数据的差值小于阈值(FILTER_A)时,才会更新滤波缓冲区
- 输出值为缓冲区数据的平均值
- 可以有效抑制突发干扰,同时保持数据的实时性
3.3 实际测试结果
经过实际测试,系统能够稳定输出温湿度、气压和海拔数据:
code复制Temp:25.3C, Humi:45.2%, Press:1012.34hPa, ASL:120.45m
Temp:25.3C, Humi:45.1%, Press:1012.35hPa, ASL:120.43m
Temp:25.4C, Humi:45.1%, Press:1012.33hPa, ASL:120.47m
数据稳定性良好,温湿度测量结果与专业温湿度计对比,误差在传感器标称范围内。
4. 常见问题与解决方案
4.1 AHT20初始化失败
问题现象:AHT20初始化总是失败,读取的状态字Bit[3]始终为0。
可能原因:
- 电源电压不稳定,导致传感器无法正常完成校准
- I2C通信时序问题,命令未能正确发送
- 传感器本身故障
解决方案:
- 检查电源电压,确保在2.0V-5.5V范围内,最好使用3.3V
- 增加初始化重试次数和延时
- 用逻辑分析仪抓取I2C波形,确认时序符合规范
- 更换传感器测试
4.2 BMP280数据异常
问题现象:BMP280读取的气压值明显偏离实际值。
可能原因:
- 校准数据读取错误
- 补偿计算时数据溢出
- 温度补偿不正确
解决方案:
- 检查校准数据读取函数,确认24字节校准参数正确获取
- 将补偿计算过程中的中间变量打印出来,排查计算错误
- 确保先读取温度并计算t_fine,再计算气压补偿
- 使用官方提供的补偿算法参考代码进行对比
4.3 I2C通信不稳定
问题现象:偶尔读取数据失败,特别是长时间运行时。
可能原因:
- 总线受干扰
- 上拉电阻不合适
- 从设备未及时响应
解决方案:
- 缩短I2C总线长度,远离干扰源
- 选择合适的I2C上拉电阻(通常4.7kΩ)
- 增加重试机制,如代码中的Sensors_I2C_ReadRegister函数已经实现
- 降低I2C时钟频率
5. 项目优化与扩展
5.1 硬件优化建议
-
增加硬件I2C支持:虽然软件I2C方便调试,但硬件I2C效率更高。可以保留软件I2C作为备份,同时实现硬件I2C驱动。
-
优化电源设计:为传感器增加0.1μF去耦电容,减少电源噪声对测量精度的影响。
-
添加EEPROM存储:将校准参数和配置信息存储在EEPROM中,避免每次上电重新初始化。
5.2 软件优化建议
-
实现低功耗模式:利用BMP280的休眠模式,在不需要测量时降低功耗。
-
添加数据校验:对读取的数据进行CRC校验,提高系统可靠性。
-
支持多传感器:修改I2C驱动,支持同时连接多个同类型传感器。
5.3 应用扩展
-
无线传输:通过ESP8266等WiFi模块将数据上传到云平台。
-
LCD显示:添加OLED或LCD显示屏,实时显示环境参数。
-
报警功能:当温湿度或气压超过设定阈值时,触发声光报警。
这个项目完整实现了STM32F103C8T6对AHT20和BMP280传感器的驱动,涵盖了从底层I2C通信到上层数据处理的全部环节。在实际应用中,可以根据具体需求对代码进行裁剪或扩展。特别是在恶劣环境下,需要加强异常处理和抗干扰设计。