
基于GD32VW553的DHT11温湿度传感器单总线驱动移植与数据采集实战最近在立创开发板上做环境监测的小项目用到了经典的DHT11温湿度传感器。很多从STM32转过来的朋友问我在GD32VW553这款RISC-V内核的国产MCU上怎么驱动这个单总线传感器。今天我就把完整的移植过程、代码实现和调试心得分享给大家手把手教你搞定DHT11。1. 认识我们的“搭档”DHT11传感器在写代码之前咱们得先了解要驱动的对象。DHT11是一款性价比很高的数字温湿度传感器它最大的特点就是只用一根数据线就能完成通信非常适合引脚资源紧张的小型项目。1.1 DHT11的基本参数根据数据手册DHT11的主要规格如下参数规格工作电压3.3V - 5.5V工作电流1mA湿度量程20% - 90% RH湿度精度±5% RH温度量程0°C - 50°C温度精度±2°C通信协议单总线引脚数量3 Pin (VCC, GND, DATA)注意DHT11输出的是已经校准过的数字信号我们不需要自己再做复杂的模数转换直接读取数据就行。1.2 单总线通信一根线搞定所有单总线通信听起来很神奇其实原理不复杂。你可以把它想象成两个人用一根电话线聊天只不过要遵守严格的时间约定——什么时候说话什么时候听说多久都有明确的规定。DHT11的数据传输分为四个阶段起始信号MCU主机先发起对话请求响应信号DHT11从机回应我准备好了数据传输DHT11发送40位数据先发高位结束信号数据传输完成总线恢复空闲这40位数据的具体含义是8位湿度整数部分8位湿度小数部分DHT11的小数部分始终为08位温度整数部分8位温度小数部分8位校验和前4个字节的和校验和的作用很重要它能帮我们判断这次读取的数据是否可靠。如果校验和不匹配说明传输过程中可能出错了需要重新读取。2. 硬件连接让开发板和传感器“握手”硬件连接很简单但有几个细节要注意。我使用的是立创的GD32VW553开发板连接方式如下传感器引脚开发板引脚说明VCC5V电源正极3.3V-5.5V都可以DATAPA5数据线需要接上拉电阻开发板内部已上拉GNDGND电源地提示虽然DHT11支持3.3V-5.5V宽电压但为了和MCU电平匹配建议使用3.3V供电。如果使用5V供电DATA引脚需要电平转换或分压。3. 代码移植从零开始构建DHT11驱动接下来是重头戏——代码实现。我会按照实际开发的顺序一步步带你完成驱动移植。3.1 创建驱动文件首先在工程中新建两个文件bsp_dht11.c和bsp_dht11.h。bsp是Board Support Package板级支持包的缩写这种命名方式能让代码结构更清晰。3.2 头文件定义bsp_dht11.h头文件主要定义引脚、宏和函数声明#ifndef BSP_CODE_BSP_DHT11_H_ #define BSP_CODE_BSP_DHT11_H_ #include gd32vw55x.h #include systick.h // 延时函数宏定义 #ifndef delay_ms #define delay_ms(x) delay_1ms(x) #endif #ifndef delay_us #define delay_us(x) delay_1us(x) #endif // DHT11引脚定义 #define DHT11_DAT_GPIO_RCU RCU_GPIOA // GPIOA时钟 #define DHT11_DAT_GPIO_PORT GPIOA // GPIOA端口 #define DHT11_DAT_GPIO_PIN GPIO_PIN_5 // PA5引脚 // 使能GPIO时钟 #define DHT11_DAT_GPIO_CLK_ENABLE() rcu_periph_clock_enable(DHT11_DAT_GPIO_RCU) // 设置DATA引脚输出电平 #define DATA_GPIO_OUT(x) gpio_bit_write(DHT11_DAT_GPIO_PORT, DHT11_DAT_GPIO_PIN, x) // 读取DATA引脚输入电平 #define DATA_GPIO_IN gpio_input_bit_get(DHT11_DAT_GPIO_PORT, DHT11_DAT_GPIO_PIN) // 函数声明 void DHT11_Init(void); // 初始化DHT11 uint8_t DHT11_Read_Data(float *temperature, float *humidity); // 读取温湿度数据 #endif /* BSP_CODE_BSP_DHT11_H_ */这里有几个关键点引脚定义我选择了PA5你可以根据实际接线修改延时宏确保工程中有delay_1ms()和delay_1us()函数电平操作宏简化代码提高可读性3.3 初始化函数bsp_dht11.c初始化函数主要配置GPIO引脚void DHT11_Init(void) { /* 使能DHT11数据引脚时钟 */ DHT11_DAT_GPIO_CLK_ENABLE(); /* 配置DHT11数据引脚为上拉输出模式 */ gpio_mode_set(DHT11_DAT_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, DHT11_DAT_GPIO_PIN); /* 设置DHT11数据引脚为推挽输出速度25MHz */ gpio_output_options_set(DHT11_DAT_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, DHT11_DAT_GPIO_PIN); /* 设置DHT11数据引脚输出高电平 */ DATA_GPIO_OUT(1); delay_1ms(100); // 等待DHT11稳定 }初始化完成后DATA引脚会保持高电平这是单总线的空闲状态。3.4 GPIO模式切换函数因为单总线通信中DATA引脚需要在输出和输入模式间切换所以我们封装两个辅助函数// 设置为输出模式 void DHT11_GPIO_Mode_OUT(void) { gpio_mode_set(DHT11_DAT_GPIO_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, DHT11_DAT_GPIO_PIN); gpio_output_options_set(DHT11_DAT_GPIO_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, DHT11_DAT_GPIO_PIN); } // 设置为输入模式上拉 void DHT11_GPIO_Mode_IN(void) { gpio_mode_set(DHT11_DAT_GPIO_PORT, GPIO_MODE_INPUT, GPIO_PUPD_NONE, DHT11_DAT_GPIO_PIN); }3.5 发送起始信号起始信号是MCU告诉DHT11我要开始读取数据了。时序要求很严格void DHT11_Start(void) { DHT11_GPIO_Mode_OUT(); // 切换到输出模式 DATA_GPIO_OUT(1); // 先拉高 DATA_GPIO_OUT(0); // 拉低至少18ms delay_ms(20); // 这里给20ms留点余量 DATA_GPIO_OUT(1); // 拉高20-40us delay_us(25); // 给25us DHT11_GPIO_Mode_IN(); // 切换到输入模式准备接收响应 }这个时序我调试了很久才稳定关键点拉低时间必须≥18ms但不要超过30ms拉高时间20-40us太短DHT11可能检测不到太长会影响响应3.6 检查DHT11响应发送起始信号后DHT11会给出响应。我们需要检测这个响应uint8_t DHT11_CheckResponse(void) { uint32_t timeout 0; // 等待DHT11拉低总线80us左右 timeout DHT11_TIMEOUT; while(DATA_GPIO_IN timeout--) { if(timeout 0) { printf(DHT11_CheckResponse Failed[1]!!\r\n); return 0; // 超时失败 } } // 等待DHT11拉高总线80us左右 timeout DHT11_TIMEOUT; while((!DATA_GPIO_IN) timeout--) { if(timeout 0) { printf(DHT11_CheckResponse Failed[2]!!\r\n); return 0; // 超时失败 } } // 等待DHT11再次拉低进入数据前导位 timeout DHT11_TIMEOUT; while(DATA_GPIO_IN timeout--) { if(timeout 0) { printf(DHT11_CheckResponse Failed[3]!!\r\n); return 0; // 超时失败 } } return 1; // 响应正确 }注意DHT11_TIMEOUT我定义为1000这个值需要根据系统时钟调整。如果超时设置太小可能在高速MCU上误判太大则响应慢。3.7 读取单个数据位DHT11的数据位0和1是通过高电平持续时间区分的位054us低电平 27us高电平位154us低电平 74us高电平uint8_t DHT11_ReadBit(void) { uint8_t bit 0; uint8_t timeCount 0; uint32_t timeout; // 等待前导低电平结束 timeout DHT11_TIMEOUT; while((!DATA_GPIO_IN) timeout--) { if(timeout 0) { printf(DHT11_ReadBit Failed[1]!!\r\n); return 0; } } // 测量高电平持续时间 timeout DHT11_TIMEOUT; while(DATA_GPIO_IN timeout--) { timeCount; delay_us(1); // 等待1us if(timeout 0) { printf(DHT11_ReadBit Failed[2]!!\r\n); return 0; } } // 高电平时间大于30us判为1否则为0 if(timeCount 30) bit 1; // 数据1 else bit 0; // 数据0 return bit; }这里有个小技巧理论上位0的高电平是27us位1是74us。我取30us作为分界点这样即使有些时序偏差也能正确识别。3.8 完整数据读取与校验最后是主函数读取40位数据并校验uint8_t DHT11_Read_Data(float *temperature, float *humidity) { int i; uint8_t data[5] {0}; uint64_t val 0; // 发送起始信号 DHT11_Start(); // 检查响应是否正常 if(0 DHT11_CheckResponse()) { printf(DHT11_CheckResponse Failed!!\r\n); return 0; } // 读取40位数据 for(i 0; i 40; i) { val 1; // 左移一位 val | DHT11_ReadBit(); // 读取新位 } // 数据解析 data[0] (val 32) 0xFF; // 湿度整数 data[1] (val 24) 0xFF; // 湿度小数DHT11始终为0 data[2] (val 16) 0xFF; // 温度整数 data[3] (val 8) 0xFF; // 温度小数 data[4] val 0xFF; // 校验和 // 调试信息输出 #if DHT11_DEBUG printf(data[0] %d\r\n,data[0]); printf(data[1] %d\r\n,data[1]); printf(data[2] %d\r\n,data[2]); printf(data[3] %d\r\n,data[3]); printf(data[4] %d\r\n,data[4]); #endif // 校验和检查 if ((data[4] ! (data[0] data[1] data[2] data[3])) || (data[4] 0)) { printf(CheckSum Failed!!!\r\n); return 0; // 校验失败 } // 计算温湿度值 *humidity data[0] (data[1] * 0.1f); // 湿度% *temperature data[2] (data[3] * 0.1f); // 温度℃ return 1; // 读取成功 }4. 在主函数中调用驱动写好了现在在主函数中测试一下#include bsp_dht11.h int main(void) { // 系统初始化时钟、LED、串口等 systick_config(); gd_eval_led_init(LED1); gd_eval_com_init(EVAL_COM0); // 初始化DHT11 DHT11_Init(); // 空读两次让传感器稳定 DHT11_Read_Data(NULL, NULL); delay_1ms(1000); DHT11_Read_Data(NULL, NULL); delay_1ms(1000); printf(\r\n DHT11 Demo Start \r\n); while(1) { float temperature 0.0; float humidity 0.0; if(DHT11_Read_Data(temperature, humidity)) { printf(\n); printf(Temperature %.2f℃\r\n, temperature); printf(Humidity %.2f%%\r\n, humidity); } else { printf(\r\nRead Error!!\r\n); } delay_ms(2000); // 每2秒读取一次 } }5. 调试经验与常见问题在实际项目中我遇到过几个坑这里分享给大家5.1 时序精度问题DHT11对时序要求很严格特别是微秒级的延时。如果发现读取不稳定检查系统时钟配置是否正确确保delay_us()函数精度足够可以用逻辑分析仪或示波器抓取波形对比DHT11的时序图5.2 上拉电阻问题虽然开发板内部有上拉电阻但有些DHT11模块需要外部上拉通常4.7kΩ-10kΩ。如果通信不稳定可以尝试在DATA引脚和VCC之间加一个4.7kΩ电阻缩短传感器到MCU的连线长度5.3 读取频率限制DHT11两次读取之间需要至少1秒的间隔。如果读取太频繁传感器可能不响应。我的做法是主循环中至少延时2秒再读下一次首次上电后空读两次让传感器稳定5.4 校验失败处理如果经常校验失败可能是电源不稳定给DHT11的VCC加一个100nF电容信号干扰尽量远离电机、继电器等干扰源时序偏差适当调整delay_us()的精度最后提醒一点DHT11的湿度小数部分始终为0这是正常现象不是bug。如果你需要更高精度可以考虑DHT22精度更高但价格也贵一些。按照上面的步骤你应该能在GD32VW553上稳定读取DHT11的数据了。这个驱动我在好几个项目里都用过只要时序调好了稳定性还是很不错的。