STM32+FreeRTOS下DHT11与MQ传感器驱动设计
1. 项目背景与技术选型分析
在智慧安全厨房监控系统中,温湿度与可燃气体浓度是核心环境参数。DHT11数字温湿度传感器与MQ系列模拟气体传感器构成感知层基础。本项目运行于STM32F103C8T6平台,采用FreeRTOS实时操作系统进行多任务调度。选择该组合并非偶然:STM32F103具备完整的外设资源(USART、TIM、ADC、GPIO)和成熟的HAL库支持;FreeRTOS提供轻量级任务管理、同步机制与内存管理能力;而DHT11与MQ系列传感器则在成本、功耗与可靠性之间取得良好平衡。
需特别注意的是,DHT11的单总线通信协议对时序精度要求严苛——数据位采样窗口仅维持在20–40μs范围内,任何超过5μs的任务切换或中断延迟都可能导致通信失败。这与FreeRTOS默认的1ms系统节拍(SysTick)存在根本性冲突。若直接在任务上下文中调用DHT11读取函数,RTOS调度器可能在数据位采样过程中强制切换至其他高优先级任务,导致采样点偏移,最终解析出错误的温湿度值。因此,必须建立一种机制,在DHT11通信窗口内完全屏蔽任务调度,确保时序完整性。
MQ系列传感器(MQ-2/MQ-4/MQ-7)则依赖ADC模数转换获取气体浓度模拟量。其输出电压范围为0–5V,而STM32F103的ADC输入耐压上限为VREF+(通常为3.3V)。若直接将MQ模块AO引脚接入ADC通道,高电平可能超出ADC安全输入范围,造成采样失真甚至器件损伤。这要求在硬件信号链中引入分压网络,将0–5V线性映射至0–3.3V区间,同时保证信号带宽满足气体浓度变化速率需求。
2. 硬件接口与电气设计
2.1 DHT11单总线接口配置
DHT11采用单总线(1-Wire)协议,仅需一根GPIO线完成双向通信。本项目选用PB12引脚作为数据线。该引脚配置为开漏输出模式(Open-Drain),外接10kΩ上拉电阻至3.3V电源。此设计确保总线空闲时保持高电平,符合DHT11协议规范。当MCU需要发送起始信号或响应DHT11的响应脉冲时,通过控制PB12引脚电平实现总线驱动。
关键电气参数如下:
- 上拉电阻:10kΩ(兼顾上升沿速度与功耗)
- 总线电容:PCB走线寄生电容 < 50pF(避免信号过冲与振铃)
- 通信距离:≤2米(长线需增加驱动能力)
2.2 MQ系列传感器信号调理电路
MQ-2传感器模块输出AO(Analog Output)电压信号,其幅度与检测气体浓度呈反比关系:浓度越高,内部气敏电阻阻值越低,AO端输出电压越高。典型输出范围为0.2V(洁净空气)至4.8V(高浓度气体)。为适配STM32F103的ADC输入范围(0–3.3V),设计两级信号调理:
第一级:精密分压网络
采用R1=10kΩ(上臂)、R2=4.7kΩ(下臂)构成分压器,连接于MQ模块AO引脚与GND之间。分压后信号从R1/R2节点引出,接入STM32的ADC1_IN0通道(PA0引脚)。分压比计算如下:
V_out = V_in × R2 / (R1 + R2) = V_in × 4.7 / (10 + 4.7) ≈ V_in × 0.32
当V_in=5V时,V_out≈1.6V;当V_in=0.2V时,V_out≈0.064V。此设计虽未完全利用ADC 0–3.3V满量程,但留有足够裕量应对传感器批次差异与温度漂移,且避免了接近3.3V时可能出现的非线性区。
第二级:RC低通滤波
在分压器输出端串联100Ω电阻,并对地并联100nF电容,构成截止频率f_c=1/(2πRC)≈15.9kHz的低通滤波器。该滤波器有效抑制高频噪声(如开关电源纹波、无线模块射频耦合),同时不影响气体浓度缓慢变化的动态响应(厨房气体扩散时间常数通常为秒级)。
2.3 火焰传感器接口
火焰传感器模块输出数字信号(DO),内置比较器与可调电位器,用于设定火焰检测阈值。其DO引脚直接连接至STM32的GPIO输入(如PA1),配置为浮空输入(Floating Input)模式。当检测到火焰红外辐射时,DO输出低电平(0V);无火焰时输出高电平(3.3V)。该设计简化了软件处理逻辑,无需ADC采样与阈值判断,仅需检测电平跳变即可触发报警。
3. FreeRTOS环境下的DHT11驱动实现
3.1 微秒级精准延时机制
FreeRTOS的SysTick定时器最小分辨率为1ms,无法满足DHT11所需的微秒级延时(如延时30μs、80μs)。若强行使用 vTaskDelay() 会导致通信失败。解决方案是独立配置一个通用定时器(TIM3)作为专用延时外设,避开RTOS内核定时器资源。
TIM3配置参数如下:
- 时钟源:APB1总线时钟(36MHz,经AHB/APB预分频后)
- 预分频器(PSC):35 → 计数器时钟频率 = 36MHz / (35+1) = 1MHz → 计数周期 = 1μs
- 自动重装载值(ARR):0xFFFF(65535)→ 最大延时65.535ms,覆盖DHT11所有时序需求
- 计数模式:向上计数(Upcounting)
- 中断:禁用(纯软件轮询模式)
延时函数实现核心逻辑:
void Delay_us(uint16_t us) {
__HAL_TIM_SET_COUNTER(&htim3, 0); // 清零计数器
__HAL_TIM_ENABLE(&htim3); // 启动定时器
while (__HAL_TIM_GET_COUNTER(&htim3) < us); // 等待计数值达到us
__HAL_TIM_DISABLE(&htim3); // 停止定时器
}
void Delay_ms(uint16_t ms) {
for(uint16_t i = 0; i < ms; i++) {
Delay_us(1000); // 调用微秒延时实现毫秒延时
}
}
此方案优势在于:完全脱离RTOS调度器,延时精度达±1μs;无中断开销,避免上下文切换延迟;代码简洁,易于验证。
3.2 DHT11通信时序保护机制
DHT11读取流程包含严格时序约束:
1. 起始信号 :MCU拉低总线80μs,再拉高80μs
2. 响应信号 :DHT11拉低80μs,再拉高80μs
3. 数据位传输 :每个数据位以50μs低电平开始,高电平持续27μs(‘0’)或70μs(‘1’)
为保障整个通信过程不被RTOS任务切换打断,必须在读取函数入口处调用 vTaskSuspendAll() 挂起所有任务调度,在函数出口处调用 xTaskResumeAll() 恢复调度。此操作使当前任务进入“临界区”,期间即使更高优先级任务就绪,也不会发生上下文切换。
DHT11读取函数框架如下:
typedef struct {
uint8_t humidity_int; // 湿度整数部分
uint8_t humidity_dec; // 湿度小数部分(恒为0)
uint8_t temp_int; // 温度整数部分
uint8_t temp_dec; // 温度小数部分(恒为0)
uint8_t checksum; // 校验和
} DHT11_DataTypeDef;
uint8_t DHT11_Read_Data(DHT11_DataTypeDef *data) {
uint8_t buf[5] = {0};
uint8_t i, j;
vTaskSuspendAll(); // 关闭RTOS调度,进入临界区
// 1. 发送起始信号
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET);
Delay_us(800);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);
Delay_us(30);
// 2. 等待DHT11响应(此处省略详细电平检测逻辑)
// ...
// 3. 读取40位数据
for(i = 0; i < 5; i++) {
for(j = 0; j < 8; j++) {
// 检测每一位的高电平持续时间,判断'0'或'1'
// ...
}
}
// 4. 校验
if(buf[0] + buf[1] + buf[2] + buf[3] == buf[4]) {
data->humidity_int = buf[0];
data->temp_int = buf[2];
xTaskResumeAll(); // 恢复RTOS调度
return 0; // 成功
} else {
xTaskResumeAll();
return 1; // 校验失败
}
}
关键点说明:
- vTaskSuspendAll() 与 xTaskResumeAll() 必须成对出现,且位于同一函数内,避免调度状态不一致。
- 在临界区内禁止调用任何可能引起阻塞的RTOS API(如 xQueueSend() 、 vTaskDelay() ),否则将导致系统死锁。
- 实际工程中,应在临界区外完成数据结构初始化与校验后处理,最大限度缩短临界区时长。
4. MQ系列传感器ADC采集与标定
4.1 ADC外设配置与DMA自动传输
STM32F103的ADC1配置为连续扫描模式,采集PA0(MQ-2)、PA1(MQ-4)、PA2(MQ-7)、PA3(火焰传感器AO)共4个通道。关键参数设置如下:
- 分辨率:12位(0–4095)
- 数据对齐:右对齐(便于直接使用低12位)
- 采样时间:固定为239.5个ADC时钟周期(保证高精度采样)
- 触发方式:软件触发(由任务主动启动)
- DMA:启用,循环模式,传输至4元素数组 adc_raw[4]
ADC初始化代码片段:
ADC_ChannelConfTypeDef sConfig = {0};
// 使能ADC1时钟
__HAL_RCC_ADC1_CLK_ENABLE();
// 配置ADC句柄
hadc1.Instance = ADC1;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
if (HAL_ADC_Init(&hadc1) != HAL_OK) { /* 错误处理 */ }
// 配置通道0 (PA0)
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { /* 错误处理 */ }
// 配置通道1 (PA1)
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { /* 错误处理 */ }
// ... 配置通道2、3
// 启动ADC并使能DMA
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_raw, 4,
DMA_MINC_ENABLE, DMA_PRIORITY_HIGH);
DMA配置确保ADC转换结果自动存入RAM,无需CPU干预,释放处理器资源用于数据处理与通信。
4.2 气体浓度标定方法论
MQ传感器输出非线性,需通过标定建立浓度-电压映射关系。标定分两步:
第一步:基准点标定
在洁净空气中(标准大气,25℃,50%RH)采集100次ADC值,取平均作为 V_clean (对应浓度0ppm)。本项目实测 V_clean ≈ 1.25V (ADC值≈1550)。
第二步:特征点标定
使用标准气体发生装置,分别注入已知浓度(如1000ppm甲烷、500ppm一氧化碳)气体,记录稳定后的ADC值 V_gas 。根据MQ数据手册提供的灵敏度曲线,拟合指数模型:
Rs/R0 = (Vcc - Vout) / Vout × Rl / R0
其中 Rs 为气敏电阻阻值, R0 为洁净空气阻值, Rl 为负载电阻(模块内置)。实际工程中,常采用简化线性插值:
Concentration = K × (V_clean - V_gas) + C0
系数K、C0通过最小二乘法拟合多个标定点获得。对于学习项目,可暂用经验值:MQ-2对甲烷的K≈2000ppm/V,C0≈0。
标定注意事项 :
- 标定环境温度需稳定(±2℃),因气敏电阻温度系数显著;
- 每次标定后需充分通风,确保传感器恢复至基线;
- 长期运行需定期重新标定,补偿器件老化。
5. 多任务协同架构设计
5.1 任务划分与优先级策略
系统定义4个FreeRTOS任务,按功能与实时性要求分配优先级(数值越大优先级越高):
| 任务名称 | 优先级 | 功能描述 | 周期/触发条件 |
|---|---|---|---|
vTaskWifiConnect |
3 | Wi-Fi连接与EMQX服务器通信 | 启动后执行一次,成功后删除自身 |
vTaskDHT11Reader |
2 | 定期读取DHT11温湿度 | 2秒周期(2000ms) |
vTaskMQReader |
2 | 定期读取MQ传感器数据 | 1秒周期(1000ms) |
vTaskUartTransmit |
1 | 串口数据上报(调试/EMQX) | 事件触发(数据就绪) |
优先级设定依据:Wi-Fi连接任务需抢占式执行以快速建立网络;DHT11与MQ采集任务实时性要求相近,故同级;串口上报任务优先级最低,避免阻塞高优先级数据采集。
5.2 任务间同步与数据共享
温湿度与气体浓度数据需汇总后统一上报。采用以下同步机制:
1. 共享数据结构
定义全局结构体存储最新传感器数据:
typedef struct {
uint8_t dht_humidity;
uint8_t dht_temperature;
uint16_t mq2_value;
uint16_t mq4_value;
uint16_t mq7_value;
uint8_t flame_status; // 0: no flame, 1: flame detected
uint32_t last_update_ms;
} SensorData_TypeDef;
SensorData_TypeDef g_sensor_data;
2. 互斥访问保护
DHT11与MQ任务均需更新 g_sensor_data ,为防止数据竞争,创建二进制信号量 xMutexSensorData :
// 初始化
xMutexSensorData = xSemaphoreCreateBinary();
xSemaphoreGive(xMutexSensorData); // 初始可用
// DHT11任务中更新数据
if (xSemaphoreTake(xMutexSensorData, portMAX_DELAY) == pdTRUE) {
g_sensor_data.dht_humidity = dht_data.humidity_int;
g_sensor_data.dht_temperature = dht_data.temp_int;
g_sensor_data.last_update_ms = HAL_GetTick();
xSemaphoreGive(xMutexSensorData);
}
// MQ任务中更新数据(同理)
3. 事件通知机制 vTaskUartTransmit 任务处于阻塞态,等待数据就绪事件。DHT11与MQ任务在更新完数据后,向事件组 xEventGroupSensor 发送 SENSOR_DATA_READY_BIT 位:
// 在DHT11/MQ任务末尾
xEventGroupSetBits(xEventGroupSensor, SENSOR_DATA_READY_BIT);
// vTaskUartTransmit任务中等待
EventBits_t uxBits = xEventGroupWaitBits(
xEventGroupSensor,
SENSOR_DATA_READY_BIT,
pdTRUE, // 清除该位
pdFALSE, // 不要求所有位都置位
portMAX_DELAY
);
if ((uxBits & SENSOR_DATA_READY_BIT) != 0) {
// 打包并发送数据
UART_Transmit_SensorData(&g_sensor_data);
}
此设计解耦了数据采集与通信,使系统更具可扩展性——未来增加新传感器仅需在对应任务中更新 g_sensor_data 并置位事件,无需修改上报任务逻辑。
6. 调试与稳定性增强实践
6.1 串口调试信息分级
为兼顾开发效率与生产环境静默需求,实现三级日志系统:
- Level 0 (ERROR) :硬故障(ADC超限、DHT11校验失败),始终启用;
- Level 1 (INFO) :周期性传感器读数,开发阶段启用;
- Level 2 (DEBUG) :详细时序波形(如DHT11每一位采样值),仅调试时启用。
通过宏定义控制编译:
#define LOG_LEVEL 1
#if LOG_LEVEL >= 1
printf("DHT11: %d%%RH, %d°C\r\n",
g_sensor_data.dht_humidity,
g_sensor_data.dht_temperature);
#endif
6.2 抗干扰与容错设计
DHT11通信容错 :
- 连续3次读取失败后,记录错误计数并尝试硬件复位(拉低PB12 1s);
- 引入软件看门狗,在 vTaskDHT11Reader 中喂狗,若任务卡死则系统重启。
ADC数据滤波 :
对MQ传感器原始ADC值实施滑动平均滤波(窗口大小N=8):
uint16_t mq2_filtered = 0;
static uint16_t mq2_history[8] = {0};
static uint8_t mq2_idx = 0;
// 新采样值入队
mq2_history[mq2_idx] = adc_raw[0];
mq2_idx = (mq2_idx + 1) % 8;
// 计算平均值
for(uint8_t i = 0; i < 8; i++) {
mq2_filtered += mq2_history[i];
}
mq2_filtered /= 8;
电源噪声抑制 :
- 在MQ模块VCC引脚就近放置10μF电解电容 + 100nF陶瓷电容;
- ADC参考电压(VREF+)单独敷铜,避免与数字地混用。
6.3 实际部署经验
在真实厨房环境中部署时,发现两个典型问题及解决方案:
问题1:油烟冷凝导致DHT11读数漂移
现象:运行2小时后,湿度读数持续上升至95%RH,实际环境湿度约60%。
根因:油烟蒸汽在DHT11外壳冷凝,形成水膜影响电容式湿度传感元件。
解决:加装金属防尘网(目数≥200),并在DHT11外壳涂覆疏水涂层(如Parylene C),阻隔液态水接触。
问题2:燃气灶点火脉冲干扰MQ-2
现象:每次点火瞬间,MQ-2读数骤升至满量程,触发误报警。
根因:点火器产生的高压脉冲(>10kV)通过空间耦合进入传感器信号线。
解决:在MQ模块AO引脚与PA0之间串联10Ω磁珠,并在PA0与GND间并联100pF陶瓷电容,构成π型滤波器,有效衰减100MHz以上射频干扰。
这些经验源于多次现场调试,证明嵌入式系统设计不仅是代码编写,更是电磁兼容、材料科学与环境工程的综合实践。
更多推荐

所有评论(0)