3. ZigBee 协议栈基础:Z-Stack 的下载、安装与工程结构
好,咱们进入第三章。这一章是纯基础,但也是你后面所有实战的根基。说白了,ZigBee 协议栈就是一套帮你把无线通信做好的软件框架。你不需要从零去写 MAC 层、网络层,TI 已经帮你封装好了,就是 Z-Stack。
我记得刚接触 ZigBee 那会儿,面对一堆文件夹和代码,完全不知道从哪下手。后来摸透了它的套路,才发现其实结构非常清晰。今天我就带你把它拆开看看。
3.1 Z-Stack 的下载与安装
先搞定工具。Z-Stack 是 TI 官方的协议栈,目前主流版本是 Z-Stack 3.0.x 和 Z-Stack Home 1.2.x。我建议你直接用 Z-Stack 3.0.2,它支持最新的 ZigBee 3.0 规范,兼容性最好。
C:\TI\Z-Stack 3.0.2。
安装步骤很简单,双击 exe,一路 Next 就行。但要注意一点:安装前关掉杀毒软件,否则有些文件会被误删。我曾经遇到过安装完编译报错,查了半天发现是 dll 被隔离了,很坑。
3.2 协议栈工程结构解析
安装完后,打开 C:\TI\Z-Stack 3.0.2\Projects\zstack\Samples\SampleApp\CC2530DB,用 IAR 打开 SampleApp.eww。你会看到左侧 Workspace 里有一堆文件夹。别慌,我带你一层层剥开。
Z-Stack 的架构严格遵循 ZigBee 协议分层,从下到上依次是:
| 层级 | 文件夹/模块 | 作用 |
|---|---|---|
| 物理层 (PHY) | HAL | 硬件抽象层,管射频、GPIO、UART、定时器 |
| MAC 层 | MAC | 信道访问、CSMA/CA、信标管理 |
| 网络层 (NWK) | NWK | 组网、路由、地址分配 |
| 应用支持子层 (APS) | APS | 数据绑定、端点管理、安全 |
| ZigBee 设备对象 (ZDO) | ZDO | 设备发现、绑定管理、网络管理 |
| 应用层 (APL) | App | 你的用户代码,比如 SampleApp |
你看,每一层都对应一个文件夹。我刚开始做项目时,总喜欢在 App 层里直接操作寄存器,后来发现这样破坏了分层结构,移植性很差。正确的做法是:通过 HAL 接口访问硬件,通过 APS 接口发送数据。
3.2.1 HAL 层
HAL 是硬件抽象层,说白了就是把芯片的寄存器操作封装成函数。比如你要控制一个 LED,直接调 HalLedSet() 就行,不用管具体是哪个 GPIO 口。我在项目中遇到过一个问题:换了块板子,LED 引脚变了,但只要改 HAL 层的配置,上层代码一行都不用动。这就是抽象的好处。
3.2.2 MAC 层
MAC 层负责信道访问和帧收发。它实现了 CSMA/CA 机制,也就是发数据前先听信道是否空闲。你想想看,如果两个设备同时发数据,就会碰撞。MAC 层会自动退避重发,你基本不用管。
3.2.3 NWK 层
网络层是 ZigBee 的核心。它负责设备入网、地址分配、路由发现。比如协调器启动后,NWK 层会自动建立一个网络,其他设备加入时,NWK 层会分配一个 16 位短地址。我调试时经常用抓包工具看 NWK 层的命令帧,能快速定位组网失败的原因。
3.2.4 APS 层与 ZDO
APS 层提供端到端的数据传输服务,支持单播、广播、组播。ZDO 则负责设备管理和服务发现。比如你要知道网络里有哪些设备,就通过 ZDO 发送 Device_annce 请求。
3.3 OSAL 操作系统抽象层原理
Z-Stack 不是跑在 RTOS 上的,它用的是自己的一套轻量级调度机制——OSAL(Operating System Abstraction Layer)。你把它理解成一个微型操作系统就行。
OSAL 的核心是 任务轮询。它维护一个任务列表,每个任务有一个事件标志位。主循环不断检查哪个任务有事件待处理,然后调用对应的处理函数。没有中断抢占,没有时间片轮转,就是简单的轮询。
为什么会这样设计?因为 ZigBee 节点资源有限(CC2530 只有 8KB RAM),跑一个完整的 RTOS 太奢侈。OSAL 足够轻量,而且能满足无线通信的实时性要求。
3.3.1 任务与事件调度机制
OSAL 的调度流程可以用下面这张图表示:
具体来说,每个任务都有一个 uint16 events 变量,每一位代表一个事件。比如 SYS_EVENT_MSG 是 0x8000,表示有消息到来。OSAL 主循环里调用 osal_run_system(),它会遍历任务列表,找到优先级最高的、有事件的任务,然后调用其处理函数。
我举个例子。你在 SampleApp 里会看到这样的代码:
// 任务初始化函数
void SampleApp_Init( uint8 task_id )
{
SampleApp_TaskID = task_id;
// 注册端点、初始化硬件等
}
// 事件处理函数
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
if ( events & SYS_EVENT_MSG )
{
// 处理消息
return (events ^ SYS_EVENT_MSG);
}
if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
{
// 定时发送数据
osal_start_timerEx( SampleApp_TaskID,
SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
}
return 0;
}
你看,SampleApp_ProcessEvent 就是任务的事件处理函数。OSAL 检测到有事件,就调它。处理完后,返回未处理的事件标志。如果返回 0,表示全部处理完毕。
3.3.2 定时器与消息
OSAL 提供了两个重要的机制:定时器和消息队列。
- 定时器: 用
osal_start_timerEx()启动一个定时器,到期后会自动设置对应的事件标志。比如你想每 5 秒发一次数据,就在事件处理函数里重新启动定时器。 - 消息: 用
osal_msg_send()发送消息,接收方在事件处理函数里用osal_msg_receive()取出。消息是动态分配的,用完后要osal_msg_deallocate()释放,否则内存泄漏。
我刚开始用 OSAL 时,总忘记释放消息,结果跑了一晚上,节点死机了。后来养成习惯:每次收到消息,处理完立刻释放。
3.4 本章小结
这一章我们讲了 Z-Stack 的下载安装、工程结构(HAL、MAC、NWK、APS、ZDO),以及 OSAL 的任务调度机制。你不需要记住每一层的细节,但一定要理解:Z-Stack 是一个分层架构,OSAL 是一个事件驱动的轮询系统。后面写代码、调 bug,都会用到这些知识。