嵌入式学习总结:从 I2C 中断驱动到 DHT11 温湿度采集实战

1. 引言

在嵌入式开发的学习道路上,从裸机点灯到驱动外设,再到理解中断与总线协议,是一条必经的进阶之路。本文以 ARM 平台 + I2C 中断驱动 + DHT11 温湿度传感器 为主线,记录一次完整的嵌入式学习与实战总结,涵盖硬件原理、驱动框架、中断机制、协议解析与代码实现,希望能为同样在路上的读者提供一份可参考的路线图。

2. 学习目标与整体框架

本次学习围绕三个核心关键词展开:

  • ARM:以 ARM Cortex-M 系列(如 STM32F103)为硬件平台,理解其内核架构、中断控制器(NVIC)与 GPIO 复用。
  • I2C 中断驱动:掌握 I2C 总线协议,并学会用中断方式(而非轮询)驱动 I2C 外设,提升 CPU 利用率。
  • DHT11:一款单总线数字温湿度传感器,通过自定义时序协议与主机通信,是理解时序驱动与位级解析的绝佳案例。

整体学习路径如下:
#mermaid-svg-9wh4nqZmI6vTcnp9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9wh4nqZmI6vTcnp9 .error-icon{fill:#552222;}#mermaid-svg-9wh4nqZmI6vTcnp9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9wh4nqZmI6vTcnp9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .marker.cross{stroke:#333333;}#mermaid-svg-9wh4nqZmI6vTcnp9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9wh4nqZmI6vTcnp9 p{margin:0;}#mermaid-svg-9wh4nqZmI6vTcnp9 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .cluster-label text{fill:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .cluster-label span{color:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .cluster-label span p{background-color:transparent;}#mermaid-svg-9wh4nqZmI6vTcnp9 .label text,#mermaid-svg-9wh4nqZmI6vTcnp9 span{fill:#333;color:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .node rect,#mermaid-svg-9wh4nqZmI6vTcnp9 .node circle,#mermaid-svg-9wh4nqZmI6vTcnp9 .node ellipse,#mermaid-svg-9wh4nqZmI6vTcnp9 .node polygon,#mermaid-svg-9wh4nqZmI6vTcnp9 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .rough-node .label text,#mermaid-svg-9wh4nqZmI6vTcnp9 .node .label text,#mermaid-svg-9wh4nqZmI6vTcnp9 .image-shape .label,#mermaid-svg-9wh4nqZmI6vTcnp9 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9wh4nqZmI6vTcnp9 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .rough-node .label,#mermaid-svg-9wh4nqZmI6vTcnp9 .node .label,#mermaid-svg-9wh4nqZmI6vTcnp9 .image-shape .label,#mermaid-svg-9wh4nqZmI6vTcnp9 .icon-shape .label{text-align:center;}#mermaid-svg-9wh4nqZmI6vTcnp9 .node.clickable{cursor:pointer;}#mermaid-svg-9wh4nqZmI6vTcnp9 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .arrowheadPath{fill:#333333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9wh4nqZmI6vTcnp9 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9wh4nqZmI6vTcnp9 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9wh4nqZmI6vTcnp9 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9wh4nqZmI6vTcnp9 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .cluster text{fill:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 .cluster span{color:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9wh4nqZmI6vTcnp9 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9wh4nqZmI6vTcnp9 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9wh4nqZmI6vTcnp9 .icon-shape,#mermaid-svg-9wh4nqZmI6vTcnp9 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9wh4nqZmI6vTcnp9 .icon-shape p,#mermaid-svg-9wh4nqZmI6vTcnp9 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9wh4nqZmI6vTcnp9 .icon-shape .label rect,#mermaid-svg-9wh4nqZmI6vTcnp9 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9wh4nqZmI6vTcnp9 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9wh4nqZmI6vTcnp9 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9wh4nqZmI6vTcnp9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ARM 平台基础
GPIO 与中断系统
I2C 协议与中断驱动
DHT11 单总线时序
综合实战:数据采集与显示

3. ARM 平台与开发环境准备

3.1 硬件平台

本次实验选用 STM32F103C8T6(ARM Cortex-M3 内核),搭配:

  • I2C1 总线:用于连接 EEPROM(AT24C02)等标准 I2C 设备,验证中断驱动。
  • 普通 GPIO(如 PA0):用于连接 DHT11 数据引脚。
  • 串口(USART1):用于打印采集到的温湿度数据。

3.2 软件环境

  • 集成开发环境:Keil MDK 或 STM32CubeIDE。
  • 固件库:标准外设库(SPL)或 HAL 库。
  • 调试工具:ST-Link / J-Link,串口调试助手。

建议先跑通一个 GPIO 点灯例程,再逐步引入中断与总线协议,避免一步到位带来的挫败感。

4. 中断系统:嵌入式驱动的灵魂

4.1 什么是中断

中断是 CPU 处理异步事件的机制。当外设(如定时器、串口、I2C)发生特定事件时,会向 CPU 发出中断请求,CPU 暂停当前任务,跳转到对应的中断服务函数(ISR)处理,处理完毕后再返回原任务继续执行。

4.2 ARM 中断控制器(NVIC)

在 Cortex-M 内核中,NVIC(嵌套向量中断控制器) 负责中断的使能、优先级管理与响应。每个外设中断都有独立的 IRQn 编号,通过库函数配置即可:

c 复制代码
/* 使能 I2C1 事件中断 */
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = I2C1_EV_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

4.3 中断 vs 轮询

方式 CPU 占用 实时性 适用场景
轮询 高(持续查询标志位) 一般 简单、低速外设
中断 低(事件触发才处理) 高 高速、多任务场景

学习心得:中断让 CPU 从「忙等」中解放出来,是嵌入式系统从「裸机」走向「实时操作系统」的关键一步。

5. I2C 总线协议与中断驱动实战

5.1 I2C 协议基础

I2C(Inter-Integrated Circuit)是一种两线制串行总线:

  • SCL:时钟线,由主机控制。
  • SDA:数据线,双向传输。

通信流程包括:起始条件(START)、地址帧、数据帧、应答位(ACK/NACK)、停止条件(STOP)。

5.2 中断方式驱动 I2C

相比轮询方式,中断驱动 I2C 的核心思路是:发起传输后立即返回,在中断服务函数中根据事件标志推进状态机。

以 HAL 库为例,中断发送数据的核心代码如下:

c 复制代码
/* 主机以中断方式向从机地址 0xA0 发送 2 字节数据 */
uint8_t pData[2] = {0x12, 0x34};
HAL_I2C_Master_Transmit_IT(&hi2c1, 0xA0, pData, 2);

对应的中断回调函数:

c 复制代码
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c == &hi2c1)
    {
        /* 传输完成,置标志位供主循环处理 */
        g_i2c_tx_done = 1;
    }
}

5.3 中断驱动的状态机设计

实际项目中,一次完整的 I2C 中断读写往往需要多个步骤,建议用状态机管理:

c 复制代码
typedef enum {
    I2C_IDLE,
    I2C_START,
    I2C_SEND_ADDR,
    I2C_SEND_DATA,
    I2C_STOP,
    I2C_DONE
} I2C_State_t;

学习心得:中断驱动看似复杂,但一旦掌握「事件 + 状态机」的思维,就能优雅地处理各种异步外设,也为后续学习 RTOS 的事件驱动模型打下基础。

6. DHT11 温湿度传感器:单总线时序解析

6.1 DHT11 简介

DHT11 是一款数字温湿度传感器,采用**单总线(Single-Wire)**通信,仅需一根数据线即可完成双向通信。测量范围:湿度 20%--90% RH,温度 0--50℃,精度一般,但胜在便宜、简单,非常适合入门。

6.2 通信时序

DHT11 的通信由主机发起,时序如下:

  1. 主机拉低数据线至少 18ms,然后释放(拉高),启动通信。
  2. DHT11 响应:先拉低 80us,再拉高 80us,表示准备好发送数据。
  3. 随后连续发送 40 位数据(5 字节):湿度整数、湿度小数、温度整数、温度小数、校验和。
  4. 每位数据通过高电平持续时间区分:26--28us 表示「0」,70us 表示「1」。

6.3 位级读取代码实现

读取一位的核心逻辑(基于 GPIO 中断或定时器捕获):

c 复制代码
uint8_t DHT11_ReadBit(void)
{
    uint8_t bit = 0;
    /* 等待高电平开始 */
    while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_PIN) == GPIO_PIN_RESET);
    /* 延时 40us 后采样 */
    Delay_us(40);
    if (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_PIN) == GPIO_PIN_SET)
    {
        bit = 1;  /* 高电平持续较长,表示 '1' */
    }
    /* 等待高电平结束 */
    while (HAL_GPIO_ReadPin(DHT11_GPIO_PORT, DHT11_PIN) == GPIO_PIN_SET);
    return bit;
}

6.4 数据校验

接收完 40 位数据后,需校验:

c 复制代码
if ((hum_int + hum_dec + temp_int + temp_dec) == checksum)
{
    /* 数据有效 */
}
else
{
    /* 数据错误,丢弃本次读取 */
}

学习心得:DHT11 的时序解析让我深刻理解了「时序就是协议」这句话。没有硬件 SPI/I2C 控制器时,用 GPIO 模拟时序是嵌入式开发的必备技能。

7. 综合实战:I2C 中断 + DHT11 数据采集系统

7.1 系统设计

将两部分结合,构建一个完整的数据采集系统:

  • I2C 中断驱动:负责与 EEPROM 通信,保存温湿度历史数据。
  • DHT11:负责实时采集温湿度。
  • 串口:打印数据,便于观察。

7.2 主程序流程

c 复制代码
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART1_UART_Init();

    while (1)
    {
        /* 1. 读取 DHT11 温湿度 */
        if (DHT11_ReadData(&hum, &temp) == DHT11_OK)
        {
            /* 2. 通过 I2C 中断方式写入 EEPROM */
            uint8_t buf[2] = {temp, hum};
            HAL_I2C_Mem_Write_IT(&hi2c1, EEPROM_ADDR, 0x00, buf, 2);

            /* 3. 串口打印 */
            printf("Temp: %d.%d C, Hum: %d.%d %%\r\n",
                   temp / 10, temp % 10, hum / 10, hum % 10);
        }
        HAL_Delay(2000);  /* DHT11 采样间隔建议 >= 1s */
    }
}

7.3 中断回调完善

在 I2C 写完成回调中,可以继续发起读操作,实现「写后读」验证:

c 复制代码
void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    if (hi2c == &hi2c1)
    {
        /* 写完成,接着发起读验证 */
        HAL_I2C_Mem_Read_IT(&hi2c1, EEPROM_ADDR, 0x00, g_read_buf, 2);
    }
}

8. 踩坑记录与调试心得

8.1 常见问题

问题现象 可能原因 解决方案
I2C 总线卡死 SCL/SDA 未加上拉电阻 外接 4.7kΩ 上拉电阻
DHT11 一直无响应 起始信号时序不满足 确保低电平时间 >= 18ms
数据校验失败 位读取延时不准 用定时器或 DWT 精确延时
中断不触发 NVIC 未使能或优先级配置错误 检查 NVIC 配置与中断标志清除

8.2 调试技巧

  • 逻辑分析仪:抓取 I2C 与单总线波形,直观对比时序。
  • 串口打印:在关键节点打印状态标志,快速定位卡在哪一步。
  • 分步验证:先单独验证 I2C 读写 EEPROM,再单独验证 DHT11,最后联调。
相关推荐
万物智能信息科技1 小时前
气体传感器MQ-2芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
驱动开发·嵌入式硬件·华为·开源·harmonyos
星恒随风1 小时前
Linux进程间通信(二):深入理解System V共享内存、消息队列、信号量与内核IPC资源管理
linux·运维·服务器·笔记·学习·状态模式
li星野1 小时前
【学习记录】PCB设计常用操作全解析:单位、图层、过滤器、布线、规则与快捷键
学习
优化Henry1 小时前
LTE基站传输割接后S1连接失败排查实录
运维·网络·笔记·学习·信息与通信
fly_sunnn2 小时前
STM32-TIM输入捕获
stm32·单片机·嵌入式硬件
创界工坊工作室2 小时前
25-基于单片机的智能水产养殖控制系统设计
stm32·单片机·嵌入式硬件·proteus
安生生申2 小时前
ESP32-CAM视觉语音AI助手(千问平台):摄像头、INMP441、MAX98357与单片机数据接入
人工智能·单片机·嵌入式硬件
碧海银沙音频科技研究院2 小时前
基于炬芯ATS3089X的ST LIS2DW12 三轴 MEMS 加速度传感器(G-Sensor)敲击按键功能实现
人工智能·嵌入式硬件
ccbw162 小时前
CH32L103低功耗RISC-V MCU的集成外设与选型参考
单片机·嵌入式硬件·risc-v·低功耗mcu·潜创微·青稞risc-v4c