地铁刷卡、上下班门禁打卡、高铁进站刷身份证、Apple Pay 购物……这些日常使用场景,都使用了 NFC 非接触式识别和互联技术,极大地方便了人们的生活。
不过,目前市面上常见的NFC无线读卡器都使用 WIFI 或蓝牙进行数据传输,功耗较高、且传输距离有限。而如果采用 LoRaWAN® 传输,则可以解决上述问题。事实上,基于 LoRaWAN® 的 NFC 读卡器优点突出:
1、LoRaWAN®的传输距离远、接收灵敏度高、且功耗低
2、采用LoRaWAN® 无线传输的读卡器安装部署方便
3、能使用电池供电、可持续使用半年以上。
本文将通过瑞科慧联的模块化开发套件 WisBlock 教大家快速搭建一个支持LoRaWAN® 的无线读卡器,让这个读卡器读到电子标签数据时,可以自动将数据上传到 LoRaWAN® 服务器上。WisBlock 其实是一个物联网解决方案设计生态系统,由可拼接的模块和易于使用的软件工具组成,可加快物联网产品生产周期、缩短上市时间。
搭建 LoRaWAN® NFC 读卡器概述
本次搭建使用的硬件是瑞科慧联(RAK)的 WisBlock 套件,MCU 选择的是RAK4631 WisBlock Core 模块,该模块采用强大的 Nordic nRF52840 MCU,可以支持蓝牙 5.0(蓝牙低能耗),以及 Semtech 最新的 LoRa® 收发器 SX1262,支持 LoRa® 和蓝牙两种通信模式。
该 NFC 还选择了 WisBlock 套件的 RAK13600 NFC 读卡器模组,它使用的是 PN532 芯片,可以支持 ISO/ICE 14443A/B 卡类型的读写,而且还搭配了一个蜂鸣器模组 RAK18001,当 NFC 刷卡有效时,蜂鸣器会发出响声提醒。
对了,该 NFC 读卡器的搭建还会使用到瑞科慧联(RAK)的低代码开发平台 RUI3,它为 WisBlock 提供包含传感器驱动接口、无线发送接口等丰富的 API接口函数,这样我们只需要写少量的应用代码就可以完成此产品搭建了。
硬件电路搭建
硬件准备
首先我们需要准备 RAK4631 模块、RAK5005-O 底板、RAK13600 NFC 读卡器、RAK18001 蜂鸣器、两张 ISO 14443B 卡、一根 LoRa® 天线、一根 NFC天线、一个 Unify 外壳、一根蓝牙天线(安装在外壳内)。
RAK4631 模块、RAK19007 底板、RAK13600 NFC 读卡器等硬件准备
硬件组装
把 RAK4631 模块扣在 CPU SLOT 的位置,RAK13600 扣在 IO SLOT 的位置,RAK18001 扣在 SLOT A(或者SLOT B),并且使用螺丝把模组固定。
连接 NFC 天线、LoRa® 天线、蓝牙天线,并安装至外壳中。硬件组装完成之后就可以进行软件设置。
软件环境搭建
打开 Arduino IDE,进入“文件 > 首选项”
打开 Arduino IDE
单击图中图标,修改“附加开发板管理器网址”选项,将 RAK4631-R WisBlock Core 添加中 Arduino 开发板管理器中。
在 Arduino IDE上修改“附加开发板管理器网址”
现在复制这个 URL https://raw.githubusercontent.com/RAKWireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless.com_rui_index.json 并粘贴至下图所示区域。如果已存在其他链接,将上述链接粘贴至新的一行。完成后,单击“好”。
在Arduino IDE上粘贴复制好的URL
重启 Arduino IDE。 进入“工具 > 开发板:“RAK4631” > 开发板管理器”。
重启Arduino IDE并执行操作
在搜索框中输入“RAK”,窗口会自动出现可用的RAKwireless WisBlock Core Boards,选择“RAKwireless RUI nRF Boards”并安装。
选择并安装 RAKwireless RUI nRF Boards
BSP 安装完成后,根据图中路径选择 RAKwireless WisBlock Core模块。
选择 RAKwireless WisBlock Core 模块
安装使用到的库
现在安装 RAK13600-PN532 库和 Adafruit bus 库:
安装 RAK13600-PN532 库
安装 Adafruit bus 库
代码开发
LoRaWAN® 部分的初始化,此函数可以初始化协议栈的所有参数,入网方式是OTAA,用户需要根据自己的频段,入网参数修改此宏定义,代码中使用的频段是 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 控制,所以要记住在未使用蜂鸣器时,记得关闭输出。
pinMode(BUZZER_CONTROL,OUTPUT); noTone(BUZZER_CONTROL);
NFC 芯片初始化代码,采用 IIC 通信协议,初始化结束后,就可以使用 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 秒循环读取是否有 NFC 卡存在,如果读取 ID 成功,蜂鸣器会响 150 毫秒左右,然后发送卡 ID 到 LoRaWAN® 服务器上。
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"); } }
数据日志
本地串口日志的信息如下所示:
RAK7268 内置 LoRaWAN® 服务器日志:
备注:全部源代码如下所示
/** @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! \r\n"); return; } if (!api.lorawan.appkey.set(node_app_key, 16)) { Serial.printf("LoRaWan OTAA - set application key is incorrect! \r\n"); return; } if (!api.lorawan.deui.set(node_device_eui, 8)) { Serial.printf("LoRaWan OTAA - set device EUI is incorrect! \r\n"); return; } if (!api.lorawan.band.set(OTAA_BAND)) { Serial.printf("LoRaWan OTAA - set band is incorrect! \r\n"); return; } if (!api.lorawan.deviceClass.set(RAK_LORA_CLASS_A)) { Serial.printf("LoRaWan OTAA - set device class is incorrect! \r\n"); 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! \r\n"); return; } if (!api.lorawan.join()) // Join to Gateway { Serial.printf("LoRaWan OTAA - join fail! \r\n"); 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! \r\n"); return; } if (!api.lorawan.rety.set(1)) { Serial.printf("LoRaWan OTAA - set retry times is incorrect! \r\n"); return; } if (!api.lorawan.cfm.set(1)) { Serial.printf("LoRaWan OTAA - set confirm mode is incorrect! \r\n"); return; } /** Check LoRaWan Status*/ Serial.printf("Duty cycle is %s\r\n", api.lorawan.dcs.get()? "ON" : "OFF"); // Check Duty Cycle status Serial.printf("Packet is %s\r\n", 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%02X\r\n", assigned_dev_addr[0], assigned_dev_addr[1], assigned_dev_addr[2], assigned_dev_addr[3]); // Check Device Address Serial.println(""); }