一、协议栈概述:RFID协议栈架构、ISO/IEC 18000-6C标准解读、协议栈分层模型

各位同学,咱们今天聊聊RFID协议栈。说实话,我刚入行那会儿,看到协议栈这三个字就头大。总觉得这东西离底层硬件很远,是搞软件的人该操心的。后来踩了几个坑才明白——不懂协议栈,你连标签为什么读不到都查不出原因。

1.1 RFID协议栈长什么样?

RFID协议栈,说白了就是一套通信规则。它规定了读写器和电子标签之间怎么说话、用什么调子说、说错了怎么办。我习惯把它想象成两个人打电话:

  • 物理层:决定你用有线还是无线,用2G还是5G
  • 数据链路层:决定你怎么拨号、怎么挂断、怎么确认对方听清了
  • 应用层:决定你说的是中文还是英文,内容是什么

RFID协议栈也是这个道理。只不过它更精简,毕竟标签那边资源有限——一个标签可能只有几千个逻辑门,你没法让它跑TCP/IP那一套。

核心要点:RFID协议栈是轻量级、低功耗、半双工的通信协议。它不需要复杂的路由和重传机制,但必须保证在恶劣的电磁环境下还能稳定工作。

1.2 ISO/IEC 18000-6C标准解读

这个标准,圈内人通常叫它“6C标准”或者“EPC Class 1 Gen 2”。它是UHF频段RFID的事实标准。我做过一个项目,客户非要自己搞一套私有协议,结果标签和读写器死活对不上。后来老老实实切回6C标准,一天就调通了。

6C标准的核心参数,我列个表给大家看:

参数项 数值 说明
工作频率 860-960 MHz 各国频段不同,中国是920-925 MHz
调制方式 DSB-ASK / SSB-ASK / PR-ASK 读写器到标签用ASK,标签回传用反向散射
编码方式 PIE编码(前向)/ FM0或Miller编码(反向) PIE编码抗干扰能力强
数据速率 40-640 kbps(前向)/ 40-640 kbps(反向) 速率越高,通信距离越短
防碰撞算法 Q算法(时隙ALOHA变种) 支持多标签同时读取
标签存储容量 EPC区96-496 bits,用户区可扩展 EPC区用于标识,用户区存业务数据

嗯,这里要注意一个坑。6C标准里读写器到标签用的是PIE编码,标签回传用的是FM0或Miller编码。为什么这么设计?因为标签那边没有独立的时钟源,它得从读写器的载波里提取时钟。FM0编码对时钟抖动容忍度高,适合标签这种低成本器件。

避坑指南:我曾经在一个项目中,标签回传一直丢包。查了三天,最后发现是读写器端的Miller编码子载波参数配错了。Miller编码有M=2、M=4、M=8三种模式,M值越大,抗干扰越强,但速率越慢。别一上来就选M=8,除非你的应用场景干扰特别大。

1.3 协议栈分层模型

RFID协议栈的分层,和OSI七层模型不太一样。它更精简,通常分为三层:

  • 物理层(PHY):负责信号的调制解调、编码解码、载波生成。说白了就是让0和1能在空中飞起来。
  • 链路层(MAC):负责帧的封装与解析、CRC校验、防碰撞处理。这一层决定了多个标签同时出现时,读写器能不能把它们一个个区分开。
  • 应用层(APP):负责命令的组装与解析、标签状态机的管理。比如你要读标签的EPC码,应用层会组装一个“Read”命令,交给链路层去发送。

我个人的习惯是,在移植协议栈时,先把这三层的接口定义清楚。物理层只暴露“发送比特流”和“接收比特流”两个接口。链路层只暴露“发送帧”和“接收帧”两个接口。应用层只管业务逻辑。这样分层清晰,出了问题也好定位。

举个例子,一个典型的读标签操作,协议栈内部是这样跑的:

// 应用层:组装读命令
uint8_t cmd[] = {0x00, 0x20, 0x00, 0x00, 0x00, 0x00}; // Read命令示例
uint16_t crc = calc_crc16(cmd, 4);
cmd[4] = (crc >> 8) & 0xFF;
cmd[5] = crc & 0xFF;

// 链路层:封装成帧,添加前导码和帧同步
uint8_t frame[64];
int frame_len = build_frame(cmd, 6, frame);

// 物理层:调制发送
phy_send(frame, frame_len, MODE_ASK, RATE_160KBPS);

// 等待标签回传
uint8_t rx_buf[64];
int rx_len = phy_receive(rx_buf, TIMEOUT_10MS);

// 链路层:解析帧,校验CRC
if (check_frame(rx_buf, rx_len)) {
    // 应用层:解析响应
    parse_response(rx_buf, rx_len);
}

你看,每一层各司其职。我在做移植时,最常遇到的问题就是层与层之间的耦合太紧。比如物理层直接调用了应用层的回调函数,或者链路层里混了业务逻辑。这种代码,说白了就是“意大利面条”,改一处崩一片。

个人经验:我建议大家在设计协议栈接口时,多用回调函数和事件队列。物理层收到数据后,通过回调通知链路层;链路层处理完帧后,通过事件队列通知应用层。这样各层之间是松耦合的,方便后续优化和扩展。

最后说一句,协议栈移植这件事,七分靠理解,三分靠调试。你先把分层模型吃透了,后面遇到问题才能快速定位。别一上来就对着代码改,那样容易把自己绕进去。