尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

STM32F103C8T6 HAL库驱动DHT11:从CubeMX配置到OLED显示的实战解析

STM32F103C8T6 HAL库驱动DHT11:从CubeMX配置到OLED显示的实战解析
📅 发布时间:2026/6/30 10:50:46

1. 项目背景与硬件准备

STM32F103C8T6作为经典的Cortex-M3内核微控制器,凭借其丰富的外设资源和亲民的价格,一直是嵌入式开发者的心头好。这次我们要用它来驱动DHT11温湿度传感器,并通过OLED实时显示数据。这个项目特别适合刚接触HAL库的开发者,因为整个过程会涉及到单总线通信、精确延时控制、I2C驱动等多个实用技能点。

硬件清单里除了主角STM32F103C8T6最小系统板,还需要准备这几样东西:DHT11模块(注意要买带PCB板的那种,直接裸露的传感器不方便接线)、0.96寸OLED屏幕(I2C接口)、USB-TTL模块(我用的是CH340G芯片的)以及ST-Link下载器。特别提醒新手,DHT11有方向性,凸起面朝向你时从左到右分别是VCC、DATA、NC、GND,别接反了。我刚开始玩的时候就把电源接反过,幸好这模块有保护电路没烧坏。

接线方面有个小技巧:虽然CubeMX生成的代码会自动配置引脚,但建议先在原理图上确认PB6/PB7用作I2C1,PA9/PA10用作USART1。DHT11的数据线我接在PB12,这个引脚在最小系统板上容易找到。实际接线时,OLED的VCC接3.3V,SCL接PB6,SDA接PB7;DHT11的DATA线需要接10K上拉电阻到3.3V,这个细节很多人会忽略导致通信失败。

2. CubeMX工程配置详解

打开CubeMX新建工程时,记得选择STM32F103C8系列,具体到C8T6型号。时钟配置是个重点,我推荐使用外部8MHz晶振,经过PLL倍频到72MHz主频,这样后续的微秒级延时才能算得准。在Clock Configuration标签页里,把HSE选为Crystal/Ceramic Resonator,然后在PLL配置区把MUL设为9倍频,注意系统时钟源要切换为PLLCLK。

外设配置环节需要开启两个关键外设:I2C1和USART1。I2C1模式选择I2C,参数保持默认的100kHz就行,OLED对速率要求不高。USART1配置为异步模式,波特率115200,这个速率在串口助手上显示比较舒服。GPIO配置里要把PB12设为GPIO_Output(后续代码里会动态切换输入输出模式),初始电平设为高。

生成代码前有个重要设置:在Project Manager标签页里,把Toolchain/IDE选为MDK-ARM,勾选"Generate peripheral initialization as a pair of .c/.h files"。这样每个外设都会生成独立的文件,方便维护。我第一次用CubeMX时没注意这个选项,结果所有初始化代码都堆在main.c里,后期维护特别麻烦。

3. DHT11驱动开发实战

DHT11的通信协议看似简单,实际调试时却容易踩坑。它的时序分为三个关键阶段:起始信号、响应信号和数据传输。起始信号要求主机拉低总线至少18ms,然后拉高20-40us。这里有个细节:很多例程用HAL_Delay()实现毫秒延时,但微秒级延时需要自己实现。我的方案是用SysTick定时器:

void Delay_us(uint32_t us) { uint32_t ticks = us * (SystemCoreClock / 1000000); uint32_t start = DWT->CYCCNT; while((DWT->CYCCNT - start) < ticks); }

记得在初始化时启用DWT单元:

CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;

数据读取阶段要注意电平判定的时间窗口。DHT11用高电平持续时间区分0和1:26-28us表示0,70us表示1。实测中发现STM32的GPIO读取速度很快,但HAL_GPIO_ReadPin()函数有额外开销,所以我的做法是直接操作寄存器:

#define DHT11_PIN_IN() {GPIOB->CRH &= ~(0xF << 16); GPIOB->CRH |= (4 << 16);} #define DHT11_PIN_OUT() {GPIOB->CRH &= ~(0xF << 16); GPIOB->CRH |= (3 << 16);} #define DHT11_READ() (GPIOB->IDR & GPIO_PIN_12)

校验机制也不能忽视。DHT11传输的5字节数据中,前4字节的和应该等于第5字节。我在代码里添加了校验失败重试机制,最多尝试5次:

uint8_t retry = 5; do { if(DHT11_Read(data) == SUCCESS) { if((data[0]+data[1]+data[2]+data[3]) == data[4]) break; } HAL_Delay(200); } while(retry--);

4. OLED显示与数据融合

OLED驱动我推荐使用u8g2库的简化版,只保留SSD1306驱动部分。在CubeMX生成的I2C初始化代码基础上,需要添加几个关键函数。写命令和写数据的函数要区分开:

void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] = {0x00, cmd}; HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS, buf, 2, 100); } void OLED_WriteData(uint8_t dat) { uint8_t buf[2] = {0x40, dat}; HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS, buf, 2, 100); }

数据显示部分建议做成两级结构:底层是基本绘图函数,上层是业务逻辑。比如温度显示可以这样实现:

void ShowTemp(float temp) { char str[16]; sprintf(str, "%.1f℃", temp); OLED_ShowString(60, 1, str); }

实际项目中我发现直接频繁刷新整个屏幕会导致闪烁,更好的做法是局部刷新。比如温度值只有最后一位变化时,只需要重写最后两个字符的位置。这需要维护一个显示缓存区,比较前后帧数据差异。

串口输出建议采用JSON格式,方便上位机解析:

printf("{\"temp\":%.1f,\"humi\":%.1f}\r\n", temperature, humidity);

调试时遇到过I2C总线锁死的情况,后来在代码里添加了总线恢复机制:

void I2C_Recovery() { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); for(int i=0; i<9; i++) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); Delay_us(5); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); Delay_us(5); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); Delay_us(5); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); } MX_I2C1_Init(); }

5. 常见问题排查指南

第一个容易出问题的是DHT11无响应。先检查硬件:测量VCC和GND之间是否有3.3V,DATA线是否接了上拉电阻。然后用逻辑分析仪抓取起始信号波形,确认18ms低电平和20-40us高电平是否符合要求。如果没有逻辑分析仪,可以临时改成用LED指示各阶段状态。

I2C通信失败时,先用万用表测量SCL和SDA线电压。正常时应为3.3V(上拉后),如果始终为低说明总线被锁死。这时候可以调用前面提到的I2C_Recovery()函数。OLED不显示还可能是因为地址不对,SSD1306的地址通常是0x78或0x7A,可以用I2C扫描程序确认。

延时不准是个隐形杀手。建议在调试时输出SysTick的值来校准微秒延时函数。有个小技巧:用PWM输出一个1MHz的方波,然后用延时函数控制GPIO翻转,用示波器测量实际周期。我实测发现72MHz主频下,减去函数调用开销后,每个nop大约消耗14ns。

数据校验经常失败的话,可以尝试降低系统时钟频率,或者优化GPIO读取速度。DHT11对时序要求严格,在while循环里判断电平变化时,建议加上超时机制:

uint32_t timeout = 1000; // 1ms超时 while(DHT11_READ() == RESET && timeout--) Delay_us(1); if(timeout == 0) return TIMEOUT_ERROR;

最后提醒一个STM32的坑:PB3/PB4默认是JTAG功能,如果要用作普通GPIO,需要在初始化时先禁用JTAG:

__HAL_AFIO_REMAP_SWJ_NOJTAG();

相关新闻

  • GTA5线上小助手:终极免费开源工具,让你的洛圣都冒险更自由高效
  • 烽火HG680-MC TTL救砖与刷机实战:从备份分区到纯净当贝桌面的完整指南
  • 解决 vLLM 启动报错,AMD 显卡常见的五个坑与填法

最新新闻

  • BMS系统专栏:BMS_InfoTaskEntry信息管理任务
  • 2026实测必看|5款主流AI编程工具上手教程,前端vibe coding从零落地
  • TikTokCommentScraper:3分钟掌握抖音评论数据采集的终极指南
  • 终极指南:如何快速免费解包微信小程序源码
  • 如何3步搞定魔兽争霸3卡顿问题:WarcraftHelper的终极兼容性解决方案
  • 工业4-20mA电流环接收器设计与实现

日新闻

  • 【计算机毕业设计案例】基于 Spring Boot+Vue 的电影售票系统设计与实现 前后端分离架构下影院在线购票管理平台(程序+文档+讲解+定制)
  • 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦
  • Google限制Meta使用Gemini模型 凸显AI授权竞争白热化

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号