用ZigBee CC253X构建无线考勤机:硬件、协议栈与固件设计详解
2026/9/15 4:51:18 网站建设 项目流程

简介:基于ZStack协议栈的ZigBee CC2530/CC2531无线IC卡考勤机完整项目资料,适合嵌入式开发者和物联网学习者开展实战训练。资料覆盖CC253X系列单片机,包含从硬件接口、ZStack网络配置到RFID刷卡逻辑、安全传输与低功耗管理的完整实现思路。压缩包共313个文件,以C语言源码和头文件为主,含148个h、122个c文件,另有库文件、链接脚本、工程配置及调试脚本,整体大小6.51MB,便于直接导入IAR工程进行编译与烧录。目前已有160人学习下载。项目中涉及ZStack协议栈的ZDO设备对象、ZCL通用集群、OTA升级、密钥建立等关键模块,结合示例可用于理解ZigBee设备入网、数据上报和远程维护机制;对希望掌握无线传感网络产品开发、快速搭建考勤或门禁原型的工程师,是一份结构清晰、可参考性强的实际工程样例。

1. 用 ZigBee CC253X 做无线考勤机,先想清楚链路再谈刷卡

把 CC2530 或 CC2531 做成无线 IC 卡考勤机,卡位很多,但不是刷卡难。刷卡只是 13.56MHz 读卡模块把 UID 吐到串口,真正的工程量在 ZigBee 那一侧:节点怎么上报、协调器怎么收、掉线怎么重连。这套方案基于 TI 的 ZStack,跑的是 IEEE 802.15.4 协议,2.4GHz 频段和 WiFi、蓝牙挤在一起,现场干扰和丢帧才是考勤数据对不上的根源。

标题里写的 CC253X 是整条产品线:CC2530 当考勤节点,CC2531 多了 USB 控制器,适合当协调器或抓包器。下文按做这类设备最常见的路径走:先定硬件最小系统和读卡接口,再在 ZStack 工程里跑通第一帧,给出一套带 ACK 重传的固件状态机,最后专门讲仿真器驱动安装和抓包验证。新手能照着把工程立起来,熟手重点看状态机设计和现场排错那两节。

2. 硬件从新大陆 zigbee 模块原理图起步:CC2530 最小系统与读卡接口

很多人第一步就踩坑:CC2530 焊在洞洞板上,读卡模块也通了,串口能打印卡号,但 ZigBee 帧就是发不出去。原因基本都在射频前端和供电上。CC2530 是 8051 内核加上片内 2.4GHz 收发器,模拟链路对电源和天线匹配都很敏感,最小系统缺一个元件都可能让发射功率掉到没法用的程度。

2.1 CC253X 选型:CC2530 当节点,CC2531 当协调器

CC2530 和 CC2531 的射频内核、协议栈支持完全一致,区别只在 USB 外设。做考勤机时我一般按下面的分工选:

芯片内核Flash/RAM特色外设考勤机里的角色
CC2530F2568051256KB / 8KB双 USART、8 路 ADC考勤节点,接读卡模块
CC2531F2568051256KB / 8KBUSB 2.0 全速控制器协调器,USB 直连上位机
CC2530F32805132KB / 8KB同上,Flash 小只跑简单应用时的低成本节点

同理,CC2531 刷上 TI 的抓包固件就是协议分析器,现场排查 ZigBee 丢帧特别方便,这一点最后一章细说。注意 CC2531 的引脚复用比 CC2530 紧张,如果协调器还要同时接读卡模块和显示,优先考虑 CC2530 加 USB 转串口芯片的方案,不要硬上 CC2531。

提示:ZStack 工程里 CoordinatorEB、RouterEB、EndDeviceEB 三个 workspace 的源码是同一套,烧录器和协调器用的是不同编译配置,选型时只要确定 Flash 容量不低于 256KB。

2.2 参考公共原理图搭最小系统:晶振、复位、RF 前端三者缺一不可

公开渠道能查到的“新大陆 zigbee 模块原理图”这类资料,主线基本都是 TI 官方 CC2530 参考设计:32MHz 主晶振加 32.768kHz 睡眠晶振、AVDD 和 DVDD 分开供电滤波、RF_P/RF_N 差分对经过 balun 转成单端接天线。抄这类原理图时不要只抄芯片外围,注意三处:

第一,32MHz 晶振的负载电容必须按晶体手册选,常见取 27pF 到 33pF,焊错容值会导致 ZigBee 协议栈起振失败。第二,睡眠晶振不能省,ZStack 的 OSAL 定时器和网络定时依赖它,只留 32MHz 的话休眠唤醒后时钟会漂,考勤时间戳就对不上。第三,RF 前端 balun 的元件值和布局严格按参考设计来,陶瓷天线下方要避开铺铜和走线。我见过把 balun 电路 0402 电容换成 0603 后发射距离从 80 米掉到 10 米的例子。

供电上,读卡模块瞬间电流能到上百毫安,射频发送时也有 29mA 左右的脉冲电流,两者最好分两路 LDO 供电,输出端并 1uF 加 10nF 陶瓷电容,避免刷卡瞬间把 VDD 拉低导致 802.15.4 帧 CRC 错误。

2.3 读卡接口:选 UART 直出 UID 的 13.56MHz 模块最省事

IC 卡考勤机用的是 13.56MHz 的 ISO14443A 协议,常见做法有两种:一种直接用 RC522 这类读卡芯片自己发命令读卡,另一种用一体式模块,模块上电后就自动检测卡片,卡号通过串口吐出来。做考勤机我首选后者,省去在 CC2530 里写 RC522 时序的功夫,只需要做好串口解析。模块接法很简单:模块的 TX 接 CC2530 的 RX,模块的 RX 接 CC2530 的 TX,电平确认是 3.3V TTL,共地不能少。

CC2530 的串口初始化放在 ZStack 的 HAL 层,代码是这样的:

halUARTCfg_t uartConfig; uartConfig.configured = TRUE; uartConfig.baudRate = HAL_UART_BR_9600; // 读卡模块常见 9600 uartConfig.flowControl = FALSE; uartConfig.flowControlThreshold = 8; uartConfig.rx.maxBufSize = 64; // 接收缓冲,卡号一帧足够 uartConfig.tx.maxBufSize = 16; uartConfig.idleTimeout = 6; // 空闲 6 个字符时间后回调 uartConfig.callBackFunc = rfidUartRxCb; // 收到数据后进回调 HalUARTOpen(HAL_UART_PORT_0, &uartConfig);

这段配置里有几个参数值得注意。baudRate 一定先查模块手册,很多 125kHz 模块是 9600,13.56MHz 模块有 9600 也有 19200。idleTimeout 设为 6,意味着串口空闲超过 6 个字符时间就触发一次回调,卡号的 ASCII 字符串是一气呵成发完的,回调里就能按一帧处理。maxBufSize 给到 64 字节,算上模块上线时主动上报的厂商信息也够用。最后串口回调里千万不要做耗时处理,只把数据拷贝进自己的环形缓冲,然后置一个 OSAL 事件,解析放到任务循环里做。

还有一部分读卡模块输出的不是串口而是 Wiegand 26/34 格式,D0、D1 两根线各接一个带中断的 GPIO。Wiegand 协议里 D0 拉低表示 0,D1 拉低表示 1,每一位脉冲约 50 微秒,需要用定时器做 25ms 无脉冲超时判断一帧结束。这个我一般放在第四章的固件里讲,接口选择上只要记住:能走 UART 就走 UART,Wiegand 解码占用的中断资源和定时器在 ZStack 里跟协议栈定时器有冲突风险,不是必须就别选。

3. ZStack 工程里跑通第一帧:端点、簇、AF_DataRequest 与回调

ZStack 和裸机程序最大的区别不是多了一堆 API,而是整个程序跑在 OSAL 任务调度器上。协议栈的 MAC、NWK、APS 各层都是一个个任务,你的考勤应用也要注册成任务,收到刷卡事件后通过 AF 层接口把数据交给协议栈,剩下的路由、重发、组网都由 ZStack 接管。

3.1 从工程目录看懂 ZStack 的分层和任务模型

ZStack-CC2530 工程解压后,Projects 目录下按芯片平台堆放,进入后能看到 CoordinatorEB、RouterEB、EndDeviceEB 三个 IAR workspace,打开哪个决定编译出来的固件角色。源码里 zstack 目录下 MAC、NWK、APS、ZDO 各层分得清清楚楚,应用层在 App 目录,硬件驱动在 HAL 目录。

应用任务注册有固定套路:在 OSAL 的 tasksArr 数组里加一个任务处理函数,比如 attendance_event_loop,它接收 task_id 和 event 两个参数,用 switch 处理自己定义的事件位。OSAL 每轮循环把 tasksEvents 里置位的任务找出来调度,所以刷卡、串口数据、定时器超时这些事件都要通过 osal_set_event 或 osal_start_timerEx 转成任务事件,不能直接在中断里调用协议栈 API。

注意:在串口中断回调里直接调 AF_DataRequest 发送数据,是很多第一次用 ZStack 的人必踩的坑。协议栈不是中断安全的,必须先置事件,回到任务上下文再发。

3.2 端点、簇和绑定:ZigBee 协议里的三张连接表

ZigBee 协议和应用层打交道靠的是端点和簇。一个端点是设备上的一个应用实例,考勤机用一个端点比如 8,协调器也用同一个端点号。簇 ID 表示这个端点提供什么服务,我一般自定义两个簇:0xCC01 用来上报考勤记录,0xCC02 用来回 ACK。

绑定解决的是“发给谁”的问题。协调器和考勤机都声明自己的 SimpleDescriptor,协调器发起 Match_Desc_req 找到同簇的考勤机端点,之后考勤机发送时用 afAddrNotPresent 地址模式,协议栈自动查绑定表把帧发到绑定对象。绑定表维护在协调器侧,考勤机多了以后每台机器只和协调器单向绑定,现场不用手工填 16 位短地址,这是最稳的工程做法。

3.3 最小发送代码:AF_DataRequest 的八个参数

在 ZStack 里发一帧应用数据,入口是 AF_DataRequest。下面这段是考勤机上报卡号的核心函数:

static uint8 cardSeq = 0; static void attendanceSendReport(uint8 *payload, uint8 len) { afAddrType_t dstAddr; dstAddr.addrMode = (afAddrMode_t)afAddrNotPresent; // 走绑定表,不指定短地址 dstAddr.endPoint = ATTEND_ENDPOINT; // 目标端点 8 afStatus_t st = AF_DataRequest( &dstAddr, // 目标地址结构体 &attendanceEndPointDesc, // 本端点的 SimpleDescriptor ATTEND_CLUSTER_REPORT, // 簇 ID 0xCC01 len, // 载荷长度 payload, // 载荷指针:帧头+卡号+序号 &cardSeq, // 事务序号,协议栈回填并随帧发出 AF_ACK_REQUEST, // 请求 MAC/APS 确认 AF_DEFAULT_RADIUS // 广播半径,单播时用默认值 ); cardSeq++; // 每次发送自增,对端据此去重 if (st != afStatus_SUCCESS) { // 失败原因一般是不在绑定表或网络未就绪 } }

参数逐个说:addrMode 用 afAddrNotPresent 而不是 afAddr16Bit,是因为绑定表里存了协调器的短地址,由协议栈解析;端点和簇 ID 必须和协调器一致,否则对端应用收不到,抓包却能看到帧,这是最常见的“发了没反应”原因。cardSeq 是事务序号,随帧带过去,协调器回 ACK 时把同样的序号送回考勤机,配合第四章的 ACK 重传。AF_ACK_REQUEST 只是让协议栈做链路层确认,不保证应用层处理成功,所以还要自己做应用层 ACK。

接收侧在任务处理函数里解析 afIncomingMSGPacket_t:

static void attendanceProcessIncoming(afIncomingMSGPacket_t *pkt) { if (pkt->clusterId == ATTEND_CLUSTER_ACK) { uint8 *p = pkt->cmd.Data; if (p[0] == 0xA1 && p[1] == cardSeq) { osal_stop_timerEx(attendanceTaskId, EVT_ACK_TIMEOUT); attendanceState = ST_IDLE; // ACK 序号对上,刷卡流程结束 } } }

回 ACK 的帧也要走同一条绑定链路,这样考勤机不用维护协调器地址,只要判断簇 ID 和载荷前两字节。注意 pkt->cmd.Data 的长度上限是 APS 层最大载荷,超过 100 字节的考勤记录要分帧,实际场景里一卡一帧足够,不要在这里做批量上传。

4. 考勤机固件状态机:卡号解析、ACK 重传与掉线重连

考勤机看着简单,但刷卡是随机事件,无线链路会丢帧,协调器可能重启,所以固件必须是一个能自恢复的有限状态机,而不是“收到卡号就发”的顺序程序。下面这套状态机我沿用了好几个项目,核心是用应用层 ACK 配合超时重传,把丢帧率控制在千分之一以内。

4.1 从串口到卡号:环形缓冲与 Wiegand 解码两条路径

串口回调收到的是字节流,先放进环形缓冲,再在任务循环里按帧解析。13.56MHz 一体模块通常输出 8 字节 ASCII 卡号加回车换行,解析逻辑就是找起始符、凑满长度、校验回车。解析成功后置 EVT_CARD_READ 事件,同时把卡号拷进全局缓冲区,防止发送期间被下一次刷卡覆盖。

如果用了 Wiegand 模块,解码要在 GPIO 中断里做,思路是按位收,25ms 超时收尾:

__interrupt void P1_ISR(void) { if (P1IFG & BV(0)) wiegandValue = (wiegandValue << 1) | 0; // D0 拉低 = 0 if (P1IFG & BV(1)) wiegandValue = (wiegandValue << 1) | 1; // D1 拉低 = 1 wiegandBits++; P1IFG = 0; P1IF = 0; osal_start_timerEx(wiegandTaskId, EVT_WIEGAND_TIMEOUT, 25); }

Wiegand 一帧 26 位,首尾是奇偶校验位,中间 24 位才是卡号。收到EVT_WIEGAND_TIMEOUT事件后取出中间 24 位即为卡号,但需要把 P1 中断优先级和 T1 的定时器正确配置,避免和 ZStack 的 MAC 定时器抢占冲突。

4.2 状态定义与转移:从刷卡到 ACK 是一个完整事务

一个完整的刷卡事务包含四态,外加一个离线态:

状态进入条件主要动作超时去向
ST_IDLE初始化或收到 ACK等待刷卡中断
ST_WAIT_ACK卡号就绪并已发送启动 300ms 定时器重发,重发次数加 1
ST_RETRYACK 超时且重发次数小于 3按 300/600/1200ms 退避重发3 次无 ACK 进 ST_OFFLINE
ST_OFFLINE重发耗尽停止发送,继续判卡,缓存记录收到心跳 ACK 后回 ST_IDLE

状态机的事件有四个:EVT_CARD_READ、EVT_ACK_OK、EVT_ACK_TIMEOUT、EVT_NET_LOST。核心循环这样写:

static uint8 attendanceTaskRun(uint8 event) { if (event & EVT_CARD_READ) { if (attendanceState == ST_IDLE || attendanceState == ST_OFFLINE) { attendanceBuildFrame(cardNoBuf, &sendBuf); attendanceSendReport(sendBuf, sendLen); attendanceState = ST_WAIT_ACK; retryCount = 0; osal_start_timerEx(taskId, EVT_ACK_TIMEOUT, 300); } } if (event & EVT_ACK_OK) { osal_stop_timerEx(taskId, EVT_ACK_TIMEOUT); attendanceState = ST_IDLE; // 事务成功,回到待刷卡 } if (event & EVT_ACK_TIMEOUT) { if (++retryCount <= MAX_RETRY) { attendanceSendReport(sendBuf, sendLen); osal_start_timerEx(taskId, EVT_ACK_TIMEOUT, 300 << retryCount); } else { attendanceState = ST_OFFLINE; // 挽救失败,进入离线缓存 } } return events; }

退避时间用300 << retryCount,重发间隔 300/600/1200ms,这是 802.15.4 信道冲突后一个比较平缓的节奏,不会把协调器收帧队列打爆。每次重发前从 sendBuf 重新发,注意 cardSeq 在重发时不能再自增,否则对端去重逻辑就失效了。

4.3 离线缓存与掉线自恢复

考勤机最怕的是无线断了还傻等,用户连续刷卡全丢。进 ST_OFFLINE 后不再发无线帧,卡号按“时间戳+卡号”拼成一条记录写进外部 SPI Flash 或 AT24C16 这类 EEPROM,每台机器按最大 1000 条记录封顶,满了覆盖最老的。CC2530 片内没有 EEPROM,但 ZStack 的 osal_nv 可以用内部 Flash 页存小量参数,存考勤记录不够用,外挂存储是常见做法。

掉线检测用应用层心跳比用协议栈状态更直观:考勤机每 60 秒发一帧 0xF0 心跳,协调器回 0xA1 帧号,连续三次心跳无 ACK 就主动切 ST_OFFLINE。重新上线靠 ZStack 自身的重连机制,网络恢复后绑定关系还在,心跳 ACK 回来了就把状态拉回 ST_IDLE,同时把缓存记录按序补传,补传帧头部带缓存序号,协调器按序落库。

4.4 工程里必须锁死的一组参数

参数推荐值说明
信道 DZDEFAULT_CHANLIST0x00000800(信道 11)避开 WiFi 拥挤的 1/6/11 信道
PAN ID ZDAPP_CONFIG_PAN_ID固定如 0x2233防止现场多个考勤系统串网
串口波特率9600与读卡模块一致
应用 ACK 超时300ms小于 ZigBee APS 重试窗口
最大重发次数3超过即离线
心跳周期60s协调器据此判断节点在线
缓存容量1000 条按 16 字节每条约 16KB Flash

信道选择这里单独提醒:2.4GHz 的 ZigBee 信道 11 到 26,现场如果有 WiFi 覆盖,优先做一次信道扫描,选和 WiFi AP 中心频点错开至少 20MHz 的信道。固定 PAN ID 的作用是隔离现场可能存在的中继器或调试节点,烧录前在出厂配置里写死。

5. zigbee 仿真器驱动安装、CC2531 抓包与现场三项验证

ZStack 工程能编译只是第一步,烧录和验证反而最卡人。这一章把 zigbee 仿真器驱动安装和 CC2531 抓包这两件必做的事讲透,最后给一套现场验收的动作。

5.1 zigbee 仿真器驱动安装:Win10/11 下超六成问题出在签名

CC Debugger 或 SmartRF04EB 插到 Windows 10/11 后,设备管理器里经常出现带感叹号的未知设备,这不是仿真器坏了,是驱动没有成功加载。老烧录软件自带的驱动没有微软签名,新系统默认拒绝安装。常见做法是先在设备管理器里手动更新驱动,指向 SmartRF Flash Programmer 安装目录下的 Driver 文件夹;如果系统仍然拒绝,需要临时关闭驱动签名强制重启一次,装完再恢复。安装完成后设备管理器里应出现 TI CC Debugger 或对应的仿真器节点,SmartRF Flash Programmer 能识别到芯片型号才是真的通了。

注意:烧录考勤机固件用 SmartRF Flash Programmer,在线调试用 IAR EW8051,两套工具不能混用。IAR 连不上仿真器时,先确认仿真器固件版本,用 SmartRF Flash Programmer 给仿真器本身升级一次。

5.2 用 CC2531 把 ZigBee 无线帧变成可读日志

CC2531 USB 板刷入 Packet Sniffer 固件后,插电脑就能把空中抓到的 802.15.4 帧按协议栈解出来。打开 TI Packet Sniffer,选择 CC2531 USB,设置要监听的信道,注意这个信道必须和考勤机编译进固件的信道一致,否则一帧都抓不到。抓包界面上能看到三样关键信息:关联请求和关联响应帧、APS Data 帧的载荷,以及每一帧的 RSSI 和 LQI 值。

现场排查时,先让考勤机发一帧,看抓包里载荷前两字节是不是 0xAA 帧头和卡号 ASCII。如果抓包有帧但协调器上位机没数据,问题在协调器的串口或者上位机解析,不在无线链路;如果抓包都看不到帧,问题在节点侧,重点查绑定表是否丢失。

5.3 现场验收:三项验证一个技巧

第一项,单机自检:烧录完考勤机先不组网,串口接电脑刷 100 次卡片,确认卡号解析无错码,这一步把读卡链路和无线链路彻底隔离。第二项,组网验证:协调器上电后观察考勤机的短地址是否分配成功,ZStack 里可以定时打印 mac 层的关联状态。第三项,整链路测试:连续刷 200 次卡,协调器上位机比对收到的卡号和次数。

压箱底的一个技巧是:把应用层 ACK 设计成“回显请求帧的帧序号加 CRC8”,抓包时用 Packet Sniffer 的过滤功能只保留 0xCC02 这个 ACK 簇,然后直接数时序图上同一帧序号的请求和回显对。丢包率按“发出的帧序号数量减去 ACK 回显数量再除以发出数”计算,连续测 100 次刷卡,这个数低于 1‰ 再放行进场。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询