STM32与ZigBee智能大棚监测系统源码实战解析
简介一套基于STM32F103C8T6与ZigBee无线通信的智能大棚环境监测系统源码面向物联网、嵌入式或农业信息化方向的开发者与课程设计人群解决大棚内温度、光照、土壤湿度远程实时监测与通信异常预警问题。系统采用主机与从机分布式架构主机端完成多传感器采集与无线组网从机端负责数据解析、LCD1602显示和断链提示代码按host/slave、zigbee、sensors等模块划分便于移植扩展。压缩包共37个文件以16个C源码和13个头文件为主配合Makefile、链接脚本、STM32标准外设库启动文件及README等整体仅79KB结构清晰、无冗余工程文件。当前已有41人学习下载。资料包含完整可编译的MDK工程源码、传感器驱动与ZigBee协议栈适配层实现并给出看门狗断链检测、ADCDMA采样及低功耗供电策略等关键处理思路适合作为毕业设计、智能农业项目或课程实践的直接参考。1. 这棵“菜园子电路板”到底能种出什么北方的冬天大棚里凌晨三点温度跌破零度等你早上发现一垄草莓已经冻得发黑南方的梅雨季土壤湿度饱和到根系缺氧补种都来不及。这些损失本质上是同一个问题环境数据靠人盯盯不住。而我要拆的这份 STM32 智能大棚监测系统源码就是来干掉“人肉巡检”的。它用 STM32 做主控配合 ZigBee 无线网络把温湿度、光照、土壤湿度这些关键参数采上来超过阈值直接驱动风机、滴灌、补光灯动作全程不需要你在现场。适合谁正在做物联网毕业设计的学生、想给自家大棚做低成本改造的种植户或者刚入门嵌入式想找一个完整闭环参考的开发者。这套源码不是只能看的 demo它是一套能真正跑起来、能改参数、能扩展节点的大棚环境控制框架。2. 架构与数据链路STM32 和 ZigBee 各司其职2.1 为什么是 STM32 ZigBee而不是 WiFi 或蓝牙动手拆这套源码之前得先明白它的选型逻辑。很多新手第一反应是大棚里装 WiFi 不行吗行但代价很高。大棚环境的特点是金属骨架多、湿度大、距离远WiFi 穿两堵墙信号就衰减得厉害而且大棚区域往往没有稳定的宽带接入。蓝牙就更不用提了传输距离十几米一个标准大棚动辄几十米长根本不现实。ZigBee 在这套方案里的角色是“低速低功耗的本地通信网”。单个节点的功耗极低两节五号电池能撑几个月而且自组网能力很强某个节点掉线了数据会自动绕道其他节点传回来。STM32 在这套系统里扮演的是“大脑”它直接挂载传感器和执行器同时通过串口连接 ZigBee 模块把数据往协调器上传。一个协调器可以带几十个终端节点这意味着你不需要每个大棚都配一套控制器一个主机管一片区域就够了。提示ZigBee 的 2.4G 频段和 WiFi 重叠但 ZigBee 信道跳频机制能避开部分干扰。实测在一个 WiFi 信号很密的环境里丢包率依然能控制在 5% 以下。2.2 数据链路逐级拆解从传感器读到上位机显示这套系统的数据流向我用大白话描述一遍STM32 读取传感器数据 → 通过串口发给 ZigBee 终端节点 → 无线传输到 ZigBee 协调器 → 协调器通过 USB 转串口连到电脑上位机。反向链路也很重要上位机下发控制指令 → 协调器 → 终端节点 → STM32 → 驱动继电器。源码里有个关键文件叫usart.c它同时处理两路串口一路用于调试打印另一路专门用于和 ZigBee 模块通信。这里有个设计细节值得学习调试串口和通信串口物理隔离避免调试信息混进无线数据包里这在定位问题时能节省大量时间。我给这份源码画过一张极简的数据链路图传感器(DHT11/光敏/土壤探头) ↓ GPIO/ADC STM32F103 (主控跑逻辑判断) ↓ USART2 波特率9600 ZigBee终端模块(透传模式) ↓ 2.4G 无线 ZigBee协调器(中心节点) ↓ USB转TTL 上位机软件(实时曲线报警弹窗)从源码实现角度看ZigBee 模块配置在透明传输模式也就是串口发什么无线就传什么对 STM32 来说它就是一个“没有线的串口”。这种设计的巧妙之处在于你不需要理解 ZigBee 协议栈细节只需要把它当作一根无限长的串口线。2.3 源码目录结构与关键文件定位打开源码包后首先看 README 和目录结构。这份源码的组织方式很常规但值得照着学。核心目录大概长这样project/ ├── User/ # 主函数、中断入口 ├── Hardware/ # 传感器驱动、继电器驱动 ├── APP/ # 业务逻辑阈值判断、模式切换 ├── ZigBee/ # 串口协议封装 ├── System/ # 时钟配置、延时函数 └── OBJ/ # 编译产物hex文件Hardware目录下每个传感器一个文件bsp_dht11.c、bsp_light.c、bsp_soil.c接口统一命名为xxx_Init()和xxx_ReadData()。我挺喜欢这种命名习惯的后期如果要换传感器型号只需要改对应文件内部实现不用动上层逻辑。APP目录下的control.c是核心它把多个传感器数据汇总做阈值判断后决定是否开启电机或水泵。源码包里默认参数是温度上限 35 度、土壤湿度下限 30%这两个值在control.c顶部宏定义里后面讲参数修改时会细说。3. 源码实战拆解从传感器采集到电机控制3.1 DHT11 温湿度读取时序与校验的边界DHT11 是这套系统里最“亲民”的传感器也是新手最容易翻车的地方。它的通信协议是单总线一根数据线既要发指令又要收数据时序要求微秒级精确。源码里bsp_dht11.c的实现用的是 GPIO 模拟时序没有依赖定时器捕获这在 F103 的 72MHz 主频下够用但对中断优先级要求很高读数据期间任何中断打断都会导致时序错乱。核心读取逻辑大概长这样uint8_t DHT11_ReadData(DHT11_Data_t *data) { uint8_t buffer[5] {0, 0, 0, 0, 0}; GPIO_ResetBits(DHT11_PORT, DHT11_PIN); // 主机拉低总线发起起始信号 Delay_Ms(20); // 至少拉低 18ms GPIO_SetBits(DHT11_PORT, DHT11_PIN); // 释放总线 Delay_Us(30); // 释放后延时 30us if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 0) { // 检测响应信号 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 0); // 等待拉高 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1); // 等待拉低 for (int i 0; i 40; i) { // 连续读取 40 位数据 while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 0); Delay_Us(40); // 40us 后采样判断 0 或 1 if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1) { buffer[i / 8] | (0x80 (i % 8)); } } } // 校验buffer[0] buffer[1] buffer[2] buffer[3] 应等于 buffer[4] if ((buffer[0] buffer[1] buffer[2] buffer[3]) buffer[4]) { >uint16_t ADC_GetAverageValue(uint8_t channel, uint8_t sample_count) { uint32_t sum 0; for (uint8_t i 0; i sample_count; i) { sum ADC_ReadSingle(channel); Delay_Ms(10); } return (uint16_t)(sum / sample_count); }默认采样次数是 10 次这个值兼顾了实时性和稳定性。如果你发现采集值跳变剧烈可以改成 30 次但代价是响应速度变慢电机启动会更迟钝。ADC 的转换结果和实际物理量之间不是线性关系源码里预设了一组经验系数光照强度 ADC 值 × 0.5土壤湿度百分比 100 - (ADC 值 / 40)。这几个系数是拿常见模块标定出来的换一个品牌的探头就得重新标定直接套用会导致湿度显示偏差 10% 以上。3.3 控制逻辑阈值判断与继电器动作的联动control.c是整个系统的“决策大脑”这套源码写的核心逻辑是典型的三段式采集→比较→动作。动作执行部分用了一个简单的状态机避免继电器频繁开关。比如温度超过 35 度就打开风机降到 32 度才关闭风机这叫回差控制目的是防止电机在阈值附近反复启停烧坏继电器。void Control_Process(void) { // 1. 更新传感器数据 DHT11_ReadData(dht11_data); light_value ADC_GetAverageValue(ADC_CHANNEL_LIGHT, 10); soil_value ADC_GetAverageValue(ADC_CHANNEL_SOIL, 10); // 2. 温度控制超过上限开风扇降到下限以下才关 if (dht11_data.temperature_int TEMP_OPEN_THRESHOLD) { Relay_Control(RELAY_FAN, RELAY_ON); } else if (dht11_data.temperature_int TEMP_CLOSE_THRESHOLD) { Relay_Control(RELAY_FAN, RELAY_OFF); } // 3. 土壤湿度控制太干开滴灌够了就关 if (soil_value SOIL_OPEN_THRESHOLD) { Relay_Control(RELAY_WATER, RELAY_ON); } else if (soil_value SOIL_CLOSE_THRESHOLD) { Relay_Control(RELAY_WATER, RELAY_OFF); } }这段代码最值得学习的点是回差区间TEMP_OPEN_THRESHOLD和TEMP_CLOSE_THRESHOLD不是同一个值中间差了 3 度。如果设成同一个值风扇会在临界点反复抖动继电器几小时就报废了。这套源码把回差用两个宏分开定义我觉得比用绝对值判断要直观得多。电机驱动部分用的是继电器模块STM32 GPIO 输出低电平触发源码里Relay_Control函数封装了 GPIO 翻转和状态记录方便上位机查询当前执行器开关状态。3.4 上位机通信协议帧格式与解析无线链路不只是传数据还要能区分“传感器上报”和“控制指令”。源码里设计了一个极简帧协议帧头 0xAA 功能码 数据长度 数据体 CRC 校验。这个协议是这套源码里最有移植价值的部分不管换什么传感器、什么网关只要帧格式不变上位机就不用改。uint8_t Packet_Send(uint8_t func_code, uint8_t *data, uint8_t len) { uint8_t buffer[64]; buffer[0] 0xAA; // 帧头 buffer[1] func_code; // 0x01上报环境数据0x02下发控制指令 buffer[2] len; // 数据体长度 memcpy(buffer[3], data, len); uint8_t crc 0; for (int i 0; i len 3; i) { crc ^ buffer[i]; // 简单异或校验够用但别用于正式工业场景 } buffer[len 3] crc; UART_SendBytes(buffer, len 4); }这里的 CRC 用的是单字节异或工程上如果传输距离远、干扰大建议换成 CRC16。源码里其他部分不动只替换校验算法就行。解析端会先找帧头再按功能码分发数据这避免了一个很常见的 bug上位机把上一次的数据残留误当作新数据。我强调一次这个帧格式设计得虽然简单但它给出了一个完整的“封包→发送→解析”闭环思维很多刚学完串口的新手写代码时连帧头都不加导致数据错位。4. 换个大棚就要改的六个参数与接线细节4.1 主控与传感器的引脚映射表源码包里的stm32f103_gpio.c定义了所有外设的引脚连接。拆包后第一件事是把你手里的开发板原理图拿出来对着这个表核对。引脚定义错一位整个系统就起不来。外设模块信号线STM32 引脚备注DHT11DATAPA0开漏模式需外接 4.7kΩ 上拉光敏模块AOPA1ADC1 通道1土壤探头AOPA2ADC1 通道2继电器1风机IN1PB0低电平触发继电器2水泵IN2PB1低电平触发ZigBee 模块TXPA9USART1 接收ZigBee 模块RXPA10USART1 发送这个表里有个细节DHT11 的 DATA 引脚必须配置为开漏输出并外加上拉电阻因为 DHT11 是开漏结构它只能拉低总线不能主动拉高。如果配置成推挽输出通信大概率失败。光敏和土壤探头都是模拟输出所以必须接在 ADC 引脚上接普通 GPIO 读不到电压。4.2 阈值宏定义与回差调整打开control.c顶部你会看到这样几行// 温度控制阈值单位摄氏度 #define TEMP_OPEN_THRESHOLD 35 #define TEMP_CLOSE_THRESHOLD 32 // 土壤湿度控制阈值单位百分比 #define SOIL_OPEN_THRESHOLD 30 #define SOIL_CLOSE_THRESHOLD 45这里TEMP_OPEN_THRESHOLD和TEMP_CLOSE_THRESHOLD之间差了 3 度就是回差区间。改参数时要注意上限和下限不能相等至少拉开 2 度否则会出现在阈值点反复跳变的情况。土壤湿度这块OPEN是低于 30% 打开滴灌CLOSE是高于 45% 关闭这样设置是为了让水充分渗透到根系而不是表面一湿就停。4.3 ZigBee 模块的串口配置与组网对码这套源码的 ZigBee 模块工作在透传模式出厂默认波特率是 9600。如果模块的波特率不是这个值就得用 USB 转串口工具配合厂商的配置软件改。源码里 USART2 初始化代码是硬编码的/* 初始化 ZigBee 通信串口 */ USART_InitStructure.USART_BaudRate 9600; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No;三个关键点波特率必须和模块配置一致终端节点必须配置成“透传模式”协调器和终端必须处于同一个网络 IDPAN ID。如果上位机收不到数据先别怀疑代码直接用 USB 转 TTL 插到 STM32 的 ZigBee 串口上用串口助手看有没有数据输出。这一步能隔离出问题出在 STM32 还是无线链路。4.4 继电器驱动方式与电源隔离继电器模块是这套系统里最容易出问题的硬件。源码里 PB0、PB1 输出低电平触发但 STM32 的 GPIO 驱动能力只有几毫安直接驱动继电器线圈是不可能的。所以需要在 GPIO 和继电器之间加一个 NPN 三极管或 ULN2003 驱动芯片。这里的电源隔离也有学问继电器的线圈电源和传感器电源最好分开。继电器吸合瞬间会产生很大的电流尖峰如果和传感器共地ADC 采样值会在吸合瞬间跳变看起来就像“土壤湿度突然升高了”。我一般会这样处理STM32 的 3.3V 给传感器供电继电器模块的 VCC 接独立的 5V 电源两个电源的地线在末端单点相连。即使是同一块开发板上的 VCC 和 5V也建议在继电器模块的电源引脚上并一个大电容比如 470uF用来吸收浪涌电流。源码包的原生设计里可能没有电容这就属于必加的硬件改动之一。5. 移植与联调避坑三个最容易闷头干半天的环节5.1 现象一烧录正常但 DHT11 永远返回校验失败这个坑我估计五个人里能碰到三个。某开发者的板子和源码包一致线路连了四根线反复检查没问题但串口打印一直是DHT11 Checksum Error。原因大概率不是源码逻辑而是 GPIO 模式配置问题。DHT11 的数据引脚在源码里初始化成了推挽输出但前面说过 DHT11 是开漏结构需要外接上拉电阻才能正常拉高电平。检查你的板子上的 PA0 引脚附近有没有一个 4.7kΩ 的电阻如果没有直接在 PA0 和 3.3V 之间飞线焊一个。这个问题我现在写出来很轻松当初排查的时候花了一个晚上把示波器都接上了最后才发现是硬件设计缺了个电阻。解决方式也简单源码里那行GPIO_Mode_Out_PP改成GPIO_Mode_Out_OD并且确认外接上拉。如果板子上实在没有位置放电阻程序里也可以不修改但稳定性会大打折扣夏天湿度高的时候尤其明显。5.2 现象二ZigBee 组网不稳定数据一会儿有一会儿没这种“薛定谔的通信”问题新手最容易怀疑是代码 bug反复重刷固件。其实根源在 ZigBee 模块的供电。ZigBee 模块对电源噪声很敏感模块的 TX/RX 在发射瞬间会有约 50mA 的电流脉冲如果用 STM32 开发板上那个 3.3V 稳压芯片直接供电电压会被拉低导致模块不断重启。解决方法是给 ZigBee 模块单独加一个 LDO 稳压芯片或者至少加一个 100uF 的滤波电容。提示检查 ZigBee 模块的供电引脚和 STM32 之间是不是直连的。用手摸模块温度如果明显发烫供电电压可能偏高了如果信号时好时坏优先加电容而不是换模块。5.3 现象三上位机显示的都是 0xFF 或者乱码串口波特率不对是最基础但最容易忽略的问题。源码里 ZigBee 用的波特率是 9600但好多 USB 转 TTL 模块出厂默认是 115200。排查步骤就三步先把 STM32 的调试串口接出来用串口助手看有没有正常打印如果正常把波特率切到 9600 再看如果还是乱码检查两个串口的 GND 是否共地。另外有一种情况是帧协议解析错了上位机程序里限制了数据体长度但实际收到的包长度对不上这就会出现“一帧数据被拆成两条显示”的现象。源码包里帧头是0xAA拿串口助手的 HEX 模式看原始数据新到的包应该是AA 01 05 ...这样的格式如果你看到数据里频繁出现AA AA连续帧头说明下位机发送逻辑里可能进了重复发送的 bug。5.4 连线前必做的一步用万用表量电压再插电这是我个人的习惯也是被烧了好几个传感器换来的教训。每次拿到一块新的开发板或者新一批传感器上电之前先拿万用表量一下引脚电压。特别是土壤探头有的商家做的模块输出的是 5V 电平直接接 STM32 的 PA2 引脚会把 ADC 烧掉。这个坑跟源码逻辑没关系但比任何源码问题都致命。上电前量两个位置传感器的 VCC 引脚对地电压确认是 3.3V 还是 5V信号输出引脚静态电压光照模块在光亮环境下应该输出 0~3.3V 之间的值如果有 5V说明模块自带电平转换需要用分压电阻或者确认 STM32 的 ADC 引脚是否容忍 5V 输入。正常流程应该是不接传感器烧录程序 → 串口助手打印 0xFF 或空值 → 逐个接传感器 → 看数值变化。一次接一个不要图省事全插上去再找问题。6. 最后一步用串口日志逆向验证整套系统的真实行为整套系统跑起来之后心里还是不踏实怎么办我惯用的手法是写一个 20 行的“状态观测函数”把系统日志全部打到串口 1 上。这不属于源码包自带的功能但每个合格工程师拿到这套源码后都会第一时间给自己加这个“后悔药”。所谓后悔药就是当系统在某个深夜自动打开水泵而你不在现场时回来的第一件事能查日志还原当时发生了什么。状态观测函数的核心逻辑就一句话每次主循环跑完把当前所有传感器值和控制状态格式化打印出来。这个函数你可以放在control.c里也可以是独立文件。它不会影响控制逻辑只在调试时打开上线后关掉打印就行void Debug_PrintStatus(void) { printf([LOG] Temp%d.%dC, Humi%d%%, Light%d, Soil%d%%, Fan%d, Water%d\r\n, dht11_data.temperature_int, dht11_data.temperature_dec, dht11_data.humidity_int, light_value, soil_value, relay_GetState(RELAY_FAN), relay_GetState(RELAY_WATER)); }这里的relay_GetState是从源码里Relay_Control函数记录的状态变量里读取的。调试时每两秒打印一行时间戳是串口助手自动加的。这样当你发现某天晚上土壤湿度数据异常飙升时回头翻日志看到继电器在对应时间点吸合过就能判断是传感器误报还是真实环境变化。日志分析方法也有讲究。不要眯着眼睛盯串口助手把日志保存成文本文件用文本编辑器的搜索功能查关键字段比如查Soil100出现的频率如果一小时内出现三次以上基本可以断定土壤探头电极极化或者接线松动。还有一个验证技巧人为干扰测试。拿一杯热水靠近温度传感器看日志里风扇是否在超过 35 度时真的启动拿一杯水浇到土壤探头附近看滴灌继电器是否关闭。这套验证流程走完系统才算真正交接。从那以后我每次拿到类似的环境监测源码都会走一遍这个流程——先查日志再人为制造边界条件最后才谈自动化控制。比起盯着数据曲线发呆日志会让你看到每一个决策背后的依据。这套源码的设计让这种验证变得很顺手串口帧格式和状态记录都留了接口。希望帮到你。本文还有配套的精品资源点击获取