Post

ISO-15693协议详解

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-帧开始标志
Flags8 bits标志字段
Command8 bits命令代码
Mask length8 bits掩码长度
Mask value0 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 读写器端

读写器端采用递归的方式处理碰撞,通过逐步细分掩码来识别所有标签:

  1. 初始化:从空掩码开始,发送INVENTORY(mask=NULL)请求,表示当前没有掩码,这相当于一个全掩码,意味着所有标签都可能响应。
  2. 碰撞处理
    • 如果某个时隙发生碰撞,将对应的mask和slot number压入栈
    • 碰撞表示有多个标签的UID在当前mask和slot number下匹配到同一时隙
  3. 递归细分:处理完当前层级后,从栈中取出碰撞点(mask, slot_number),发送新的INVENTORY请求INVENTORY(mask=concat(slot_number, mask))。
    • 这相当于在二进制树中向下搜索,直到找到唯一的标签。
  4. 标签识别:当某个时隙只有一个标签响应时,成功识别该标签。

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等信息。ReturnCoderfal_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库

参考:RFAL User Manual

3.1 架构

RFAL库的架构如下图所示:

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 Subcarrier662 kbits/s (fc/2048)2648 kbits/s (fc/512)
Dual Subcarrier667 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_26p48RFAL_BR_1p6629.8 tags/s14 cm4.5 cm
RFAL_BR_26p48RFAL_BR_26p4895.8 tags/s14cm4.5cm

4.2 多标签读取测试

接下来,我们测试了不同数量标签的读取速度。

4.2.1 标准读取

标签数量读取速度 (16-slot)读取速度 (1-slot)
110 tags/s95.8 tags/s
219.6 tags/s-
326.6 tags/s-
437.6 tags/s-
546 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。

This post is licensed under CC BY 4.0 by the author.