IoT 工业 - 硬件开发

ZZ-D541SM 超高频 RFID 阅读器串口通信协议完全指南

记录设备接入、BioLab、工业 IoT、软件架构与项目沉淀的静态文章站。

#IoT #RFID #串口通信 #嵌入式

ZZ-D541SM 超高频 RFID 阅读器串口通信协议完全指南

基于实际项目踩坑经验整理,从零开始掌握这款工业级 RFID 阅读器的通信细节。

背景

在物联网和仓储管理场景中,超高频 RFID 技术被广泛用于远距离、批量化的资产追踪。ZZ-D541SM 是一款性价比不错的国产一体化超高频阅读器,支持 EPCglobal UHF Class 1 Gen2 / ISO 18000-6C 标准。

本文档是我在实际项目开发过程中,通过串口抓包和反复调试整理出来的协议详解,希望能帮到同样在使用这款设备的开发者。

设备接口参数

参数 说明
物理接口 RS232 / USB 转串口 DB9 母头或 USB 虚拟串口
波特率 115200 bps 出厂默认,可配置
数据位 8
停止位 1
校验位 None
流控
设备地址 0x0001 默认地址
广播地址 0x0000 用于排查通信

踩坑提示: 如果 115200 通信失败,先别急着怀疑代码,试试 9600 / 19200 / 38400 等波特率。有时候厂家出货时会改默认值。

数据帧格式

标准帧结构

大多数命令都遵循这个标准帧格式:

1
2
3
4
5
6
┌─────────┬────────┬─────────────┬─────────┐
│ 帧头 │ 长度 │ 数据域 │ CRC16 │
│ 2 Bytes │ 1 Byte │ N Bytes │ 2 Bytes │
├─────────┼────────┼─────────────┼─────────┤
│ 7E 55 │ 0xNN │ 命令+参数 │ 低 高 │
└─────────┴────────┴─────────────┴─────────┘
字段 长度 说明
帧头 2 字节 固定为 0x7E 0x55
长度 1 字节 数据域字节数 + CRC 字节数(即总长度 − 3)
数据域 N 字节 保留/地址 + 功能码 + 参数 + 负载
CRC16 2 字节 小端序,CRC-16/X-25

长度计算示例(获取系统参数请求):

1
2
7E 55 09 00 00 01 00 F5 00 00 BD 91
↑ 长度 0x09 = 数据域 7 字节 + CRC 2 字节

⚠️ 盘点响应帧(特殊格式)

重要提醒:盘点标签响应帧的结构和标准帧不一样!第 3 字节是保留字段 0x00,不是长度字段。这是我在项目中踩的第一个大坑。

1
2
3
4
5
6
┌─────────┬────────┬────────┬──────────┬──────────┬────────┬──────────┬─────────────┬────────┐
│ 帧头 │ 保留 │ 状态 │ 保留 │ 响应标识 │ 功能码 │ 保留 │ 数据域长度 │ 保留 │
│ 2 Bytes │ 1 Byte │ 1 Byte │ 3 Bytes │ 1 Byte │ 1 Byte │ 1 Byte │ 1 Byte │ 1 Byte │
├─────────┴────────┴────────┴──────────┴──────────┴────────┴──────────┴─────────────┴────────┤
│ 7E 55 │ 00 │ 01 │ 00 00 00 │ 1F │ 10 │ 00 │ 0F/1C │ 00 │
└─────────┴────────┴────────┴──────────┴──────────┴────────┴──────────┴─────────────┴────────┘

盘点响应总长度 = 12(固定头) + 数据域长度(第 11 字节,索引 10) + 2(CRC)

1
2
总长度 = 12 + frame[10] + 2
CRC 范围 = frame[2] .. frame[12 + frame[10] - 1]

响应状态码

状态码 含义
0x00 失败 / 错误
0x01 成功 / 正常

所有响应帧的第 8 字节(索引 7)固定为 0x1F,这是通用响应标识。

命令详解

1. 获取系统参数(0xF5)

请求帧

1
7E 55 09 00 00 01 00 F5 00 00 BD 91
字节 说明
0~1 7E 55 帧头
2 09 长度 = 9
3~4 00 00 保留/地址
5 01 子命令
6 00 保留
7~8 F5 00 功能码 + 参数索引
9 00 附加数据
10~11 BD 91 CRC-16

响应帧(成功)

1
7E 55 10 01 00 00 00 1F F5 00 01 00 01 00 00 00 00 9D 53

2. 蜂鸣器控制(0xF4)

控制阅读器的蜂鸣器开关,参数索引 0x01

关闭蜂鸣器

1
7E 55 0D 00 00 01 00 F4 00 01 00 00 00 00 8F 54

开启蜂鸣器

1
7E 55 0D 00 00 01 00 F4 00 01 01 00 00 00 34 48
字节 说明
0~1 7E 55 帧头
2 0D 长度 = 13
3~6 00 00 01 00 保留/地址
7~8 F4 00 功能码
9 01 参数索引:蜂鸣器
10 00 / 01 参数值:0x00=关,0x01=开

响应帧(成功)

1
7E 55 0B 01 00 00 00 1F F4 00 00 00 BE E8

3. 设置天线功率(0xF0)

这个命令用于设置射频发射功率,直接影响读取距离。

数据域结构

偏移 说明
保留/地址 00 00 01 00(4 字节)
功能码 F0 00
功率 dBm 原值(如 33 → 0x21,30 → 0x1E
天线端口 01(Ant1)
频段参数 07 3B
保留 00 00 00 00
设备标识 D1

常用功率预设

目标功率 功率字节 完整请求帧
20 dBm 0x14 7E 55 11 00 00 01 00 F0 00 14 01 07 3B 00 00 00 00 D1 6A E2
25 dBm 0x19 7E 55 11 00 00 01 00 F0 00 19 01 07 3B 00 00 00 00 D1 B8 EF
30 dBm 0x1E 7E 55 11 00 00 01 00 F0 00 1E 01 07 3B 00 00 00 00 D1 5A 06
33 dBm 0x21 7E 55 11 00 00 01 00 F0 00 21 01 07 3B 00 00 00 00 D1 DF 98

设备射频功率范围为 0~33 dBm。功率字节 = dBm 十进制值。

响应帧(成功)

1
7E 55 0B 01 00 00 00 1F F0 00 00 00 52 9A

⚠️ 响应为固定确认不返回实际功率值。设置成功不代表射频已输出到目标 dBm,需结合现场读距验证。

4. 盘点标签(0x10)

这是最常用的命令,用于读取附近的 RFID 标签。

请求帧

1
7E 55 0B 00 00 01 00 10 00 01 00 01 F8 1E

响应帧数据域(从字节 12 起)

1
2
3
4
┌──────────────┬────────────┬─────────────┬────────────┬─────────────┐
│ 标签数量 │ EPC1长度 │ EPC1数据 │ EPC2长度 │ EPC2数据 │
│ 2 Bytes LE │ 1 Byte │ N Bytes │ 1 Byte │ N Bytes │
└──────────────┴────────────┴─────────────┴────────────┴─────────────┘

标签数量小端序01 00 = 1 张,02 00 = 2 张。

示例:单标签读取

1
7E 55 00 01 00 00 00 1F 10 00 0F 00 01 00 0C 13 59 22 80 65 79 24 14 35 14 20 03 8A B9
  • 数据域长度 0x0F = 15
  • EPC: 135922806579241435142003

示例:双标签读取

1
7E 55 00 01 00 00 00 1F 10 00 1C 00 02 00 0C 13 59 22 80 65 79 24 14 35 14 20 02 0C 13 59 22 80 65 79 24 14 35 14 20 03 79 6B
  • 数据域长度 0x1C = 28
  • 标签 1: 135922806579241435142002
  • 标签 2: 135922806579241435142003

EPC 解析规则

标签数据格式

每张标签:[EPC长度 1B] + [EPC数据 NB],长度常为 0x0C(12 字节)。

Code 规则

EPC Code = 各字节 Hex 直接拼接为大写字符串,不做十进制转换。

1
13 59 22 80 65 79 24 14 35 14 20 02  →  135922806579241435142002

.NET 解析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;
using System.Collections.Generic;

public class RfidParser
{
/// <summary>
/// 解析盘点响应帧,返回 EPC 列表
/// </summary>
public static List<string> ParseInventoryResponse(byte[] rawBytes)
{
// 验证帧头
if (rawBytes[0] != 0x7E || rawBytes[1] != 0x55)
throw new ArgumentException("无效帧头");

// 验证状态成功
if (rawBytes[3] != 0x01)
throw new InvalidOperationException("盘点失败");

// 验证盘点响应标识
if (rawBytes[7] != 0x1F || rawBytes[8] != 0x10)
throw new ArgumentException("非盘点响应帧");

byte dataLen = rawBytes[10];
// 标签数量(小端序)
int tagCount = rawBytes[12] | (rawBytes[13] << 8);

int offset = 14;
var epcs = new List<string>();

for (int i = 0; i < tagCount; i++)
{
byte epcLen = rawBytes[offset];
byte[] epcData = new byte[epcLen];
Array.Copy(rawBytes, offset + 1, epcData, 0, epcLen);

// 转换为大写 Hex 字符串
string epc = BitConverter.ToString(epcData).Replace("-", "").ToUpper();
epcs.Add(epc);

offset += 1 + epcLen;
}

// 验证数据长度
if (offset - 12 != dataLen)
throw new InvalidOperationException("数据长度不匹配");

return epcs;
}

// 使用示例
public static void Main()
{
byte[] raw = Convert.FromHexString("7E5500010000001F10000F0001000C1359228065792414351420038AB9");
var epcs = ParseInventoryResponse(raw);
Console.WriteLine(string.Join(", ", epcs)); // 135922806579241435142003
}
}

CRC-16 校验算法

本协议使用 CRC-16/X-25 校验算法:

  • 多项式: 0x8408(反射)
  • 初始值: 0xFFFF
  • 最终异或: 0xFFFF
  • 输出: 小端序
  • 计算范围: 长度字段 + 数据域(不含帧头 7E 55
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;

public class CrcCalculator
{
/// <summary>
/// 计算 CRC-16/X-25 校验值
/// </summary>
public static byte[] Crc16(byte[] data)
{
int crc = 0xFFFF;

foreach (byte b in data)
{
crc ^= b;
for (int i = 0; i < 8; i++)
{
if ((crc & 0x0001) != 0)
crc = (crc >> 1) ^ 0x8408;
else
crc >>= 1;
}
}

crc ^= 0xFFFF;

// 返回小端序字节数组
return new byte[]
{
(byte)(crc & 0xFF),
(byte)((crc >> 8) & 0xFF)
};
}

// 使用示例
public static void Main()
{
byte[] frameWithoutCrc = Convert.FromHexString("0900000100F50000");
byte[] crc = Crc16(frameWithoutCrc);
Console.WriteLine(BitConverter.ToString(crc).Replace("-", "").ToLower()); // bd91
}
}

Web Demo 有效标签策略

配套项目 zz-d541sm-web-serial-demo 对「当前标签」采用以下策略(与单次盘点历史分离):

参数 说明
统计窗口 5 秒 滑动时间窗
有效判定 ≥ 5 次 同一 EPC 在窗口内读取次数
界面刷新 每 5 秒 统一判定并更新列表
定时盘点 ×100ms 如输入 1 = 100ms,5 = 500ms

推荐操作顺序: 连接 → 系统参数 → 设置功率(默认 33dBm)→ 盘点 / 定时盘点 → 蜂鸣器开关

完整命令速查表

请求帧

命令 功能码 完整帧(Hex)
获取系统参数 F5 7E 55 09 00 00 01 00 F5 00 00 BD 91
关闭蜂鸣器 F4 7E 55 0D 00 00 01 00 F4 00 01 00 00 00 00 8F 54
开启蜂鸣器 F4 7E 55 0D 00 00 01 00 F4 00 01 01 00 00 00 34 48
设置功率 20dBm F0 7E 55 11 00 00 01 00 F0 00 14 01 07 3B 00 00 00 00 D1 6A E2
设置功率 25dBm F0 7E 55 11 00 00 01 00 F0 00 19 01 07 3B 00 00 00 00 D1 B8 EF
设置功率 30dBm F0 7E 55 11 00 00 01 00 F0 00 1E 01 07 3B 00 00 00 00 D1 5A 06
设置功率 33dBm F0 7E 55 11 00 00 01 00 F0 00 21 01 07 3B 00 00 00 00 D1 DF 98
盘点标签 10 7E 55 0B 00 00 01 00 10 00 01 00 01 F8 1E

响应帧

命令 完整响应帧(Hex)
获取系统参数(成功) 7E 55 10 01 00 00 00 1F F5 00 01 00 01 00 00 00 00 9D 53
蜂鸣器控制(成功) 7E 55 0B 01 00 00 00 1F F4 00 00 00 BE E8
天线功率设置(成功) 7E 55 0B 01 00 00 00 1F F0 00 00 00 52 9A

故障排查

现象 可能原因 排查方法
无响应 波特率不匹配 尝试 9600 / 19200 / 38400 / 115200
port already open 端口未关闭重复打开 断开后重连;Web 端使用已授权端口快速重连
CRC 错误(盘点) 按标准帧解析盘点响应 确认第 3 字节为 0x00,按专用格式解析
30dBm 读不到、33dBm 可以 功率不足或响应无回读 提高功率;响应帧不反映实际输出
读不到标签 距离 / 功率 / 协议 提高功率至 25~33 dBm;确认 Gen2 标签
蜂鸣器无反应 参数索引或值错误 对照开/关帧格式

命令码总表

系统控制

命令码 说明
0xF8 获取射频模块温度
0xF7 获取版本信息
0xF6 获取天线反射损耗
0xF5 读取系统工作参数
0xF4 设置系统工作参数(含蜂鸣器)
0xF1 获取天线工作参数
0xF0 设置天线工作参数(含功率)
0x04 软件复位

标签操作(ISO 18000-6C)

命令码 说明
0x10 盘点标签
0x11 自定义盘点标签
0x12 读取标签存储区
0x13 写入标签存储区
0x20 快速盘点标签

写在最后

这份协议文档是我在实际项目中踩坑后整理的,特别是盘点响应帧的特殊格式,差点让我调试了一整天。希望这篇文章能帮你少走一些弯路。

如果你也在做 RFID 相关的开发,欢迎在评论区交流。


文档版本记录

  • V1.0 (2026-06-17) - 初版,基于串口抓包整理
  • V1.1 (2026-06-18) - 补充盘点响应专用帧格式;蜂鸣器开/关;33dBm 功率;Web Demo 有效标签策略

声明: 本协议为设备厂商专有自定义协议,部分内容基于实际串口交互逆向整理。开启蜂鸣器帧由关闭帧参数取反推导,若与厂商文档不一致请以官方为准。