STM32F103指南者上,用软件I2C驱动AHT20温湿度传感器的保姆级避坑指南
2026/5/28 11:44:48 网站建设 项目流程

STM32F103指南者上软件I2C驱动AHT20温湿度传感器的实战指南

最近在调试STM32F103的温湿度传感器时,发现硬件I2C总有些"水土不服"——要么初始化失败,要么数据读取不稳定。后来改用软件模拟I2C,问题迎刃而解。如果你也遇到过类似困扰,这篇实战指南将带你避开那些我踩过的坑。

1. 为什么选择软件I2C?

在嵌入式开发中,I2C通信有两种实现方式:硬件I2C和软件I2C。硬件I2C虽然方便,但在STM32F103这类资源有限的MCU上常常遇到这些问题:

  • 引脚冲突:硬件I2C固定占用PB6/PB7,当这些引脚被其他功能占用时
  • 库兼容性问题:不同厂家的I2C设备对时序要求各异,标准库可能不兼容
  • 调试困难:硬件I2C出错时往往难以定位问题根源

相比之下,软件I2C的优势很明显:

  1. 引脚自由:可以任意选择GPIO作为SCL和SDA
  2. 时序可控:完全由代码控制,可以适配各种特殊时序要求
  3. 调试直观:可以通过逻辑分析仪清晰看到每个时钟脉冲和数据变化

提示:当你的项目需要同时驱动多个I2C设备,或者遇到硬件I2C不稳定时,软件I2C往往是更可靠的选择。

2. 硬件准备与引脚配置

2.1 所需材料

  • STM32F103指南者开发板
  • AHT20温湿度传感器模块
  • 杜邦线若干
  • 逻辑分析仪(可选,但强烈推荐)

2.2 引脚连接建议

我推荐使用以下GPIO连接方案:

传感器引脚STM32引脚备注
VCC3.3V注意AHT20是3.3V器件
GNDGND
SCLPB8可自定义
SDAPB9可自定义

选择PB8/PB9的原因是:

  • 这两个引脚通常不会被其他功能占用
  • 在指南者开发板上位置方便接线
  • 支持开漏输出模式,符合I2C规范

2.3 GPIO初始化代码

// 软件I2C引脚初始化 void SW_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 使能GPIOB时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 配置PB8(SCL)和PB9(SDA)为开漏输出 GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; 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); // 初始状态拉高 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET); }

3. 软件I2C时序实现细节

3.1 基础时序函数

软件I2C的核心是精确控制SCL和SDA的时序。以下是必须实现的四个基本函数:

  1. 起始条件(S)
void I2C_Start(void) { SDA_HIGH(); SCL_HIGH(); delay_us(5); // 保持时间>4.7us SDA_LOW(); delay_us(5); SCL_LOW(); }
  1. 停止条件(P)
void I2C_Stop(void) { SDA_LOW(); SCL_LOW(); delay_us(5); SCL_HIGH(); delay_us(5); SDA_HIGH(); delay_us(5); }
  1. 发送一个字节
void I2C_SendByte(uint8_t byte) { for(int i=0; i<8; i++) { if(byte & 0x80) SDA_HIGH(); else SDA_LOW(); delay_us(2); SCL_HIGH(); delay_us(5); SCL_LOW(); delay_us(3); byte <<= 1; } // 等待ACK SDA_HIGH(); SCL_HIGH(); delay_us(5); // 这里可以检查ACK状态 SCL_LOW(); }
  1. 接收一个字节
uint8_t I2C_ReadByte(uint8_t ack) { uint8_t byte = 0; SDA_HIGH(); for(int i=0; i<8; i++) { byte <<= 1; SCL_HIGH(); delay_us(3); if(SDA_READ()) byte |= 0x01; delay_us(2); SCL_LOW(); delay_us(5); } // 发送ACK/NACK if(ack) SDA_LOW(); else SDA_HIGH(); delay_us(2); SCL_HIGH(); delay_us(5); SCL_LOW(); SDA_HIGH(); return byte; }

3.2 时序调试技巧

调试I2C时最常见的三个问题及解决方法:

  1. 无ACK响应

    • 检查设备地址是否正确(AHT20地址是0x38)
    • 确认上拉电阻是否接好(通常4.7kΩ)
    • 用逻辑分析仪查看起始条件是否符合标准
  2. 数据读取错误

    • 调整SCL时钟频率(AHT20建议100kHz-400kHz)
    • 检查延时函数精度,必要时使用定时器实现精确延时
  3. 通信不稳定

    • 缩短连接线长度
    • 在SCL和SDA上增加10-100pF的滤波电容

4. AHT20驱动实现

4.1 初始化流程

AHT20需要特定的初始化序列才能开始工作:

  1. 上电后等待至少100ms
  2. 发送0xBE命令进行校准
  3. 等待校准完成(约10ms)
  4. 可以开始正常测量
void AHT20_Init(void) { // 发送初始化命令 I2C_Start(); I2C_SendByte(0x38 << 1); // 设备地址 + 写 I2C_SendByte(0xBE); // 初始化命令 I2C_SendByte(0x08); // 参数 I2C_SendByte(0x00); // 参数 I2C_Stop(); // 等待初始化完成 HAL_Delay(10); }

4.2 读取温湿度数据

AHT20的数据读取有特定的时序要求:

  1. 触发测量命令(0xAC)
  2. 等待测量完成(约80ms)
  3. 读取6字节数据
  4. 计算实际温湿度值
void AHT20_Read(float *temperature, float *humidity) { uint8_t data[6]; // 触发测量 I2C_Start(); I2C_SendByte(0x38 << 1); // 设备地址 + 写 I2C_SendByte(0xAC); // 触发测量 I2C_SendByte(0x33); // 参数 I2C_SendByte(0x00); // 参数 I2C_Stop(); // 等待测量完成 HAL_Delay(80); // 读取数据 I2C_Start(); I2C_SendByte(0x38 << 1 | 0x01); // 设备地址 + 读 for(int i=0; i<5; i++) { data[i] = I2C_ReadByte(1); // 发送ACK } data[5] = I2C_ReadByte(0); // 最后一个字节发送NACK I2C_Stop(); // 数据处理 uint32_t hum_raw = ((uint32_t)data[1] << 12) | ((uint32_t)data[2] << 4) | (data[3] >> 4); uint32_t temp_raw = (((uint32_t)data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5]; *humidity = (float)hum_raw * 100 / 0x100000; *temperature = (float)temp_raw * 200 / 0x100000 - 50; }

5. 常见问题排查

在实际项目中,我遇到过各种奇怪的问题,这里分享几个典型案例:

案例1:读取的数据全是0xFF

  • 原因:SDA线接触不良,实际处于浮空状态
  • 解决:检查连接,确保上拉电阻正常工作

案例2:偶尔读取失败

  • 原因:电源不稳定导致传感器复位
  • 解决:在VCC和GND之间增加100μF电容

案例3:温度值明显偏高

  • 原因:传感器靠近MCU或其他发热元件
  • 解决:将传感器远离热源,或等待温度稳定后再读取

调试时建议准备一个逻辑分析仪,可以直观地看到I2C总线上的实际波形。当通信失败时,对比标准I2C时序图,很容易发现是起始条件、停止条件还是数据位时序出了问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询