在智能硬件项目中,传感器负责把真实环境转换成程序可以处理的数据。温湿度采集是最常见的入门实验之一,空气温度、环境湿度、机房监测、农业大棚、仓储管理、智能家居都离不开这类基础数据。对于 Python 硬件编程学习而言,DHT11 实验的价值不只是打印温度和湿度,而是理解程序如何通过 GPIO 与外部传感器完成一次完整通信。
本实验使用 CanMV K210 开发板连接 DHT11 温湿度传感器,DATA 数据线接到 IO6。程序启动后等待 2 秒,让传感器和数据总线稳定,再每隔 2 秒读取一次温湿度数据。读取成功时,串口终端会输出温度和湿度;读取失败时,程序会根据数据缺失或校验错误输出对应错误信息。
| 学习目标 | 说明 |
|---|---|
| 理解温湿度采集流程 | 认识 DHT11 如何把环境温度和湿度转换成程序可读取的数据 |
| 掌握 GPIO 单总线通信 | 在同一个 DATA 引脚上完成启动信号发送和数据接收 |
| 理解输入输出模式切换 | 发送启动信号时使用输出模式,接收数据时切换为输入模式 |
| 学会数据校验 | 通过 40 位数据解析和校验字节判断读取结果是否可靠 |
| 建立传感器调试思路 | 从接线、电源、上拉、读取频率和错误码分析常见问题 |
DHT11 属于低速环境传感器,读取频率不能过高。当前代码每隔 2 秒读取一次数据,既能保证串口中持续看到结果,也能减少缺失数据和校验错误。这个实验很适合作为环境监测课程的基础案例,后续可以继续扩展 LCD 显示、蜂鸣器报警、LED 状态提示、数据保存和远程上传。
文章目录
理论基础
DHT11 是常见的数字温湿度传感器,模块内部已经完成了温湿度采集和基础数据处理,外部主控只需要通过 DATA 数据线与它通信即可。它和普通模拟传感器不同,DHT11 不需要额外 ADC 模块,返回的不是连续模拟电压,而是一组带有格式和校验的数据。
本实验中的 DATA 线既要发送信号,也要接收信号。程序开始读取时,先把 GPIO 设置成输出模式,将数据线拉低一段时间,通知 DHT11 开始通信;随后释放数据线,并把 GPIO 切换成输入模式,等待传感器返回响应波形和 40 位数据。这个过程对时序比较敏感,所以代码里同时使用了毫秒级延时和微秒级延时。
DHT11 返回的数据通常由 5 个字节组成,分别表示湿度整数部分、湿度小数部分、温度整数部分、温度小数部分和校验字节。程序读取到 40 位二进制数据后,会把它们转换成 5 个字节,再用前 4 个字节计算校验值。如果计算结果和第 5 个字节一致,说明本次读取有效;如果不一致,就会返回 CRC 错误。
CanMV K210
Python 程序发起读取
IO6 / GPIOHS3
切换为输出模式
DATA 拉低约 20ms
发送启动信号
释放 DATA 总线
切换为输入模式
DHT11 响应
返回高低电平波形
采集 40 位数据
湿度 / 温度 / 校验
字节转换与校验
判断数据是否有效
串口输出
温度 / 湿度 / 错误信息
供电与共地
VCC / GND
读取间隔
建议约 2 秒
这张流程图展示的是 DHT11 的电路通信和数据解析过程。CanMV K210 不是直接读取一个固定电平,而是先发起通信,再根据 DHT11 返回的高低电平持续时间解析数据。对于硬件编程入门而言,这个实验能很好地展示 GPIO 不只是输入或输出,还可以在同一根线上根据通信阶段动态切换角色。
实验中还需要关注电平安全。部分 DHT11 模块支持 3.3V 或 5V 供电,但 CanMV K210 的 IO 更适合 3.3V 电平。如果模块供电接 5V,并且 DATA 被模块上拉到 5V,不建议直接接入 K210 IO。更稳妥的做法是优先使用 3.3V 供电,或者在 5V 供电场景中加入电平转换,避免数据线电平超出开发板 IO 承受范围。
硬件设施
本实验只围绕代码中真实出现的硬件对象展开,也就是 CanMV K210 开发板、DHT11 温湿度传感器和 GPIO 数据通信。代码没有使用 LCD、按键、蜂鸣器、摄像头或电机,因此硬件重点集中在 DHT11 的 DATA、VCC、GND 三个接口,以及 CanMV IO6 的数据通信功能。
接线关系可以通过下面这张图建立整体印象。DHT11 的 DATA / S / OUT 接到 CanMV IO6,VCC 接到合适的供电端,GND 接到开发板 GND。通电前重点核对 DATA、VCC、GND 的顺序,避免把数据线和电源线接反。

| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,并通过 GPIO 与 DHT11 通信 |
| DHT11 温湿度传感器 | 环境数据采集模块 | 采集空气温度和湿度,并通过单总线 DATA 引脚返回数据 |
| CanMV IO6 | 传感器数据接口 | 代码中通过 fm.register() 将 IO6 映射为 GPIOHS3 |
maix.GPIO |
GPIO 控制模块 | 用于切换输出模式、输入模式和读取电平变化 |
fpioa_manager.fm |
引脚功能映射模块 | 用于把物理引脚 IO6 绑定到 GPIOHS3 功能 |
time |
延时控制模块 | 用于控制 DHT11 启动信号、微秒采样和 2 秒读取间隔 |
实验中使用到的核心零件如下。DHT11 负责采集环境温湿度,CanMV K210 负责发起读取和解析数据,连接线负责建立 DATA、VCC、GND 三条基础连接。若模块上标注为 DATA、S 或 OUT,本质上都表示传感器的数据输出引脚。

接线关系已经在代码注释中给出。DHT11 的 DATA、S 或 OUT 引脚连接到 CanMV IO6,电源引脚连接 3.3V 或合适的模块供电端,GND 连接开发板 GND。代码中只对 DATA 数据线进行了 GPIO 控制,因此表格重点整理数据通信映射。
| 传感器引脚 | CanMV 接口 | GPIO 功能 | 代码变量 | 说明 |
|---|---|---|---|---|
| DATA / S / OUT | IO6 | GPIOHS3 | dht_gpio |
用于发送启动信号并接收 DHT11 返回数据 |
| VCC | 3.3V 优先 | 无 | 无 | 为 DHT11 提供工作电源,若使用 5V 模块需关注 DATA 电平安全 |
| GND | GND | 无 | 无 | 与开发板共地,保证数据电平有统一参考 |
完成接线后的整体状态如下。实验运行前应检查 DHT11 模块方向、DATA 数据线位置、电源电压和 GND 是否正确。程序启动后不会立即打印温湿度,而是先等待 2 秒,让传感器和数据总线进入稳定状态。

| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | 串口输出 DHT11 humiture test start |
没有输出时检查脚本运行和串口连接 |
| 等待稳定 | 启动后约 2 秒开始读取 | 立即读取可能出现缺失数据或校验错误 |
| 读取成功 | 串口打印温度和湿度 | 数值长期不变时检查传感器供电和环境变化 |
| 缺失数据 | 串口提示 missing data |
常见于 DATA 接线错误、传感器未供电或时序不稳定 |
| 校验错误 | 串口提示 crc error |
常见于线材过长、干扰明显或读取频率过高 |
| 模块异常发热 | 正常情况下 DHT11 不应明显发烫 | 立即断电检查 VCC、GND、DATA 是否接反 |
软件代码
本实验代码围绕 DHT11 的单总线数据读取展开。程序包含读取结果类、DHT11 驱动类、硬件配置和主程序循环。读取结果类负责统一保存错误码、温度和湿度;驱动类负责完成启动信号、数据采样、位解析、字节转换和校验;硬件配置负责把 IO6 映射为 GPIOHS3;主循环负责定时读取并打印结果。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能运行基础 print() 测试 |
| CanMV 固件 | 提供 maix.GPIO 和 fpioa_manager 模块 |
固件环境需要支持 GPIO 模式切换和 time.sleep_us() |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口工具中能看到对应端口 |
maix.GPIO |
创建 GPIO 对象 | 支持 GPIO.OUT、GPIO.IN、GPIO.PULL_UP 和 value() |
fpioa_manager.fm |
完成物理引脚与 GPIOHS 映射 | IO6 能映射到 GPIOHS3 |
time |
延时与时序控制 | 用于启动等待、20ms 拉低、30us 释放和 2 秒采样间隔 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CanMV K210 DHT11 温湿度传感器实验 - 单文件稳定版
接线说明:
DHT11 DATA / S / OUT -> CanMV IO6
DHT11 VCC -> 3.3V / 5V
DHT11 GND -> GND
说明:
DHT11 读取频率不能太高,建议 2 秒读取一次。
启动后等待 2 秒,让传感器和 DATA 总线先稳定。
"""
import time
from maix import GPIO
from fpioa_manager import fm
# =========================
# DHT11 读取结果类
# =========================
class DHT11Result:
ERR_NO_ERROR = 0
ERR_MISSING_DATA = 1
ERR_CRC = 2
def __init__(self, error_code, temperature, humidity):
self.error_code = error_code
self.temperature = temperature
self.humidity = humidity
def is_valid(self):
return self.error_code == DHT11Result.ERR_NO_ERROR
# =========================
# DHT11 驱动类
# =========================
class DHT11:
def __init__(self, gpio):
self._gpio = gpio
def read(self):
# 空闲状态为高电平
self._gpio.mode(GPIO.OUT)
self._gpio.value(1)
time.sleep_ms(50)
# 主机拉低至少 18ms,通知 DHT11 开始发送数据
self._gpio.value(0)
time.sleep_ms(20)
# 释放总线
self._gpio.value(1)
time.sleep_us(30)
# 切换为输入模式,优先尝试启用上拉
try:
self._gpio.mode(GPIO.IN, GPIO.PULL_UP)
except:
self._gpio.mode(GPIO.IN)
data = self.__collect_input()
pull_up_lengths = self.__parse_data_pull_up_lengths(data)
if len(pull_up_lengths) != 40:
return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)
bits = self.__calculate_bits(pull_up_lengths)
the_bytes = self.__bits_to_bytes(bits)
if len(the_bytes) != 5:
return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)
checksum = self.__calculate_checksum(the_bytes)
if the_bytes[4] != checksum:
return DHT11Result(DHT11Result.ERR_CRC, 0, 0)
humidity = the_bytes[0] + float(the_bytes[1]) / 10
temperature = the_bytes[2] + float(the_bytes[3]) / 10
return DHT11Result(DHT11Result.ERR_NO_ERROR, temperature, humidity)
def __collect_input(self):
unchanged_count = 0
max_unchanged_count = 120
last = -1
data = []
while True:
current = self._gpio.value()
data.append(current)
if current != last:
unchanged_count = 0
last = current
else:
unchanged_count += 1
if unchanged_count > max_unchanged_count:
break
if len(data) > 8000:
break
return data
def __parse_data_pull_up_lengths(self, data):
STATE_INIT_PULL_DOWN = 1
STATE_INIT_PULL_UP = 2
STATE_DATA_FIRST_PULL_DOWN = 3
STATE_DATA_PULL_UP = 4
STATE_DATA_PULL_DOWN = 5
state = STATE_INIT_PULL_DOWN
lengths = []
current_length = 0
for current in data:
current_length += 1
if state == STATE_INIT_PULL_DOWN:
if current == 0:
state = STATE_INIT_PULL_UP
continue
if state == STATE_INIT_PULL_UP:
if current == 1:
state = STATE_DATA_FIRST_PULL_DOWN
continue
if state == STATE_DATA_FIRST_PULL_DOWN:
if current == 0:
state = STATE_DATA_PULL_UP
continue
if state == STATE_DATA_PULL_UP:
if current == 1:
current_length = 0
state = STATE_DATA_PULL_DOWN
continue
if state == STATE_DATA_PULL_DOWN:
if current == 0:
lengths.append(current_length)
state = STATE_DATA_PULL_UP
continue
return lengths
def __calculate_bits(self, pull_up_lengths):
shortest_pull_up = min(pull_up_lengths)
longest_pull_up = max(pull_up_lengths)
halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
bits = []
for length in pull_up_lengths:
bits.append(length > halfway)
return bits
def __bits_to_bytes(self, bits):
the_bytes = []
byte = 0
for i in range(len(bits)):
byte = byte << 1
if bits[i]:
byte = byte | 1
if (i + 1) % 8 == 0:
the_bytes.append(byte)
byte = 0
return the_bytes
def __calculate_checksum(self, the_bytes):
return (the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3]) & 255
# =========================
# 硬件配置
# =========================
DHT_PIN = 6
fm.register(DHT_PIN, fm.fpioa.GPIOHS3, force=True)
try:
dht_gpio = GPIO(GPIO.GPIOHS3, GPIO.OUT, GPIO.PULL_UP)
except:
dht_gpio = GPIO(GPIO.GPIOHS3, GPIO.OUT)
sensor = DHT11(dht_gpio)
# =========================
# 主程序
# =========================
print("DHT11 humiture test start")
# 上电后等待传感器稳定
time.sleep_ms(2000)
while True:
result = sensor.read()
if result.is_valid():
print("--------------------")
print("Temperature: %-3.1f C" % result.temperature)
print("Humidity: %-3.1f %%" % result.humidity)
else:
if result.error_code == DHT11Result.ERR_MISSING_DATA:
print("Read error, code: 1, missing data")
elif result.error_code == DHT11Result.ERR_CRC:
print("Read error, code: 2, crc error")
else:
print("Read error, code:", result.error_code)
# DHT11 建议 2 秒读取一次
time.sleep_ms(2000)
这段代码可以理解成三个层级。底层是 GPIO 映射和 DATA 数据线控制,负责把 IO6 变成 dht_gpio 对象;中间层是 DHT11 类,负责发送启动信号、采集电平波形、解析 40 位数据和完成校验;上层是主循环,负责定时读取、判断结果并打印温湿度或错误信息。
DHT11Result 用来封装一次读取的结果。它不直接操作硬件,只负责保存错误码、温度和湿度。ERR_NO_ERROR 表示读取成功,ERR_MISSING_DATA 表示没有采集到完整 40 位数据,ERR_CRC 表示校验失败。主循环通过 is_valid() 判断读取是否有效,这样错误处理逻辑会更清晰。
DHT11.read() 是驱动流程的入口。程序先把 GPIO 设置为输出模式,让数据线保持高电平,再拉低 20 毫秒发送启动信号。DHT11 接收到启动信号后,程序释放总线并切换到输入模式,开始采集传感器返回的高低电平序列。由于 DHT11 通过高电平持续时间区分 0 和 1,所以代码先收集电平变化,再分析每一段高电平长度。
__collect_input() 会连续读取 DATA 引脚电平,并把采样结果保存到列表中。当电平长时间不再变化,或者采样数据超过限制,采集过程结束。__parse_data_pull_up_lengths() 使用状态机解析返回波形,提取出 40 个数据位对应的高电平长度。__calculate_bits() 根据最短和最长高电平计算中间阈值,大于阈值的位判断为 1,小于阈值的位判断为 0。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
DHT11Result.__init__() |
保存错误码、温度和湿度 | 每次读取都有统一的数据结果 |
DHT11Result.is_valid() |
判断读取是否成功 | 主循环根据结果决定打印数据或错误 |
DHT11.__init__() |
保存 GPIO 对象 | 驱动类获得 DATA 数据线控制权 |
DHT11.read() |
完成一次 DHT11 读取流程 | 串口输出温度、湿度或错误信息 |
__collect_input() |
采集 DATA 引脚电平变化 | 获取传感器返回的原始波形数据 |
__parse_data_pull_up_lengths() |
提取 40 位数据的高电平长度 | 把原始电平变化转换为可判断的时序片段 |
__calculate_bits() |
根据高电平长度判断 0 和 1 | 得到 DHT11 返回的 40 位二进制数据 |
__bits_to_bytes() |
将 40 位数据转换为 5 个字节 | 形成湿度、温度和校验数据 |
__calculate_checksum() |
计算校验值 | 判断本次读取数据是否可靠 |
time.sleep_ms(2000) |
控制读取间隔 | 每隔约 2 秒读取一次温湿度 |
主程序中的 time.sleep_ms(2000) 很关键。DHT11 的读取频率不能太高,启动后也需要一定时间稳定。如果连续快速读取,传感器可能还没有准备好新数据,串口就会出现缺失数据或 CRC 错误。当前程序采用 2 秒读取一次的节奏,符合 DHT11 这类低速温湿度传感器的使用特点。
扩展应用
DHT11 实验的调试重点通常不在算法本身,而在接线、电源、DATA 数据线时序和读取频率。程序已经对缺失数据和 CRC 错误做了分类输出,因此排查时可以根据串口信息定位问题方向。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| 串口一直没有输出 | 程序没有运行、开发板未连接、串口工具未打开 | 检查脚本是否执行,确认终端能看到 DHT11 humiture test start |
一直提示 missing data |
DATA 线未接到 IO6、引脚映射不匹配、传感器未供电 | 核对 DHT11 DATA 是否连接 CanMV IO6,检查 VCC 和 GND |
偶尔出现 crc error |
数据线干扰、读取时序不稳定、供电不稳 | 缩短连线、确认共地,保持 2 秒读取间隔 |
| 温湿度数值一直为 0 | 传感器数据没有被正确解析,或模块连接异常 | 重新检查接线方向,确认 DATA、VCC、GND 没有接反 |
| 读取成功率低 | 上拉不稳定、线材过长、启动等待不足 | 保留 GPIO.PULL_UP,必要时增加外部上拉电阻,保持上电后等待 2 秒 |
| 读取速度无法提高 | DHT11 本身采样频率较低 | 不建议把 time.sleep_ms(2000) 改得过小,否则错误率会升高 |
| 使用 5V 供电后通信异常 | DATA 电平可能与 K210 IO 不匹配 | 优先使用 3.3V 供电;若必须 5V 供电,应加入电平转换 |
| 模块明显发热 | VCC、GND 接反或供电异常 | 立即断电检查接线,不要继续运行程序 |
DHT11 实验可以自然延伸到很多环境监测场景。当前代码已经完成传感器初始化、数据读取、错误判断和周期输出,这些能力是环境采集类项目的基础。后续只需要把串口打印替换成屏幕显示、数据保存、网络上传或报警逻辑,就可以从单一实验扩展成完整应用。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 室内温湿度监测 | 定时读取 DHT11 数据,并在串口或屏幕显示 | 后续可扩展 LCD 显示当前环境数据 |
| 智能家居环境反馈 | 将温湿度作为房间环境状态输入 | 可结合风扇、加湿器等设备形成自动控制逻辑 |
| 机房或设备箱监控 | 周期采集温湿度,发现异常时输出提示 | 可扩展蜂鸣器或 LED 实现报警 |
| 农业大棚采集 | 读取环境湿度和温度,辅助判断作物环境 | 可扩展数据记录和远程上传 |
| 教学演示实验 | 用真实传感器解释 GPIO 输入、时序和校验 | 适合讲解单总线通信和二进制数据解析 |
| 数据采集项目原型 | 将 sensor.read() 作为统一采集接口 |
可继续接入多个传感器,形成环境数据采集系统 |
| 环境异常提醒 | 温度或湿度超过阈值时触发提示 | 可结合蜂鸣器、LED 或屏幕显示形成多通道提醒 |
从工程角度看,当前程序已经具备较好的模块化基础。DHT11 类把底层时序读取封装起来,主循环只需要调用 sensor.read() 获取结果。这样的结构有利于后续扩展,屏幕显示、数据上传、文件记录和报警判断都可以围绕读取结果继续开发,而不需要频繁修改底层通信代码。
总结
本实验通过 CanMV K210 开发板完成了 DHT11 温湿度传感器读取,核心能力包括 FPIOA 引脚映射、GPIO 输入输出模式切换、单总线通信、微秒级时序采样、二进制数据解析、字节转换和校验判断。代码不是简单读取一个数值,而是完整展示了主机发起通信、传感器返回数据、程序解析数据、串口输出结果的全过程。
DHT11 实验很适合作为传感器课程的起点。它能让抽象的 GPIO 输入输出、数据线时序、错误校验变成可观察的温湿度结果。实验过程中需要注意供电电压、DATA 数据线电平和读取间隔,避免因接线、电平不匹配或过快读取造成异常。后续课程可以继续扩展到 LCD 显示、蜂鸣器报警、LED 状态提示、数据存储、网络上传和环境监测系统等方向。