ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ZigBee温室大棚监控系统C源码实战:从编译到上位机可视化

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_ON=FALSE
信道与 PAN IDf8wConfig.cfg默认信道 11,PAN ID 可改,避免与附近 ZigBee 设备冲突
预编译宏工程 Options → C/C++ Compiler → Preprocessor确认ZTOOL_P1、MT_TASK等宏与串口调试匹配

编译时如果报Error[e16]: Segment XDATA_Z is too long,说明 RAM 不够,通常是全局变量开太多。解决办法是把不用的调试宏关掉,或者把大数组改成动态分配。这个坑我在第一次编译时踩过,查了半天以为是协议栈版本问题,其实是自己加了一个 512 字节的缓冲区。

2.3 烧录与最小系统验证

编译通过后,先别接传感器。我一般会先烧录协调器和终端节点,用最简配置验证组网是否正常。步骤是:

  1. 协调器烧录后上电,观察 LED 是否按预期闪烁(通常建网成功后 LED 常亮或慢闪)。
  2. 终端节点烧录后上电,等待加入网络,加入成功后协调器串口会打印设备短地址。
  3. 用串口助手打开协调器对应的 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是协调器的网络地址,通常固定为0x0000;App_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_ON=FALSE。改完重新编译烧录,一般就能入网。

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, timeout=1) # 用双端队列保存最近 50 个数据点 temp_data = deque([0]*50, maxlen=50) humi_data = deque([0]*50, maxlen=50) fig, (ax1, ax2) = plt.subplots(2, 1) line1, = ax1.plot([], [], 'r-', label='Temperature') line2, = ax2.plot([], [], 'b-', label='Humidity') 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', errors='ignore') 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, interval=1000) plt.show()

这段代码的关键是parse_line函数,它按协调器输出的逗号分隔格式解析数据。如果你的协调器输出格式不同,改这里就行。deque的maxlen=50表示只保留最近 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 相关的项目,都会先把串口输出格式定死,再写上位机解析,避免后期改来改去。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表