/* * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ /* Example using M5UnitUnified for ST25R3916 NFC-F Emulation mode */ #include #include #include #include #include // ************************************************************* // Choose one define symbol to match the unit you are using // ************************************************************* #if !defined(USING_UNIT_NFC) && !defined(USING_CAP_CC1101) // For UnitNFC // #define USING_UNIT_NFC // For CapNFC // #define USING_CAP_CC1101 #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 #elif defined(USING_CAP_CC1101) #pragma message "Choose CapCC1101NFC" m5::unit::CapCC1101NFC unit{}; // CapCC1101 (SPI) #else #error Choose unit please! #endif m5::nfc::EmulationLayerF emu_f{unit}; PICC picc{}; constexpr Type type{Type::FeliCaLiteS}; // constexpr uint8_t IDm[8] = {0x01, 0x2E, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; // See also SONY specification // documents constexpr uint8_t IDm[8] = {0x01, 0x2E, 0x50, 0xE5, 0x3C, 0x4B, 0x4F, 0x29}; constexpr uint8_t PMm[8] = {0x00, 0xF1, 0x00, 0x00, 0x00, 0x01, 0x43, 0x00}; // See also SONY specification documents 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) 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) { M5_LOGE("Failed to begin"); lcd.fillScreen(TFT_RED); while (true) { m5::utility::delay(10000); } } #elif defined(USING_CAP_CC1101) 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 initialized"); M5_LOGI("%s", Units.debugInfo().c_str()); if (lcd.height() > lcd.width()) { 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(); } }