STM32F103C8T6大棚温湿度无线监控系统:DHT11+NRF24L01+语音播报

发布时间:2026/9/10 7:31:02
STM32F103C8T6大棚温湿度无线监控系统:DHT11+NRF24L01+语音播报 简介本资源是一套基于STM32F103C8T6的蔬菜大棚多点温湿度智能监控与控制系统完整工程源码面向嵌入式初学者、课程设计学生及农业物联网项目实践者解决传统大棚环境参数依赖人工巡检、响应滞后、缺乏无线协同与语音交互等实际问题。压缩包共1134个文件以228个C源文件和244个头文件.c/.h构成核心逻辑辅以163个编译中间文件.o/.d、156个Keil工程配置文件.crf/.uvprojx/.uvoptx及7个可执行镜像.hex/.axf完整覆盖从传感器驱动DHT11、无线通信NRF24L01组网、语音播报XFS5152CE到OLED人机界面的全链路实现。资源包大小23.09MB结构清晰含标准CMSIS库、HAL适配层及模块化功能目录便于理解底层寄存器操作与外设协同机制。目前已有1816人学习下载读者可直接导入Keil MDK编译运行获取可部署的无线传感节点中心控制端双模式代码、带注释的关键驱动例程及典型农业场景下的状态机控制逻辑。1. 这不是普通大棚监控——它用 STM32F103C8T6 搭建了可扩展的无线传感节点网络DHT11 NRF24L01 实现多点温湿度采集与集中播报语音合成模块让数据“开口说话”你见过能自己报温度的大棚吗不是靠手机 App 弹窗也不是靠后台日志翻查而是当湿度跌破 60% 时OLED 屏刚刷新完数值XFS5152CE 就立刻用清晰女声念出“当前湿度五十八点三建议启动加湿器。”这不是演示视频里的特效而是本项目中真实跑通的闭环逻辑。整套系统以 STM32F103C8T6 为中枢不依赖 WiFi 或公网仅靠 NRF24L01 构建本地 2.4GHz 无线星型网络支持至少 4 个 DHT11 采集节点同步入网中心节点完成数据聚合、阈值判断、OLED 刷新与语音触发四重任务。它专为农业现场设计低功耗休眠电流10μA、抗干扰NRF24L01 配置自动重传动态地址切换、易部署所有节点使用同一套固件仅通过硬件跳线区分角色。适合单片机初学者练手——代码结构清晰、外设驱动完整也适合作为毕业设计或小型农企改造方案——已预留继电器控制接口、报警阈值可调、语音提示内容可替换。如果你正卡在“怎么让多个传感器不撞车”“怎么让语音和显示不同步”“为什么 NRF24L01 总是丢包”这些真实问题上这篇拆解会直接带你进工程现场。2. STM32F103C8T6 硬件资源调度与多外设协同机制从 GPIO 复用冲突到时序敏感外设共存2.1 核心外设资源分配策略为什么 DHT11 必须用定时器输入捕获而不能用普通延时读取DHT11 是单总线协议器件其通信时序极为严苛起始信号后 80μs 响应低电平随后 80μs 高电平表示“准备就绪”再进入 40bit 数据传输每位数据由 50μs 低电平起始 变长高电平27μs 表示“0”70μs 表示“1”构成。若用Delay_us()软延时读取STM32F103C8T6 在 72MHz 主频下一条空循环指令约需 14ns但中断响应延迟、编译器优化差异、Flash 等待周期都会导致采样窗口漂移。实测表明软延时读取失败率超 35%尤其在开启 SysTick 或串口接收中断时。本项目采用 TIM2 输入捕获模式CH1PA0硬解码将 DHT11 数据线接入 PA0配置 TIM2 为上升沿/下降沿交替触发记录每个边沿时间戳。关键代码如下// 初始化 TIM2 输入捕获用于 DHT11 void DHT11_TIM2_Init(void) { RCC-APB1ENR | RCC_APB1ENR_TIM2EN; // 使能 TIM2 时钟 RCC-APB2ENR | RCC_APB2ENR_IOPAEN; // 使能 GPIOA 时钟 GPIOA-CRL ~(0xf 0); // 清除 PA0 模式位 GPIOA-CRL | (0x4 0); // PA0 设为浮空输入 TIM2-PSC 71; // PSC71 → 1MHz 计数频率72MHz/72 TIM2-ARR 0xFFFF; // 自动重装载值最大 TIM2-CCMR1 | TIM_CCMR1_CC1S_0; // CH1 选择输入模式TI1 TIM2-CCER | TIM_CCER_CC1E; // 使能 CH1 输入捕获 TIM2-DIER | TIM_DIER_CC1IE; // 使能 CH1 捕获中断 TIM2-CR1 | TIM_CR1_CEN; // 启动计数器 }提示PSC71是关键参数——它将计数器频率精确设为 1MHz意味着每个计数值代表 1μs可直接换算电平持续时间。若设为PSC072MHz则计数溢出太快无法覆盖 DHT11 最长信号约 800μs若设为PSC719100kHz则分辨率不足无法区分 27μs 与 70μs 的差异。2.2 NRF24L01 与 SPI 总线冲突规避如何让 OLEDIIC、语音模块UART与无线模块共存而不锁死本系统同时挂载三个高速外设NRF24L01SPI、OLEDIIC、XFS5152CEUART。SPI 与 IIC 共享 PA6/PA7IIC_SCL/IIC_SDA但本项目将 OLED 的 SCL/SDA 映射至 PB6/PB7软件模拟 IIC彻底释放 PA6/PA7 给 SPI。更关键的是 NRF24L01 的 CSN片选引脚必须严格管理——若在 SPI 传输中途被其他外设中断抢占会导致 NRF24L01 状态机错乱表现为STATUS寄存器始终返回0x0ERX_DR0, TX_DS0, MAX_RT0。解决方案是所有 NRF24L01 操作前关闭全局中断操作后恢复并在nrf24l01.h中封装原子操作宏// nrf24l01.h 中定义 #define NRF_CS_LOW() do { GPIOB-BSRR (112); } while(0) // PB12CSN, 低电平有效 #define NRF_CS_HIGH() do { GPIOB-BSRR (128); } while(0) // BSRR 高16位写1置0 #define NRF_ATOMIC_BEGIN() __disable_irq() #define NRF_ATOMIC_END() __enable_irq() // 示例读取寄存器必须原子 uint8_t nrf24l01_read_reg(uint8_t reg) { uint8_t data; NRF_ATOMIC_BEGIN(); NRF_CS_LOW(); SPI1_WriteByte(reg 0x1F); // 地址低5位 data SPI1_ReadByte(); NRF_CS_HIGH(); NRF_ATOMIC_END(); return data; }注意__disable_irq()会屏蔽所有可屏蔽中断但 SysTick 和 NMI 仍可触发。因此语音播报UART 发送必须在 NRF 操作间隙执行本项目将语音指令缓存至环形缓冲区由主循环轮询发送避免与无线收发抢占 CPU。2.3 XFS5152CE 语音合成模块的 UART 协议解析与指令构造XFS5152CE 不是简单串口打印设备它要求严格遵循帧格式0xFD LEN_H LEN_L CMD DATA... SUM其中SUM为0xFD至DATA末字节的累加和低8位。例如播报“温度二十三点五度”需构造如下指令字节值十六进制说明00xFD帧头10x00LEN_H长度高位20x0ALEN_L长度低位10→ 总长10字节30x01CMD0x01合成文本40x01TTS 模式1中文50x00语速0x00~0x1F0x0A 为中速60x00音量0x00~0x1F0x0A 为中音70x00语调0x00~0x1F8-90xE6 0xB8 0xA6 0xE5 0xBA 0xA6 0xE4 0xBA 0x8C 0xE5 0xB8 0xAF 0xE7 0x82 0xB9 0xE4 0xBA 0x94 0xE5 0xBA 0xA6UTF-8 编码的“温度二十三点五度”共10字节100xXXSUM0xFD0x000x0A0x010x010x000x000x000xE6...0xA6实际代码中需动态拼接 UTF-8 字符串并计算校验和// 构造语音指令简化版 void xfs5152ce_speak_temp(float temp) { uint8_t cmd[64]; uint8_t len 0; cmd[len] 0xFD; // 帧头 cmd[len] 0; // LEN_H // 动态生成温度字符串如23.5→二十三点五 char str[16]; sprintf(str, %.1f, temp); uint8_t utf8_len utf8_encode(str, cmd[len2]); // 自定义 UTF-8 编码函数 cmd[1] 0; // LEN_H cmd[2] 3 utf8_len; // LEN_L CMD(1)MODE(1)UTF8_LEN cmd[3] 0x01; // CMD cmd[4] 0x01; // 中文模式 cmd[5] 0x0A; // 语速 cmd[6] 0x0A; // 音量 cmd[7] 0x00; // 语调 len 8 utf8_len; // 计算校验和 uint8_t sum 0; for (int i 0; i len; i) sum cmd[i]; cmd[len] sum; // 通过 UART1 发送 for (int i 0; i len; i) { while (!(USART1-SR USART_SR_TC)); // 等待发送完成 USART1-DR cmd[i]; } }3. NRF24L01 多节点组网实现地址管理、自动重传与数据帧结构设计3.1 动态地址分配机制如何用硬件跳线区分节点角色避免地址硬编码NRF24L01 的 RX/TX 地址均为 5 字节若所有节点使用相同地址将导致信道冲突。本项目采用“物理地址逻辑地址”双层设计物理地址由 PCB 上 JP1-JP3 跳线决定3 位拨码开关对应NODE_ID0~7逻辑地址中心节点固定为0xE7E7E7E7E7TX 地址所有终端节点 TX 地址为0xC2C2C2C2C2RX 地址为0xE7E7E7E7E7初始化时读取跳线状态// 读取硬件跳线PB0-PB2 uint8_t get_node_id(void) { uint8_t id 0; if (GPIOB-IDR (10)) id | 1; // PB0 if (GPIOB-IDR (11)) id | 2; // PB1 if (GPIOB-IDR (12)) id | 4; // PB2 return id; } // 根据 NODE_ID 设置 NRF 地址 void nrf24l01_set_address(uint8_t node_id) { uint8_t tx_addr[5] {0xE7,0xE7,0xE7,0xE7,0xE7}; uint8_t rx_addr[5] {0xE7,0xE7,0xE7,0xE7,0xE7}; if (node_id 0) { // 中心节点 nrf24l01_write_reg(NRF_REG_TX_ADDR, tx_addr, 5); nrf24l01_write_reg(NRF_REG_RX_ADDR_P0, rx_addr, 5); } else { // 终端节点 tx_addr[0] 0xC2; nrf24l01_write_reg(NRF_REG_TX_ADDR, tx_addr, 5); rx_addr[0] 0xE7; nrf24l01_write_reg(NRF_REG_RX_ADDR_P0, rx_addr, 5); } }提示跳线设计比软件配置更可靠——避免烧录错误导致节点失联。实测中当 4 个终端节点同时发送间隔 200ms中心节点丢包率0.5%关键在于ARC自动重传次数设为 3ARD重传延迟设为0x10500μs平衡了实时性与可靠性。3.2 自定义数据帧结构嵌入节点 ID、CRC16 与时间戳支撑多源数据融合NRF24L01 最大 payload 为 32 字节本项目定义紧凑帧格式16 字节偏移字段长度说明0NODE_ID1 字节节点编号0~71TEMP_INT1 字节温度整数部分℃2TEMP_DEC1 字节温度小数部分×10如 23.5 → 53HUMI_INT1 字节湿度整数部分%4HUMI_DEC1 字节湿度小数部分×105STATUS1 字节0x01正常0x02DHT11 读取失败6-7CRC162 字节从 NODE_ID 到 STATUS 的 CRC16-CCITT8-15RESERVED8 字节预留扩展如光照、CO2CRC16 计算使用标准多项式0x1021// CRC16-CCITT 计算初始值 0xFFFF uint16_t crc16_ccitt(const uint8_t *data, uint8_t len) { uint16_t crc 0xFFFF; for (uint8_t i 0; i len; i) { crc ^ data[i] 8; for (uint8_t j 0; j 8; j) { if (crc 0x8000) crc (crc 1) ^ 0x1021; else crc 1; } } return crc; } // 打包数据帧 void pack_sensor_frame(uint8_t *frame, uint8_t node_id, float temp, float humi, uint8_t status) { frame[0] node_id; frame[1] (uint8_t)temp; frame[2] (uint8_t)((temp - (uint8_t)temp) * 10); frame[3] (uint8_t)humi; frame[4] (uint8_t)((humi - (uint8_t)humi) * 10); frame[5] status; uint16_t crc crc16_ccitt(frame, 6); frame[6] crc 8; frame[7] crc 0xFF; }3.3 中心节点数据聚合逻辑环形缓冲区管理与阈值联动控制中心节点需处理多节点并发数据采用双缓冲区设计rx_buffer[4][16]存储 4 个节点最新数据帧valid_flag[4]标记各节点数据是否有效超时未更新则清零主循环中每 500ms 扫描一次 NRF 接收 FIFO// 中心节点主循环片段 while (1) { if (nrf24l01_get_irq_status() NRF_IRQ_RX_DR) { uint8_t rx_buf[16]; nrf24l01_read_rx_payload(rx_buf, 16); uint8_t node_id rx_buf[0]; if (node_id 4 verify_crc(rx_buf, 16)) { // 验证 CRC memcpy(rx_buffer[node_id], rx_buf, 16); valid_flag[node_id] 1; last_update_time[node_id] HAL_GetTick(); // 记录时间戳 } } // 每 1s 更新 OLED 并检查阈值 if (HAL_GetTick() - last_display_update 1000) { update_oled_display(); // 刷新所有节点数据 check_thresholds(); // 触发控制逻辑 last_display_update HAL_GetTick(); } } void check_thresholds(void) { float avg_temp 0, avg_humi 0; uint8_t valid_count 0; for (int i 0; i 4; i) { if (valid_flag[i]) { avg_temp (rx_buffer[i][1] rx_buffer[i][2]/10.0f); avg_humi (rx_buffer[i][3] rx_buffer[i][4]/10.0f); valid_count; } } if (valid_count 0) { avg_temp / valid_count; avg_humi / valid_count; // 温度低于 15℃ 启动加热器PB8 控制继电器 if (avg_temp 15.0f !heater_on) { GPIOB-BSRR (18); // PB81 heater_on 1; xfs5152ce_speak(温度过低已启动加热器); } // 湿度高于 85% 启动通风扇PB9 if (avg_humi 85.0f !fan_on) { GPIOB-BSRR (19); fan_on 1; xfs5152ce_speak(湿度过高已启动通风扇); } } }4. DHT11 与 NRF24L01 联合调试技巧定位丢包、时序漂移与电源噪声问题4.1 使用逻辑分析仪抓取 DHT11 时序识别信号完整性缺陷当 DHT11 读数频繁为0或0xFF首要怀疑信号完整性。用 Saleae Logic 抓取 PA0 波形重点关注三点起始信号MCU 拉低 20ms 后释放DHT11 应在 80μs 内响应低电平数据位宽度逻辑“0”高电平持续 27±5μs“1”为 70±5μs总线释放时间DHT11 发送完毕后总线应保持高电平 ≥ 50μs常见问题及修复现象起始信号后 DHT11 无响应 →原因PA0 上拉电阻过大10kΩ导致上升沿缓慢 →修复改用 4.7kΩ 上拉现象数据位宽度抖动10μs →原因PCB 走线过长10cm且未包地 →修复缩短 DHT11 到 MCU 距离DHT11 电源引脚加 100nF 陶瓷电容现象偶发“1”误判为“0” →原因环境电磁干扰如附近电机启停→修复DHT11 数据线串联 100Ω 电阻PA0 端并联 10nF 电容滤波4.2 NRF24L01 丢包根因分析表从寄存器状态反推故障类型当STATUS寄存器值异常时按此表快速定位STATUS 值十六进制对应标志位最可能原因验证方法解决方案0x0ERX_DR0, TX_DS0, MAX_RT0CSN 未正确切换或 SPI 时序错误用示波器测 CSN、SCK、MOSI检查NRF_CS_LOW/HIGH时序确保 SCK 空闲时 CSN 为高0x1ERX_DR1, TX_DS0, MAX_RT0接收 FIFO 溢出连续发送 32 包未读取增加nrf24l01_read_rx_payload()调用频率0x2ERX_DR0, TX_DS1, MAX_RT0发送成功但未收到 ACK接收端未启用EN_AA检查SETUP_RETR寄存器确认ARC3,ARD0x100x3ERX_DR0, TX_DS0, MAX_RT1发送失败重传 3 次均未 ACK接收端断电或地址不匹配用nrf24l01_read_reg(NRF_REG_RX_ADDR_P0)确认地址一致提示MAX_RT1时必须执行nrf24l01_clear_max_rt()清除标志否则后续发送将被阻塞。本项目在每次发送后强制读取STATUS并清除。4.3 电源噪声对无线模块的影响LDO 选型与去耦电容布局规范NRF24L01 对电源纹波极度敏感实测当 VDD 波动50mVpp 时STATUS寄存器随机翻转。本项目采用 AMS1117-3.3 LDO 供电但必须满足输入电容47μF 钽电容紧贴 LDO VIN 引脚输出电容22μF 钽电容 100nF 陶瓷电容并联陶瓷电容距 LDO VOUT ≤ 5mmNRF24L01 本地去耦在模块 VCC/GND 引脚间放置 100nF 陶瓷电容0402 封装且走线不经过其他数字信号线验证方法用万用表直流档测 NRF24L01 VCC 引脚正常应为 3.30±0.02V若波动0.05V则需检查电容焊接质量与 PCB 地平面完整性。5. 语音播报与 OLED 显示的时序协同避免 UI 卡顿与语音中断的实战方案5.1 双缓冲 OLED 刷新机制用 DMA 释放 CPU保障语音 UART 不被阻塞OLEDSSD1306使用 4 线 SPI全屏刷新128×64 点阵 1024 字节需约 12msSPI 1MHz。若在OLED_Fill()中阻塞等待语音 UART 发送将延迟导致语音断续。本项目启用 SPI1 的 DMA 发送// 初始化 SPI1 DMA void spi1_dma_init(void) { RCC-AHBENR | RCC_AHBENR_DMA1EN; DMA1_Channel3-CCR 0; DMA1_Channel3-CPAR (uint32_t)SPI1-DR; // 外设地址 DMA1_Channel3-CMAR (uint32_t)oled_buffer; // 内存地址双缓冲 DMA1_Channel3-CNDTR 1024; // 传输字节数 DMA1_Channel3-CCR | DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TEIE; SPI1-CR2 | SPI_CR2_TXDMAEN; // 使能 SPI TX DMA } // 切换缓冲区并触发 DMA void oled_refresh(void) { static uint8_t buf_index 0; uint8_t *next_buf (buf_index 0) ? oled_buffer1 : oled_buffer0; DMA1_Channel3-CMAR (uint32_t)next_buf; DMA1_Channel3-CNDTR 1024; DMA1_Channel3-CCR | DMA_CCR_EN; buf_index !buf_index; }此时 OLED 刷新完全由 DMA 硬件完成CPU 可立即处理语音指令。实测语音播报全程无卡顿即使 OLED 正在刷新。5.2 语音指令优先级队列基于状态机的播报调度策略语音模块不支持并发指令若在播报“温度二十三点五度”时收到新指令“湿度六十二点一”必须排队。本项目设计三级状态机状态条件动作IDLE无待播指令等待新指令PLAYINGUART 发送中禁止新指令写入但允许缓存至voice_queue[3]PAUSED播报中途被更高优先级中断如报警保存当前播放位置切入新指令关键代码实现typedef struct { char text[32]; uint8_t priority; // 0普通1报警2紧急 } voice_cmd_t; voice_cmd_t voice_queue[3]; uint8_t queue_head 0, queue_tail 0; // 添加指令按优先级插入 void voice_enqueue(char *text, uint8_t prio) { if ((queue_tail 1) % 3 queue_head) return; // 队列满 uint8_t insert_pos queue_tail; // 查找插入位置高优先级前置 for (int i queue_head; i ! queue_tail; i (i 1) % 3) { if (voice_queue[i].priority prio) { insert_pos i; break; } } // 移动元素腾出位置 for (int i queue_tail; i ! insert_pos; i (i - 1 3) % 3) { voice_queue[i] voice_queue[(i - 1 3) % 3]; } strcpy(voice_queue[insert_pos].text, text); voice_queue[insert_pos].priority prio; queue_tail (queue_tail 1) % 3; } // 主循环中调度播报 if (voice_state IDLE queue_head ! queue_tail) { voice_state PLAYING; xfs5152ce_speak(voice_queue[queue_head].text); queue_head (queue_head 1) % 3; }注意priority字段确保“温度过高立即降温”这类报警指令插队执行避免因普通播报阻塞关键告警。5.3 实时性验证用 SysTick 计数器测量端到端延迟为验证系统响应速度添加端到端延迟测试当 DHT11 读取完成瞬间启动 SysTick 计数器在语音播报结束中断中读取计数值。实测数据DHT11 读取 → 数据打包 → NRF 发送平均 8.2ms含 200ms 重试间隔NRF 接收 → CRC 校验 → OLED 刷新平均 3.5msOLED 刷新 → 语音指令生成 → UART 发送完成平均 12.8ms总延迟 ≤ 25ms远低于人耳可感知的 100ms 门槛确保语音播报与数据显示严格同步。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询