
基于立创GD32E230C8T6开发板的SHT20温湿度传感器I2C驱动移植与数据采集实战最近在做一个环境监测的小项目需要用到温湿度传感器。SHT20这款瑞士Sensirion的传感器口碑一直不错精度高、体积小而且直接输出数字信号用起来很方便。正好手头有立创的GD32E230C8T6开发板我就想着把SHT20的驱动移植上去。整个过程下来发现网上虽然资料多但针对GD32E230的完整教程还真不多踩了几个小坑。今天我就把从硬件连接到软件驱动再到数据读取和计算的完整过程手把手分享给大家希望能帮你快速上手。这篇文章适合正在学习GD32或者需要快速实现温湿度采集功能的嵌入式初学者。咱们不扯那些虚的直接看代码、连电路、调通它。1. 认识我们的“主角”SHT20传感器在写代码之前咱们得先了解要驱动的对象。SHT20是一款数字式温湿度一体传感器它最大的优点就是“省事”。数字接口它使用I2C通信只需要两根线SCL时钟线和SDA数据线就能和单片机“对话”省去了模拟信号采集和复杂的校准过程。高精度温度测量精度能达到±0.3℃湿度精度为±3%RH对于大多数环境监测应用来说完全够用。低功耗工作电流非常小在测量间隙可以进入休眠模式特别适合电池供电的物联网设备。出厂已校准传感器出厂时已经完成了全量程标定我们拿到手直接读取数据按照公式计算就能得到真实的温湿度值无需自己校准。它的核心参数我整理成了下面这个表格方便你查阅参数规格工作电压2.1V ~ 3.6V (与GD32E230的3.3V完美匹配)通信接口I2C (兼容标准、快速模式)温度范围-40℃ ~ 125℃温度精度±0.3℃湿度范围0% ~ 100% RH湿度精度±3% RH引脚数量4 Pin (VCC, GND, SCL, SDA)注意SHT20的I2C设备地址是固定的0x407位地址。在通信时我们需要根据读/写操作组合成8位的地址字节。2. 硬件连接让板子和传感器“握手”硬件连接非常简单可以说是“即插即用”。立创GD32E230C8T6开发板上有丰富的GPIO口我们任意选两个支持复用功能的引脚模拟I2C即可。这里我选择的是PA1和PA2。请按照下表连接你的开发板和SHT20模块SHT20模块引脚连接到 GD32E230C8T6 开发板说明VCC3.3V电源正极GNDGND电源地SCLPA1I2C时钟线SDAPA2I2C数据线连接好之后硬件部分就搞定了。接下来就是重头戏——软件驱动。3. 软件驱动移植手把手编写I2C模拟程序GD32E230C8T6虽然有硬件I2C外设但有时候为了时序更可控或者引脚更灵活我们采用软件模拟I2C也叫“GPIO模拟I2C”。这种方式更直观也更容易理解I2C协议的底层时序。3.1 建立工程文件结构首先在你的GD32工程中为SHT20驱动创建两个文件bsp_sht20.c– 驱动源文件包含所有函数实现。bsp_sht20.h– 驱动头文件包含宏定义和函数声明。3.2 编写头文件 (bsp_sht20.h)头文件里主要做两件事定义我们使用的引脚以及声明外部可调用的函数。#ifndef _BSP_SHT20_H_ #define _BSP_SHT20_H_ #include gd32e23x.h // 1. 定义I2C引脚 (根据你的实际连接修改) #define RCU_SCL RCU_GPIOA // SCL引脚所在的GPIO端口时钟 #define PORT_SCL GPIOA // SCL引脚端口 #define GPIO_SCL GPIO_PIN_1 // SCL引脚号 (PA1) #define RCU_SDA RCU_GPIOA // SDA引脚所在的GPIO端口时钟 #define PORT_SDA GPIOA // SDA引脚端口 #define GPIO_SDA GPIO_PIN_2 // SDA引脚号 (PA2) // 2. 定义引脚操作宏 (方便时序控制) #define SDA_IN() {gpio_mode_set(PORT_SDA, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_SDA);} // 设置SDA为输入模式 #define SDA_OUT() {gpio_mode_set(PORT_SDA, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_SDA);} // 设置SDA为输出模式 #define SCL(BIT) gpio_bit_write(PORT_SCL, GPIO_SCL, BIT?SET:RESET) // 控制SCL电平 #define SDA(BIT) gpio_bit_write(PORT_SDA, GPIO_SDA, BIT?SET:RESET) // 控制SDA电平 #define GETSDA() gpio_input_bit_get(PORT_SDA, GPIO_SDA) // 读取SDA电平 // 3. 函数声明 void SHT20_GPIO_Init(void); // 初始化GPIO float SHT20_Read(unsigned char regaddr); // 读取温湿度值 #endif3.3 编写源文件 (bsp_sht20.c)这是驱动的核心包含了I2C总线所有基本时序的操作函数以及最终读取传感器的函数。第一步GPIO初始化首先我们需要把用到的两个GPIO引脚配置好。注意I2C总线是“线与”结构要求引脚是**开漏输出Open-Drain**模式并且外部或内部需要上拉电阻。GD32的GPIO内部可以配置上拉很方便。#include bsp_sht20.h #include systick.h #include stdio.h void SHT20_GPIO_Init(void) { // 1. 使能GPIOA的时钟因为SCL和SDA都在PA口 rcu_periph_clock_enable(RCU_SCL); rcu_periph_clock_enable(RCU_SDA); // 2. 配置SCL引脚开漏输出上拉速度2MHz gpio_mode_set(PORT_SCL, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_SCL); gpio_output_options_set(PORT_SCL, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_SCL); SCL(1); // 初始化为高电平 // 3. 配置SDA引脚开漏输出上拉速度2MHz gpio_mode_set(PORT_SDA, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_SDA); gpio_output_options_set(PORT_SDA, GPIO_OTYPE_OD, GPIO_OSPEED_2MHZ, GPIO_SDA); SDA(1); // 初始化为高电平 }第二步实现I2C基础时序I2C通信就像两个人按严格的礼仪对话有开始、结束、应答等信号。我们用GPIO的高低电平变化来模拟这些信号。// I2C起始信号SCL高电平时SDA产生一个下降沿 void IIC_Start(void) { SDA_OUT(); // 确保SDA是输出模式 SDA(1); delay_1us(5); SCL(1); delay_1us(5); SDA(0); // 产生下降沿 delay_1us(5); SCL(0); // 钳住总线准备发送数据 delay_1us(5); } // I2C停止信号SCL高电平时SDA产生一个上升沿 void IIC_Stop(void) { SDA_OUT(); SCL(0); SDA(0); delay_1us(5); SCL(1); delay_1us(5); SDA(1); // 产生上升沿 delay_1us(5); } // 主机发送应答(ACK)或非应答(NACK)信号 // ack0: 发送ACK (拉低SDA) // ack1: 发送NACK (保持SDA高) void IIC_Send_Ack(unsigned char ack) { SDA_OUT(); SCL(0); if(!ack) SDA(0); // 发送ACK else SDA(1); // 发送NACK delay_1us(5); SCL(1); delay_1us(5); SCL(0); SDA(1); // 释放SDA线 } // 主机等待从机(SHT20)的应答信号 // 返回0: 从机应答正常 // 返回1: 从机无应答可能设备地址错误或设备忙 unsigned char IIC_Wait_Ack(void) { unsigned char ack_flag 10; // 超时计数 SDA_IN(); // 将SDA设置为输入准备读取 SDA(1); // 释放SDA线由上拉电阻拉高 delay_1us(5); SCL(1); delay_1us(5); // 等待SDA被从机拉低ACK信号 while( (GETSDA()1) (ack_flag) ) { ack_flag--; delay_1us(5); } if( ack_flag 0 ) // 超时无应答 { IIC_Stop(); return 1; } else // 收到应答 { SCL(0); SDA_OUT(); // 切换回输出模式为后续操作做准备 return 0; } } // I2C写入一个字节数据 void IIC_Write(unsigned char dat) { int i 0; SDA_OUT(); SCL(0); // 拉低时钟线开始数据传送 for( i 0; i 8; i ) { // 先放数据再拉高时钟再拉低时钟形成一个脉冲 SDA( (dat 0x80) 7 ); // 取出最高位(MSB first) delay_1us(3); dat 1; // 左移准备发送下一位 delay_1us(6); SCL(1); // 时钟上升沿从机采样数据位 delay_1us(4); SCL(0); // 时钟下降沿 delay_1us(4); } } // I2C读取一个字节数据 unsigned char IIC_Read(void) { unsigned char i, receive 0; SDA_IN(); // SDA设置为输入准备读取从机数据 for(i 0; i 8; i ) { SCL(0); delay_1us(5); SCL(1); // 时钟上升沿主机采样数据位 delay_1us(5); receive 1; // 左移为下一位数据腾出空间 if( GETSDA() ) // 如果SDA为高则该位为1 { receive | 1; } delay_1us(5); } SCL(0); return receive; }第三步实现SHT20数据读取函数有了基础的I2C“对话”能力现在就可以按照SHT20的数据手册来组织一次完整的“提问”和“获取答案”的过程。SHT20有两个测量命令触发温度测量0xF3触发湿度测量0xF5// 读取温湿度值 // 参数regaddr: 0xF3读取温度0xF5读取湿度 // 返回值: 计算后的实际温度(℃)或湿度(%RH) float SHT20_Read(unsigned char regaddr) { unsigned char data_H 0; // 数据高字节 unsigned char data_L 0; // 数据低字节 float temp 0; // 1. 发送起始信号 IIC_Start(); // 2. 发送设备地址 写标志 (0x80 0x40 1 | 0) IIC_Write(0x80 | 0); if( IIC_Wait_Ack() 1 ) printf(error -1: No ACK after address write\r\n); // 3. 发送测量命令 (温度或湿度) IIC_Write(regaddr); if( IIC_Wait_Ack() 1 ) printf(error -2: No ACK after command write\r\n); // 4. 等待测量完成 (主机Hold模式查询方式) // 不断发送起始信号和读地址直到传感器应答ACK表示数据就绪 do{ delay_1us(10); // 短暂延时 IIC_Start(); IIC_Write(0x80 | 1); // 设备地址 读标志 } while( IIC_Wait_Ack() 1 ); // 如果返回1(NACK)则继续查询 delay_1us(20); // 小延时确保数据稳定 // 5. 读取两个字节的数据 (MSB first) data_H IIC_Read(); IIC_Send_Ack(0); // 读取第一个字节后发送ACK data_L IIC_Read(); IIC_Send_Ack(1); // 读取第二个字节后发送NACK表示读取结束 // 6. 发送停止信号结束本次通信 IIC_Stop(); // 7. 将原始数据合并并转换为实际物理值 // 数据格式两个字节高字节在前。数据低两位是状态位计算前需清零。 unsigned short raw_data ((data_H 8) | data_L) 0xFFFC; // 清除低2位状态位 if( regaddr 0xf3 ) // 温度计算 { // 公式: T -46.85 175.72 * (raw_data / 2^16) temp (float)raw_data / 65536.0 * 175.72 - 46.85; } if( regaddr 0xf5 ) // 湿度计算 { // 公式: RH -6 125 * (raw_data / 2^16) temp (float)raw_data / 65536.0 * 125.0 - 6; } return temp; }关键点解析主机Hold模式代码中do...while循环就是在实现这个模式。发送测量命令后传感器需要时间进行AD转换。在此期间如果主机尝试读取传感器会回复NACK。主机需要不断尝试读取直到收到ACK说明数据准备好了。数据转换传感器返回的是14位温度或12位湿度的原始数据存储在两个字节中。我们需要将两个字节合并成一个16位数然后清除最低两位的状态位 0xFFFC最后代入公式计算。精度计算时使用float类型和65536.0即2^16来保证计算精度。4. 实战测试在主程序中读取并打印数据驱动写好了最后一步就是在主函数里调用它并通过串口把数据打印出来看看。#include gd32e23x.h #include systick.h #include bsp_usart.h #include stdio.h #include bsp_sht20.h int main(void) { // 系统初始化 systick_config(); // 初始化系统滴答定时器用于delay函数 usart_gpio_config(115200); // 初始化串口0波特率115200用于打印数据 SHT20_GPIO_Init(); // 初始化SHT20的I2C引脚 delay_1ms(1000); // 上电后等待传感器稳定 printf(SHT20 Demo Start\r\n); while(1) { // 读取并打印温度 float temperature SHT20_Read(0xf3); printf(Temperature %.2f ℃\r\n, temperature); // 读取并打印湿度 float humidity SHT20_Read(0xf5); printf(Humidity %.2f %%RH\r\n, humidity); printf(-----------------\r\n); delay_1ms(2000); // 每隔2秒测量一次 } }将代码编译下载到开发板打开串口调试助手如XCOM、Putty等设置好波特率115200你就能看到每隔2秒输出的温湿度数据了。5. 可能遇到的问题与调试技巧无数据输出或全是0检查硬件连接确保VCC、GND、SCL、SDA四根线没有接错或虚焊。检查引脚定义核对bsp_sht20.h文件中的GPIO_SCL和GPIO_SDA是否与你实际的连接一致。检查上拉电阻如果SCL和SDA线上没有外部上拉电阻通常4.7K~10K务必在代码中启用GPIO的内部上拉GPIO_PUPD_PULLUP。数据明显错误或不变检查I2C地址确保设备地址是0x40写地址是0x80读地址是0x81。检查时序延时delay_1us函数的准确性很重要。如果单片机主频不是72MHz需要调整延时。可以尝试增大delay_1us(5)中的延时值比如改为delay_1us(10)。检查ACK等待在IIC_Wait_Ack函数中如果总是超时返回1可能是传感器没正常工作或者起始/停止信号时序不对。如何提高稳定性在实际产品中读取函数SHT20_Read里可以加入超时机制避免因传感器故障导致程序死等在do...while循环里。对于精度要求高的场合可以连续读取多次然后取平均值。注意传感器不要长时间处于凝露或结冰环境以免损坏。好了整个移植过程就是这样。从硬件连接到软件调试一步步跟着做下来你的GD32E230开发板应该就能成功读取SHT20的温湿度数据了。这个过程虽然涉及到底层的时序模拟但理解后对掌握I2C通信原理非常有帮助。遇到问题别着急多用逻辑分析仪或者示波器看看SCL和SDA线上的波形对照时序图分析问题总能解决的。