<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

      RFID實踐——.NET IoT程序讀取高頻RFID卡/標簽

      這篇文章是一份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 邏輯引腳 物理引腳
      +5V 5V 2
      +3.3V 3V3 1
      RST GPIO4(GPCLK0) 7
      NSS GPIO3(SCL) 5
      MOSI GPIO10(SPI0-MOSI) 19
      MISO GPIO9(SPI0-MISO) 21
      SCK GPIO11(SPI0-SCLK) 23
      BUSY GPIO2(SDA) 3
      GND GND 9
      GPIO -
      IRQ -
      AUX -
      REQ -

      下圖灰色區域中 1~40 是物理引腳編號,兩側標注的是邏輯引腳,例如物理引腳編號3的標注是 GPIO2 ,也就是對應的邏輯引腳編號為2。
      gpio-pinout-diagram.png (1881×1080)
      image

      到了這里,準備工作已經完成,接下來就是編碼了。

      編寫.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 實例。

      var spi = SpiDevice.Create(new SpiConnectionSettings(0, 1) { ClockFrequency = Pn5180.MaximumSpiClockFrequency, Mode = Pn5180.DefaultSpiMode, DataFlow = DataFlow.MsbFirst });
      
      // Reset the device
      var gpioController = new GpioController();
      gpioController.OpenPin(4, PinMode.Output);
      gpioController.Write(4, PinValue.Low);
      Thread.Sleep(10);
      gpioController.Write(4, PinValue.High);
      Thread.Sleep(10);
      
      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 讀寫器實例。構造函數定義如下:

      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協議卡比較簡單,調用 ListenToCardIso14443TypeAListenToCardIso14443TypeB 輪詢射頻場中的 PICC,然后選中卡進行操作,下邊是監聽 ISO14443-A 和 ISO14443-B 類型卡的示例代碼:

      do
      {
         if (pn5180.ListenToCardIso14443TypeA(TransmitterRadioFrequencyConfiguration.Iso14443A_Nfc_PI_106_106, ReceiverRadioFrequencyConfiguration.Iso14443A_Nfc_PI_106_106, out Data106kbpsTypeA? cardTypeA, 1000))
         {
      	   Console.WriteLine($"ISO 14443 Type A found:");
      	   Console.WriteLine($"  ATQA: {cardTypeA.Atqa}");
      	   Console.WriteLine($"  SAK: {cardTypeA.Sak}");
      	   Console.WriteLine($"  UID: {BitConverter.ToString(cardTypeA.NfcId)}");
         }
         else
         {
      	   Console.WriteLine($"{nameof(cardTypeA)} is not configured correctly.");
         }
      
         if (pn5180.ListenToCardIso14443TypeB(TransmitterRadioFrequencyConfiguration.Iso14443B_106, ReceiverRadioFrequencyConfiguration.Iso14443B_106, out Data106kbpsTypeB? card, 1000))
         {
      	   Console.WriteLine($"ISO 14443 Type B found:");
      	   Console.WriteLine($"  Target number: {card.TargetNumber}");
      	   Console.WriteLine($"  App data: {BitConverter.ToString(card.ApplicationData)}");
      	   Console.WriteLine($"  App type: {card.ApplicationType}");
      	   Console.WriteLine($"  UID: {BitConverter.ToString(card.NfcId)}");
      	   Console.WriteLine($"  Bit rates: {card.BitRates}");
      	   Console.WriteLine($"  Cid support: {card.CidSupported}");
      	   Console.WriteLine($"  Command: {card.Command}");
      	   Console.WriteLine($"  Frame timing: {card.FrameWaitingTime}");
      	   Console.WriteLine($"  Iso 14443-4 compliance: {card.ISO14443_4Compliance}");
      	   Console.WriteLine($"  Max frame size: {card.MaxFrameSize}");
      	   Console.WriteLine($"  Nad support: {card.NadSupported}");
         }
         else
         {
      	   Console.WriteLine($"{nameof(card)} is not configured correctly.");
         }
      }
      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)進行數據交互。尋卡過程的步驟如下:

      1. 加載ISO 15693協議到RF寄存器
      2. 開啟射頻場
      3. 清除中斷寄存器IRQ_STATUS
      4. 把PN5180設置為IDLE狀態
      5. 激活收發程序
      6. 向卡/標簽(VICC)發送16時隙防沖突的尋卡指令
      7. 循環16次以下操作
        1. 讀取RX_STATUS寄存器,判斷是否有卡/標簽響應
        2. 如果有響應,發送讀卡指令然后讀取卡的響應
        3. 在下一次射頻通信中只發送EOF(幀結束)而不發送數據。
        4. 把PN5180設置為IDLE狀態
        5. 激活收發程序
        6. 清除中斷寄存器IRQ_STATUS
        7. 向卡/標簽(VICC)發送EOF(幀結束)
      8. 關閉射頻場
        上述步驟中只有步驟6向卡/標簽(VICC)發送16時隙防沖突的尋卡指令和步驟7.7向卡/標簽(VICC)發送EOF(幀結束)是 PN5180 和卡/標簽(VICC)之間的數據交互,其余的步驟都是PN5180 與主機之間通過SPI通信。

      PN5180與主機通信

      PN5180設計了24個主機接口命令,涉及讀寫寄存器、讀寫EEPROM、寫數據到發送緩沖區,從接收緩沖區讀數據,加載RF配置到寄存器,開啟關閉射頻場。包含44個寄存器,它們控制著PN5180處理器的行為。每個寄存器占4個字節。主機處理器可以通過4個不同的命令改變寄存器的值:write_registerwrite_register_and_maskwrite_register_or_maskwrite_register_multiple
      以下是本文中用到的主機接口命令說明:

      write_register

      這個命令將一個32位的值寫入配置寄存器。

      負載 長度 值/描述
      命令編碼 1 0x00
      參數 1 寄存器地址
      參數 4 寄存器內容
      WRITE_REGISTER_OR_MASK

      該命令使用邏輯或操作修改寄存器的內容。先讀取寄存器的內容,并使用提供的掩碼執行邏輯或操作,然后把修改后的內容寫回寄存器。

      負載 長度 值/描述
      命令編碼 1 0x01
      參數 1 寄存器地址
      參數 4 邏輯或操作的掩碼
      WRITE_REGISTER_AND_MASK

      該命令使用邏輯與操作修改寄存器的內容。先讀取寄存器的內容,并使用提供的掩碼執行邏輯與操作,然后把修改后的內容寫回寄存器。

      負載 長度 值/描述
      命令編碼 1 0x02
      參數 1 寄存器地址
      參數 4 邏輯與操作的掩碼
      LOAD_RF_CONFIG

      該命令用于將射頻配置從EEPROM加載到配置寄存器中。

      負載 長度 值/描述
      命令編碼 1 0x11
      參數 1 發送器配置的值
      寫入的數據 1 接收機配置的值
      RF_ON

      該命令打開內部射頻場。

      負載 長度 值/描述
      命令編碼 1 0x16
      參數 1 1,根據 ISO/IEC 18092 禁用沖突避免
      RF_OFF

      該命令關閉內部射頻場

      負載 長度 值/描述
      命令編碼 1 0x17
      參數 1 虛字節

      PN5180和卡/標簽(VICC)數據交互

      PN5180和卡/標簽(VICC)數據交互本質上也是主機發送命令給 PN5180 模塊,然后 PN5180 把數據寫入緩沖區,接著射頻傳輸給卡/標簽(VICC),卡/標簽(VICC)響應后通過射頻傳出給 PN5180 模塊的接收緩沖區,主機發送命令讀取緩沖區數據。

      SEND_DATA

      該命令將數據寫入射頻傳輸緩沖區,開始射頻傳輸。

      負載 長度 值/描述
      命令編碼 1 0x09
      參數 1 最后一個字節的有效位數
      寫入的數據 1~260 最大長度為260的數組

      最后一個字節的有效位數為0表示最后一字節所有的bit都被傳輸,1~7表示要傳輸的最后一個字節內的位數。

      READ_DATA

      從VICC成功接收數據后,該命令從射頻接收緩沖區讀取數據。

      負載 長度 值/描述
      命令編碼 1 0x0A
      參數 1 0x00
      讀取的數據 1~508 最大長度為508的數組

      代碼實現輪詢ISO15693卡

      PN5180 和卡/標簽(VICC)之間的數據交互都是遵循RFID基礎——ISO15693標簽存儲結構及訪問控制命令說明中的命令。只需用代碼實現 PN5180 的主機接口指令以及ISO15693的訪問控制命令即可。首先Fork dotnet/iot版本庫,然后在 Pn5180.cs中加入以下監聽 ISO15693 協議卡的代碼:

      /// <summary>
      /// Listen to 15693 cards with 16 slots
      /// </summary>
      /// <param name="transmitter">The transmitter configuration, should be compatible with 15693 card</param>
      /// <param name="receiver">The receiver configuration, should be compatible with 15693 card</param>
      /// <param name="cards">The 15693 cards once detected</param>
      /// <param name="timeoutPollingMilliseconds">The time to poll the card in milliseconds. Card detection will stop once the detection time will be over</param>
      /// <returns>True if a 15693 card has been detected</returns>
      public bool ListenToCardIso15693(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver,
      #if NET5_0_OR_GREATER
      [NotNullWhen(true)]
      #endif
      out IList<Data26_53kbps>? cards, int timeoutPollingMilliseconds)
      {
      	cards = new List<Data26_53kbps>();
      	var ret = LoadRadioFrequencyConfiguration(transmitter, receiver);
      	// Switch on the radio frequence field and check it
      	ret &= SetRadioFrequency(true);
      
      	Span<byte> inventoryResponse = stackalloc byte[10];
      	Span<byte> dsfid = stackalloc byte[1];
      	Span<byte> uid = stackalloc byte[8];
      
      	int numBytes = 0;
      
      	DateTime dtTimeout = DateTime.Now.AddMilliseconds(timeoutPollingMilliseconds);
      
      	try
      	{
      		// Clears all interrupt
      		SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 });
      		// Sets the PN5180 into IDLE state
      		SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF });
      		// Activates TRANSCEIVE routine
      		SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 });
      		// Sends an inventory command with 16 slots
      		ret = SendDataToCard(new byte[] { 0x06, 0x01, 0x00 });
      		if (dtTimeout < DateTime.Now)
      		{
      			return false;
      		}
      
      		for (byte slotCounter = 0; slotCounter < 16; slotCounter++)
      		{
      			(numBytes, _) = GetNumberOfBytesReceivedAndValidBits();
      			if (numBytes > 0)
      			{
      				ret &= ReadDataFromCard(inventoryResponse, inventoryResponse.Length);
      				if (ret)
      				{
      					cards.Add(new Data26_53kbps(slotCounter, 0, 0, inventoryResponse[1], inventoryResponse.Slice(2, 8).ToArray()));
      				}
      			}
      
      			// Send only EOF (End of Frame) without data at the next RF communication
      			SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.TX_CONFIG, new byte[] { 0x3F, 0xFB, 0xFF, 0xFF });
      			// Sets the PN5180 into IDLE state
      			SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF });
      			// Activates TRANSCEIVE routine
      			SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 });
      			// Clears the interrupt register IRQ_STATUS
      			SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 });
      			// Send EOF
      			SendDataToCard(new Span<byte> { });
      		}
      
      		if (cards.Count > 0)
      		{
      			return true;
      		}
      		else
      		{
      			return false;
      		}
      	}
      	catch (TimeoutException)
      	{
      		return false;
      	}
      }
      

      需要注意的是,尋卡指令SendDataToCard(new byte[] { 0x06, 0x01, 0x00 })發送的數據只有請求標志、命令、掩碼長度,并沒有CRC校驗碼,我推測是 PN5180 m模塊內部進行了CRC校驗,目前并沒有找到相關資料證實這個猜測。同樣,用 PN5180 讀寫標簽數據塊以及其他訪問控制指令也不需要CRC校驗碼。

      讀寫ISO15693協議卡

      由于支持 ISO15693 協議的讀寫器不只是 PN5180 ,因此把對 ISO15693 協議卡的具體讀寫操作放在 PN5180 的實現類中不太合適。這里定義了一個 IcodeCard 的類型,該類實現了 ISO15693 協議中常用的命令,并在構造函數中注入 RFID 讀寫器。執行指定操作時,調用 RFID 讀寫器的 Transceive 方法傳輸請求指令并接收響應進行處理。以下是主要代碼:

      public class IcodeCard
      {
      	public IcodeCard(CardTransceiver rfid, byte target)
      	{
      		_rfid = rfid;
      		Target = target;
      		_logger = this.GetCurrentClassLogger();
      	}
      	
      	/// <summary>
      	/// Run the last setup command. In case of reading bytes, they are automatically pushed into the Data property
      	/// </summary>
      	/// <returns>-1 if the process fails otherwise the number of bytes read</returns>
      	private int RunIcodeCardCommand()
      	{
      		byte[] requestData = Serialize();
      		byte[] dataOut = new byte[_responseSize];
      
      		var ret = _rfid.Transceive(Target, requestData, dataOut.AsSpan(), NfcProtocol.Iso15693);
      		_logger.LogDebug($"{nameof(RunIcodeCardCommand)}: {_command}, Target: {Target}, Data: {BitConverter.ToString(requestData)}, Success: {ret}, Dataout: {BitConverter.ToString(dataOut)}");
      		if (ret > 0)
      		{
      			Data = dataOut;
      		}
      
      		return ret;
      	}
      
      	/// <summary>
      	/// Serialize request data according to the protocol
      	/// Request format: SOF, Flags, Command code, Parameters (opt.), Data (opt.), CRC16, EOF
      	/// </summary>
      	/// <returns>The serialized bits</returns>
      	private byte[] Serialize()
      	{
      		byte[]? ser = null;
      		switch (_command)
      		{
      			case IcodeCardCommand.ReadSingleBlock:
      				// Flags(1 byte), Command code(1 byte), UID(8 byte), BlockNumber(1 byte)
      				ser = new byte[2 + 8 + 1];
      				ser[0] = 0x22;
      				ser[1] = (byte)_command;
      				ser[10] = BlockNumber;
      				Uid?.CopyTo(ser, 2);
      				_responseSize = 5;
      				return ser;
      			// 略去代碼....
      			default:
      				return new byte[0];
      		}
      	}
      
      	/// <summary>
      	/// Perform a read and place the result into the 4 bytes Data property on a specific block
      	/// </summary>
      	/// <param name="block">The block number to read</param>
      	/// <returns>True if success. This only means whether the communication between VCD and VICC is successful or not </returns>
      	public bool ReadSingleBlock(byte block)
      	{
      	    BlockNumber = block;
      	    _command = IcodeCardCommand.ReadSingleBlock;
      	    var ret = RunIcodeCardCommand();
      	    return ret >= 0;
      	}
      }
      

      只需以下代碼就可以監聽射頻場中的 ISO15693 類型的卡并進行讀寫操作:

      if (pn5180.ListenToCardIso15693(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26, out IList<Data26_53kbps>? cards, 20000))
      {
          pn5180.ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26);
          foreach (Data26_53kbps card in cards)
          {
              Console.WriteLine($"Target number: {card.TargetNumber}");
              Console.WriteLine($"UID: {BitConverter.ToString(card.NfcId)}");
              Console.WriteLine($"DSFID: {card.Dsfid}");
              if (card.NfcId[6] == 0x04)
      {
          IcodeCard icodeCard = new IcodeCard(pn5180, card.TargetNumber)
          {
              Afi = 1,
              Dsfid= 1,
              Uid = card.NfcId,
              Capacity = IcodeCardCapacity.IcodeSlix,
          };
          
          for (byte i = 0; i < 28; i++)
          {
              if (icodeCard.ReadSingleBlock(i))
              {
                  Console.WriteLine($"Block {i} data is :{BitConverter.ToString(icodeCard.Data)}");
              }
              else
              {
                  icodeCard.Data = new byte[] { };
              }
          }
      }
      else
      {
          Console.WriteLine("Only Icode cards are supported");
      }
          }
      }
      

      最后,就是把程序部署到 Raspberry pi 上,具體操作可以參照 Raspberry pi 上部署調試.Net的IoT程序

      posted @ 2025-02-20 11:02  czwy  閱讀(1101)  評論(5)    收藏  舉報
      主站蜘蛛池模板: 美女自卫慰黄网站| 亚洲国产区男人本色| 久青草国产在视频在线观看| 国产激情无码一区二区三区| 韩国午夜理伦三级| 大尺度国产一区二区视频| AV免费网址在线观看| 亚洲国内精品一区二区| 和黑人中出一区二区三区| 激情综合色综合久久综合| 日本免费精品| 亚洲中文字幕无码中字| 成人一区二区三区在线午夜| 中文字幕亚洲人妻一区| 少妇精品导航| 国产精品熟女亚洲av麻豆| 日本不卡片一区二区三区| 人人玩人人添人人澡超碰| 国产精品任我爽爆在线播放6080| 亚洲av熟女国产一二三| 日本在线视频网站www色下载| 久久久久免费看成人影片| 亚洲精品久久麻豆蜜桃| 《特殊的精油按摩》3| 精品免费看国产一区二区| 婷婷色综合成人成人网小说| 污污网站18禁在线永久免费观看| 精品亚洲女同一区二区| 欧美日本精品一本二本三区| 精品一区二区三区在线成人| 欧美日韩国产亚洲沙发| 中国女人熟毛茸茸A毛片| 国产精品视频午夜福利| 最新亚洲人成网站在线观看| 不卡在线一区二区三区视频| 92精品国产自产在线观看481页| 免费观看全黄做爰大片国产| 99久久成人亚洲精品观看| 久久精品无码免费不卡| 亚洲va中文字幕无码久久| 国产一二三五区不在卡|