RFID实践——.NET IoT程序读取高频RFID卡/标签

打印 上一主题 下一主题

主题 993|帖子 993|积分 2979

这篇文章是一份RFID实践的保姆级教程,将详细介绍如何用 Raspberry Pi 连接 PN5180 模块,并开发 .NET IoT 程序读写ISO14443 和 ISO15693协议的卡/标签。
设备清单


  • Raspberry Pi必需套件(主板、电源、TF卡)
  • PN5180
  • ISO15693标签
  • 杜邦线
  • 面包板 ( 可选)
  • GPIO扩展板 (可选 )
    本文中用到的树莓派型号是 Raspberry Pi Zero 2 W,电源直接利用充电宝替代(官方电源是5.1V / 2.5A DC)。
    RFID基础——高频RFID协议、读写模块和标签中介绍过 PN5180 是一款价格便宜且支持全部高频 RFID 协议的读写模块。网购 PN5180 模块时通常会送一张ICODE SLIX卡和 Mifare S50 卡。
    ISO15693标签选用的是国产的复旦微电子芯片的标签。额外购买是为了测试多张标签同时在射频场中防碰撞功能。
    杜邦线用于连接  Raspberry Pi Zero 2 W 和 PN5180 模块。GPIO扩展板会标注逻辑引脚,共同面包板利用,方便连接多种传感器。
树莓派连接 PN5180

在 PN5180 上,您会注意到它上有13个引脚,这些引脚中只有9个是必要连接到树莓派的GPIO引脚。对应关系如下表所示。表中特意标注出逻辑引脚和物理引脚,是因为后边程序中必要设置引脚编号。详情在后续代码部门会举行表明。
NXP5180逻辑引脚物理引脚+5V5V2+3.3V3V31RSTGPIO4(GPCLK0)7NSSGPIO3(SCL)5MOSIGPIO10(SPI0-MOSI)19MISOGPIO9(SPI0-MISO)21SCKGPIO11(SPI0-SCLK)23BUSYGPIO2(SDA)3GNDGND9GPIO-IRQ-AUX-REQ-下图灰色区域中 1~40 是物理引脚编号,两侧标注的是逻辑引脚,例如物理引脚编号3的标注是 GPIO2 ,也就是对应的逻辑引脚编号为2。


到了这里,预备工作已经完成,接下来就是编码了。
编写.NET IoT程序

.NET IoT库中已经实现了 PN5180 的部门功能。好比轮询 ISO14443-A 和 ISO14443-B 类型的卡,以及对它们的读写操作,但是没有实现对 ISO15693 协议卡的支持。
PN5180通过SPI和GPIO举行工作,它以特定的方式通过GPIO利用SPI举行通讯。这就必要手动管理SPI的引脚选择,Busy 引脚用于了解 PN5180 什么时候可以吸收和发送信息。
首先,引用- System.Device.GpioIot.Device.Bindings两个包,然后用下面的代码创建SPI驱动程序、重置 PN5180 和创建 PN5180 实例。
  1. var spi = SpiDevice.Create(new SpiConnectionSettings(0, 1) { ClockFrequency = Pn5180.MaximumSpiClockFrequency, Mode = Pn5180.DefaultSpiMode, DataFlow = DataFlow.MsbFirst });
  2. // Reset the device
  3. var gpioController = new GpioController();
  4. gpioController.OpenPin(4, PinMode.Output);
  5. gpioController.Write(4, PinValue.Low);
  6. Thread.Sleep(10);
  7. gpioController.Write(4, PinValue.High);
  8. Thread.Sleep(10);
  9. var pn5180 = new Pn5180(spi, 2, 3);
复制代码
第1行代码创建 SpiDevice 实例,其中设置 DataFlow = DataFlow.MsbFirst ,即首先发送最高有效位。必要注意的是,这里指的是主机与 PN5180 模块之间的 SPI 总线的传输次序,RFID基础——ISO15693标签存储结构及访问控制命令阐明中协议规定首先传输最低有效位指的是 VCD 与 VICC 之间的射频通讯,两者是不同的数据传输过程。
第4行代码创建 GpioController 实例,GpioController 类 的无参构造函数利用逻辑引脚编号方案作为默认方案。
第5行代码开启编号4的引脚,这个编号也就是指的逻辑引脚编号 GPIO4。
第11行创建 PN5180 读写器实例。构造函数界说如下:
  1. public Pn5180 (System.Device.Spi.SpiDevice spiDevice, int pinBusy, int pinNss, System.Device.Gpio.GpioController? gpioController = default, bool shouldDispose = true);
复制代码
第一个参数是 spi 设备实例,第二个参数是 Busy 引脚编号,第三个参数是 Nss 引脚编号,这里都是指的逻辑编号。代码中的参数需和前面引脚对应表中指定的一致。
访问ISO14443协议卡

访问ISO14443协议卡比较简单,调用 ListenToCardIso14443TypeA, ListenToCardIso14443TypeB 轮询射频场中的 PICC,然后选中卡举行操作,下边是监听 ISO14443-A 和 ISO14443-B 类型卡的示例代码:
  1. do
  2. {
  3.    if (pn5180.ListenToCardIso14443TypeA(TransmitterRadioFrequencyConfiguration.Iso14443A_Nfc_PI_106_106, ReceiverRadioFrequencyConfiguration.Iso14443A_Nfc_PI_106_106, out Data106kbpsTypeA? cardTypeA, 1000))
  4.    {
  5.            Console.WriteLine($"ISO 14443 Type A found:");
  6.            Console.WriteLine($"  ATQA: {cardTypeA.Atqa}");
  7.            Console.WriteLine($"  SAK: {cardTypeA.Sak}");
  8.            Console.WriteLine($"  UID: {BitConverter.ToString(cardTypeA.NfcId)}");
  9.    }
  10.    else
  11.    {
  12.            Console.WriteLine($"{nameof(cardTypeA)} is not configured correctly.");
  13.    }
  14.    if (pn5180.ListenToCardIso14443TypeB(TransmitterRadioFrequencyConfiguration.Iso14443B_106, ReceiverRadioFrequencyConfiguration.Iso14443B_106, out Data106kbpsTypeB? card, 1000))
  15.    {
  16.            Console.WriteLine($"ISO 14443 Type B found:");
  17.            Console.WriteLine($"  Target number: {card.TargetNumber}");
  18.            Console.WriteLine($"  App data: {BitConverter.ToString(card.ApplicationData)}");
  19.            Console.WriteLine($"  App type: {card.ApplicationType}");
  20.            Console.WriteLine($"  UID: {BitConverter.ToString(card.NfcId)}");
  21.            Console.WriteLine($"  Bit rates: {card.BitRates}");
  22.            Console.WriteLine($"  Cid support: {card.CidSupported}");
  23.            Console.WriteLine($"  Command: {card.Command}");
  24.            Console.WriteLine($"  Frame timing: {card.FrameWaitingTime}");
  25.            Console.WriteLine($"  Iso 14443-4 compliance: {card.ISO14443_4Compliance}");
  26.            Console.WriteLine($"  Max frame size: {card.MaxFrameSize}");
  27.            Console.WriteLine($"  Nad support: {card.NadSupported}");
  28.    }
  29.    else
  30.    {
  31.            Console.WriteLine($"{nameof(card)} is not configured correctly.");
  32.    }
  33. }
  34. while (!Console.KeyAvailable);
复制代码
有关 ISO14443协议的更多操作可以检察Iot.Device.Bindings中 PN5180 的文档iot/src/devices/Pn5180 at main · dotnet/iot
访问ISO15693协议卡

由于Iot.Device.Bindings中的 PN5180 并没有实现对 ISO15693协议的支持,因此必要自行实现这部门功能。
PN5180 模块的工作原理可以简单的明白为主机向 PN5180 模块发送开启、配置射频场、操作卡/标签(VICC)的命令,PN5180 模块吸收到操作卡/标签(VICC)的命令时,通过射频信号与卡/标签(VICC)举行数据交互。寻卡过程的步调如下:

  • 加载ISO 15693协议到RF寄存器
  • 开启射频场
  • 清除中断寄存器IRQ_STATUS
  • 把PN5180设置为IDLE状态
  • 激活收发程序
  • 向卡/标签(VICC)发送16时隙防辩论的寻卡指令
  • 循环16次以下操作

    • 读取RX_STATUS寄存器,判定是否有卡/标签响应
    • 假如有响应,发送读卡指令然后读取卡的响应
    • 在下一次射频通讯中只发送EOF(帧竣事)而不发送数据。
    • 把PN5180设置为IDLE状态
    • 激活收发程序
    • 清除中断寄存器IRQ_STATUS
    • 向卡/标签(VICC)发送EOF(帧竣事)

  • 关闭射频场
    上述步调中只有步调6向卡/标签(VICC)发送16时隙防辩论的寻卡指令和步调7.7向卡/标签(VICC)发送EOF(帧竣事)是 PN5180 和卡/标签(VICC)之间的数据交互,别的的步调都是PN5180 与主机之间通过SPI通讯。
PN5180与主机通讯

PN5180计划了24个主机接口命令,涉及读写寄存器、读写EEPROM、写数据到发送缓冲区,从吸收缓冲区读数据,加载RF配置到寄存器,开启关闭射频场。包含44个寄存器,它们控制着PN5180处理器的行为。每个寄存器占4个字节。主机处理器可以通过4个不同的命令改变寄存器的值:write_register、 write_register_and_mask、 write_register_or_mask、write_register_multiple。
以下是本文中用到的主机接口命令阐明:
write_register

这个命令将一个32位的值写入配置寄存器。
负载长度值/形貌命令编码10x00参数1寄存器地址参数4寄存器内容WRITE_REGISTER_OR_MASK

该命令利用逻辑或操作修改寄存器的内容。先读取寄存器的内容,并利用提供的掩码执行逻辑或操作,然后把修改后的内容写回寄存器。
负载长度值/形貌命令编码10x01参数1寄存器地址参数4逻辑或操作的掩码WRITE_REGISTER_AND_MASK

该命令利用逻辑与操作修改寄存器的内容。先读取寄存器的内容,并利用提供的掩码执行逻辑与操作,然后把修改后的内容写回寄存器。
负载长度值/形貌命令编码10x02参数1寄存器地址参数4逻辑与操作的掩码LOAD_RF_CONFIG

该命令用于将射频配置从EEPROM加载到配置寄存器中。
负载长度值/形貌命令编码10x11参数1发送器配置的值写入的数据1吸收机配置的值RF_ON

该命令打开内部射频场。
负载长度值/形貌命令编码10x16参数11,根据 ISO/IEC 18092 禁用辩论避免RF_OFF

该命令关闭内部射频场
负载长度值/形貌命令编码10x17参数1虚字节PN5180和卡/标签(VICC)数据交互

PN5180和卡/标签(VICC)数据交互本质上也是主机发送命令给 PN5180 模块,然后 PN5180 把数据写入缓冲区,接着射频传输给卡/标签(VICC),卡/标签(VICC)响应后通过射频传出给 PN5180 模块的吸收缓冲区,主机发送命令读取缓冲区数据。
SEND_DATA

该命令将数据写入射频传输缓冲区,开始射频传输。
负载长度值/形貌命令编码10x09参数1末了一个字节的有效位数写入的数据1~260最大长度为260的数组末了一个字节的有效位数为0表示末了一字节所有的bit都被传输,1~7表示要传输的末了一个字节内的位数。
READ_DATA

从VICC成功吸收数据后,该命令从射频吸收缓冲区读取数据。
负载长度值/形貌命令编码10x0A参数10x00读取的数据1~508最大长度为508的数组代码实现轮询ISO15693卡

PN5180 和卡/标签(VICC)之间的数据交互都是遵循[[RFID基础——ISO15693标签存储结构及访问控制命令阐明]]中的命令。只需用代码实现 PN5180 的主机接口指令以及ISO15693的访问控制命令即可。首先Fork dotnet/iot版本库,然后在 Pn5180.cs中加入以下监听 ISO15693 协议卡的代码:
  1. /// <summary>
  2. /// Listen to 15693 cards with 16 slots
  3. /// </summary>
  4. /// <param name="transmitter">The transmitter configuration, should be compatible with 15693 card</param>
  5. /// <param name="receiver">The receiver configuration, should be compatible with 15693 card</param>
  6. /// <param name="cards">The 15693 cards once detected</param>
  7. /// <param name="timeoutPollingMilliseconds">The time to poll the card in milliseconds. Card detection will stop once the detection time will be over</param>
  8. /// <returns>True if a 15693 card has been detected</returns>
  9. public bool ListenToCardIso15693(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver,
  10. #if NET5_0_OR_GREATER
  11. [NotNullWhen(true)]
  12. #endif
  13. out IList<Data26_53kbps>? cards, int timeoutPollingMilliseconds)
  14. {
  15.         cards = new List<Data26_53kbps>();
  16.         var ret = LoadRadioFrequencyConfiguration(transmitter, receiver);
  17.         // Switch on the radio frequence field and check it
  18.         ret &= SetRadioFrequency(true);
  19.         Span<byte> inventoryResponse = stackalloc byte[10];
  20.         Span<byte> dsfid = stackalloc byte[1];
  21.         Span<byte> uid = stackalloc byte[8];
  22.         int numBytes = 0;
  23.         DateTime dtTimeout = DateTime.Now.AddMilliseconds(timeoutPollingMilliseconds);
  24.         try
  25.         {
  26.                 // Clears all interrupt
  27.                 SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 });
  28.                 // Sets the PN5180 into IDLE state
  29.                 SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF });
  30.                 // Activates TRANSCEIVE routine
  31.                 SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 });
  32.                 // Sends an inventory command with 16 slots
  33.                 ret = SendDataToCard(new byte[] { 0x06, 0x01, 0x00 });
  34.                 if (dtTimeout < DateTime.Now)
  35.                 {
  36.                         return false;
  37.                 }
  38.                 for (byte slotCounter = 0; slotCounter < 16; slotCounter++)
  39.                 {
  40.                         (numBytes, _) = GetNumberOfBytesReceivedAndValidBits();
  41.                         if (numBytes > 0)
  42.                         {
  43.                                 ret &= ReadDataFromCard(inventoryResponse, inventoryResponse.Length);
  44.                                 if (ret)
  45.                                 {
  46.                                         cards.Add(new Data26_53kbps(slotCounter, 0, 0, inventoryResponse[1], inventoryResponse.Slice(2, 8).ToArray()));
  47.                                 }
  48.                         }
  49.                         // Send only EOF (End of Frame) without data at the next RF communication
  50.                         SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.TX_CONFIG, new byte[] { 0x3F, 0xFB, 0xFF, 0xFF });
  51.                         // Sets the PN5180 into IDLE state
  52.                         SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF });
  53.                         // Activates TRANSCEIVE routine
  54.                         SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 });
  55.                         // Clears the interrupt register IRQ_STATUS
  56.                         SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 });
  57.                         // Send EOF
  58.                         SendDataToCard(new Span<byte> { });
  59.                 }
  60.                 if (cards.Count > 0)
  61.                 {
  62.                         return true;
  63.                 }
  64.                 else
  65.                 {
  66.                         return false;
  67.                 }
  68.         }
  69.         catch (TimeoutException)
  70.         {
  71.                 return false;
  72.         }
  73. }
复制代码
必要注意的是,寻卡指令SendDataToCard(new byte[] { 0x06, 0x01, 0x00 })发送的数据只有请求标志、命令、掩码长度,并没有CRC校验码,我推测是 PN5180 m模块内部举行了CRC校验,目前并没有找到相关资料证明这个猜测。同样,用 PN5180 读写标签数据块以及其他访问控制指令也不必要CRC校验码。
读写ISO15693协议卡

由于支持 ISO15693 协议的读写器不但是 PN5180 ,因此把对 ISO15693 协议卡的详细读写操作放在 PN5180 的实现类中不太符合。这里界说了一个 IcodeCard 的类型,该类实现了 ISO15693 协议中常用的命令,并在构造函数中注入 RFID 读写器。执行指定操作时,调用 RFID 读写器的 Transceive 方法传输请求指令并吸收响应举行处理。以下是主要代码:
  1. public class IcodeCard
  2. {
  3.         public IcodeCard(CardTransceiver rfid, byte target)
  4.         {
  5.                 _rfid = rfid;
  6.                 Target = target;
  7.                 _logger = this.GetCurrentClassLogger();
  8.         }
  9.        
  10.         /// <summary>
  11.         /// Run the last setup command. In case of reading bytes, they are automatically pushed into the Data property
  12.         /// </summary>
  13.         /// <returns>-1 if the process fails otherwise the number of bytes read</returns>
  14.         private int RunIcodeCardCommand()
  15.         {
  16.                 byte[] requestData = Serialize();
  17.                 byte[] dataOut = new byte[_responseSize];
  18.                 var ret = _rfid.Transceive(Target, requestData, dataOut.AsSpan(), NfcProtocol.Iso15693);
  19.                 _logger.LogDebug($"{nameof(RunIcodeCardCommand)}: {_command}, Target: {Target}, Data: {BitConverter.ToString(requestData)}, Success: {ret}, Dataout: {BitConverter.ToString(dataOut)}");
  20.                 if (ret > 0)
  21.                 {
  22.                         Data = dataOut;
  23.                 }
  24.                 return ret;
  25.         }
  26.         /// <summary>
  27.         /// Serialize request data according to the protocol
  28.         /// Request format: SOF, Flags, Command code, Parameters (opt.), Data (opt.), CRC16, EOF
  29.         /// </summary>
  30.         /// <returns>The serialized bits</returns>
  31.         private byte[] Serialize()
  32.         {
  33.                 byte[]? ser = null;
  34.                 switch (_command)
  35.                 {
  36.                         case IcodeCardCommand.ReadSingleBlock:
  37.                                 // Flags(1 byte), Command code(1 byte), UID(8 byte), BlockNumber(1 byte)
  38.                                 ser = new byte[2 + 8 + 1];
  39.                                 ser[0] = 0x22;
  40.                                 ser[1] = (byte)_command;
  41.                                 ser[10] = BlockNumber;
  42.                                 Uid?.CopyTo(ser, 2);
  43.                                 _responseSize = 5;
  44.                                 return ser;
  45.                         // 略去代码....
  46.                         default:
  47.                                 return new byte[0];
  48.                 }
  49.         }
  50.         /// <summary>
  51.         /// Perform a read and place the result into the 4 bytes Data property on a specific block
  52.         /// </summary>
  53.         /// <param name="block">The block number to read</param>
  54.         /// <returns>True if success. This only means whether the communication between VCD and VICC is successful or not </returns>
  55.         public bool ReadSingleBlock(byte block)
  56.         {
  57.             BlockNumber = block;
  58.             _command = IcodeCardCommand.ReadSingleBlock;
  59.             var ret = RunIcodeCardCommand();
  60.             return ret >= 0;
  61.         }
  62. }
复制代码
只需以下代码就可以监听射频场中的 ISO15693 类型的卡并举行读写操作:
  1. if (pn5180.ListenToCardIso15693(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26, out IList<Data26_53kbps>? cards, 20000))
  2. {
  3.     pn5180.ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26);
  4.     foreach (Data26_53kbps card in cards)
  5.     {
  6.         Console.WriteLine($"Target number: {card.TargetNumber}");
  7.         Console.WriteLine($"UID: {BitConverter.ToString(card.NfcId)}");
  8.         Console.WriteLine($"DSFID: {card.Dsfid}");
  9.         if (card.NfcId[6] == 0x04)
  10. {
  11.     IcodeCard icodeCard = new IcodeCard(pn5180, card.TargetNumber)
  12.     {
  13.         Afi = 1,
  14.         Dsfid= 1,
  15.         Uid = card.NfcId,
  16.         Capacity = IcodeCardCapacity.IcodeSlix,
  17.     };
  18.    
  19.     for (byte i = 0; i < 28; i++)
  20.     {
  21.         if (icodeCard.ReadSingleBlock(i))
  22.         {
  23.             Console.WriteLine($"Block {i} data is :{BitConverter.ToString(icodeCard.Data)}");
  24.         }
  25.         else
  26.         {
  27.             icodeCard.Data = new byte[] { };
  28.         }
  29.     }
  30. }
  31. else
  32. {
  33.     Console.WriteLine("Only Icode cards are supported");
  34. }
  35.     }
  36. }
复制代码
末了,就是把程序摆设到 Raspberry pi 上,详细操作可以参照 Raspberry pi 上摆设调试.Net的IoT程序

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表