【CanMV K210】传感器实验 DHT11 温湿度读取与环境监测

在智能硬件项目中,传感器负责把真实环境转换成程序可以处理的数据。温湿度采集是最常见的入门实验之一,空气温度、环境湿度、机房监测、农业大棚、仓储管理、智能家居都离不开这类基础数据。对于 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.GPIOfpioa_manager 模块 固件环境需要支持 GPIO 模式切换和 time.sleep_us()
USB 串口驱动 让电脑识别开发板串口 串口工具中能看到对应端口
maix.GPIO 创建 GPIO 对象 支持 GPIO.OUTGPIO.INGPIO.PULL_UPvalue()
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 状态提示、数据存储、网络上传和环境监测系统等方向。

相关推荐
笑小枫4 小时前
行业新趋势:官网数字人成标配,具身交互重构用户触达
人工智能·交互
梦想的初衷~4 小时前
AI辅助下基于ArcGIS Pro的SWAT模型全流程高效建模实践与深度进阶应用
人工智能·arcgis·气候·水文·地理信息·环境科学
数智工坊4 小时前
RT-DETRv2训练自定义数据集的排坑全记录
人工智能
凤山老林4 小时前
AI辅助编程:Copilot在Java开发中的最佳实践
java·人工智能·copilot
老王谈企服4 小时前
制造业安全生产无人化巡检,未来将全面普及吗?[2026实效定调:智能体企业引领工业安全新范式]
人工智能·安全·ai
启途AI4 小时前
ChatPPT×Banana2+Image-2创意绘图生成模式:精准可控,解锁AI PPT创作新体验
大数据·人工智能
ZC跨境爬虫4 小时前
模块化烹饪小程序开发日记 Day5:(后端Flask接口开发与AI智能解析菜谱的实现)
前端·人工智能·后端·python·ui·flask
渡我白衣4 小时前
第十五章:海纳百川——集成学习的高级策略与Stacking硬核实战
人工智能·深度学习·神经网络·机器学习·自然语言处理·语音识别·集成学习
XMAIPC_Robot4 小时前
深度无人机自动驾驶仪,中小型无人机硬件在环仿真飞行
运维·arm开发·人工智能·fpga开发·无人机·边缘计算