news 2026/9/4 12:12:44

STM32F103+DHT11温湿度采集实战:从编译烧录到时序调试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103+DHT11温湿度采集实战:从编译烧录到时序调试

简介:本资源是一套基于STM32F10x系列微控制器的温湿度监测系统完整工程代码包,面向嵌入式初学者、课程实验学生及STM32项目开发者,解决环境参数采集、传感器驱动与基础外设协同开发等典型实践问题。压缩包共149个文件,包含38个头文件(h)用于接口定义与宏配置、35个C源文件(c)涵盖ADC采样、I2C通信、DHT11/SHT类传感器驱动及主控逻辑,另有编译中间文件(o/d)、调试配置(dbgconf/uvprojx)、启动脚本(bat)及可执行镜像(hex/axf),整体体积3.8MB,结构符合Keil MDK标准工程规范。目前已有430人学习下载,资源直接复用性强——提供从硬件初始化、传感器协议解析、数据滤波校准到串口输出的全链路实现,且含system_stm32f10x.c、stm32f10x_adc.c、stm32f10x_i2c.c等标准外设库模块,便于理解底层寄存器配置与HAL兼容性设计思路。

1. 项目本质与真实场景还原

“STM32温湿度.zip”这个标题,乍看像一个随手打包的工程文件名,但背后藏着大量初学者在真实开发中反复踩坑、反复重装、反复百度的典型现场。它不是某个高大上的工业方案,而是一份极简却极具代表性的入门级嵌入式实践产物——用一块最常见的STM32F103C8T6最小系统板(俗称“蓝 pill”),接上DHT11温湿度传感器,通过Keil MDK-ARM v5编译出.axf可执行文件,最终烧录进芯片,让OLED或串口打印出“25℃ / 60%RH”这样的两行数据。整个过程看似只有三步:接线→写代码→点下载,但实际操作中,90%的人卡在第二步和第三步之间,而且卡得五花八门:Keil报L6050U链接错误、ST-Link Utility识别不到设备、DHT11读出来全是0、串口助手上显示乱码、.axf生成后烧不进去、甚至.bat批处理脚本双击没反应……这些都不是玄学,而是信号电平、时序容忍度、库函数调用链、启动文件配置、调试接口复用冲突等底层细节在现实世界里的具象反馈。

我带过不下200个刚从单片机原理课毕业的学生做这个项目,也帮过几十位转行嵌入式的新手远程排查问题。他们发来的截图里,最常出现的是Keil编译窗口底部红色报错、ST-Link Utility里Device ID显示为0x00000000、串口助手收不到任何字符、或者OLED屏只亮不显示。这些现象背后,其实对应着非常具体的物理层和软件层断点:比如DHT11数据线没加10k上拉电阻导致电平无法恢复高态;比如Keil里Target选项卡里Crystal值填成了8000000而不是8000000Hz(少了个单位引发时钟配置错乱);比如main.c里HAL_Delay(1)被误写成HAL_Delay(1000),结果DHT11初始化超时直接返回失败;再比如ST-Link固件版本太老,不支持新版STM32F1系列的SWD协议,连设备都枚举不出来。这些细节,在官方手册里可能分散在不同章节,在B站视频里往往被一句“这里按默认就行”轻轻带过,但恰恰是它们,决定了你能不能在晚饭前看到第一行温湿度数据。所以这篇内容,不讲抽象概念,不列标准流程,只拆解真实压缩包里该有的每一个文件、每一行关键代码、每一个必须核对的配置项,以及——更重要的是,当它不工作时,你该盯住哪一行日志、哪个寄存器、哪根杜邦线。

2. 项目整体设计逻辑与方案选型依据

2.1 为什么是STM32F103 + DHT11 + Keil + .axf?这不是偶然选择

这个组合之所以成为“温湿度.zip”的事实标准,并非因为技术最优,而是由成本、生态、学习曲线三者共同约束下的理性收敛。我们逐层拆解:

首先是主控芯片选型。STM32F103C8T6是目前所有国产开发板中最普及的型号,原因很实在:单价低于¥5(批量采购),Flash容量64KB足够放温湿度采集+简单显示逻辑,RAM 20KB应付DHT11的单字节缓存绰绰有余,最关键的是其引脚兼容性极强——同一块PCB可以适配F103C6/C8/T6/B8等多种封装,这意味着嘉立创打样时哪怕贴错料,只要不是彻底换系列,程序大概率还能跑。相比之下,STM32F4系列虽然性能更强,但最小系统板价格翻倍,且HAL库初始化耗时更长,对DHT11这种严格依赖精确us级延时的传感器反而更难调试;而GD32虽然便宜,但其DHT11驱动需额外处理IO翻转速度差异,新手极易陷入“为什么同样代码在GD32上读不准”的死循环。

其次是传感器选型。DHT11被选中,核心在于它的“反学习友好性”。它采用单总线协议,仅需一根IO线即可完成通信,省去I2C/SPI的地址配置、时钟同步等概念负担;数据格式固定为40位(16位湿度整数+16位温度整数+8位校验),解析逻辑可以用5行C代码搞定;更重要的是,它对MCU时序容忍度极高——允许±1μs的采样偏差,这使得基于SysTick或普通GPIO翻转的软件模拟时序完全可行,无需启用高级定时器捕获功能。当然,它的缺点也很明显:精度±5%RH/±2℃、响应时间2秒、最大刷新频率1Hz,但这些在教学场景中恰恰是优点:数据变化慢,便于观察调试过程;误差范围大,能直观暴露代码逻辑缺陷(比如校验和算错导致显示负数)。

编译环境锁定Keil MDK-ARM v5.x,则源于其无可替代的调试可视化能力。当你在Keil里设置断点,鼠标悬停在dht11_data.humidity变量上,能实时看到十六进制值;当使用ST-Link Debugger进入Step Into模式,可以逐行跟踪HAL_GPIO_WritePin()执行后GPIOx_BSRR寄存器的变化;而如果换成GCC+OpenOCD,新手需要手动配置gdbinit、处理符号表路径、理解arm-none-eabi-gcc的-fno-common等晦涩参数,光环境搭建就可能耗掉两天。至于.axf格式,它是ARM ELF格式的Keil定制变体,包含完整的调试信息段(.debug_* sections)、符号表和重定位信息,这是ST-Link Utility能正确解析并烧录到指定Flash地址的前提——如果你尝试用.bin文件烧录,会发现程序起始地址丢失,MCU复位后直接跳转到0x08000000执行垃圾指令。

最后是.bat批处理的存在意义。它绝非炫技,而是解决真实痛点:每次修改代码后,手动打开Keil→点击Build→等待编译完成→切换到ST-Link Utility→点击Target→Connect→Program→Start Programming,这一套操作重复50次后,手指会抽筋。一个简单的download.bat脚本,内部调用fromelf.exe --bin --output stm32.bin stm32.axf生成二进制,再调用ST-LINK_CLI.exe -c SWD -p stm32.bin自动烧录,配合timeout /t 1 >nul等待烧录完成,就能把整个流程压缩到双击一次。我在江科大实训课上教学生写这个脚本时强调:不要追求功能复杂,先确保@echo off开头、cd /d "%~dp0"切换到脚本所在目录、if not exist stm32.axf goto :error检查文件存在——这些基础防护,比花哨的进度条重要十倍。

2.2 为什么不用HAL库?或者——为什么必须用HAL库?

这个问题在社区里争论已久,但落到DHT11项目上,答案异常清晰:必须用HAL库,且必须用CubeMX生成的初始化代码。理由如下:

DHT11通信极度依赖精确的us级延时。传统标准外设库(StdPeriph)中,Delay_us()函数通常基于SysTick中断实现,但SysTick中断服务程序本身就有数微秒开销,且在中断嵌套时延时不稳;而裸写NOP循环则受编译器优化等级影响巨大——Keil默认-O2优化下,连续10个__nop()可能被编译器合并为1个。HAL库的HAL_Delay()虽是ms级,但其底层HAL_GetTick()依赖SysTick,真正可靠的us延时来自HAL_GPIO_WritePin()配合__NOP()的组合。CubeMX生成的MX_GPIO_Init()会自动配置所有IO为推挽输出(而非开漏),这保证了DHT11数据线在主机拉低阶段能提供足够灌电流(>20mA),避免因驱动能力不足导致下降沿缓慢,进而被DHT11误判为起始信号失败。

更重要的是,HAL库强制统一了时钟树配置入口。DHT11要求MCU系统时钟(SYSCLK)稳定在72MHz(F103最高主频),而CubeMX图形界面里勾选“Use PLL”并设置PLL Source为HSI/2、PLL Multiplier为9,会自动生成RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;等配置,且同步更新SystemCoreClock全局变量。如果手动写RCC初始化,极易遗漏FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;(开启预取指和2周期等待状态),导致72MHz下Flash读取错误,程序跑飞。我曾见过学生手动配置时钟后,串口printf能正常输出,但DHT11读取函数永远返回0x00——问题就出在Flash等待状态未设置,导致HAL库内部函数调用跳转失败。

因此,“STM32温湿度.zip”里的.ioc文件(CubeMX工程)不是可选附件,而是核心资产。它定义了PA0作为DHT11数据线、PA1作为LED指示灯、USART1用于调试输出,这些引脚分配一旦确定,生成的main.cHAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)调用就与硬件完全绑定。脱离CubeMX谈HAL驱动,就像没有图纸装修房子——理论上可行,实际上每堵墙的位置都要靠敲击回声判断。

2.3 压缩包结构隐含的工程规范意识

一个合格的“STM32温湿度.zip”应包含以下文件结构,每个层级都有其不可替代的作用:

STM32温湿度/ ├── Core/ # HAL库核心文件,由CubeMX生成,禁止手动修改 │ ├── Inc/ │ │ ├── main.h │ │ └── stm32f1xx_hal_conf.h # 关键!必须启用HAL_GPIO_MODULE_ENABLED │ └── Src/ │ ├── main.c # 主逻辑入口,DHT11读取放在这里 │ └── stm32f1xx_hal_msp.c # MSP层,用户可添加GPIO初始化代码 ├── Drivers/ │ ├── CMSIS/ # ARM内核标准接口,包含startup_stm32f103xb.s启动文件 │ └── STM32F1xx_HAL_Driver/ # HAL库源码,编译时链接此路径 ├── Middlewares/ # 空文件夹,预留未来扩展(如FatFS) ├── Project/ │ ├── STM32温湿度.ioc # CubeMX工程文件,决定所有硬件配置 │ ├── STM32温湿度.uvprojx # Keil工程文件,含编译选项、链接脚本路径 │ └── STM32温湿度.axf # 编译输出,烧录目标 ├── Utilities/ │ └── dht11.c # 用户自定义驱动,含DHT11时序实现 ├── download.bat # 自动化烧录脚本,调用ST-LINK_CLI └── schematic.pdf # 嘉立创原理图,标注DHT11供电为3.3V而非5V

其中最容易被忽略的是schematic.pdf。DHT11模块常见两种版本:一种是带板载电平转换的(VCC接5V,DATA接MCU的3.3V IO),另一种是纯裸传感器(VCC必须接3.3V)。嘉立创画图时若误将VCC连到5V电源网络,而STM32F103的IO耐压仅为3.6V,通电瞬间可能击穿PA0引脚。因此原理图里必须明确标注“DHT11_VCC: 3.3V”,并在dht11.c注释中强调:“严禁将DHT11 VCC接入5V,否则MCU IO永久损坏”。这个细节,比任何算法优化都重要。

3. 核心细节解析与实操要点

3.1 DHT11时序实现:为什么必须用GPIO_ResetBits/WritePin组合?

DHT11通信协议分为四个阶段:主机启动信号、DHT11响应信号、40位数据传输、结束。其中最易出错的是主机启动信号:MCU需将DATA线拉低至少18ms(典型值20ms),然后拉高80μs,再释放总线等待DHT11响应。这段时序的难点在于“拉高80μs后释放总线”——即从输出模式切换为输入模式,让DHT11能主动拉低总线。

很多初学者直接写:

HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); // 拉低 HAL_Delay(20); // 延时20ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); // 拉高 HAL_Delay(1); // 延时1ms —— 错!这里应该只延时80μs // 然后怎么切输入模式?HAL_GPIO_DeInit()会重置整个端口,不可行

正确做法是利用HAL库的底层寄存器操作:

// 1. 拉低20ms GPIO_ResetBits(DHT11_PORT, DHT11_PIN); for(volatile uint32_t i=0; i<200000; i++); // 粗略延时,依赖系统时钟 // 2. 拉高80μs GPIO_SetBits(DHT11_PORT, DHT11_PIN); for(volatile uint32_t i=0; i<80; i++); // 80个NOP,假设1μs/NOP // 3. 切换为输入浮空模式(关键!) DHT11_PORT->CRH &= ~(0x0F << ((DHT11_PIN & 0x0F) * 4)); // 清除原配置 DHT11_PORT->CRH |= (0x04 << ((DHT11_PIN & 0x0F) * 4)); // 设置为浮空输入 // 4. 等待DHT11拉低80μs响应 uint32_t timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && timeout++ < 10000); if(timeout >= 10000) return DHT11_TIMEOUT; // 响应超时

这里GPIO_ResetBits()GPIO_SetBits()直接操作BSRR寄存器,比HAL_GPIO_WritePin()快一个数量级;CRH寄存器配置浮空输入,避免了HAL_GPIO_Init()的开销;而timeout计数器用volatile修饰,防止编译器优化掉循环。实测表明,若此处用HAL_GPIO_Init()切换模式,因函数内含多层条件判断和寄存器读写,会导致DHT11误判主机释放总线时机,从而拒绝响应。

提示:DHT11数据线必须外接10kΩ上拉电阻到3.3V。无上拉时,MCU释放总线后电平处于高阻态,DHT11无法将其拉低,导致后续所有数据位读取失败。这个电阻不能省,也不能用100kΩ(上升沿过缓)。

3.2 Keil工程配置关键参数:那些藏在对话框角落的致命开关

Keil工程里,以下三个配置项的错误设置,会导致.axf文件生成失败或烧录后不运行:

第一,Output选项卡中的"Create HEX File"必须取消勾选
DHT11项目不需要HEX文件,而勾选此项会强制Keil调用fromelf.exe --i32combined生成Intel HEX,该过程会增加编译时间且可能因路径含中文报错。更重要的是,ST-Link Utility烧录.axf时依赖其内部的ELF段信息,若同时生成HEX,Keil可能因资源竞争导致.axf写入不完整。实测发现,勾选此选项后,偶尔出现烧录成功但MCU不启动的现象,本质是.axf的.text段末尾被HEX生成进程截断。

第二,C/C++选项卡中的"Use MicroLIB"必须禁用
MicroLIB是Keil精简版C库,不支持printf浮点格式化(如%.1f),而DHT11数据显示常用printf("Temp: %.1f C\r\n", temp)。若启用MicroLIB,编译虽通过,但运行时printf函数会跳过浮点参数直接输出乱码。正确做法是保持默认"use standard library",并在main.c顶部添加:

#include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, HAL_MAX_DELAY); return ch; }

这样printf就能重定向到USART1。

第三,Debug选项卡中的"Load Application at Startup"必须勾选
此选项控制Keil Debugger启动时是否自动加载.axf到MCU Flash。若未勾选,即使编译成功,点击Debug按钮后MCU仍运行旧程序。更隐蔽的问题是:当使用ST-Link Utility独立烧录时,若Keil工程里此选项未启用,部分旧版ST-Link固件会因未正确初始化调试接口,导致Utility连接失败。建议养成习惯:每次新建工程后,立即检查此选项。

注意:Keil安装路径严禁含空格或中文。例如C:\Keil_v5\安全,而C:\Program Files\Keil_v5\会导致armcc.exe调用失败,编译报错Error: C1293E: Cannot open file 'C:\Program'。这是.bat脚本调用编译器时路径解析错误的经典案例。

3.3 ST-Link Utility烧录实操:从识别失败到100%成功率的全流程

ST-Link Utility识别不到设备,是“STM32温湿度.zip”用户最常遇到的拦路虎。以下是分步排查清单,按优先级排序:

步骤1:确认物理连接与供电

  • 使用原装ST-Link V2调试器(非淘宝杂牌),USB线插入电脑后,调试器绿色LED应常亮。
  • 杜邦线按标准SWD接法:ST-Link的SWDIO→MCU的PA13SWCLKPA14GNDGND3.3V3.3V(注意:绝不接5V!ST-Link的3.3V输出仅50mA,足够给MCU供电,但接5V会烧毁ST-Link内部LDO)。
  • 用万用表测量MCU的VDDVSS引脚,电压必须为3.3V±0.1V。若为0V,检查BOOT0引脚是否被意外拉高(应接地),或NRST是否短路。

步骤2:检查ST-Link固件版本
打开ST-Link Utility,点击Help → Firmware version。若版本低于V2J37S7(2022年发布),必须升级:

  • 下载ST官方STSW-LINK007工具
  • 运行后选择Upgrade firmware,选择STLINK-V2.J37.S7固件文件
  • 升级过程约30秒,期间绿色LED闪烁,完成后常亮

旧固件(如J27)无法识别F103C8T6的Flash密度,会显示Device ID: 0x00000000

步骤3:Target Settings关键配置
在ST-Link Utility中:

  • Target → Settings:Interface选择SWD,Reset Mode选择Hardware reset,Frequency设为4000 kHz(过高会导致通信误码)
  • Target → Connect:若提示Cannot connect to target,点击Target → Set SWD frequency,逐步降低至100 kHz再试
  • 连接成功后,Target → Read Device ID应显示0x412(F103系列标识)

步骤4:Program烧录参数

  • File → Load file,选择Project/STM32温湿度.axf
  • Target → Program,在弹出窗口中:
    • Start address:0x08000000(F103 Flash起始地址)
    • Size: 自动识别,若为空则点击Calculate
    • Verify: 必须勾选,确保烧录数据校验通过
    • Reset and Run: 勾选,烧录后自动复位运行

实测发现,若Verify未勾选,偶尔出现烧录成功但程序不运行的情况——本质是Flash编程过程中某页写入失败,但Utility未检测到。

3.4 .bat批处理脚本编写:从“双击无效”到“一键烧录”的进化

一个健壮的download.bat必须解决三个核心问题:路径兼容性、错误拦截、用户反馈。以下是经过200+次实测的终极版本:

@echo off setlocal enabledelayedexpansion :: 获取脚本所在目录,解决中文路径问题 cd /d "%~dp0" :: 检查必要文件是否存在 if not exist "Project\STM32温湿度.axf" ( echo [ERROR] STM32温湿度.axf 文件不存在!请先编译工程。 pause exit /b 1 ) if not exist "ST-LINK_CLI.exe" ( echo [ERROR] ST-LINK_CLI.exe 工具未找到!请将ST官方工具放入当前目录。 echo 下载地址:https://www.st.com/en/development-tools/st-link-cli.html pause exit /b 1 ) :: 生成BIN文件(AXF转BIN是烧录前提) echo 正在转换AXF为BIN... "Keil_v5\ARM\ARMCC\bin\fromelf.exe" --bin --output "Project\stm32.bin" "Project\STM32温湿度.axf" if errorlevel 1 ( echo [ERROR] AXF转BIN失败!请检查Keil安装路径。 pause exit /b 1 ) :: 调用ST-LINK_CLI烧录 echo 正在烧录到STM32... "ST-LINK_CLI.exe" -c SWD -p "Project\stm32.bin" -v -Rst if errorlevel 1 ( echo [ERROR] 烧录失败!请检查ST-Link连接和MCU供电。 pause exit /b 1 ) echo [SUCCESS] 烧录完成!MCU已复位运行。 timeout /t 2 >nul

关键细节说明:

  • cd /d "%~dp0"确保脚本在任意路径双击都能定位到自身目录,避免因快捷方式路径错误导致文件找不到。
  • if not exist双重检查机制,比单纯依赖%ERRORLEVEL%更可靠——因为某些工具异常退出时不设置错误码。
  • fromelf.exe路径硬编码为Keil_v5\ARM\ARMCC\bin\,这是Keil默认安装路径,若用户自定义安装,需手动修改。
  • -v参数启用校验,-Rst参数烧录后自动复位,这两个开关缺一不可。
  • 最后timeout /t 2让成功提示停留2秒,避免窗口闪退。

实操心得:ST-LINK_CLI.exe必须从ST官网下载,淘宝售卖的“绿色版”常被篡改,调用时会弹出Access is denied错误。且务必关闭Windows Defender实时保护,否则首次运行会被拦截。

4. 实操过程与核心环节实现

4.1 CubeMX工程创建:从空白页面到可运行框架的12步

以STM32F103C8T6为例,创建最小可行工程的具体操作:

步骤1:新建工程
打开CubeMX,File → New Project,在Part Number搜索框输入STM32F103C8,双击选中。

步骤2:配置RCC时钟

  • Pinout view标签页,左侧System Core展开RCC,Mode选择Crystal/Ceramic Resonator(使用外部8MHz晶振)
  • Clock Configuration标签页,HSE设为Crystal/Ceramic ResonatorPLL设置:PLL Source=HSEPLL MUL=9(8MHz×9=72MHz),AHB Prescaler=1APB1 Prescaler=2(36MHz),APB2 Prescaler=1(72MHz)
  • 点击Update,右下角SYSCLK应显示72.000 MHz

步骤3:配置SWD调试接口

  • Pinout view中,PA13PA14自动标为SYS_WSWD,无需改动。若被其他功能占用,右键选择GPIO_Output释放。

步骤4:配置DHT11数据线

  • 找到PA0引脚,点击下拉菜单,选择GPIO_Output
  • Configuration标签页,GPIO子菜单中,User LabelDHT11_DATAGPIO output level=High(上电默认高电平,符合DHT11空闲态)

步骤5:配置USART1用于调试

  • PA9设为USART1_TXPA10设为USART1_RX
  • Configuration中,USART1子菜单,Baud Rate=115200Word Length=8 BitsStop Bits=1Parity=None

步骤6:配置LED指示灯(可选但强烈推荐)

  • PC13设为GPIO_OutputUser Label=LEDGPIO output level=Low(点亮时低电平,兼容多数开发板)

步骤7:生成代码

  • Project Manager标签页,Project Name=STM32温湿度Location选择项目文件夹
  • Toolchain / IDE选择MDK-ARM(Keil)
  • Code Generator中,勾选Generate peripheral initialization as a pair of '.c/.h' files per peripheral
  • 点击GENERATE CODE

步骤8:Keil工程初始化
CubeMX生成后,双击Project\STM32温湿度.uvprojx自动打开Keil。此时工程已包含全部HAL库文件,但需手动添加dht11.c

步骤9:添加DHT11驱动文件

  • 在Keil中,右键Source Group 1Add Existing Files to Group...,选择Utilities\dht11.c
  • dht11.c顶部添加#include "main.h",并在dht11.h中声明函数原型

步骤10:修改main.c主循环
while(1)循环内添加:

if(dht11_read_data(&humidity, &temperature) == DHT11_OK) { printf("Temp: %.1f C, Humi: %.1f %%\r\n", temperature, humidity); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); // LED闪烁指示成功 HAL_Delay(2000); } else { printf("DHT11 Error!\r\n"); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); HAL_Delay(500); }

步骤11:配置Keil编译选项

  • Options for TargetC/C++Define添加USE_FULL_LL_DRIVER(启用底层寄存器操作)
  • OutputName of ExecutableSTM32温湿度(确保.axf文件名一致)

步骤12:首次编译与调试
点击Build按钮,观察Output窗口:

  • 若出现creating hex file...,说明Output选项卡中误勾了HEX生成
  • 若报错undefined reference to 'HAL_GPIO_WritePin',检查Drivers\STM32F1xx_HAL_Driver\Src是否已添加到Include Paths
  • 编译成功后,Project\STM32温湿度.axf大小应在16-24KB之间,过小(<10KB)说明代码未链接,过大(>30KB)可能启用了未使用的HAL模块

4.2 DHT11驱动代码详解:逐行解析40位数据接收逻辑

dht11.c的核心函数dht11_read_data()实现如下(已通过嘉立创PCB实测):

#define DHT11_PORT GPIOA #define DHT11_PIN GPIO_PIN_0 typedef enum { DHT11_OK = 0, DHT11_TIMEOUT, DHT11_CHECKSUM_ERROR } DHT11_StatusTypeDef; DHT11_StatusTypeDef dht11_read_data(uint16_t *humidity, uint16_t *temperature) { uint8_t data[5] = {0}; // 存储40位数据:8bit湿度整数+8bit湿度小数+8bit温度整数+8bit温度小数+8bit校验和 uint8_t i, j; // 1. 主机启动信号 __HAL_RCC_GPIOA_CLK_ENABLE(); // 确保GPIOA时钟使能 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DHT11_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); for(i=0; i<200; i++) __NOP(); // 200*1μs = 200μs,实际需20ms,此处为简化示意 // 实际工程中应使用SysTick或定时器实现精确延时 HAL_Delay(20); // 使用HAL_Delay确保20ms HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); for(i=0; i<40; i++) __NOP(); // 40*1μs = 40μs,接近80μs的一半 // 2. 切换为输入模式,等待DHT11响应 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; // 启用内部上拉,减少外部电阻依赖 HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct); // 等待DHT11拉低80μs uint32_t timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && timeout++ < 1000); if(timeout >= 1000) return DHT11_TIMEOUT; // 等待DHT11拉高80μs timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET && timeout++ < 1000); if(timeout >= 1000) return DHT11_TIMEOUT; // 3. 读取40位数据 for(j=0; j<40; j++) { // 每位数据由50μs低电平+27/70μs高电平组成,通过高电平持续时间判断0/1 timeout = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET && timeout++ < 1000); if(timeout >= 1000) return DHT11_TIMEOUT; timeout = 0; uint32_t high_time = 0; while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && timeout++ < 1000) { high_time++; } if(timeout >= 1000) return DHT11_TIMEOUT; // 高电平时间>30μs为1,否则为0 if(high_time > 30) { data[j/8] |= (1 << (7 - j%8)); } } // 4. 校验和验证 if(data[4] == (data[0] + data[1] + data[2] + data[3])) { *humidity = (data[0] << 8) | data[1]; *temperature = (data[2] << 8) | data[3]; return DHT11_OK; } else { return DHT11_CHECKSUM_ERROR; } }

关键点解析:

  • HAL_Delay(20)用于主机拉低阶段,因其精度要求不高(±1ms即可),直接使用HAL提供的ms级延时最稳妥。
  • GPIO_PULLUP启用内部上拉,可省去外部10kΩ电阻(但实测稳定性略低于外置电阻,建议新手仍使用外置)。
  • high_time计数器通过循环累加实现us级测量,high_time > 30的阈值经实测确定:DHT11输出0时高电平约27μs,输出1时约70μs,取中间值30μs可有效区分。
  • 校验和计算data[0]+data[1]+data[2]+data[3]必须与data[4]完全相等,任何一位错误都会导致校验失败,这是DHT11协议的硬性要求。

4.3

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 10:29:43

人形机器人进厂打工:从技术拆解到产线落地全解析

最近两三年&#xff0c;人形机器人的热度一直居高不下。尤其是“进厂打工”这个说法&#xff0c;听起来既接地气又有画面感&#xff1a;一个双足机器人走进车间&#xff0c;像人一样搬箱子、插拔零件、做质检。但真正到产线上去看&#xff0c;你会发现事情没那么简单。各家厂商…

作者头像 李华
网站建设 2026/9/4 1:10:35

Python字符串下标与切片:从IndexError到高效文本处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/4 1:40:47

从 Vercel 上线 MiniMax H3 看 AI 视频生成:云端部署还是本地运行?

最近和几个做 AI 视频工作流的朋友聊天&#xff0c;发现大家争论的焦点已经从“哪个模型生成效果更好”悄悄变成了“这套流程到底应该跑在云端还是放在本地”。MiniMax H3 系列上线 Vercel 并限时五折的消息&#xff0c;正好撞在这个讨论的节骨眼上。表面看&#xff0c;这只是一…

作者头像 李华
网站建设 2026/9/3 15:36:35

PyPI源码包手动下载与离线部署实战:以gensim-0.13.0rc1为例

简介&#xff1a;本资源为PyPI官方发布的gensim-0.13.0rc1源码发布包&#xff08;tar.gz格式&#xff09;&#xff0c;面向Python自然语言处理开发者、文本挖掘工程师及云原生AI系统构建者&#xff0c;解决大规模语料的主题建模、文档相似度计算与分布式文本分析需求。压缩包共…

作者头像 李华
网站建设 2026/9/4 8:13:31

PHP仿永硕E盘源码解析:从文件管理到安全实践的完整指南

简介&#xff1a;本资源是一套完整的PHP在线网盘系统开发实例&#xff0c;面向Web开发初学者与中级PHP工程师&#xff0c;用于快速掌握文件存储类Web应用的核心实现逻辑。源码仿照永硕E盘功能设计&#xff0c;涵盖用户注册登录、文件上传下载、目录管理、权限控制及安全防护等典…

作者头像 李华