Add NFC-B(WIP)

This commit is contained in:
GOB
2025-12-15 20:54:16 +09:00
parent 6e34c91ee5
commit 76909dcb2d
27 changed files with 1403 additions and 93 deletions
@@ -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;
}
}
}
+19
View File
@@ -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");
}
}
}
+31
View File
@@ -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
+1
View File
@@ -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"
+62
View File
@@ -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
View File
@@ -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
View File
@@ -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
+63
View File
@@ -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
View File
@@ -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
+15 -9
View File
@@ -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);
}
+8 -1
View File
@@ -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;
+220
View File
@@ -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
+192
View File
@@ -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
+1 -1
View File
@@ -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"
+1 -2
View File
@@ -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"
*/
+1 -2
View File
@@ -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"
+1 -2
View File
@@ -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"
*/
+1 -1
View File
@@ -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