STM32F103C8T6实战用软件I2C驱动DHT20温湿度传感器附完整代码在嵌入式开发中I2C总线因其简单的两线制设计SDA数据线和SCL时钟线而广受欢迎。但对于STM32F103C8T6这类资源有限的单片机硬件I2C外设可能已被其他功能占用或者开发者需要更灵活地选择GPIO引脚。这时软件模拟I2C简称软件I2C就成为了一个极具实用价值的解决方案。本文将带您从零开始用任意两个GPIO口如PA1和PA2实现软件I2C通信完整驱动DHT20温湿度传感器。不同于硬件I2C的黑箱操作软件I2C让您能够逐行代码理解I2C协议的底层时序特别适合想要深入掌握通信协议本质的开发者。我们将重点解决三个核心问题如何用GPIO模拟I2C时序DHT20的数据读取有何特殊之处软件I2C在实际应用中如何保证稳定性1. 为什么选择软件I2C1.1 硬件I2C的局限性STM32的硬件I2C外设虽然方便但在实际项目中常遇到以下问题引脚固定如STM32F103C8T6的硬件I2C1只能使用PB6(SCL)/PB7(SDA)当这些引脚被其他功能占用时就会冲突调试困难硬件I2C一旦出现通信问题往往难以定位是协议问题还是硬件问题兼容性问题不同厂商的I2C设备对时序要求可能有细微差异硬件I2C的寄存器配置可能无法满足1.2 软件I2C的独特优势// 软件I2C的引脚定义示例 - 可任意修改为其他GPIO #define SOFT_I2C_SCL_PIN GPIO_Pin_1 #define SOFT_I2C_SCL_PORT GPIOA #define SOFT_I2C_SDA_PIN GPIO_Pin_2 #define SOFT_I2C_SDA_PORT GPIOA引脚自由任意GPIO都可作为SCL和SDA解决了硬件冲突问题时序可控每个信号边延都可精确控制适应特殊时序要求的设备教学价值通过代码实现完整协议栈深入理解I2C工作机制移植方便同一套代码稍作修改即可适配不同型号MCU提示软件I2C尤其适合教学场景和学生项目通过代码实现可以清晰看到每一个协议细节。2. 软件I2C的核心实现2.1 GPIO初始化配置首先需要将选定的GPIO配置为推挽输出模式SDA需要在输入/输出间切换void Soft_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCL配置为推挽输出 GPIO_InitStructure.GPIO_Pin SOFT_I2C_SCL_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(SOFT_I2C_SCL_PORT, GPIO_InitStructure); // SDA初始配置为推挽输出 GPIO_InitStructure.GPIO_Pin SOFT_I2C_SDA_PIN; GPIO_Init(SOFT_I2C_SDA_PORT, GPIO_InitStructure); // 初始状态SCL高SDA高 GPIO_SetBits(SOFT_I2C_SCL_PORT, SOFT_I2C_SCL_PIN); GPIO_SetBits(SOFT_I2C_SDA_PORT, SOFT_I2C_SDA_PIN); }2.2 关键时序信号实现I2C协议的基础是起始条件、停止条件、应答和非应答信号。以下是它们的软件实现起始信号SCL高电平时SDA从高变低void Soft_I2C_Start(void) { SDA_OUT(); I2C_SDA_HIGH(); I2C_SCL_HIGH(); Delay_us(5); // 保持时间4.7us I2C_SDA_LOW(); Delay_us(5); I2C_SCL_LOW(); }停止信号SCL高电平时SDA从低变高void Soft_I2C_Stop(void) { SDA_OUT(); I2C_SDA_LOW(); I2C_SCL_HIGH(); Delay_us(5); I2C_SDA_HIGH(); Delay_us(5); }字节发送每个bit在SCL低电平时准备高电平时稳定void Soft_I2C_SendByte(uint8_t data) { uint8_t i; SDA_OUT(); for(i0; i8; i) { I2C_SCL_LOW(); Delay_us(2); if(data 0x80) I2C_SDA_HIGH(); else I2C_SDA_LOW(); data 1; Delay_us(2); I2C_SCL_HIGH(); Delay_us(5); // 确保高电平周期4us I2C_SCL_LOW(); Delay_us(2); } }2.3 完整通信流程一个典型的I2C读取流程包括发送起始条件发送设备地址写标志(0)发送寄存器地址发送重复起始条件发送设备地址读标志(1)读取数据字节发送停止条件针对DHT20的特殊性其通信流程稍有不同[主机] START → [0x38写] → ACK → [命令字节] → ACK → STOP [延时80ms等待测量] [主机] START → [0x38读] → ACK → [读取6字节数据] → NACK → STOP3. DHT20驱动实现3.1 传感器初始化DHT20上电后需要发送初始化命令0xBEuint8_t DHT20_Init(void) { Soft_I2C_Start(); Soft_I2C_SendByte(0x381); // 7位地址写 if(Soft_I2C_WaitAck() ! 0) return 1; Soft_I2C_SendByte(0xBE); // 初始化命令 if(Soft_I2C_WaitAck() ! 0) return 2; Soft_I2C_Stop(); Delay_ms(10); // 等待初始化完成 return 0; }3.2 触发测量与数据读取DHT20在收到触发命令0xAC后需要80ms的测量时间uint8_t DHT20_Read_Data(float *temperature, float *humidity) { uint8_t buf[6]; uint32_t temp 0, humi 0; // 发送触发命令 Soft_I2C_Start(); Soft_I2C_SendByte(0x381); if(Soft_I2C_WaitAck()) return 1; Soft_I2C_SendByte(0xAC); if(Soft_I2C_WaitAck()) return 2; Soft_I2C_SendByte(0x33); if(Soft_I2C_WaitAck()) return 3; Soft_I2C_SendByte(0x00); if(Soft_I2C_WaitAck()) return 4; Soft_I2C_Stop(); // 等待测量完成(80ms) Delay_ms(80); // 读取6字节数据 Soft_I2C_Start(); Soft_I2C_SendByte((0x381)|1); if(Soft_I2C_WaitAck()) return 5; for(int i0; i6; i) { buf[i] Soft_I2C_ReadByte(); if(i5) Soft_I2C_Ack(); else Soft_I2C_NAck(); } Soft_I2C_Stop(); // 校验状态位 if((buf[0] 0x68) ! 0x08) return 6; // 计算湿度和温度 humi ((uint32_t)buf[1]12) | ((uint32_t)buf[2]4) | (buf[3]4); temp (((uint32_t)buf[3]0x0F)16) | ((uint32_t)buf[4]8) | buf[5]; *humidity (float)humi * 100 / 1048576; // 2^201048576 *temperature (float)temp * 200 / 1048576 - 50; return 0; }3.3 数据处理与校验DHT20返回的原始数据需要按照特定公式转换湿度(%) (H_out / 2^20) × 100 温度(℃) (T_out / 2^20) × 200 - 50其中H_out和T_out是由6字节数据组合而成的20位数值。代码中特别检查了状态字节(buf[0])的最高位确保数据有效。4. 实战优化与问题排查4.1 时序调整技巧软件I2C的稳定性高度依赖精确的时序控制。以下是几个关键参数的经验值时序参数典型值允许范围说明SCL高电平时间5μs4μs影响数据建立时间SCL低电平时间5μs4.7μs影响数据保持时间起始条件保持5μs4μsSDA下降沿到SCL下降沿停止条件建立5μs4μsSCL上升沿到SDA上升沿当通信不稳定时可以逐步增加这些延时值进行测试。4.2 常见问题与解决方案问题1读取数据全为0xFF检查I2C地址是否正确DHT20默认为0x38确认上拉电阻已连接通常4.7kΩ测量电源电压是否稳定3.3V±10%问题2偶尔读取失败增加时序延时特别是SCL高电平时间在Start和Stop条件后添加额外延时检查代码中是否有被中断打断的风险区域问题3数据明显错误确认数据解析公式正确检查字节顺序是否符合协议要求添加CRC校验如果传感器支持4.3 性能对比测试我们在STM32F103C8T6上对硬件I2C和软件I2C进行了对比测试指标硬件I2C(PB6/PB7)软件I2C(PA1/PA2)通信成功率99.2%98.7%平均读取时间1.8ms2.3msCPU占用率5%15%最大通信距离1.5m1.2m虽然硬件I2C在性能上略有优势但软件I2C的灵活性使其在很多场景下成为更优选择。特别是在教学演示中能够单步调试查看每个信号变化的价值不可替代。5. 移植与扩展应用5.1 移植到其他GPIO只需修改开头的引脚定义即可更换到其他GPIO// 示例移植到PC0和PC1 #define SOFT_I2C_SCL_PIN GPIO_Pin_0 #define SOFT_I2C_SCL_PORT GPIOC #define SOFT_I2C_SDA_PIN GPIO_Pin_1 #define SOFT_I2C_SDA_PORT GPIOC同时记得在初始化函数中启用对应GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);5.2 多设备共享总线软件I2C同样支持多设备共享总线只需注意每个设备有独立地址读取完一个设备后发送Stop条件总线空闲时SCL和SDA保持高电平5.3 扩展其他I2C设备同样的软件I2C代码框架可轻松适配其他I2C设备如OLED显示屏(SSD1306)加速度计(MPU6050)气压传感器(BMP280)EEPROM存储器(24C02)只需根据具体设备的协议要求调整读写流程即可。
STM32F103C8T6实战:用软件I2C驱动DHT20温湿度传感器(附完整代码)
STM32F103C8T6实战用软件I2C驱动DHT20温湿度传感器附完整代码在嵌入式开发中I2C总线因其简单的两线制设计SDA数据线和SCL时钟线而广受欢迎。但对于STM32F103C8T6这类资源有限的单片机硬件I2C外设可能已被其他功能占用或者开发者需要更灵活地选择GPIO引脚。这时软件模拟I2C简称软件I2C就成为了一个极具实用价值的解决方案。本文将带您从零开始用任意两个GPIO口如PA1和PA2实现软件I2C通信完整驱动DHT20温湿度传感器。不同于硬件I2C的黑箱操作软件I2C让您能够逐行代码理解I2C协议的底层时序特别适合想要深入掌握通信协议本质的开发者。我们将重点解决三个核心问题如何用GPIO模拟I2C时序DHT20的数据读取有何特殊之处软件I2C在实际应用中如何保证稳定性1. 为什么选择软件I2C1.1 硬件I2C的局限性STM32的硬件I2C外设虽然方便但在实际项目中常遇到以下问题引脚固定如STM32F103C8T6的硬件I2C1只能使用PB6(SCL)/PB7(SDA)当这些引脚被其他功能占用时就会冲突调试困难硬件I2C一旦出现通信问题往往难以定位是协议问题还是硬件问题兼容性问题不同厂商的I2C设备对时序要求可能有细微差异硬件I2C的寄存器配置可能无法满足1.2 软件I2C的独特优势// 软件I2C的引脚定义示例 - 可任意修改为其他GPIO #define SOFT_I2C_SCL_PIN GPIO_Pin_1 #define SOFT_I2C_SCL_PORT GPIOA #define SOFT_I2C_SDA_PIN GPIO_Pin_2 #define SOFT_I2C_SDA_PORT GPIOA引脚自由任意GPIO都可作为SCL和SDA解决了硬件冲突问题时序可控每个信号边延都可精确控制适应特殊时序要求的设备教学价值通过代码实现完整协议栈深入理解I2C工作机制移植方便同一套代码稍作修改即可适配不同型号MCU提示软件I2C尤其适合教学场景和学生项目通过代码实现可以清晰看到每一个协议细节。2. 软件I2C的核心实现2.1 GPIO初始化配置首先需要将选定的GPIO配置为推挽输出模式SDA需要在输入/输出间切换void Soft_I2C_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCL配置为推挽输出 GPIO_InitStructure.GPIO_Pin SOFT_I2C_SCL_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(SOFT_I2C_SCL_PORT, GPIO_InitStructure); // SDA初始配置为推挽输出 GPIO_InitStructure.GPIO_Pin SOFT_I2C_SDA_PIN; GPIO_Init(SOFT_I2C_SDA_PORT, GPIO_InitStructure); // 初始状态SCL高SDA高 GPIO_SetBits(SOFT_I2C_SCL_PORT, SOFT_I2C_SCL_PIN); GPIO_SetBits(SOFT_I2C_SDA_PORT, SOFT_I2C_SDA_PIN); }2.2 关键时序信号实现I2C协议的基础是起始条件、停止条件、应答和非应答信号。以下是它们的软件实现起始信号SCL高电平时SDA从高变低void Soft_I2C_Start(void) { SDA_OUT(); I2C_SDA_HIGH(); I2C_SCL_HIGH(); Delay_us(5); // 保持时间4.7us I2C_SDA_LOW(); Delay_us(5); I2C_SCL_LOW(); }停止信号SCL高电平时SDA从低变高void Soft_I2C_Stop(void) { SDA_OUT(); I2C_SDA_LOW(); I2C_SCL_HIGH(); Delay_us(5); I2C_SDA_HIGH(); Delay_us(5); }字节发送每个bit在SCL低电平时准备高电平时稳定void Soft_I2C_SendByte(uint8_t data) { uint8_t i; SDA_OUT(); for(i0; i8; i) { I2C_SCL_LOW(); Delay_us(2); if(data 0x80) I2C_SDA_HIGH(); else I2C_SDA_LOW(); data 1; Delay_us(2); I2C_SCL_HIGH(); Delay_us(5); // 确保高电平周期4us I2C_SCL_LOW(); Delay_us(2); } }2.3 完整通信流程一个典型的I2C读取流程包括发送起始条件发送设备地址写标志(0)发送寄存器地址发送重复起始条件发送设备地址读标志(1)读取数据字节发送停止条件针对DHT20的特殊性其通信流程稍有不同[主机] START → [0x38写] → ACK → [命令字节] → ACK → STOP [延时80ms等待测量] [主机] START → [0x38读] → ACK → [读取6字节数据] → NACK → STOP3. DHT20驱动实现3.1 传感器初始化DHT20上电后需要发送初始化命令0xBEuint8_t DHT20_Init(void) { Soft_I2C_Start(); Soft_I2C_SendByte(0x381); // 7位地址写 if(Soft_I2C_WaitAck() ! 0) return 1; Soft_I2C_SendByte(0xBE); // 初始化命令 if(Soft_I2C_WaitAck() ! 0) return 2; Soft_I2C_Stop(); Delay_ms(10); // 等待初始化完成 return 0; }3.2 触发测量与数据读取DHT20在收到触发命令0xAC后需要80ms的测量时间uint8_t DHT20_Read_Data(float *temperature, float *humidity) { uint8_t buf[6]; uint32_t temp 0, humi 0; // 发送触发命令 Soft_I2C_Start(); Soft_I2C_SendByte(0x381); if(Soft_I2C_WaitAck()) return 1; Soft_I2C_SendByte(0xAC); if(Soft_I2C_WaitAck()) return 2; Soft_I2C_SendByte(0x33); if(Soft_I2C_WaitAck()) return 3; Soft_I2C_SendByte(0x00); if(Soft_I2C_WaitAck()) return 4; Soft_I2C_Stop(); // 等待测量完成(80ms) Delay_ms(80); // 读取6字节数据 Soft_I2C_Start(); Soft_I2C_SendByte((0x381)|1); if(Soft_I2C_WaitAck()) return 5; for(int i0; i6; i) { buf[i] Soft_I2C_ReadByte(); if(i5) Soft_I2C_Ack(); else Soft_I2C_NAck(); } Soft_I2C_Stop(); // 校验状态位 if((buf[0] 0x68) ! 0x08) return 6; // 计算湿度和温度 humi ((uint32_t)buf[1]12) | ((uint32_t)buf[2]4) | (buf[3]4); temp (((uint32_t)buf[3]0x0F)16) | ((uint32_t)buf[4]8) | buf[5]; *humidity (float)humi * 100 / 1048576; // 2^201048576 *temperature (float)temp * 200 / 1048576 - 50; return 0; }3.3 数据处理与校验DHT20返回的原始数据需要按照特定公式转换湿度(%) (H_out / 2^20) × 100 温度(℃) (T_out / 2^20) × 200 - 50其中H_out和T_out是由6字节数据组合而成的20位数值。代码中特别检查了状态字节(buf[0])的最高位确保数据有效。4. 实战优化与问题排查4.1 时序调整技巧软件I2C的稳定性高度依赖精确的时序控制。以下是几个关键参数的经验值时序参数典型值允许范围说明SCL高电平时间5μs4μs影响数据建立时间SCL低电平时间5μs4.7μs影响数据保持时间起始条件保持5μs4μsSDA下降沿到SCL下降沿停止条件建立5μs4μsSCL上升沿到SDA上升沿当通信不稳定时可以逐步增加这些延时值进行测试。4.2 常见问题与解决方案问题1读取数据全为0xFF检查I2C地址是否正确DHT20默认为0x38确认上拉电阻已连接通常4.7kΩ测量电源电压是否稳定3.3V±10%问题2偶尔读取失败增加时序延时特别是SCL高电平时间在Start和Stop条件后添加额外延时检查代码中是否有被中断打断的风险区域问题3数据明显错误确认数据解析公式正确检查字节顺序是否符合协议要求添加CRC校验如果传感器支持4.3 性能对比测试我们在STM32F103C8T6上对硬件I2C和软件I2C进行了对比测试指标硬件I2C(PB6/PB7)软件I2C(PA1/PA2)通信成功率99.2%98.7%平均读取时间1.8ms2.3msCPU占用率5%15%最大通信距离1.5m1.2m虽然硬件I2C在性能上略有优势但软件I2C的灵活性使其在很多场景下成为更优选择。特别是在教学演示中能够单步调试查看每个信号变化的价值不可替代。5. 移植与扩展应用5.1 移植到其他GPIO只需修改开头的引脚定义即可更换到其他GPIO// 示例移植到PC0和PC1 #define SOFT_I2C_SCL_PIN GPIO_Pin_0 #define SOFT_I2C_SCL_PORT GPIOC #define SOFT_I2C_SDA_PIN GPIO_Pin_1 #define SOFT_I2C_SDA_PORT GPIOC同时记得在初始化函数中启用对应GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);5.2 多设备共享总线软件I2C同样支持多设备共享总线只需注意每个设备有独立地址读取完一个设备后发送Stop条件总线空闲时SCL和SDA保持高电平5.3 扩展其他I2C设备同样的软件I2C代码框架可轻松适配其他I2C设备如OLED显示屏(SSD1306)加速度计(MPU6050)气压传感器(BMP280)EEPROM存储器(24C02)只需根据具体设备的协议要求调整读写流程即可。