ZigBee温室大棚监控系统C源码实战:从编译到上位机可视化
简介这份资源是面向计算机、通信工程、自动化等专业学生与嵌入式初学者的ZigBee温室大棚智能监控系统完整毕业设计资料包含可运行的C源码、代码注释、项目说明及配套文档适合作为毕设、课程设计或项目立项参考。包内共24个文件约7.08MB涵盖C源码、Makefile构建脚本、驱动模块、字体库、位图素材及doc说明文档结构清晰便于按模块查阅。系统由两个搭载DHT11的ZigBee终端节点采集温湿度协调器经杜邦线连接GEC6818开发板接收并显示数据实现环境监控、超温报警与灯光控制温度超过27度时自动熄灭四盏LED、蜂鸣器报警并显示即时温度与报警信息同时支持自主控制LED亮灭。该源码经测试运行成功答辩评审平均分达94.5分已有1275人学习下载可帮助读者快速理解ZigBee组网、传感器采集与嵌入式显示联调思路。1. 从一份能跑通的 ZigBee 温室大棚源码说起如果你正在做物联网或电子信息方向的毕业设计大概率绕不开一个经典题目温室大棚环境监控。而 ZigBee 方案几乎是这个题目的标配——低功耗、自组网、节点成本可控答辩时老师也认。但真正动手时你会发现网上能找到的资料要么只有论文没有代码要么代码缺注释、缺文档、编译报错一堆最后变成「论文写完了系统跑不起来」的尴尬局面。这份资源是一套完整的 ZigBee 温室大棚智能监控系统 C 源码包附带代码注释和项目说明文档。它解决的核心问题不是「教你 ZigBee 协议栈原理」而是给你一套能编译、能烧录、能演示的工程底子。适合谁正在做物联网毕业设计、需要快速搭出可演示原型、又不想从零写协调器和终端节点通信逻辑的人。下面我从代码结构、编译配置、传感器接入、通信调试几个层面拆开讲把能抄的步骤和容易翻车的地方都标出来。2. 代码结构与编译环境先把工程跑起来再说2.1 源码目录拆解与模块职责拿到压缩包解压后先别急着打开 IDE。我一般会先用命令行把目录树打出来看清楚工程的组织方式。典型的 ZigBee 温室监控工程目录大致长这样# 查看工程目录结构Linux/macOS 或 Git Bash find . -maxdepth 3 -type d | sort # Windows 下用 tree 命令 tree /F /A常见输出结构如下├── Coordinator/ # 协调器节点工程 │ ├── App/ # 应用层数据汇聚、串口上传 │ ├── HAL/ # 硬件抽象LED、按键、串口 │ ├── MAC/ # MAC 层配置 │ ├── ZDO/ # ZigBee 设备对象 │ └── main.c # 入口 ├── EndDevice/ # 终端节点工程 │ ├── App/ # 传感器采集、无线发送 │ ├── HAL/ │ └── main.c ├── Common/ # 公共头文件与工具函数 │ ├── sensor.h # 传感器接口声明 │ └── debug.h # 调试打印宏 ├── Docs/ # 项目说明文档 │ ├── 系统设计说明.md │ └── 引脚连接表.md └── README.md这个结构的核心逻辑是协调器负责建网和汇聚数据终端节点负责采集温湿度、光照等参数并通过 ZigBee 网络上报。Common/目录放的是两个工程共用的定义改传感器类型时只需要动这里和对应的 App 层。注意不同版本的工程可能用 IAR 或 Keil 打开先看 README 里写的 IDE 版本别用错编译器导致一堆链接错误。2.2 编译环境搭建与工程配置ZigBee 工程通常基于 TI 的 Z-Stack 协议栈编译环境以 IAR Embedded Workbench 为主。如果你之前只写过 STM32 的 Keil 工程这一步容易卡住。常见做法是# 确认 IAR 版本以 IAR for 8051 为例Z-Stack 常用版本 # 查看安装路径下是否有 8051 目录 ls C:/Program Files/IAR Systems/Embedded Workbench 8.0/8051 # 如果没有需要先安装对应版本的 IAR # 安装完成后用 IAR 打开 Coordinator 下的 .eww 工作区文件打开工程后重点检查三个配置项配置项位置说明设备类型Tools/f8wConfig.cfg协调器设为ZDO_COORDINATOR终端设为RFD_RCVC_ALWAYS_ONFALSE信道与 PAN IDf8wConfig.cfg默认信道 11PAN ID 可改避免与附近 ZigBee 设备冲突预编译宏工程 Options → C/C Compiler → Preprocessor确认ZTOOL_P1、MT_TASK等宏与串口调试匹配编译时如果报Error[e16]: Segment XDATA_Z is too long说明 RAM 不够通常是全局变量开太多。解决办法是把不用的调试宏关掉或者把大数组改成动态分配。这个坑我在第一次编译时踩过查了半天以为是协议栈版本问题其实是自己加了一个 512 字节的缓冲区。2.3 烧录与最小系统验证编译通过后先别接传感器。我一般会先烧录协调器和终端节点用最简配置验证组网是否正常。步骤是协调器烧录后上电观察 LED 是否按预期闪烁通常建网成功后 LED 常亮或慢闪。终端节点烧录后上电等待加入网络加入成功后协调器串口会打印设备短地址。用串口助手打开协调器对应的 COM 口波特率一般 115200看是否有Device Annce之类的入网消息。// 在 Coordinator/App 中常见的入网回调用于确认节点加入 void ZDO_JoinIndicationCB(uint16 shortAddr, uint8 *extAddr, uint8 type) { // shortAddr: 分配给新节点的短地址 // extAddr: 节点的 IEEE 地址全球唯一 // type: 设备类型0 表示协调器1 表示路由器2 表示终端 HalUARTWrite(0, New Device Joined\r\n, 19); // 把短地址转成字符串打印方便调试 char buf[16]; sprintf(buf, Addr: 0x%04X\r\n, shortAddr); HalUARTWrite(0, (uint8 *)buf, strlen(buf)); }这段代码的作用是在协调器端监听入网事件把新节点的短地址通过串口打出来。参数shortAddr是 ZigBee 网络内部使用的 16 位地址extAddr是 64 位物理地址。调试阶段建议把这两个都打印出来后面绑定和通信时要用到。如果串口一直没输出先检查波特率是否和hal_uart.c里的配置一致再检查串口线是不是只接了 TX 没接 RX。3. 传感器数据采集与无线上报从引脚到数据包3.1 温湿度与光照传感器的接入方式温室监控的核心数据一般是空气温湿度、土壤湿度、光照强度。这份工程里常见的传感器组合是 DHT11温湿度和 BH1750光照也有用 SHT30 的版本。接线之前先看Docs/引脚连接表.md里面会写清楚每个传感器接在终端节点的哪个引脚上。以 DHT11 为例它用的是单总线协议对时序要求比较严。Z-Stack 的 HAL 层默认没有 DHT11 驱动需要自己加一个。常见做法是// Common/sensor.h 中声明 DHT11 读取函数 #ifndef SENSOR_H #define SENSOR_H #include hal_types.h // 定义 DHT11 数据引脚根据实际接线修改 #define DHT11_PIN P0_6 // 返回值0 成功1 超时失败 uint8 DHT11_Read(uint8 *temp, uint8 *humi); #endif// Common/sensor.c 中实现读取逻辑简化版省略微秒级延时细节 #include sensor.h #include hal_mcu.h uint8 DHT11_Read(uint8 *temp, uint8 *humi) { uint8 buf[5] {0}; uint8 i, j; // 主机发送开始信号拉低至少 18ms DHT11_PIN 0; halMcuWaitMs(20); DHT11_PIN 1; // 等待 DHT11 响应超时则返回失败 halMcuWaitUs(30); if (DHT11_PIN ! 0) return 1; while (DHT11_PIN 0); // 等待 80us 低电平结束 while (DHT11_PIN 1); // 等待 80us 高电平结束 // 读取 40 位数据8 位湿度整数 8 位湿度小数 8 位温度整数 8 位温度小数 8 位校验 for (i 0; i 5; i) { for (j 0; j 8; j) { while (DHT11_PIN 0); // 等待 50us 低电平结束 halMcuWaitUs(40); // 延时 40us 后采样 buf[i] 1; if (DHT11_PIN 1) { buf[i] | 1; while (DHT11_PIN 1); // 等待高电平结束 } } } // 校验前四字节之和等于第五字节 if (buf[4] (buf[0] buf[1] buf[2] buf[3])) { *humi buf[0]; *temp buf[2]; return 0; } return 1; }这段代码的关键在于微秒级延时的准确性。Z-Stack 里halMcuWaitUs的精度受系统时钟影响如果读出来的数据一直是 0 或者校验失败先用示波器看引脚波形确认延时是否偏差太大。另一个常见问题是 DHT11 上电后需要 1 秒稳定时间如果上电立刻读取第一次大概率失败建议在初始化后加halMcuWaitMs(1000)。3.2 数据打包与 ZigBee 无线发送传感器数据读出来后不能直接发原始字节需要按约定格式打包。这份工程里通常用自定义的应用层帧格式比如| 帧头(0xAA) | 节点地址(2B) | 温度(1B) | 湿度(1B) | 光照(2B) | 校验(1B) |发送时调用 Z-Stack 的AF_DataRequest函数// EndDevice/App 中发送传感器数据的典型写法 void SendSensorData(void) { uint8 temp, humi; uint16 light; uint8 payload[7]; // 读取传感器失败则用默认值并标记 if (DHT11_Read(temp, humi) ! 0) { temp 0xFF; // 0xFF 表示无效 humi 0xFF; } light BH1750_Read(); // 组包 payload[0] 0xAA; // 帧头 payload[1] (NLME_GetShortAddr() 8) 0xFF; // 短地址高字节 payload[2] NLME_GetShortAddr() 0xFF; // 短地址低字节 payload[3] temp; payload[4] humi; payload[5] (light 8) 0xFF; payload[6] light 0xFF; // 发送到协调器地址 0x0000端点号 10 AF_DataRequest(CoordinatorAddr, // 目标地址 App_epDesc, // 端点描述符 10, // 目标端点号 sizeof(payload), // 数据长度 payload, // 数据指针 AppTransID, // 事务 ID AF_DISCV_ROUTE, // 发现路由 AF_DEFAULT_RADIUS); // 跳数半径 }参数说明CoordinatorAddr是协调器的网络地址通常固定为0x0000App_epDesc是应用端点描述符在App_Init里注册端点号两端要一致否则协调器收不到。AF_DEFAULT_RADIUS默认是 30 跳温室场景下节点距离近可以改成 5 减少广播开销。如果发送返回afStatus_SUCCESS但协调器没收到先检查端点号是否匹配再确认协调器的接收回调有没有正确注册。3.3 协调器端数据接收与串口转发协调器收到数据后需要解析并转发到上位机。接收回调一般长这样// Coordinator/App 中处理接收到的无线数据 void App_ReceiveData(uint16 srcAddr, uint8 endpoint, uint8 *data, uint8 len) { // 简单校验帧头必须是 0xAA长度至少 7 字节 if (len 7 || data[0] ! 0xAA) { return; } uint8 temp data[3]; uint8 humi data[4]; uint16 light (data[5] 8) | data[6]; // 通过串口转发给上位机格式节点地址,温度,湿度,光照 char buf[64]; sprintf(buf, 0x%04X,%d,%d,%d\r\n, srcAddr, temp, humi, light); HalUARTWrite(0, (uint8 *)buf, strlen(buf)); }这里有个容易忽略的点sprintf在 8051 上很吃 RAM如果编译报内存不足可以改成手动拼接字符串。另外串口转发时建议加一个简单的帧头帧尾方便上位机做解析不然数据一多容易串包。4. 避坑与排查那些让我熬夜的翻车现场4.1 终端节点入网失败协调器毫无反应现象终端节点上电后 LED 一直快闪协调器串口没有任何入网消息。原因最常见的是信道或 PAN ID 不匹配。协调器建网时用的信道和终端节点扫描的信道不一致终端会一直扫不到网络。另一个可能是终端节点的RFD_RCVC_ALWAYS_ON设成了TRUE导致它按路由器方式工作但硬件又是终端配置。解决打开两个工程的f8wConfig.cfg确认DEFAULT_CHANLIST和ZDAPP_CONFIG_PAN_ID完全一致。终端节点确保RFD_RCVC_ALWAYS_ONFALSE。改完重新编译烧录一般就能入网。4.2 传感器数据全是 0 或 0xFF现象协调器能收到数据包但温度湿度字段一直是 0 或者 0xFF。原因DHT11 读取失败。可能是引脚定义和实际接线不一致也可能是延时函数精度不够导致时序错乱。还有一种情况是传感器供电不足DHT11 在 3.3V 下勉强能工作但波形质量差。解决先用万用表确认传感器 VCC 和 GND 正常再用示波器看数据引脚有没有波形。如果没有波形检查DHT11_PIN定义是否和实际引脚对应。如果有波形但数据不对把halMcuWaitUs的延时值微调或者换用系统滴答定时器做延时。4.3 编译报错 “cannot open source file hal_types.h”现象用 IAR 打开工程后编译提示找不到hal_types.h。原因头文件搜索路径没配好。Z-Stack 的 HAL 头文件在Components/hal/include目录下如果工程是从别的路径拷贝过来的相对路径可能失效。解决在 IAR 工程 Options → C/C Compiler → Preprocessor 里把$PROJ_DIR$\..\..\Components\hal\include加进 Additional include directories。类似地把Components\stack\include、Components\zmac\include等路径都补上。这个坑的本质是工程路径变了但 include 路径还是绝对路径。4.4 串口打印乱码现象协调器串口输出的全是乱码或者偶尔正常偶尔乱码。原因波特率不匹配是最常见的。Z-Stack 默认串口波特率可能是 38400 或 115200而串口助手设的是 9600。另一个原因是系统时钟配置不对导致实际波特率偏移。解决先确认hal_uart.c里HAL_UART_DMA或HAL_UART_ISR的波特率设置把串口助手改成一样的。如果还是乱码检查hal_board_cfg.h里的系统时钟定义确保和晶振频率一致。我遇到过一块板子晶振是 32MHz 但配置写的是 26MHz串口一直乱码改过来就好了。4.5 节点数量一多就丢包现象单个终端节点通信正常增加到 5 个以上后协调器开始丢数据。原因ZigBee 网络默认的广播和路由发现机制在节点密集时会产生大量冗余报文。另外协调器的串口转发如果太慢缓冲区满了也会丢包。解决把AF_DEFAULT_RADIUS从 30 改成 5减少路由发现范围。终端节点的发送周期从 1 秒改成 3 秒或更长降低网络负载。协调器串口转发改成 DMA 模式避免阻塞。如果还丢考虑给终端节点加随机延时再发送避免同时上报造成碰撞。5. 进阶技巧用上位机做数据可视化与历史曲线把数据从串口读出来只是第一步答辩时老师更想看的是实时曲线和历史数据。我一般会用 Python 写一个简单的上位机用pyserial读串口matplotlib画实时曲线。这样不用装庞大的组态软件代码也可控。# upper_monitor.py读取协调器串口数据并绘制实时曲线 import serial import matplotlib.pyplot as plt import matplotlib.animation as animation from collections import deque # 打开串口波特率与协调器一致 ser serial.Serial(COM3, 115200, timeout1) # 用双端队列保存最近 50 个数据点 temp_data deque([0]*50, maxlen50) humi_data deque([0]*50, maxlen50) fig, (ax1, ax2) plt.subplots(2, 1) line1, ax1.plot([], [], r-, labelTemperature) line2, ax2.plot([], [], b-, labelHumidity) def parse_line(line): # 格式0x1234,25,60,320 parts line.strip().split(,) if len(parts) ! 4: return None try: return int(parts[1]), int(parts[2]), int(parts[3]) except ValueError: return None def update(frame): while ser.in_waiting: line ser.readline().decode(ascii, errorsignore) result parse_line(line) if result: t, h, l result temp_data.append(t) humi_data.append(h) line1.set_data(range(len(temp_data)), temp_data) line2.set_data(range(len(humi_data)), humi_data) ax1.relim() ax1.autoscale_view() ax2.relim() ax2.autoscale_view() return line1, line2 ani animation.FuncAnimation(fig, update, interval1000) plt.show()这段代码的关键是parse_line函数它按协调器输出的逗号分隔格式解析数据。如果你的协调器输出格式不同改这里就行。deque的maxlen50表示只保留最近 50 个点避免内存无限增长。实际跑的时候如果曲线卡顿把interval从 1000 改成 2000减少刷新频率。还有一个技巧把数据同时写入 CSV 文件答辩前可以展示历史曲线。只需要在update里加几行# 在 update 函数内追加写入 with open(sensor_log.csv, a) as f: f.write(f{t},{h},{l}\n)这样每次收到数据就追加一行后面用 Excel 或 pandas 都能直接画图。我习惯在答辩前一天把系统跑一晚上第二天直接展示 12 小时的历史曲线比现场演示几分钟有说服力得多。从那以后我每次做 ZigBee 相关的项目都会先把串口输出格式定死再写上位机解析避免后期改来改去。希望帮到你。本文还有配套的精品资源点击获取