2026-01-05 11:16:56 +09:00
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: MIT
|
|
|
|
|
*/
|
|
|
|
|
/*
|
|
|
|
|
Example using M5UnitUnified for ST25R3916
|
|
|
|
|
NFC-F Emulation mode
|
|
|
|
|
*/
|
|
|
|
|
#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
|
|
|
|
|
// *************************************************************
|
2026-04-13 13:37:59 +09:00
|
|
|
#if !defined(USING_UNIT_NFC) && !defined(USING_CAP_CC1101)
|
2026-01-05 11:16:56 +09:00
|
|
|
// For UnitNFC
|
|
|
|
|
// #define USING_UNIT_NFC
|
|
|
|
|
// For CapNFC
|
2026-04-13 13:37:59 +09:00
|
|
|
// #define USING_CAP_CC1101
|
2026-01-05 11:16:56 +09:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
using namespace m5::nfc;
|
|
|
|
|
using namespace m5::nfc::f;
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
auto& lcd = M5.Display;
|
|
|
|
|
m5::unit::UnitUnified Units;
|
|
|
|
|
|
|
|
|
|
#if defined(USING_UNIT_NFC)
|
|
|
|
|
#pragma message "Choose UnitNFC"
|
|
|
|
|
m5::unit::UnitNFC unit{}; // I2C
|
2026-04-13 13:37:59 +09:00
|
|
|
#elif defined(USING_CAP_CC1101)
|
|
|
|
|
#pragma message "Choose CapCC1101NFC"
|
|
|
|
|
m5::unit::CapCC1101NFC unit{}; // CapCC1101 (SPI)
|
2026-01-05 11:16:56 +09:00
|
|
|
#else
|
|
|
|
|
#error Choose unit please!
|
|
|
|
|
#endif
|
|
|
|
|
m5::nfc::EmulationLayerF emu_f{unit};
|
|
|
|
|
|
|
|
|
|
PICC picc{};
|
|
|
|
|
|
|
|
|
|
constexpr Type type{Type::FeliCaLiteS};
|
2026-05-14 13:42:43 +09:00
|
|
|
// constexpr uint8_t IDm[8] = {0x01, 0x2E, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; // See also SONY specification
|
2026-01-05 11:16:56 +09:00
|
|
|
// documents
|
|
|
|
|
constexpr uint8_t IDm[8] = {0x01, 0x2E, 0x50, 0xE5, 0x3C, 0x4B, 0x4F, 0x29};
|
2026-05-14 13:42:43 +09:00
|
|
|
constexpr uint8_t PMm[8] = {0x00, 0xF1, 0x00, 0x00, 0x00, 0x01, 0x43, 0x00}; // See also SONY specification documents
|
2026-01-05 11:16:56 +09:00
|
|
|
uint8_t picc_memory[] = {
|
|
|
|
|
0x10, 0x04, 0x01, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x58, 0x00, 0x7B, // S_PAD0
|
|
|
|
|
0x91, 0x01, 0x0D, 0x55, 0x04, 0x6D, 0x35, 0x73, 0x74, 0x61, 0x63, 0x6B, 0x2E, 0x63, 0x6F, 0x6D, // 1
|
|
|
|
|
0x2F, 0x11, 0x01, 0x10, 0x54, 0x02, 0x65, 0x6E, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x4D, 0x35, // 2
|
|
|
|
|
0x53, 0x74, 0x61, 0x63, 0x6B, 0x11, 0x01, 0x1A, 0x54, 0x02, 0x6A, 0x61, 0xE3, 0x81, 0x93, 0xE3, // 3
|
|
|
|
|
0x82, 0x93, 0xE3, 0x81, 0xAB, 0xE3, 0x81, 0xA1, 0xE3, 0x81, 0xAF, 0x20, 0x4D, 0x35, 0x53, 0x74, // 4
|
|
|
|
|
0x61, 0x63, 0x6B, 0x51, 0x01, 0x11, 0x54, 0x02, 0x7A, 0x68, 0xE4, 0xBD, 0xA0, 0xE5, 0xA5, 0xBD, // 5
|
|
|
|
|
0x20, 0x4D, 0x35, 0x53, 0x74, 0x61, 0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // A
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // C
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D
|
|
|
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // REG
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // RC 0x80
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // MAC
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ID
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // D_ID
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SER_C
|
|
|
|
|
0x88, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // SYS_C
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CKV
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CK
|
|
|
|
|
0xFF, 0xFF, 0xFF, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // MC
|
|
|
|
|
0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // WCNT 0x90
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // MAC_A
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // STATE
|
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CRC_CHRCK 0xA0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void embed_idm_pmm(uint8_t* mem, const PICC& picc)
|
|
|
|
|
{
|
|
|
|
|
memcpy(mem + 17 * 16, picc.idm, 8); // ID
|
|
|
|
|
memcpy(mem + 18 * 16, picc.idm, 8); // D_ID
|
|
|
|
|
memcpy(mem + 18 * 16 + 8, picc.pmm, 8); // D_ID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr uint16_t color_table[] = {
|
|
|
|
|
// None, Off, Idle, Ready, Active, Halt };
|
|
|
|
|
TFT_BLACK, TFT_RED, TFT_BLUE, TFT_YELLOW, TFT_GREEN, TFT_MAGENTA};
|
|
|
|
|
constexpr const char* state_table[] = {"-", "O", "I", "R", "A", "H"};
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void setup()
|
|
|
|
|
{
|
|
|
|
|
M5.begin();
|
|
|
|
|
M5.setTouchButtonHeightByRatio(100);
|
|
|
|
|
|
|
|
|
|
// Emulation settings
|
|
|
|
|
auto cfg = unit.config();
|
|
|
|
|
cfg.emulation = true;
|
|
|
|
|
cfg.mode = NFC::F;
|
|
|
|
|
unit.config(cfg);
|
|
|
|
|
|
|
|
|
|
#if defined(USING_UNIT_NFC)
|
2026-04-06 19:48:03 +09:00
|
|
|
auto board = M5.getBoard();
|
|
|
|
|
bool unit_ready{};
|
|
|
|
|
// NessoN1: SoftwareI2C too slow for NFC RF timing -> use port_a (Wire) via else branch
|
|
|
|
|
if (board == m5::board_t::board_M5NanoC6) {
|
|
|
|
|
M5_LOGI("Using M5.Ex_I2C");
|
|
|
|
|
unit_ready = Units.add(unit, M5.Ex_I2C) && Units.begin();
|
|
|
|
|
} else {
|
|
|
|
|
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);
|
|
|
|
|
unit_ready = Units.add(unit, Wire) && Units.begin();
|
|
|
|
|
}
|
|
|
|
|
if (!unit_ready) {
|
2026-01-05 11:16:56 +09:00
|
|
|
M5_LOGE("Failed to begin");
|
2026-04-06 19:48:03 +09:00
|
|
|
lcd.fillScreen(TFT_RED);
|
2026-01-05 11:16:56 +09:00
|
|
|
while (true) {
|
|
|
|
|
m5::utility::delay(10000);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-13 13:37:59 +09:00
|
|
|
#elif defined(USING_CAP_CC1101)
|
2026-01-05 11:16:56 +09:00
|
|
|
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
|
2026-04-06 19:48:03 +09:00
|
|
|
M5_LOGI("M5UnitUnified initialized");
|
2026-01-05 11:16:56 +09:00
|
|
|
M5_LOGI("%s", Units.debugInfo().c_str());
|
|
|
|
|
|
2026-04-06 19:48:03 +09:00
|
|
|
if (lcd.height() > lcd.width()) {
|
2026-01-05 11:16:56 +09:00
|
|
|
lcd.setRotation(1);
|
|
|
|
|
}
|
|
|
|
|
lcd.setFont(&fonts::Font2);
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
lcd.startWrite();
|
|
|
|
|
lcd.fillScreen(TFT_RED);
|
|
|
|
|
if (picc.emulate(type, IDm, PMm)) {
|
|
|
|
|
embed_idm_pmm(picc_memory, picc);
|
|
|
|
|
if (emu_f.begin(picc, picc_memory, sizeof(picc_memory))) {
|
|
|
|
|
lcd.fillScreen(TFT_DARKGREEN);
|
|
|
|
|
lcd.setCursor(0, 16);
|
|
|
|
|
const auto& e_picc = emu_f.emulatePICC();
|
|
|
|
|
M5.Log.printf("Emulation:%s %s:%s SC:%02X\n", e_picc.typeAsString().c_str(), e_picc.idmAsString().c_str(),
|
|
|
|
|
e_picc.pmmAsString().c_str(), e_picc.emulation_sc);
|
|
|
|
|
lcd.printf("Emulation:%s\nIDm:%s\nPMm:%s\nSC:%02X\n", e_picc.typeAsString().c_str(),
|
|
|
|
|
e_picc.idmAsString().c_str(), e_picc.pmmAsString().c_str(), e_picc.emulation_sc);
|
|
|
|
|
} else {
|
|
|
|
|
M5_LOGE("Start");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
M5_LOGE("PICC");
|
|
|
|
|
}
|
|
|
|
|
lcd.fillRect(0, 0, 32, 16, color_table[0]);
|
|
|
|
|
lcd.drawString(state_table[0], 0, 0);
|
|
|
|
|
lcd.endWrite();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void loop()
|
|
|
|
|
{
|
|
|
|
|
M5.update();
|
|
|
|
|
Units.update();
|
|
|
|
|
emu_f.update(); // Need call in loop
|
|
|
|
|
|
|
|
|
|
static EmulationLayerF::State latest{};
|
|
|
|
|
auto state = emu_f.state();
|
|
|
|
|
if (latest != state) {
|
|
|
|
|
latest = state;
|
|
|
|
|
lcd.startWrite();
|
|
|
|
|
lcd.fillRect(0, 0, 32, 16, color_table[m5::stl::to_underlying(state)]);
|
|
|
|
|
lcd.drawString(state_table[m5::stl::to_underlying(state)], 0, 0);
|
|
|
|
|
lcd.endWrite();
|
|
|
|
|
}
|
|
|
|
|
}
|