mirror of
https://github.com/m5stack/M5Unit-NFC.git
synced 2026-05-20 11:48:34 -07:00
Add NFC-B(WIP)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for ST25R3916
|
||||
Detect NFC-B PICC
|
||||
*/
|
||||
// *************************************************************
|
||||
// Choose one define symbol to match the unit you are using
|
||||
// *************************************************************
|
||||
#if !defined(USING_UNIT_NFC) && !defined(USING_HACKER_CAP)
|
||||
// For UnitNFC
|
||||
// #define USING_UNIT_NFC
|
||||
// For CapNFC
|
||||
// #define USING_HACKER_CAP
|
||||
#endif
|
||||
#include "main/Detect.cpp"
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for M5Cardputer-ADV with HackerCap
|
||||
Detect NFC-B PICC
|
||||
*/
|
||||
#include <M5Unified.h>
|
||||
#include <M5UnitUnified.h>
|
||||
#include <M5UnitUnifiedNFC.h>
|
||||
#include <M5Utility.h>
|
||||
#include <vector>
|
||||
|
||||
// *************************************************************
|
||||
// Choose one define symbol to match the unit you are using
|
||||
// *************************************************************
|
||||
#if !defined(USING_UNIT_NFC) && !defined(USING_HACKER_CAP)
|
||||
// For UnitNFC
|
||||
// #define USING_UNIT_NFC
|
||||
// For CapNFC
|
||||
// #define USING_HACKER_CAP
|
||||
#endif
|
||||
|
||||
using namespace m5::nfc;
|
||||
using namespace m5::nfc::b;
|
||||
|
||||
namespace {
|
||||
auto& lcd = M5.Display;
|
||||
m5::unit::UnitUnified Units;
|
||||
|
||||
#if defined(USING_UNIT_NFC)
|
||||
#pragma message "Choose UnitNFC"
|
||||
m5::unit::UnitNFC unit{}; // I2C
|
||||
#elif defined(USING_HACKER_CAP)
|
||||
#pragma message "Choose HackerCapNFC"
|
||||
m5::unit::HackerCapNFC unit{}; // HackerCap (SPI)
|
||||
#else
|
||||
#error Choose unit please!
|
||||
#endif
|
||||
m5::unit::nfc::NFCLayerB nfc_b{unit};
|
||||
} // namespace
|
||||
|
||||
void setup()
|
||||
{
|
||||
M5.begin();
|
||||
|
||||
auto cfg = unit.config();
|
||||
cfg.mode = NFC::B;
|
||||
unit.config(cfg);
|
||||
|
||||
#if 0
|
||||
//// M5GFX 0.2.15 NG! with HackerCap
|
||||
auto board = M5.getBoard();
|
||||
if (board != lgfx::board_t::board_M5CardputerADV) {
|
||||
M5_LOGE("This is NOT M5Cardputer-ADV %U/%XH", board, board);
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USING_UNIT_NFC)
|
||||
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
|
||||
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
|
||||
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
|
||||
Wire.end();
|
||||
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
|
||||
|
||||
if (!Units.add(unit, Wire) || !Units.begin()) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.clear(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#elif defined(USING_HACKER_CAP)
|
||||
if (!SPI.bus()) {
|
||||
auto spi_sclk = M5.getPin(m5::pin_name_t::sd_spi_sclk);
|
||||
auto spi_mosi = M5.getPin(m5::pin_name_t::sd_spi_mosi);
|
||||
auto spi_miso = M5.getPin(m5::pin_name_t::sd_spi_miso);
|
||||
M5_LOGI("getPin: %d,%d,%d", spi_sclk, spi_mosi, spi_miso);
|
||||
SPI.begin(spi_sclk, spi_miso, spi_mosi /* SS is shared SD, CC1101, ST25R3916 */);
|
||||
}
|
||||
|
||||
SPISettings settings = {10000000, MSBFIRST, SPI_MODE1};
|
||||
if (!Units.add(unit, SPI, settings) || !Units.begin()) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
M5_LOGI("M5UnitUnified has been begun");
|
||||
M5_LOGI("%s", Units.debugInfo().c_str());
|
||||
|
||||
if (lcd.width() < lcd.height()) {
|
||||
lcd.setRotation(1);
|
||||
}
|
||||
lcd.setFont(&fonts::Font0);
|
||||
lcd.fillScreen(0);
|
||||
lcd.setCursor(0, 0);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
M5.update();
|
||||
Units.update();
|
||||
|
||||
std::vector<PICC> piccs;
|
||||
if (nfc_b.detect(piccs)) {
|
||||
M5.Speaker.tone(3000, 10);
|
||||
lcd.fillScreen(0);
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.printf("%zu PICC\n", piccs.size());
|
||||
M5.Log.printf("%zu PICC\n", piccs.size());
|
||||
uint32_t idx{};
|
||||
for (auto&& u : piccs) {
|
||||
M5.Log.printf("PICC:%s %s\n", u.pupiAsString().c_str(), u.typeAsString().c_str());
|
||||
lcd.printf("[%2u]:PICC:<%s>%s\n", idx, u.pupiAsString().c_str(), u.typeAsString().c_str());
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for ST25R3916
|
||||
Dump NFC-B PICC
|
||||
*/
|
||||
// *************************************************************
|
||||
// Choose one define symbol to match the unit you are using
|
||||
// *************************************************************
|
||||
#if !defined(USING_UNIT_NFC) && !defined(USING_HACKER_CAP)
|
||||
// For UnitNFC
|
||||
// #define USING_UNIT_NFC
|
||||
// For CapNFC
|
||||
// #define USING_HACKER_CAP
|
||||
#endif
|
||||
#include "main/Dump.cpp"
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*
|
||||
Example using M5UnitUnified for ST25R3916
|
||||
Dump NFC-B PICC
|
||||
*/
|
||||
#include <M5Unified.h>
|
||||
#include <M5UnitUnified.h>
|
||||
#include <M5UnitUnifiedNFC.h>
|
||||
#include <M5Utility.h>
|
||||
#include <vector>
|
||||
|
||||
// *************************************************************
|
||||
// Choose one define symbol to match the unit you are using
|
||||
// *************************************************************
|
||||
#if !defined(USING_UNIT_NFC) && !defined(USING_HACKER_CAP)
|
||||
// For UnitNFC
|
||||
// #define USING_UNIT_NFC
|
||||
// For CapNFC
|
||||
// #define USING_HACKER_CAP
|
||||
#endif
|
||||
|
||||
using namespace m5::nfc;
|
||||
using namespace m5::nfc::b;
|
||||
|
||||
namespace {
|
||||
auto& lcd = M5.Display;
|
||||
m5::unit::UnitUnified Units;
|
||||
|
||||
#if defined(USING_UNIT_NFC)
|
||||
#pragma message "Choose UnitNFC"
|
||||
m5::unit::UnitNFC unit{}; // I2C
|
||||
#elif defined(USING_HACKER_CAP)
|
||||
#pragma message "Choose HackerCapNFC"
|
||||
m5::unit::HackerCapNFC unit{}; // HackerCap (SPI)
|
||||
#else
|
||||
#error Choose unit please!
|
||||
#endif
|
||||
m5::unit::nfc::NFCLayerB nfc_b{unit};
|
||||
|
||||
} // namespace
|
||||
|
||||
void setup()
|
||||
{
|
||||
M5.begin();
|
||||
M5.setTouchButtonHeightByRatio(100);
|
||||
|
||||
auto cfg = unit.config();
|
||||
cfg.mode = NFC::B;
|
||||
unit.config(cfg);
|
||||
|
||||
#if 0
|
||||
//// M5GFX 0.2.15 NG! with HackerCap
|
||||
auto board = M5.getBoard();
|
||||
if (board != lgfx::board_t::board_M5CardputerADV) {
|
||||
M5_LOGE("This is NOT M5Cardputer-ADV %U/%XH", board, board);
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USING_UNIT_NFC)
|
||||
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
|
||||
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
|
||||
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
|
||||
Wire.end();
|
||||
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
|
||||
|
||||
if (!Units.add(unit, Wire) || !Units.begin()) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.clear(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#elif defined(USING_HACKER_CAP)
|
||||
if (!SPI.bus()) {
|
||||
auto spi_sclk = M5.getPin(m5::pin_name_t::sd_spi_sclk);
|
||||
auto spi_mosi = M5.getPin(m5::pin_name_t::sd_spi_mosi);
|
||||
auto spi_miso = M5.getPin(m5::pin_name_t::sd_spi_miso);
|
||||
M5_LOGI("getPin: %d,%d,%d", spi_sclk, spi_mosi, spi_miso);
|
||||
SPI.begin(spi_sclk, spi_miso, spi_mosi /* SS is shared SD, CC1101, ST25R3916 */);
|
||||
}
|
||||
|
||||
SPISettings settings = {10000000, MSBFIRST, SPI_MODE1};
|
||||
if (!Units.add(unit, SPI, settings) || !Units.begin()) {
|
||||
M5_LOGE("Failed to begin");
|
||||
lcd.fillScreen(TFT_RED);
|
||||
while (true) {
|
||||
m5::utility::delay(10000);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
M5_LOGI("M5UnitUnified has been begun");
|
||||
M5_LOGI("%s", Units.debugInfo().c_str());
|
||||
|
||||
if (lcd.width() < lcd.height()) {
|
||||
lcd.setRotation(1);
|
||||
}
|
||||
lcd.setFont(&fonts::Font0);
|
||||
lcd.fillScreen(0);
|
||||
lcd.setCursor(0, 0);
|
||||
lcd.printf("Please put the PICC and click BtnA");
|
||||
M5.Log.printf("Please put the PICC and click BtnA\n");
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
M5.update();
|
||||
Units.update();
|
||||
|
||||
if (M5.BtnA.wasClicked()) {
|
||||
lcd.fillRect(0, lcd.fontHeight(), lcd.width(), lcd.height() - lcd.fontHeight());
|
||||
PICC picc{};
|
||||
if (nfc_b.select(picc)) {
|
||||
M5.Speaker.tone(3000, 20);
|
||||
M5.Log.printf("==== Dump %s %s %u bytes ====\n", picc.pupiAsString().c_str(), picc.typeAsString().c_str(),
|
||||
/*picc.totalSize()*/ 0);
|
||||
nfc_b.deactivate();
|
||||
|
||||
#if 0
|
||||
|
||||
if (nfc_b.reactivate(picc)) {
|
||||
M5.Speaker.tone(3000, 20);
|
||||
M5.Log.printf("==== Dump %s %s %u bytes ====\n", picc.pupiAsString().c_str(),
|
||||
picc.typeAsString().c_str(), /*picc.totalSize()*/ 0);
|
||||
// nfc_b.dump();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
M5.Log.printf("PICC NOT exists\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
; For UnitTest and examples (Using M5UnitUnified)
|
||||
;-----------------------------------------------------------------------
|
||||
[platformio]
|
||||
;default_envs = UnitNFC_NFCA_Detect_Core_Arduino_latest, UnitNFC_NFCA_Dump_Core_Arduino_latest, UnitNFC_NFCA_ReadWrite_Core_Arduino_latest, UnitNFC_NFCA_ValueBlock_Core_Arduino_latest, UnitNFC_NFCA_NDEF_Core_Arduino_latest, UnitNFC_NFCF_Detect_Core_Arduino_latest, UnitNFC_NFCF_Dump_Core_Arduino_latest, UnitNFC_NFCF_NDEF_Core_Arduino_latest, UnitNFC_NFCF_Subtract_Core_Arduino_latest, UnitNFC_NFCF_ReadWriteMAC_Core_Arduino_latest, UnitNFC_NFCF_JapanTransportationICCard_Core_Arduino_latest, UnitNFC_NFCV_Detect_CardputerADV_Arduino_latest, UnitNFC_NFCV_Dump_CardputerADV_Arduino_latest, UnitNFC_NFCV_NDEF_Core_Arduino_latest
|
||||
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra -Wreturn-local-addr -Werror=format -Werror=return-local-addr
|
||||
@@ -282,6 +284,12 @@ build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCV/Dump>
|
||||
build_flags = ${option_release.build_flags}
|
||||
-D USING_UNIT_NFC
|
||||
|
||||
[env:UnitNFC_NFCV_NDEF_Core_Arduino_latest]
|
||||
extends=Core, option_release, arduino_latest
|
||||
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCV/NDEF>
|
||||
build_flags = ${option_release.build_flags}
|
||||
-D USING_UNIT_NFC
|
||||
|
||||
|
||||
; --------------------------------
|
||||
; CapHacker
|
||||
@@ -321,6 +329,22 @@ build_flags = ${option_release.build_flags}
|
||||
${CardputerADV.build_flags}
|
||||
-D USING_HACKER_CAP
|
||||
|
||||
; NFC-B
|
||||
[env:CapHacker_NFCB_Detect_CardputerADV_Arduino_latest]
|
||||
extends=CardputerADV, option_release, arduino_latest
|
||||
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCB/Detect>
|
||||
build_flags = ${option_release.build_flags}
|
||||
${CardputerADV.build_flags}
|
||||
-D USING_HACKER_CAP
|
||||
|
||||
[env:CapHacker_NFCB_Dump_CardputerADV_Arduino_latest]
|
||||
extends=CardputerADV, option_release, arduino_latest
|
||||
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCB/Dump>
|
||||
build_flags = ${option_release.build_flags}
|
||||
${CardputerADV.build_flags}
|
||||
-D USING_HACKER_CAP
|
||||
|
||||
|
||||
; NFC-F
|
||||
[env:CapHacker_NFCF_Detect_CardputerADV_Arduino_latest]
|
||||
extends=CardputerADV, option_release, arduino_latest
|
||||
@@ -379,4 +403,11 @@ build_flags = ${option_release.build_flags}
|
||||
${CardputerADV.build_flags}
|
||||
-D USING_HACKER_CAP
|
||||
|
||||
[env:CapHacker_NFCV_NDEF_CardputerADV_Arduino_latest]
|
||||
extends=CardputerADV, option_release, arduino_latest
|
||||
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/NFCV/NDEF>
|
||||
build_flags = ${option_release.build_flags}
|
||||
${CardputerADV.build_flags}
|
||||
-D USING_HACKER_CAP
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "unit/unit_ST25R3916.hpp"
|
||||
|
||||
#include "nfc/layer/nfc_layer_a.hpp"
|
||||
#include "nfc/layer/nfc_layer_b.hpp"
|
||||
#include "nfc/layer/nfc_layer_f.hpp"
|
||||
#include "nfc/layer/nfc_layer_v.hpp"
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file file_system.hpp
|
||||
@brief File system for MIFARE Plus, DESFire / ST25TA
|
||||
*/
|
||||
#ifndef M5_UNIT_UNIFIED_NFC_NFC_A_FILE_SYSTEM_HPP
|
||||
#define M5_UNIT_UNIFIED_NFC_NFC_A_FILE_SYSTEM_HPP
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
namespace a {
|
||||
|
||||
struct FileInfo {
|
||||
uint8_t fileId; // DESFire: FileNo, ST25TA: FileID (上位1byte省略でもOK)
|
||||
uint32_t size; // バイト数
|
||||
uint8_t type; // DESFire: file type enum, ST25TA: NDEF/CC/System enum
|
||||
uint8_t readAccess; // アクセス条件など(DESFire用)
|
||||
uint8_t writeAccess;
|
||||
};
|
||||
|
||||
class FileSystem {
|
||||
public:
|
||||
enum class Category : uint8_t {
|
||||
Unknown,
|
||||
DESFire,
|
||||
St25TA,
|
||||
};
|
||||
|
||||
explicit FileSystem(const Category c) : _category{c}
|
||||
{
|
||||
}
|
||||
virtual ~FileSystem() = default;
|
||||
|
||||
inline Category category() const
|
||||
{
|
||||
return _category;
|
||||
}
|
||||
|
||||
// ルート/アプリケーションの列挙
|
||||
virtual bool listApplications(std::vector<uint32_t>& aids) = 0; // ST25TAは空 or 固定
|
||||
virtual bool selectApplication(const uint32_t aid) = 0; // ST25TAではダミー or 常にtrue
|
||||
|
||||
// ファイル一覧
|
||||
virtual bool listFiles(std::vector<FileInfo>& files) = 0;
|
||||
virtual bool readFile(std::vector<uint8_t> rbuf, const uint8_t fileId, const uint32_t offset,
|
||||
const uint32_t rlen) = 0;
|
||||
virtual bool writeFile(const uint8_t fileId, const uint32_t offset, const uint8_t* wbuf, const uint32_t wlen) = 0;
|
||||
|
||||
private:
|
||||
Category _category{};
|
||||
};
|
||||
|
||||
} // namespace a
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
#endif
|
||||
+3
-2
@@ -115,11 +115,11 @@ constexpr uint8_t user_block_table[][2] = {
|
||||
{4, 39}, // 213 Exclusive 0-3 and last 5 pages
|
||||
{4, 129}, // 215 Exclusive 0-3 and last 5 pages
|
||||
{4, 225}, // 216 Exclusive 0-3 and last 5 pages
|
||||
//
|
||||
// ST25TA
|
||||
{0, 0},
|
||||
{0, 0},
|
||||
{0, 0},
|
||||
//
|
||||
// 14443-4
|
||||
{0, 0},
|
||||
// Plus
|
||||
{1, 126},
|
||||
@@ -151,6 +151,7 @@ constexpr uint16_t user_area_size_table[] = {
|
||||
240, 752, 1504, 3440, // Classic
|
||||
48, 48, 128, 40, 144, // Light
|
||||
144, 48, 144, 208, 144, 504, 888, // NTAG
|
||||
0, 0, 0, // ST25
|
||||
0, //
|
||||
1504, 3440, 752, // Plus
|
||||
0, 0, 0, // Desfire
|
||||
|
||||
+65
-40
@@ -205,6 +205,10 @@ bool is_user_block(const Type t, const uint16_t block);
|
||||
//! @brief Calculate bcc8
|
||||
uint8_t calculate_bcc8(const uint8_t* data, const uint32_t len);
|
||||
|
||||
/*!
|
||||
@struct ATS
|
||||
@brief Answer to request
|
||||
*/
|
||||
struct ATS {
|
||||
union {
|
||||
uint8_t header[5]{};
|
||||
@@ -237,65 +241,71 @@ struct ATS {
|
||||
*/
|
||||
struct PICC {
|
||||
uint8_t _pad{};
|
||||
Type type{}; //!< PICC type
|
||||
Type type{}; //!< PICC type
|
||||
uint8_t sak{}; //!< The SAK (Select acknowledge) returned from the PICC after successful selection
|
||||
uint8_t size{}; //!< UID size 4, 7 or 10.
|
||||
uint8_t uid[10]{}; //!< uid (Valid up to the value of size)
|
||||
uint16_t atqa{}; //!< ATQA
|
||||
uint16_t blocks{}; //!< Number of the blocks or pages
|
||||
union {
|
||||
uint8_t sub_type{}; //!< uint8_t access
|
||||
SubTypePlus sub_type_plus; //!< For Plus
|
||||
SubTypeDESFire sub_type_desfire; //!< For DESFire
|
||||
}; //!< SubType for Plus/DESFire;
|
||||
uint8_t sak{}; //!< The SAK (Select acknowledge) returned from the PICC after successful selection
|
||||
uint8_t security_level{}; //!< Security level for Plus
|
||||
uint8_t size{}; //!< UID size 4, 7 or 10.
|
||||
uint8_t uid[10]{}; //!< uid (Valid up to the value of size)
|
||||
uint16_t atqa{}; //!< ATQA
|
||||
uint16_t blocks{}; //!< Number of the blocks or pages
|
||||
union {
|
||||
uint8_t security_level{}; //!< Security level for Plus
|
||||
}; //!< Optional information
|
||||
|
||||
//! @brief Valid?
|
||||
inline bool valid() const
|
||||
{
|
||||
return (size == 4 || size == 7 || size == 10) && (type != Type::Unknown) && blocks;
|
||||
}
|
||||
//! @brief Retrieve the last 4 bytes of UID
|
||||
void tail4(uint8_t buf[4]) const
|
||||
{
|
||||
if (buf) {
|
||||
memcpy(buf, uid + size - 4, 4);
|
||||
}
|
||||
}
|
||||
std::string uidAsString() const; //!< @brief Gets the uid string
|
||||
std::string typeAsString() const; //!< @brief Gets the type string
|
||||
|
||||
///@name Type
|
||||
///@{
|
||||
//! @brief Is MIFARE?
|
||||
inline bool isMifare() const
|
||||
{
|
||||
return valid() && is_mifare(type);
|
||||
return is_mifare(type);
|
||||
}
|
||||
//! @brief Is MIFARE classic?
|
||||
inline bool isMifareClassic() const
|
||||
{
|
||||
return valid() && is_mifare_classic(type);
|
||||
return is_mifare_classic(type);
|
||||
}
|
||||
//! @brief Is MIFARE Ultralight series?
|
||||
inline bool isMifareUltralight() const
|
||||
{
|
||||
return valid() && is_mifare_ultralight(type);
|
||||
return is_mifare_ultralight(type);
|
||||
}
|
||||
//! @brief Is MIFARE Plus?
|
||||
inline bool isMifarePlus() const
|
||||
{
|
||||
return valid() && is_mifare_plus(type);
|
||||
return is_mifare_plus(type);
|
||||
}
|
||||
//! @brief Is NTAG?
|
||||
inline bool isNTAG() const
|
||||
{
|
||||
return valid() && is_ntag(type);
|
||||
return is_ntag(type);
|
||||
}
|
||||
inline bool isISO14443_4() const
|
||||
{
|
||||
return is_iso14443_4(type); // Don't check valid(), as there may exist where blocks == 0
|
||||
return is_iso14443_4(type);
|
||||
}
|
||||
///@}
|
||||
|
||||
//! @brief Supports NFC?
|
||||
inline bool supportsNFC() const
|
||||
{
|
||||
return valid() && supports_NFC(type);
|
||||
}
|
||||
//! @brief Can use FAST_READ command?
|
||||
inline bool canFastRead() const
|
||||
{
|
||||
return valid() && has_fast_read(type);
|
||||
}
|
||||
///@name Information
|
||||
///@{
|
||||
//! @brief Total size
|
||||
inline uint16_t totalSize() const
|
||||
{
|
||||
@@ -321,31 +331,36 @@ struct PICC {
|
||||
return valid() ? is_user_block(type, block) : false;
|
||||
}
|
||||
|
||||
//! @brief Retrieve the last 4 bytes
|
||||
void tail4(uint8_t buf[4]) const
|
||||
{
|
||||
if (buf) {
|
||||
memcpy(buf, uid + size - 4, 4);
|
||||
}
|
||||
}
|
||||
|
||||
//! @brief NFC ForumTag
|
||||
inline NFCForumTag nfcForumTagType() const
|
||||
{
|
||||
return get_nfc_forum_tag_type(type);
|
||||
}
|
||||
///@}
|
||||
|
||||
//! @brief Gets the uid string
|
||||
std::string uidAsString() const;
|
||||
//! @brief Gets the type string
|
||||
std::string typeAsString() const;
|
||||
//! @brief clear
|
||||
void clear()
|
||||
///@name Capability
|
||||
///@{
|
||||
//! @brief Supports NFC?
|
||||
inline bool supportsNFC() const
|
||||
{
|
||||
size = sak = blocks = 0;
|
||||
type = Type::Unknown;
|
||||
std::memset(uid, 0x00, sizeof(uid));
|
||||
return valid() && supports_NFC(type);
|
||||
}
|
||||
//! @brief Can use FAST_READ command?
|
||||
inline bool canFastRead() const
|
||||
{
|
||||
return valid() && has_fast_read(type);
|
||||
}
|
||||
#if 0
|
||||
FileSystem::Type get_file_system() const
|
||||
{
|
||||
return (isMifareClassic() || isMifarePlus())
|
||||
? FileSystem::Type::Sector
|
||||
: ((isMifareUltralight() || isNTAG())
|
||||
? FileSystem::Page
|
||||
: ((isMifareDESFire() || isST25TA()) ? FileSystem : File:FileSystem : None));
|
||||
}
|
||||
#endif
|
||||
///@}
|
||||
};
|
||||
|
||||
//! @brief Equal?
|
||||
@@ -400,6 +415,16 @@ enum class Command : uint8_t {
|
||||
READ_SIG = 0x3C, //!< NTAG 21x Read NXP ECC signature
|
||||
WRITE_SIG = 0xA9, //!< NTAG 210u Write custom signature
|
||||
LOCK_SIG = 0xAC, //!< NTAG 210u Lock/Unlock signature
|
||||
// ISO/IEC 7816
|
||||
SELECT = 0xA4, //!< Select Application or file
|
||||
READ_BINARY = 0xB0, //!< Read binary data
|
||||
UPDATE_BINARY = 0xD6, //!< Write binary data
|
||||
READ_RECORDS = 0xB2, //!< Read recode data
|
||||
APPEND_RECORD = 0xE2, //!< pend recored data
|
||||
GET_CHALLENGE = 0x84,
|
||||
INTERNAL_AUTHENTICATE = 0x88,
|
||||
EXTERNAL_AUTHENTICATE = 0x82,
|
||||
|
||||
};
|
||||
|
||||
///@name Timeout
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfcb.hpp
|
||||
@brief NFC-B definitions
|
||||
*/
|
||||
#include "nfcb.hpp"
|
||||
#include <M5Utility.hpp>
|
||||
|
||||
using namespace m5::nfc;
|
||||
using namespace m5::nfc::b;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char name_unknown[] = "Unknown";
|
||||
constexpr char name_unclassified[] = "Unclassified";
|
||||
|
||||
constexpr const char* name_table[] = {
|
||||
name_unknown, //
|
||||
name_unclassified,
|
||||
};
|
||||
|
||||
constexpr uint16_t frame_length_table[9] = {
|
||||
16, 24, 32, 40, 48, 64, 96, 128, 256,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
namespace b {
|
||||
|
||||
uint16_t maximum_frame_length(const uint8_t protocol[3])
|
||||
{
|
||||
if (protocol && ((protocol[1] >> 4) <= 8)) {
|
||||
return frame_length_table[protocol[1] >> 4];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
std::string PICC::pupiAsString() const
|
||||
{
|
||||
char buf[2 * 4 + 1]{};
|
||||
uint8_t left{};
|
||||
for (uint8_t i = 0; i < 4; ++i) {
|
||||
left += snprintf(buf + left, 3, "%02X", this->pupi[i]);
|
||||
}
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
std::string PICC::typeAsString() const
|
||||
{
|
||||
auto idx = m5::stl::to_underlying(this->type);
|
||||
return std::string((idx <= m5::stl::size(name_table)) ? name_table[idx] : name_unknown);
|
||||
}
|
||||
|
||||
} // namespace b
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
+148
-6
@@ -10,7 +10,10 @@
|
||||
#ifndef M5_UNIT_UNIFIED_NFC_NFC_B_NFCB_HPP
|
||||
#define M5_UNIT_UNIFIED_NFC_NFC_B_NFCB_HPP
|
||||
|
||||
#include "nfc/nfc.hpp"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
namespace m5 {
|
||||
namespace nfc {
|
||||
@@ -25,18 +28,157 @@ namespace b {
|
||||
@brief Type of the PICC
|
||||
*/
|
||||
enum class Type : uint8_t {
|
||||
Unknown, //!< Unknown type
|
||||
Unknown, //!< Unknown type
|
||||
Unclassified, //!< Unclassified
|
||||
};
|
||||
|
||||
/*!
|
||||
@struct PUPI
|
||||
@brief The PUPI(Pseudo-Unique PICC Identifier) of the PICC
|
||||
@enum Require
|
||||
@brief Number of slots required in the request/wakeup
|
||||
*/
|
||||
struct PUPI {
|
||||
//! @brief uid data
|
||||
uint8_t uid[8]{};
|
||||
enum class Require : uint8_t {
|
||||
Slot1,
|
||||
Slot2,
|
||||
Slot4,
|
||||
Slot8,
|
||||
Slot16,
|
||||
};
|
||||
|
||||
constexpr uint8_t ATQB_LENGTH{11}; //!< ATQB length pupi(4) + application(4) + protocol(3)
|
||||
|
||||
///@name Communication speed bits
|
||||
///@{
|
||||
constexpr uint8_t COMMUNICATION_SAME_SPEED{0x80};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_106K{0X00};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_212K_FROM_PICC{0X10};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_424K_FROM_PICC{0X20};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_847K_FROM_PICC{0X40};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_212K_TO_PICC{0X01};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_424K_TO_PICC{0X02};
|
||||
constexpr uint8_t COMMUNICATION_SPPED_847K_TO_PICC{0X04};
|
||||
///@}
|
||||
|
||||
///@name Frame option bits
|
||||
///@{
|
||||
const uint8_t FRAME_OPTION_NAD{0x02};
|
||||
const uint8_t FRAME_OPTION_CID{0x01};
|
||||
///@}
|
||||
|
||||
//! @breif Get maxumum frame length from protocl bytes
|
||||
uint16_t maximum_frame_length(const uint8_t protocol[3]);
|
||||
|
||||
inline uint8_t maximum_frame_length_bits(const uint8_t protocol[3])
|
||||
{
|
||||
return protocol ? protocol[1] >> 4 : 0x0F;
|
||||
}
|
||||
|
||||
//! @brief Supports ISO/IEC 14443-4?
|
||||
inline bool supports_iso14443_4(const uint8_t protocol[3])
|
||||
{
|
||||
return protocol ? ((protocol[1] & 0x0F) & 0x01) : false;
|
||||
}
|
||||
|
||||
//! @brief Gets the frame option bits
|
||||
inline uint8_t get_frame_option(const uint8_t protocol[3])
|
||||
{
|
||||
return protocol ? (protocol[2] & 0x03) : 0x00;
|
||||
}
|
||||
|
||||
/*!
|
||||
@struct PICC
|
||||
@brief PICC for NFC-B
|
||||
*/
|
||||
struct PICC {
|
||||
uint8_t uid[8]{};
|
||||
union {
|
||||
uint8_t atqb[ATQB_LENGTH]{}; //!< ATQB
|
||||
struct {
|
||||
uint8_t pupi[4]; //!< Pseudo-Unique PICC Identifier
|
||||
uint8_t application[4]; //!< Application Data
|
||||
uint8_t protocol[3]; //!< Protocol information
|
||||
// uint8_t cid[1];
|
||||
} __attribute__((packed));
|
||||
} __attribute__((packed));
|
||||
Type type{Type::Unknown}; //!< Type
|
||||
uint8_t cid{}; //!< CID;
|
||||
uint8_t option{};
|
||||
|
||||
//! @brief Valid?
|
||||
inline bool valid() const
|
||||
{
|
||||
return isISO14443_4();
|
||||
}
|
||||
|
||||
std::string pupiAsString() const; //!< @brief Gets the pupi string
|
||||
std::string typeAsString() const; //!< @brief Gets the type string
|
||||
|
||||
///@name Type
|
||||
///@{
|
||||
//! @brief ISO14443-4?
|
||||
inline bool isISO14443_4() const
|
||||
{
|
||||
return supports_iso14443_4(protocol);
|
||||
}
|
||||
///@}
|
||||
|
||||
///@name Information
|
||||
///@{
|
||||
inline bool supportsNAD() const
|
||||
{
|
||||
return get_frame_option(protocol) & FRAME_OPTION_NAD;
|
||||
}
|
||||
inline bool supportsCID() const
|
||||
{
|
||||
return get_frame_option(protocol) & FRAME_OPTION_CID;
|
||||
}
|
||||
inline uint16_t maximumFrmeLength() const
|
||||
{
|
||||
return maximum_frame_length(protocol);
|
||||
}
|
||||
inline uint8_t maximumFrmeLengthBits() const
|
||||
{
|
||||
return maximum_frame_length_bits(protocol);
|
||||
}
|
||||
inline uint8_t communicationSpeed() const
|
||||
{
|
||||
return protocol[0];
|
||||
}
|
||||
///@}
|
||||
};
|
||||
|
||||
//! @brief Equal?
|
||||
inline bool operator==(const PICC& a, const PICC& b)
|
||||
{
|
||||
return std::memcmp(a.atqb, b.atqb, sizeof(a.atqb)) == 0;
|
||||
}
|
||||
//! @brief Not equal?
|
||||
inline bool operator!=(const PICC& a, const PICC& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/*!
|
||||
@enum Command
|
||||
@brief ISO/IEC 14443 Type B command (Layer 3 / activation)
|
||||
*/
|
||||
enum class Command : uint8_t {
|
||||
// ISO/IEC 14443B
|
||||
REQ_WUPB = 0x05, ///< Request/Wakeup Type B
|
||||
ATTRIB = 0x1D, ///< Attribute (activate ISO-DEP)
|
||||
HLTB = 0x50, ///< Halt Type B (rarely used)
|
||||
// ISO/IEC 14443-4
|
||||
DESELECT = 0xC2, //!< DESELECT
|
||||
DESELECT_WITH_CID = 0xCA, //!< DESELECT with CID
|
||||
};
|
||||
|
||||
///@name Timeout
|
||||
///@{
|
||||
constexpr uint32_t TIMEOUT_REQ_WUP_B{5};
|
||||
constexpr uint32_t TIMEOUT_ATTRIB{5};
|
||||
constexpr uint32_t TIMEOUT_HLTB{5};
|
||||
constexpr uint32_t TIMEOUT_DESELECT{5};
|
||||
///@}
|
||||
|
||||
} // namespace b
|
||||
} // namespace nfc
|
||||
} // namespace m5
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_a.cpp
|
||||
@brief Common layer for NFC-A related units
|
||||
@brief Common layer for NFC-A
|
||||
*/
|
||||
#include "nfc_layer_a.hpp"
|
||||
#include "nfc/ndef/ndef.hpp"
|
||||
@@ -135,7 +135,7 @@ bool NFCLayerA::detect(std::vector<PICC>& piccs, const uint32_t timeout_ms)
|
||||
|
||||
bool NFCLayerA::select(m5::nfc::a::PICC& picc)
|
||||
{
|
||||
_activePICC.clear();
|
||||
_activePICC = PICC{};
|
||||
if (_impl->select(picc)) {
|
||||
_activePICC = picc;
|
||||
return true;
|
||||
@@ -145,7 +145,7 @@ bool NFCLayerA::select(m5::nfc::a::PICC& picc)
|
||||
|
||||
bool NFCLayerA::activate(const PICC& picc)
|
||||
{
|
||||
_activePICC.clear(); // (*1)
|
||||
_activePICC = PICC{};
|
||||
if (_impl->activate(picc)) {
|
||||
_activePICC = picc;
|
||||
return true;
|
||||
@@ -155,7 +155,6 @@ bool NFCLayerA::activate(const PICC& picc)
|
||||
|
||||
bool NFCLayerA::reactivate(const PICC& picc)
|
||||
{
|
||||
// If arg referrence is the same as _activePICC, it will cause an error, so it must be a separate instance (*1)
|
||||
PICC tmp = picc;
|
||||
uint16_t discard{};
|
||||
if (deactivate()) {
|
||||
@@ -167,13 +166,12 @@ bool NFCLayerA::reactivate(const PICC& picc)
|
||||
|
||||
bool NFCLayerA::deactivate()
|
||||
{
|
||||
_activePICC = PICC{};
|
||||
if (_activePICC.isISO14443_4()) {
|
||||
if (_impl->deactivate(true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_activePICC.clear();
|
||||
return _impl->deactivate(false);
|
||||
}
|
||||
|
||||
@@ -184,6 +182,10 @@ bool NFCLayerA::identify(m5::nfc::a::PICC& picc)
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool NFCLayerA::getVersion(uint8_t rx, uint16_t& rx_len)
|
||||
{
|
||||
}
|
||||
|
||||
bool NFCLayerA::identify_picc(m5::nfc::a::PICC& picc)
|
||||
{
|
||||
Type type{};
|
||||
@@ -201,11 +203,13 @@ bool NFCLayerA::identify_picc(m5::nfc::a::PICC& picc)
|
||||
}
|
||||
|
||||
// GetVersion(L4)
|
||||
uint8_t ver[8]{};
|
||||
uint8_t ver[16]{};
|
||||
if (_impl->mifare_get_version_L4(ver)) {
|
||||
type = version4_to_type(picc.sub_type, ver);
|
||||
// m5::utility::log::dump(ver, 16, false);
|
||||
} else {
|
||||
// Check historical bytes
|
||||
// m5::utility::log::dump(ats.historical.data(), ats.historical_len, false);
|
||||
type = historical_bytes_to_type_sak20(picc.sub_type, ats.historical.data(), ats.historical_len, picc.atqa);
|
||||
}
|
||||
// If it's still unclassify at this stage, read more in SystemFile
|
||||
@@ -519,8 +523,10 @@ bool NFCLayerA::write_using_write16(const uint8_t addr, const uint8_t* tx, const
|
||||
bool NFCLayerA::dump(const Key& mkey)
|
||||
{
|
||||
if (_activePICC.valid()) {
|
||||
if (_activePICC.isMifareClassic()) {
|
||||
return dump_sector_structure(_activePICC, mkey);
|
||||
if (_activePICC.isMifareClassic() || _activePICC.isMifarePlus()) {
|
||||
Key keytmp{};
|
||||
// return dump_sector_structure(_activePICC, mkey);
|
||||
return dump_sector_structure(_activePICC, keytmp);
|
||||
} else if (_activePICC.supportsNFC()) {
|
||||
return dump_page_structure(_activePICC.blocks);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_a.hpp
|
||||
@brief Common layer for NFC-A related units
|
||||
@brief Common layer for NFC-A
|
||||
|
||||
@note Glossary
|
||||
- PCD: Proximity Coupling Device (reader)
|
||||
@@ -441,6 +441,13 @@ public:
|
||||
bool ndefWrite(const std::vector<m5::nfc::ndef::TLV>& tlvs);
|
||||
///@}
|
||||
|
||||
|
||||
|
||||
bool getVersion(uint8_t rx, uint16_t& rx_len);
|
||||
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
virtual bool read(uint8_t* rx, uint16_t& rx_len, const uint8_t saddr) override;
|
||||
virtual bool write(const uint8_t saddr, const uint8_t* tx, const uint16_t tx_len) override;
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_b.hpp
|
||||
@brief Common layer for NFC-B
|
||||
|
||||
@note Glossary
|
||||
- PCD: Proximity Coupling Device (reader)
|
||||
- PICC: Proximity Integrated Circuit Card (card/tag, target device)
|
||||
|
||||
@note In NFC Forum (NDEF) context, a PICC is often called a "Tag"
|
||||
*/
|
||||
#include "nfc_layer_b.hpp"
|
||||
#include "nfc/ndef/ndef.hpp"
|
||||
#include "nfc/ndef/ndef_tlv.hpp"
|
||||
#include <inttypes.h>
|
||||
#include <M5Utility.hpp>
|
||||
#include <algorithm>
|
||||
#include <esp_random.h>
|
||||
|
||||
using namespace m5::nfc::b;
|
||||
using namespace m5::nfc::ndef;
|
||||
|
||||
namespace {
|
||||
|
||||
inline bool exists_picc(const std::vector<PICC>& v, const PICC& picc)
|
||||
{
|
||||
return std::find_if(v.begin(), v.end(), [&picc](const PICC& p) { //
|
||||
return memcmp(p.pupi, picc.pupi, 4) == 0;
|
||||
}) != v.end();
|
||||
}
|
||||
|
||||
constexpr uint8_t make_request_param(const bool wakeup, const Require slots)
|
||||
{
|
||||
return (wakeup ? 0x08 : 0x00) | (m5::stl::to_underlying(slots) & 0x07);
|
||||
}
|
||||
|
||||
constexpr uint8_t required_slots(const Require slots)
|
||||
{
|
||||
return 1U << m5::stl::to_underlying(slots);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace m5 {
|
||||
namespace unit {
|
||||
namespace nfc {
|
||||
|
||||
bool NFCLayerB::detect(m5::nfc::b::PICC& picc, const uint8_t afi, const uint32_t timeout_ms)
|
||||
{
|
||||
std::vector<PICC> piccs{};
|
||||
if (detect(piccs, afi, timeout_ms)) {
|
||||
picc = piccs.front();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NFCLayerB::detect(std::vector<m5::nfc::b::PICC>& piccs, const uint8_t afi, const uint32_t timeout_ms)
|
||||
{
|
||||
piccs.clear();
|
||||
|
||||
auto timeout_at = m5::utility::millis() + timeout_ms;
|
||||
do {
|
||||
uint8_t rx[ATQB_LENGTH * 16]{};
|
||||
uint16_t rx_len = sizeof(rx);
|
||||
if (!request(rx, rx_len, afi, Require::Slot1)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t picc_num = rx_len / ATQB_LENGTH;
|
||||
for (uint_fast8_t i = 0; i < picc_num; ++i) {
|
||||
PICC picc{};
|
||||
memcpy(picc.atqb, rx + i * ATQB_LENGTH, ATQB_LENGTH);
|
||||
|
||||
M5_LIB_LOGV("Detected: %s", picc.pupiAsString().c_str());
|
||||
if (!exists_picc(piccs, picc)) {
|
||||
hlt(picc.pupi); // If you don't perform hlt, it will be detected again
|
||||
picc.type = Type::Unclassified;
|
||||
piccs.emplace_back(picc);
|
||||
}
|
||||
}
|
||||
if (piccs.size() >= 16) {
|
||||
break;
|
||||
}
|
||||
} while (m5::utility::millis() <= timeout_at);
|
||||
|
||||
return !piccs.empty();
|
||||
}
|
||||
|
||||
bool NFCLayerB::select(m5::nfc::b::PICC& picc)
|
||||
{
|
||||
// Wakeup for READY
|
||||
uint16_t len = ATQB_LENGTH;
|
||||
if (!wakeup(picc.atqb, len)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ATTRIB
|
||||
uint8_t cmd[1 + 4 + 1 + 1 + 1 + 1] = {m5::stl::to_underlying(Command::ATTRIB)}; // without option
|
||||
memcpy(cmd + 1, picc.pupi, 4);
|
||||
cmd[5] = 0x00; // PARAM1
|
||||
cmd[6] = picc.maximumFrmeLengthBits(); // PARAM2 | com speed
|
||||
cmd[7] = picc.protocol[1] & 0x0F; // PARAM 3protocol type
|
||||
cmd[8] = 0x00; // PARAM 4
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
frame.assign(cmd, cmd + sizeof(cmd));
|
||||
|
||||
uint8_t rx[128]{};
|
||||
uint16_t rx_len = sizeof(rx);
|
||||
if (!_impl->transceive(rx, rx_len, frame.data(), frame.size(), TIMEOUT_ATTRIB) || !rx_len) {
|
||||
M5_LIB_LOGE("Failed to select");
|
||||
return false;
|
||||
}
|
||||
//m5::utility::log::dump(rx, rx_len, false);
|
||||
// rx[0] MBLI(7:4) CID(3:0)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFCLayerB::hlt(const uint8_t pupi[4])
|
||||
{
|
||||
if (pupi) {
|
||||
uint8_t cmd[1 + 4] = {m5::stl::to_underlying(Command::HLTB)};
|
||||
memcpy(cmd + 1, pupi, 4);
|
||||
uint8_t rx[1]{};
|
||||
uint16_t rx_len = sizeof(rx);
|
||||
if (!_impl->transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_HLTB) || rx_len < 1) {
|
||||
M5_LIB_LOGE("Failed to hlt %02X%02X%02X%02X", cmd[1], cmd[2], cmd[3], cmd[4]);
|
||||
return false;
|
||||
}
|
||||
return rx[0] == 0x00;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NFCLayerB::deselect(const uint8_t pupi[4], const uint8_t cid)
|
||||
{
|
||||
uint8_t cmd[2] = {m5::stl::to_underlying(cid != 0xFF ? Command::DESELECT_WITH_CID : Command::DESELECT)};
|
||||
uint16_t cmd_len = 1 + (cid != 0xFF);
|
||||
if (cid != 0xFF) {
|
||||
cmd[1] = cid;
|
||||
}
|
||||
uint8_t rx[2]{};
|
||||
uint16_t rx_len = cmd_len;
|
||||
|
||||
if (!_impl->transceive(rx, rx_len, cmd, cmd_len, TIMEOUT_DESELECT) || rx_len < cmd_len) {
|
||||
M5_LIB_LOGE("Failed to deselecte %02X:%02X", cmd[0], cmd[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(cmd, rx, cmd_len)) {
|
||||
m5::utility::log::dump(cmd, cmd_len, false);
|
||||
m5::utility::log::dump(rx, cmd_len, false);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NFCLayerB::deactivate()
|
||||
{
|
||||
PICC tmp = _activePICC;
|
||||
_activePICC = PICC{};
|
||||
return deselect(tmp.pupi) || hlt(tmp.pupi);
|
||||
}
|
||||
|
||||
//
|
||||
bool NFCLayerB::request_wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi, const Require slots,
|
||||
const bool wakeup)
|
||||
{
|
||||
if (!atqb || atqb_len < ATQB_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t cmd[] = {m5::stl::to_underlying(Command::REQ_WUPB), afi, make_request_param(wakeup, slots)};
|
||||
uint8_t rx[1 + ATQB_LENGTH + 2]{};
|
||||
uint16_t rx_len = sizeof(rx);
|
||||
uint32_t offset{};
|
||||
m5::utility::CRC16 crc16(0XFFFF, 0x1021, true, true, 0XFFFF);
|
||||
const auto max_slots = required_slots(slots);
|
||||
const auto max_rx_len = atqb_len;
|
||||
atqb_len = 0;
|
||||
|
||||
// Ignore non-responsive slots and proceed to the next one.
|
||||
if (_impl->transceive(rx, rx_len, cmd, sizeof(cmd), TIMEOUT_REQ_WUP_B, true) && rx_len == sizeof(rx) &&
|
||||
rx[0] == 0x50) {
|
||||
// Occur collision if CRC error
|
||||
const uint16_t crc = crc16.range(rx, ATQB_LENGTH + 1);
|
||||
if (crc == ((uint16_t)rx[13] << 8 | rx[12])) {
|
||||
memcpy(atqb, rx + 1, ATQB_LENGTH);
|
||||
atqb_len += ATQB_LENGTH;
|
||||
}
|
||||
// hlt(rx + 1); // If you don't perform hlt, it will be detected again
|
||||
}
|
||||
|
||||
uint8_t slot_marker[1]{};
|
||||
for (uint_fast8_t i = 1; (offset + ATQB_LENGTH) < max_rx_len && i < max_slots; ++i) {
|
||||
rx_len = sizeof(rx);
|
||||
slot_marker[0] = ((uint8_t)i << 4) | 0x05;
|
||||
// Ignore non-responsive slots and proceed to the next one.
|
||||
if (!_impl->transceive(rx, rx_len, slot_marker, sizeof(slot_marker), TIMEOUT_REQ_WUP_B, true) ||
|
||||
rx[0] != 0x50 || rx_len < sizeof(rx)) {
|
||||
continue;
|
||||
}
|
||||
// Occur collision if CRC error
|
||||
const uint16_t crc = crc16.range(rx, 1 + ATQB_LENGTH);
|
||||
if (crc == ((uint16_t)rx[13] << 8 | rx[12])) {
|
||||
memcpy(atqb + atqb_len, rx + 1, ATQB_LENGTH);
|
||||
atqb_len += ATQB_LENGTH;
|
||||
}
|
||||
// hlt(rx + 1); // If you don't perform hlt, it will be detected again
|
||||
}
|
||||
return atqb_len > 0;
|
||||
}
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_b.hpp
|
||||
@brief Common layer for NFC-B
|
||||
|
||||
@note Glossary
|
||||
- PCD: Proximity Coupling Device (reader)
|
||||
- PICC: Proximity Integrated Circuit Card (card/tag, target device)
|
||||
|
||||
@note In NFC Forum (NDEF) context, a PICC is often called a "Tag"
|
||||
*/
|
||||
#ifndef M5_UNIT_NFC_NFC_LAYER_NFC_LAYER_B_HPP
|
||||
#define M5_UNIT_NFC_NFC_LAYER_NFC_LAYER_B_HPP
|
||||
|
||||
#include "nfc/b/nfcb.hpp"
|
||||
#include "ndef_layer.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace m5 {
|
||||
|
||||
namespace unit {
|
||||
class UnitST25R3916;
|
||||
class CapST25R3916;
|
||||
|
||||
namespace nfc {
|
||||
|
||||
/*!
|
||||
@class NFCLayerB
|
||||
@brief Common interface layer for each chip of the NFC-V reader
|
||||
*/
|
||||
class NFCLayerB : public m5::nfc::NFCLayerInterface {
|
||||
public:
|
||||
struct Adapter;
|
||||
explicit NFCLayerB(UnitST25R3916& u);
|
||||
explicit NFCLayerB(CapST25R3916& u);
|
||||
|
||||
/*!
|
||||
@brief Is the specified PICC currently active?
|
||||
@param picc PICC to check
|
||||
@return True if this PICC is the one currently selected (ACTIVE state)
|
||||
*/
|
||||
inline bool isActive(const m5::nfc::b::PICC& picc) const
|
||||
{
|
||||
return _activePICC.valid() && _activePICC == picc;
|
||||
}
|
||||
/*!
|
||||
@brief Retrieve the currently activated PICC
|
||||
@return Active PICC
|
||||
@note Returns an empty PICC if no PICC is selected (no ACTIVE state)
|
||||
*/
|
||||
const m5::nfc::b::PICC& activatedPICC() const
|
||||
{
|
||||
return _activePICC;
|
||||
}
|
||||
|
||||
///@name Detection and activation
|
||||
///@{
|
||||
/*!
|
||||
@brief Send REQB to discover a PICC in IDLE
|
||||
@param[out] atqb ATQB received from PICC (at atqb_len)
|
||||
@param[in/out] in:atqb length out:actual received length
|
||||
@param afi Application Family Identifier (0x00 all)
|
||||
@param slots Number of slots required
|
||||
@return True if successful
|
||||
@note The ATQB per one of the PICC is 11 bytes
|
||||
@post PICC transitions: IDLE -> READY on successful response
|
||||
*/
|
||||
inline bool request(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi = 0x00,
|
||||
const m5::nfc::b::Require slots = m5::nfc::b::Require::Slot1)
|
||||
{
|
||||
return request_wakeup(atqb, atqb_len, afi, slots, false);
|
||||
}
|
||||
/*!
|
||||
@brief Send WUPB to wake a PICC from IDLE or HALT
|
||||
@param[out] atqb ATQB received from PICC (at least atqb_len)
|
||||
@param[in/out] in:atqb length out:actual received length
|
||||
@param afi Application Family Identifier (0x00 all)
|
||||
@param slots Number of slots required
|
||||
@return True if successful
|
||||
@note The ATQB per one of the PICC is 11 bytes
|
||||
@post PICC transitions: IDLE/HALT -> READY on successful response
|
||||
*/
|
||||
inline bool wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi = 0x00,
|
||||
const m5::nfc::b::Require slots = m5::nfc::b::Require::Slot1)
|
||||
{
|
||||
return request_wakeup(atqb, atqb_len, afi, slots, true);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Detect single idle PICC
|
||||
@param[out] picc Detected PICC
|
||||
@param afi Application Family Identifier (0x00 all)
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
@note The detected PICC is typically put into HALT during enumeration to allow discovering others
|
||||
*/
|
||||
bool detect(m5::nfc::b::PICC& picc, const uint8_t afi = 0x00, const uint32_t timeout_ms = 100U);
|
||||
/*!
|
||||
@brief Detect idle PICCs
|
||||
@param[out] piccs Detected PICC PICCs (one per activated PICC candidate)
|
||||
@param afi Application Family Identifier (0x00 all)
|
||||
@param timeout_ms Polling time budget in milliseconds
|
||||
@return True if detected
|
||||
@note The detected PICC is typically put into HALT during enumeration to allow discovering others
|
||||
*/
|
||||
bool detect(std::vector<m5::nfc::b::PICC>& piccs, const uint8_t afi = 0x00, const uint32_t timeout_ms = 1000U);
|
||||
|
||||
/*!
|
||||
*/
|
||||
bool select(m5::nfc::b::PICC& picc);
|
||||
#if 0
|
||||
bool activate(m5::nfc::b::PICC& picc);
|
||||
bool reactivate(const m5::nfc::b::PICC& picc);
|
||||
inline bool reactivate()
|
||||
{
|
||||
return reactivate(_activePICC);
|
||||
}
|
||||
#endif
|
||||
|
||||
///@}
|
||||
|
||||
///@name For activated PICC
|
||||
///@{
|
||||
bool hlt(const uint8_t pupi[4]);
|
||||
bool deselect(const uint8_t pupi[4], const uint8_t cid = 0xFF);
|
||||
bool deactivate();
|
||||
///@}
|
||||
|
||||
protected:
|
||||
bool request_wakeup(uint8_t* atqb, uint16_t& atqb_len, const uint8_t afi, const m5::nfc::b::Require slots,
|
||||
const bool wakeup);
|
||||
|
||||
virtual bool read(uint8_t* rx, uint16_t& rx_len, const uint8_t saddr) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual bool write(const uint8_t saddr, const uint8_t* tx, const uint16_t tx_len) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
inline virtual uint16_t first_user_block() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
inline virtual uint16_t last_user_block() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
inline virtual uint16_t user_area_size() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
inline virtual uint16_t unit_size_read() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
inline virtual uint16_t unit_size_write() const override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
m5::nfc::b::PICC _activePICC{};
|
||||
|
||||
private:
|
||||
std::unique_ptr<Adapter> _impl;
|
||||
m5::nfc::ndef::NDEFLayer _ndef;
|
||||
};
|
||||
|
||||
///@cond
|
||||
// Impl for units
|
||||
struct NFCLayerB::Adapter {
|
||||
virtual ~Adapter() = default;
|
||||
|
||||
virtual bool transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len,
|
||||
const uint32_t timeout_ms, const bool rx_crc = false) = 0;
|
||||
virtual bool transmit(const uint8_t* tx, const uint16_t tx_len, const uint32_t timeout_ms,
|
||||
const bool rx_crc = false) = 0;
|
||||
virtual bool receive(uint8_t* rx, uint16_t& rx_len, const uint32_t timeout_ms) = 0;
|
||||
};
|
||||
///@endcond
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace unit
|
||||
} // namespace m5
|
||||
|
||||
#endif
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_f.cpp
|
||||
@brief Common layer for NFC-F related units
|
||||
@brief Common layer for NFC-F
|
||||
*/
|
||||
#include "nfc_layer_f.hpp"
|
||||
#include "nfc/ndef/ndef.hpp"
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_f.hpp
|
||||
@brief Common layer for NFC-F related units
|
||||
@brief Common layer for NFC-F
|
||||
|
||||
@note Glossary
|
||||
- PCD: Proximity Coupling Device (reader)
|
||||
- PICC: Proximity Integrated Circuit Card (card/tag, target device)
|
||||
- IDLE/READY/ACTIVE/HALT: ISO14443-3 state names
|
||||
|
||||
@note In NFC Forum (NDEF) context, a PICC is often called a "Tag"
|
||||
*/
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_v.cpp
|
||||
@brief Common layer for NFC-V related units
|
||||
@brief Common layer for NFC-V
|
||||
*/
|
||||
|
||||
#include "nfc_layer_v.hpp"
|
||||
#include "nfc/ndef/ndef.hpp"
|
||||
#include "nfc/ndef/ndef_tlv.hpp"
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
*/
|
||||
/*!
|
||||
@file nfc_layer_v.hpp
|
||||
@brief Common layer for NFC-V related units
|
||||
@brief Common layer for NFC-V
|
||||
|
||||
@note Glossary
|
||||
- PCD: Proximity Coupling Device (reader)
|
||||
- PICC: Proximity Integrated Circuit Card (card/tag, target device)
|
||||
- IDLE/READY/ACTIVE/HALT: ISO14443-3 state names
|
||||
|
||||
@note In NFC Forum (NDEF) context, a PICC is often called a "Tag"
|
||||
*/
|
||||
|
||||
@@ -74,7 +74,7 @@ bool AdapterST25R3916ForA::select(m5::nfc::a::PICC& picc)
|
||||
{
|
||||
uint8_t lv{1}; // Cascade level 1-3
|
||||
bool completed{};
|
||||
picc.clear();
|
||||
picc = PICC{};
|
||||
do {
|
||||
if (!_u.nfcaSelectWithAnticollision(completed, picc, lv)) {
|
||||
return false;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user