地鐵刷卡、上下班門禁打卡、高鐵進(jìn)站刷身份證、Apple Pay 購物……這些日常使用場景,都使用了 NFC 非接觸式識別和互聯(lián)技術(shù),極大地方便了人們的生活。
不過,目前市面上常見的NFC無線讀卡器都使用 WIFI 或藍(lán)牙進(jìn)行數(shù)據(jù)傳輸,功耗較高、且傳輸距離有限。而如果采用 LoRaWAN® 傳輸,則可以解決上述問題。事實(shí)上,基于 LoRaWAN® 的 NFC 讀卡器優(yōu)點(diǎn)突出:
1、LoRaWAN®的傳輸距離遠(yuǎn)、接收靈敏度高、且功耗低
2、采用LoRaWAN® 無線傳輸的讀卡器安裝部署方便
3、能使用電池供電、可持續(xù)使用半年以上。
本文將通過瑞科慧聯(lián)的模塊化開發(fā)套件 WisBlock 教大家快速搭建一個支持LoRaWAN® 的無線讀卡器,讓這個讀卡器讀到電子標(biāo)簽數(shù)據(jù)時,可以自動將數(shù)據(jù)上傳到 LoRaWAN® 服務(wù)器上。WisBlock 其實(shí)是一個物聯(lián)網(wǎng)解決方案設(shè)計(jì)生態(tài)系統(tǒng),由可拼接的模塊和易于使用的軟件工具組成,可加快物聯(lián)網(wǎng)產(chǎn)品生產(chǎn)周期、縮短上市時間。
搭建 LoRaWAN® NFC 讀卡器概述
本次搭建使用的硬件是瑞科慧聯(lián)(RAK)的 WisBlock 套件,MCU 選擇的是RAK4631 WisBlock Core 模塊,該模塊采用強(qiáng)大的 Nordic nRF52840 MCU,可以支持藍(lán)牙 5.0(藍(lán)牙低能耗),以及 Semtech 最新的 LoRa® 收發(fā)器 SX1262,支持 LoRa® 和藍(lán)牙兩種通信模式。
該 NFC 還選擇了 WisBlock 套件的 RAK13600 NFC 讀卡器模組,它使用的是 PN532 芯片,可以支持 ISO/ICE 14443A/B 卡類型的讀寫,而且還搭配了一個蜂鳴器模組 RAK18001,當(dāng) NFC 刷卡有效時,蜂鳴器會發(fā)出響聲提醒。
對了,該 NFC 讀卡器的搭建還會使用到瑞科慧聯(lián)(RAK)的低代碼開發(fā)平臺 RUI3,它為 WisBlock 提供包含傳感器驅(qū)動接口、無線發(fā)送接口等豐富的 API接口函數(shù),這樣我們只需要寫少量的應(yīng)用代碼就可以完成此產(chǎn)品搭建了。
硬件電路搭建
硬件準(zhǔn)備
首先我們需要準(zhǔn)備 RAK4631 模塊、RAK5005-O 底板、RAK13600 NFC 讀卡器、RAK18001 蜂鳴器、兩張 ISO 14443B 卡、一根 LoRa® 天線、一根 NFC天線、一個 Unify 外殼、一根藍(lán)牙天線(安裝在外殼內(nèi))。
RAK4631 模塊、RAK19007 底板、RAK13600 NFC 讀卡器等硬件準(zhǔn)備
硬件組裝
把 RAK4631 模塊扣在 CPU SLOT 的位置,RAK13600 扣在 IO SLOT 的位置,RAK18001 扣在 SLOT A(或者SLOT B),并且使用螺絲把模組固定。
連接 NFC 天線、LoRa® 天線、藍(lán)牙天線,并安裝至外殼中。硬件組裝完成之后就可以進(jìn)行軟件設(shè)置。
軟件環(huán)境搭建
在Arduino IDE中添加 RAK4631-R 開發(fā)板
打開 Arduino IDE,進(jìn)入“文件 > 首選項(xiàng)”
打開 Arduino IDE
單擊圖中圖標(biāo),修改“附加開發(fā)板管理器網(wǎng)址”選項(xiàng),將 RAK4631-R WisBlock Core 添加中 Arduino 開發(fā)板管理器中。
在 Arduino IDE上修改“附加開發(fā)板管理器網(wǎng)址”
現(xiàn)在復(fù)制這個 URL https://raw.githubusercontent.com/RAKWireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless.com_rui_index.json 并粘貼至下圖所示區(qū)域。如果已存在其他鏈接,將上述鏈接粘貼至新的一行。完成后,單擊“好”。
在Arduino IDE上粘貼復(fù)制好的URL
重啟 Arduino IDE。 進(jìn)入“工具 > 開發(fā)板:“RAK4631” > 開發(fā)板管理器”。
重啟Arduino IDE并執(zhí)行操作
在搜索框中輸入“RAK”,窗口會自動出現(xiàn)可用的RAKwireless WisBlock Core Boards,選擇“RAKwireless RUI nRF Boards”并安裝。
選擇并安裝 RAKwireless RUI nRF Boards
BSP 安裝完成后,根據(jù)圖中路徑選擇 RAKwireless WisBlock Core模塊。
選擇 RAKwireless WisBlock Core 模塊
安裝使用到的庫
現(xiàn)在安裝 RAK13600-PN532 庫和 Adafruit bus 庫:
安裝 RAK13600-PN532 庫
安裝 Adafruit bus 庫
代碼開發(fā)
LoRaWAN® 部分的初始化,此函數(shù)可以初始化協(xié)議棧的所有參數(shù),入網(wǎng)方式是OTAA,用戶需要根據(jù)自己的頻段,入網(wǎng)參數(shù)修改此宏定義,代碼中使用的頻段是 AS923。
/************************************* LoRaWAN band setting: RAK_REGION_EU433 RAK_REGION_CN470 RAK_REGION_RU864 RAK_REGION_IN865 RAK_REGION_EU868 RAK_REGION_US915 RAK_REGION_AU915 RAK_REGION_KR920 RAK_REGION_AS923 *************************************/ #define OTAA_BAND (RAK_REGION_AS923) #define OTAA_DEVEUI {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} #define OTAA_APPEUI {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} #define OTAA_APPKEY {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} void lora_init()
蜂鳴器采用的是 PWM 控制,所以要記住在未使用蜂鳴器時,記得關(guān)閉輸出。
pinMode(BUZZER_CONTROL,OUTPUT); noTone(BUZZER_CONTROL);
NFC 芯片初始化代碼,采用 IIC 通信協(xié)議,初始化結(jié)束后,就可以使用 NFC的刷卡功能了。
nfc.begin(); uint32_t versiondata = nfc.getFirmwareVersion(); if (! versiondata) { Serial.print("Didn't find PN53x board"); while (1); // halt } // Got ok data, print it out! Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); // Set the max number of retry attempts to read from a card // This prevents us from waiting forever for a card, which is // the default behaviour of the PN532. nfc.setPassiveActivationRetries(0xFF); //configure board to read RFID tags nfc.SAMConfig(); Serial.println("Waiting for an ISO14443A card");
每間隔 1 秒循環(huán)讀取是否有 NFC 卡存在,如果讀取 ID 成功,蜂鳴器會響 150 毫秒左右,然后發(fā)送卡 ID 到 LoRaWAN® 服務(wù)器上。
void loop(void) { boolean success; uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID uint8_t uidLength; // Length of the UID (4 or 7 bytes dep ending on ISO14443A card type) // Wait for an ISO14443B type cards (Mifare, etc.). When one is found // 'uid' will be populated with the UID, and uidLength will indicate // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength); if (success) { tone(BUZZER_CONTROL,4000); delay(150); noTone(BUZZER_CONTROL); Serial.println("Found a card!"); Serial.print("UID Length: "); Serial.print(uidLength, DEC); Serial.println(" byte s"); Serial.print("UID Value: "); for (uint8_t i = 0; i < uidLength; i++) { Serial.print(" 0x"); Serial.print(uid[i], HEX); } Serial.println(""); digitalWrite(ledPin1, HIGH); // LED turn on when input pin value is HIGH delay(150); digitalWrite(ledPin1, LOW); // /** Send the data package */ if (api.lorawan.send(uidLength, (uint8_t *) & uid, 2, true, 1)) { Serial.println("Sending is requested"); } else { Serial.println("Sending failed"); } // Wait 1 second before continuing delay(1000); } else { // PN532 probably timed out waiting for a card Serial.println("Timed out waiting for a card"); } }
數(shù)據(jù)日志
本地串口日志的信息如下所示:
RAK7268 內(nèi)置 LoRaWAN® 服務(wù)器日志:
備注:全部源代碼如下所示
/** @file iso14443a_uid.ino @author rakwireless.com @brief This example will attempt to connect to an ISO14443A card and read card UID @version 0.1 @date 2021-10-14 @copyright Copyright (c) 2021 **/ /**************************************************************************/ #include #include #include // Click here to get the library: http://librarymanager/All#RAK13600-PN532 /************************************* LoRaWAN band setting: RAK_REGION_EU433 RAK_REGION_CN470 RAK_REGION_RU864 RAK_REGION_IN865 RAK_REGION_EU868 RAK_REGION_US915 RAK_REGION_AU915 RAK_REGION_KR920 RAK_REGION_AS923 *************************************/ #define OTAA_BAND (RAK_REGION_AS923) #define OTAA_DEVEUI {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} #define OTAA_APPEUI {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} #define OTAA_APPKEY {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88} // If using the breakout or shield with I2C, define just the pins connected #define PN532_IRQ (WB_IO6) #define PN532_RESET (WB_IO5) // Not connected by default on the NFC Shield #define BUZZER_CONTROL WB_IO1 uint8_t ledPin1 = LED_GREEN; uint8_t ledPin2 = LED_BLUE; // Or use this line for a breakout or shield with an I2C connection: NFC_PN532 nfc(PN532_IRQ, PN532_RESET); void lora_init(); void setup(void) { Serial.begin(115200); pinMode(WB_IO2, OUTPUT); digitalWrite(WB_IO2, HIGH); pinMode(BUZZER_CONTROL,OUTPUT); noTone(BUZZER_CONTROL); // initialize the LED pin as an output pinMode(ledPin1, OUTPUT); pinMode(ledPin2, OUTPUT); lora_init(); delay(300); while (!Serial) delay(10); Serial.println("Hello!"); nfc.begin(); uint32_t versiondata = nfc.getFirmwareVersion(); if (! versiondata) { Serial.print("Didn't find PN53x board"); while (1); // halt } // Got ok data, print it out! Serial.print("Found chip PN5"); Serial.println((versiondata >> 24) & 0xFF, HEX); Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); // Set the max number of retry attempts to read from a card // This prevents us from waiting forever for a card, which is // the default behaviour of the PN532. nfc.setPassiveActivationRetries(0xFF); //configure board to read RFID tags nfc.SAMConfig(); Serial.println("Waiting for an ISO14443A card"); } void loop(void) { boolean success; uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID uint8_t uidLength; // Length of the UID (4 or 7 bytes depending on ISO14443A card type) // Wait for an ISO14443B type cards (Mifare, etc.). When one is found // 'uid' will be populated with the UID, and uidLength will indicate // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight) success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength); if (success) { tone(BUZZER_CONTROL,4000); delay(150); noTone(BUZZER_CONTROL); Serial.println("Found a card!"); Serial.print("UID Length: "); Serial.print(uidLength, DEC); Serial.println(" bytes"); Serial.print("UID Value: "); for (uint8_t i = 0; i < uidLength; i++) { Serial.print(" 0x"); Serial.print(uid[i], HEX); } Serial.println(""); digitalWrite(ledPin1, HIGH); // LED turn on when input pin value is HIGH delay(150); digitalWrite(ledPin1, LOW); // /** Send the data package */ if (api.lorawan.send(uidLength, (uint8_t *) & uid, 2, true, 1)) { Serial.println("Sending is requested"); } else { Serial.println("Sending failed"); } // Wait 1 second before continuing delay(1000); } else { // PN532 probably timed out waiting for a card Serial.println("Timed out waiting for a card"); } } void lora_init() { // OTAA Device EUI MSB first uint8_t node_device_eui[8] = OTAA_DEVEUI; // OTAA Application EUI MSB first uint8_t node_app_eui[8] = OTAA_APPEUI; // OTAA Application Key MSB first uint8_t node_app_key[16] = OTAA_APPKEY; if (!api.lorawan.appeui.set(node_app_eui, 8)) { Serial.printf("LoRaWan OTAA - set application EUI is incorrect! rn"); return; } if (!api.lorawan.appkey.set(node_app_key, 16)) { Serial.printf("LoRaWan OTAA - set application key is incorrect! rn"); return; } if (!api.lorawan.deui.set(node_device_eui, 8)) { Serial.printf("LoRaWan OTAA - set device EUI is incorrect! rn"); return; } if (!api.lorawan.band.set(OTAA_BAND)) { Serial.printf("LoRaWan OTAA - set band is incorrect! rn"); return; } if (!api.lorawan.deviceClass.set(RAK_LORA_CLASS_A)) { Serial.printf("LoRaWan OTAA - set device class is incorrect! rn"); return; } if (!api.lorawan.njm.set(RAK_LORA_OTAA)) // Set the network join mode to OTAA { Serial. printf("LoRaWan OTAA - set network join mode is incorrect! rn"); return; } if (!api.lorawan.join()) // Join to Gateway { Serial.printf("LoRaWan OTAA - join fail! rn"); return; } /** Wait for Join success */ while (api.lorawan.njs.get() == 0) { Serial.print("Wait for LoRaWAN join..."); api.lorawan.join(); delay(10000); } if (!api.lorawan.adr.set(true)) { Serial.printf ("LoRaWan OTAA - set adaptive data rate is incorrect! rn"); return; } if (!api.lorawan.rety.set(1)) { Serial.printf("LoRaWan OTAA - set retry times is incorrect! rn"); return; } if (!api.lorawan.cfm.set(1)) { Serial.printf("LoRaWan OTAA - set confirm mode is incorrect! rn"); return; } /** Check LoRaWan Status*/ Serial.printf("Duty cycle is %srn", api.lorawan.dcs.get()? "ON" : "OFF"); // Check Duty Cycle status Serial.printf("Packet is %srn", api.lorawan.cfm.get()? "CONFIRMED" : "UNCONFIRMED"); // Check Confirm status uint8_t assigned_dev_addr[4] = { 0 }; api.lorawan.daddr.get(assigned_dev_addr, 4); Serial.printf("Device Address is %02X%02X%02X%02Xrn", assigned_dev_addr[0], assigned_dev_addr[1], assigned_dev_addr[2], assigned_dev_addr[3]); // Check Device Address Serial.println(""); }