ISO-15693协议详解
本文将介绍ISO-15693协议的相关内容,重点关注防碰撞算法,并介绍了意法半导体的RF/NFC abstraction layer (RFAL) 中相关的实现。
1. ISO-15693协议概述
ISO-15693中标签和读写器的通信流程如下:
- Request: 读写器发送请求命令给标签。
- Response: 标签响应读写器的请求。
当读写器的读取范围内存在多个标签时,往往会引起多个标签同时响应,这些响应信息在共享的无线信道上发生碰撞,使响应信号难以被阅读器辨认,从而引起多标签碰撞。 ISO-15693协议中定义了多标签防碰撞算法,以解决这种情况。
2. 防碰撞算法
当多个标签同时响应时,ISO-15693协议为了避免碰撞,将标签安排在不同的时隙进行响应。 由于不同标签具有唯一的UID(唯一标识符),因此可以通过UID来区分不同的标签。 ISO-15693协议采用了基于搜索树的防碰撞算法,该算法通过构建标签UID的搜索树来实现对标签响应的管理。
2.1 盘存请求与响应
盘存请求 (Inventory Request) 是读写器向标签发送的命令,用于识别和盘点标签。盘存请求的格式如下:
| 字段 | 长度 | 描述 |
|---|---|---|
| SOF | - | 帧开始标志 |
| Flags | 8 bits | 标志字段 |
| Command | 8 bits | 命令代码 |
| Mask length | 8 bits | 掩码长度 |
| Mask value | 0 to 8 bytes | 掩码值 |
| EOF | - | 帧结束标志 |
我们先重点关注 Mask length 和 Mask value 字段。Mask length 和 Mask value 字段用于指定标签的UID掩码。当读写器发送盘存请求时,读取范围内的所有标签都会接受到该请求,并根据UID和Mask的匹配情况来决定是否响应。只有UID与Mask匹配的标签才会响应读写器的请求。
接下来,我们具体介绍防碰撞算法的实现。分为标签端和读写器端两部分。
2.2 标签端
令NbS为slot的总数,ISO-15693规定NbS的值为1或者16。令SN为当前slot的序号(从0开始),则标签端的防碰撞算法如下:
- 检查如下条件是否满足:
LSB(UID, SD_length + Mask_length) == concat(LSB(SN, SN_length), LSB(Mask, Mask_length))。- 其中,
LSB(B, n)表示从二进制数B的最低位开始,取n位。 concat(A, B)表示将A和B两个二进制数连接起来。
- 其中,
SD_length=log2(NbS),表示slot的序号长度。若NbS=1,则SD_length=0;若NbS=16,则SD_length=4。- 如果满足条件,则标签响应读写器的请求。
- 如果不满足条件,则标签不响应读写器的请求。
2.3 读写器端
读写器端采用递归的方式处理碰撞,通过逐步细分掩码来识别所有标签:
- 初始化:从空掩码开始,发送INVENTORY(mask=NULL)请求,表示当前没有掩码,这相当于一个全掩码,意味着所有标签都可能响应。
- 碰撞处理:
- 如果某个时隙发生碰撞,将对应的mask和slot number压入栈
- 碰撞表示有多个标签的UID在当前mask和slot number下匹配到同一时隙
- 递归细分:处理完当前层级后,从栈中取出碰撞点(mask, slot_number),发送新的INVENTORY请求INVENTORY(mask=concat(slot_number, mask))。
- 这相当于在二进制树中向下搜索,直到找到唯一的标签。
- 标签识别:当某个时隙只有一个标签响应时,成功识别该标签。
2.4 代码实现
RFAL提供了基本的INVENTORY接口:
1
2
3
4
5
6
7
8
/* in rfal_nfcv.h */
ReturnCode rfalNfcvPollerInventory(
rfalNfcvNumSlots nSlots, // 时隙数量
uint8_t maskLen, // 掩码长度
const uint8_t *maskVal, // 掩码值
rfalNfcvInventoryRes *invRes, // 盘存响应结果
uint16_t* rcvdLen // 接收数据长度
);
其中,rfalNfcvInventoryRes结构体用于存储盘存响应结果,其中包含了UID、CRC等信息。ReturnCode在rfal_utils.h中定义,包含了各种返回码,比如RFAL_ERR_NONE表示成功,RFAL_ERR_TIMEOUT表示超时等。
防碰撞算法实现在如下函数中:
1
2
3
4
5
6
7
/* in rfal_nfcv.c */
ReturnCode rfalNfcvPollerCollisionResolution(
rfalComplianceMode compMode, // 模式设置
uint8_t devLimit, // 最大设备数量
rfalNfcvListenDevice *nfcvDevList, // 设备列表
uint8_t *devCnt // 设备数量
);
其中,compMode表示兼容模式,devLimit表示最大设备数量,nfcvDevList用于存储识别到的设备列表,devCnt用于存储设备数量。主要的防碰撞逻辑如下:
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
do
{
/* Activity 2.1 9.3.7.7 (Symbol 6 / 7) */
colPending = false;
slotNum = 0;
do
{
if( slotNum == 0U )
{
/* Send INVENTORY_REQ with 16 slots Activity 2.1 9.3.7.9 (Symbol 8) */
ret = rfalNfcvPollerInventory( RFAL_NFCV_NUM_SLOTS_16, colFound[colIt].maskLen, colFound[colIt].maskVal, &nfcvDevList[(*devCnt)].InvRes, &rcvdLen );
}
else
{
ret = rfalISO15693TransceiveEOFAnticollision( (uint8_t*)&nfcvDevList[(*devCnt)].InvRes, sizeof(rfalNfcvInventoryRes), &rcvdLen );
}
slotNum++;
/*******************************************************************************/
if( ret != RFAL_ERR_TIMEOUT )
{
if( rcvdLen < rfalConvBytesToBits(RFAL_NFCV_INV_RES_LEN + RFAL_NFCV_CRC_LEN) )
{ /* If only a partial frame was received make sure the FDT_V_INVENT_NORES is fulfilled */
platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES);
}
/* Check if response is a correct frame (no TxRx error) Activity 2.1 9.3.7.11 (Symbol 10)*/
if( (ret == RFAL_ERR_NONE) || (ret == RFAL_ERR_PROTO) )
{
/* Check if the device found is already on the list and its response is a valid INVENTORY_RES */
if( rfalNfcvCheckInvRes( nfcvDevList[(*devCnt)].InvRes.RES_FLAG, rcvdLen ) )
{
/* Activity 2.1 9.3.7.12 (Symbol 11) */
(*devCnt)++;
}
}
else /* Treat everything else as collision */
{
/* Activity 2.1 9.3.7.17 (Symbol 16) */
colPending = true;
/*******************************************************************************/
/* Ensure that this collision still fits on the container */
if( colCnt < RFAL_NFCV_MAX_COLL_SUPPORTED )
{
/* Store this collision on the container to be resolved later */
/* Activity 2.1 9.3.7.17 (Symbol 16): add the collision information
* (MASK_VAL + SN) to the list containing the collision information */
RFAL_MEMCPY(colFound[colCnt].maskVal, colFound[colIt].maskVal, RFAL_NFCV_UID_LEN);
colPos = colFound[colIt].maskLen;
colFound[colCnt].maskVal[(colPos/RFAL_BITS_IN_BYTE)] &= (uint8_t)((1U << (colPos % RFAL_BITS_IN_BYTE)) - 1U);
colFound[colCnt].maskVal[(colPos/RFAL_BITS_IN_BYTE)] |= (uint8_t)((slotNum-1U) << (colPos % RFAL_BITS_IN_BYTE));
colFound[colCnt].maskVal[((colPos/RFAL_BITS_IN_BYTE)+1U)] = (uint8_t)((slotNum-1U) >> (RFAL_BITS_IN_BYTE - (colPos % RFAL_BITS_IN_BYTE)));
colFound[colCnt].maskLen = (colFound[colIt].maskLen + 4U);
colCnt++;
}
}
}
else
{
/* Timeout */
platformDelay(RFAL_NFCV_FDT_V_INVENT_NORES);
}
/* Check if devices found have reached device limit Activity 2.1 9.3.7.13 (Symbol 12) */
if( *devCnt >= devLimit )
{
return RFAL_ERR_NONE;
}
} while( slotNum < RFAL_NFCV_MAX_SLOTS ); /* Slot loop */
colIt++;
} while( colIt < colCnt ); /* Collisions found loop */
3. RFAL库
3.1 架构
RFAL库的架构如下图所示:
3.1.1 RF HAL
RFAL库的硬件抽象层,负责与具体的RF硬件进行交互。包括如下模块:
- ST25R: 直接与NFC IC硬件交互,包括寄存器读写、命令发送等。一般不建议直接调用。(
<rfal_chip.h>) - RF configuration (analog configuration): 负责配置RF硬件的模拟参数,如功率等。(
<rfal_analogConfig.h>)自定义的配置的代码可以通过DISCO GUI生成。 - RF: 提供NFC通信的基础服务,包括:Trasceive, Listen mode, Wake-up mode. (
rfal_rf.h)
接下来,我们重点介绍RF HAL中最重要的一个函数:rfal_worker()。
3.1.2 RFAL Worker
rfal_worker()本质上是一个事件驱动的状态机引擎,它通过频繁调用来逐步推进各种NFC操作的执行。
我们以“发送一个NFC帧”为例来了解RFAL Worker的工作原理:
用户发起请求:用户调用
rfalTransceiveBlockingTx()函数来发送一个NFC帧。提交传输任务:
rfalTransceiveBlockingTx()通过调用rfalStartTransceive()发布传输任务:
1
2
3
4
5
6
7
// 保存用户传入的传输参数
gRFAL.TxRx.ctx = *ctx;
// 设置状态机初始状态
gRFAL.state = RFAL_STATE_TXRX;
gRFAL.TxRx.state = RFAL_TXRX_STATE_TX_IDLE;
gRFAL.TxRx.status = RFAL_ERR_BUSY;
- 调用Worker执行任务:不断地调用
rfal_worker()来执行任务,直到任务完成。
1
2
3
4
do{
rfalWorker();
ret = rfalGetTransceiveStatus();
}while( (rfalIsTransceiveInTx()) && (ret == RFAL_ERR_BUSY) );
4. 实际测试
测试主要关注读取速度和读取距离。我们首先介绍ISO-15693协议的可调参数,然后介绍这些参数对于读取速度和距离的影响。
4.1 可调参数
4.1.1 比特率和编码
读写器 -> 标签: 协议规定,标签端应当支持两种读写器的编码方式:1/256 编码和 1/4 编码,分别对应 165 kbps 和 2648 kbps。 在读取过程中,使用的编码方式由读写器发送的 SOF (start of frame) 告知。
标签 -> 读写器: 标签可以选用一个或者两个子载波进行响应,同时支持不同的比特率:
| 载波类型 | 低速率 | 高速率 |
|---|---|---|
| Single Subcarrier | 662 kbits/s (fc/2048) | 2648 kbits/s (fc/512) |
| Dual Subcarrier | 667 kbits/s (fc/2032) | 2669 kbits/s (fc/508) |
RFAL中提供了不同比特率的设置(但好像并没有覆盖协议中的所有设置),可以通过rfalSetBitRate()函数来设置:
1
2
3
4
5
6
7
/*! RFAL Bit rates */
typedef enum {
// ...
RFAL_BR_26p48 = 0xEC, /*!< Bit Rate 26,48 kbit/s (fc/512) NFCV VICC->VCD & VCD->VICC 1of4 */
RFAL_BR_1p66 = 0xED, /*!< Bit Rate 1,66 kbit/s (fc/8192) NFCV VCD->VICC 1of256 */
// ...
} rfalBitRate;
Note: NFC08A1开发板只支持单载波模式。
| 标签比特率 | 读写器比特率 | 单标签读取速度 | 白卡读取距离 | 2mm玻璃标签读取距离 |
|---|---|---|---|---|
| RFAL_BR_26p48 | RFAL_BR_1p66 | 29.8 tags/s | 14 cm | 4.5 cm |
| RFAL_BR_26p48 | RFAL_BR_26p48 | 95.8 tags/s | 14cm | 4.5cm |
4.2 多标签读取测试
接下来,我们测试了不同数量标签的读取速度。
4.2.1 标准读取
| 标签数量 | 读取速度 (16-slot) | 读取速度 (1-slot) |
|---|---|---|
| 1 | 10 tags/s | 95.8 tags/s |
| 2 | 19.6 tags/s | - |
| 3 | 26.6 tags/s | - |
| 4 | 37.6 tags/s | - |
| 5 | 46 tags/s | - |
首先可以看到,对于单标签读取,1-slot模式下的读取速度明显高于16-slot模式下的读取速度。这是因为,16-slot模式下有15/16的时间都浪费了。另外,随着标签数量的增加,16-slot模式下的读取速度也逐渐升高,这是因为时隙的利用率在不断提高。
4.2.2 Mask优化
一个有意思的现象是,16-slot下的读取速度与标签的UID选择也是有关系的。比如,在不同的两组标签(4个)下,读取速度可以分别是37.6 tags/s和 12.8 tags/s。这是因为,防碰撞算法的效率与标签的UID有关。如果标签的UID选择得当,防碰撞算法可以更快地识别出标签,从而提高读取速度。 但是在实际应用中,标签的UID是由厂家分配的,用户无法控制。但是,如果应用中的UID是固定的,那么我们可以实现优化好mask,从而实现更快的读取速度。
比如,我们目前有4个标签,UID分别为:
1
2
3
4
NFC-V Device 0 UID: E01624012A004E05
NFC-V Device 1 UID: E0162401070E6095
NFC-V Device 2 UID: E01634010E6AB86A
NFC-V Device 3 UID: E0162401070E4C9A
按照默认算法,会需要3次的盘存来完成对4个标签的盘存,最终的读取速度为12.8 tags/s.
1
2
3
INVENTORY(mask=NULL, len=0) -> 在第6和11个时隙上,发生冲突
INVENTORY(mask=0x05, len=4) -> 完成对Device 0和1的盘存
INVENTORY(mask=0x0a, len=4) -> 完成对Device 2和3的盘存
然而,在已知标签ID的情况下,我们实际上可以直接用后面两条盘存指令来完成对这4个标签的盘存,
1
2
INVENTORY(mask=0x05, len=4) -> 完成对Device 0和1的盘存
INVENTORY(mask=0x0a, len=4) -> 完成对Device 2和3的盘存
这样,我们可以将读取速度从12.8 tags/s提升到20.8 tags/s。
