diff --git a/src/nfc/layer/unit/nfc_layer_b_WS1850S.cpp b/src/nfc/layer/unit/nfc_layer_b_WS1850S.cpp new file mode 100644 index 0000000..b4ab701 --- /dev/null +++ b/src/nfc/layer/unit/nfc_layer_b_WS1850S.cpp @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/*! + @file nfc_layer_b_WS1850S.cpp + @brief WS1850S NFC-B adapter for common layer +*/ +#include +#include +#include "unit/unit_WS1850S.hpp" +#include + +using namespace m5::unit; +using namespace m5::unit::mfrc522; +using namespace m5::nfc::b; + +namespace m5 { +namespace nfc { + +// +struct AdapterWS1850SForB final : NFCLayerB::Adapter { + explicit AdapterWS1850SForB(UnitWS1850S& ref) : _u{ref} + { + } + + inline virtual uint16_t max_fifo_depth() const override + { + return m5::unit::mfrc522::MAX_FIFO_DEPTH; + } + + bool transceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms) override + { + return _u.nfcbTransceive(rx, rx_len, tx, tx_len, timeout_ms); + } + + bool transmit(const uint8_t* tx, const uint16_t tx_len, const uint32_t timeout_ms) override + { + // MFRC522-class chips do not have a standalone transmit; reuse transceive (rx discarded). + uint8_t dummy[m5::unit::mfrc522::MAX_FIFO_DEPTH]{}; + uint16_t dlen = sizeof(dummy); + return _u.nfcbTransceive(dummy, dlen, tx, tx_len, timeout_ms); + } + + bool receive(uint8_t* /*rx*/, uint16_t& /*rx_len*/, const uint32_t /*timeout_ms*/) override + { + // Standalone receive is not supported on MFRC522-class chips (Transceive command based). + return false; + } + + UnitWS1850S& _u; +}; + +// +namespace { +std::unique_ptr make_ws1850s_b_adapter(UnitWS1850S& u) +{ + return std::unique_ptr(new AdapterWS1850SForB(u)); +} +} // namespace + +NFCLayerB::NFCLayerB(UnitWS1850S& u) : _ndef{*this}, _isoDEP{*this}, _impl(make_ws1850s_b_adapter(u)) +{ +} + +} // namespace nfc +} // namespace m5 diff --git a/src/unit/pn512_register.hpp b/src/unit/pn512_register.hpp new file mode 100644 index 0000000..d1238a4 --- /dev/null +++ b/src/unit/pn512_register.hpp @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/*! + @file pn512_register.hpp + @brief PN512-specific register addresses (WS1850S is PN512-compatible silicon) + @details + NXP PN512 extends the MFRC522 register map with NFC-B / NFC-F support. + These constants are reserved/undefined on pure MFRC522 silicon and are + only valid on PN512 (and WS1850S, which is PN512-compatible internally). + Common registers shared with MFRC522 (CWGsP/ModGsP/RxThreshold) live in + unit_MFRC522.hpp and are NOT duplicated here. +*/ +#ifndef M5_UNIT_RFID_PN512_REGISTER_HPP +#define M5_UNIT_RFID_PN512_REGISTER_HPP + +#include + +namespace m5 { +namespace unit { +/*! + @namespace pn512 + @brief PN512-specific register addresses (NFC-B / NFC-F extensions) + */ +namespace pn512 { + +//! TypeBReg (Page 1 address 0xE, accessed as 0x1E): EOFSOFWidth, TxEGT, EOFSOFAdjust +constexpr uint8_t TYPE_B_REG{0x1E}; + +} // namespace pn512 +} // namespace unit +} // namespace m5 + +#endif diff --git a/src/unit/unit_MFRC522.cpp b/src/unit/unit_MFRC522.cpp index 5e42bab..f59c69e 100644 --- a/src/unit/unit_MFRC522.cpp +++ b/src/unit/unit_MFRC522.cpp @@ -128,19 +128,6 @@ uint16_t calculate_reload(const uint32_t timeout_ms, const uint16_t tprescaler) return static_cast(reload); } -#if 0 -inline float modulationWidth(const uint8_t tm) -{ - return tm + 1 / F_CLOCK; -} - -inline uint8_t bits_to_NVB(const uint8_t bits) -{ - // High nibble:bytes Low nibble:fraction bits - return (bits >> 3) | (bits & 0x07); -} -#endif - } // namespace namespace m5 { @@ -183,25 +170,8 @@ bool UnitMFRC522::begin() // Mode and antenna auto ret = readTPrescaler(_tprescaler) && writeRegister8(MODE_REG, _cfg.mode_reg) && - writeReceiverGain(_cfg.receiver_gain) && (_cfg.enable_antenna ? turnOnAntenna() : true); - -#if 0 - uint8_t tx_control{}, demod_reg{}, rf_cfg{}, rx_thresh{}; - readRegister8(TX_CONTROL_REG, tx_control, 0); - readRegister8(DEMOD_REG, demod_reg, 0); - readRegister8(RFC_FG_REG, rf_cfg, 0); - readRegister8(RX_THRESHOLD_REG, rx_thresh, 0); - M5_LIB_LOGE("TxCtrl=%02X Demod=%02X RfCfg=%02X RxTh=%02X", tx_control, demod_reg, rf_cfg, rx_thresh); - - uint8_t rx_sel{}, mod_width{}; - readRegister8(RX_SEL_REG, rx_sel, 0); // Include RxWait - readRegister8(MOD_WIDTH_REG, mod_width, 0); - M5_LIB_LOGE("RxSel=%02X ModWidth=%02X", rx_sel, mod_width); - - uint8_t mf_rx{}; - readRegister8(MF_RX_REG, mf_rx, 0); - M5_LIB_LOGE("MfRx=%02X (ParityDisable=%d)", mf_rx, (mf_rx >> 4) & 1); -#endif + writeReceiverGain(_cfg.receiver_gain) && (_cfg.enable_antenna ? turnOnAntenna() : true) && + configureNFCMode(_cfg.mode); return ret; } @@ -218,26 +188,6 @@ bool UnitMFRC522::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* t const uint16_t cap = MAX_FIFO_DEPTH - 2 /*CRC*/; rx_len = 0; -#if 0 - if (!rx || !rx_len_org || !tx || !tx_len) { - return false; - } - uint8_t frame[tx_len + 2 /*CRC*/]{}; - uint16_t crc{}; - if (!calculate_crc(crc, tx, tx_len)) { - return false; - } - memcpy(frame, tx, tx_len); - frame[tx_len] = crc & 0xFF; - frame[tx_len + 1] = crc >> 8; - - uint8_t discard{}; - uint8_t tmp[rx_len_org + 2 /*CRC*/]{}; - uint16_t rx_tmp = sizeof(tmp); - // uint8_t error{}; - - auto ret = transceive(tmp, rx_tmp, frame, sizeof(frame), timeout_ms, discard, 0, true); -#else if (!rx || !rx_len_org || !tx || !tx_len || tx_len > cap) { return false; } @@ -261,7 +211,6 @@ bool UnitMFRC522::nfcaTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* t uint16_t rx_tmp = rx_len_org + 2 /* CRC*/; auto ret = transceive(tmp, rx_tmp, frame, tx_len + 2 /*CRC*/, timeout_ms, discard, 0, true); -#endif rx_len = std::min(rx_len_org, rx_tmp); memcpy(rx, tmp, rx_len); @@ -370,6 +319,58 @@ bool UnitMFRC522::writeReceiverGain(const ReceiverGain gain) return false; } +bool UnitMFRC522::readGsN(uint8_t& value) +{ + return readRegister8(GSN_REG, value, 0); +} + +bool UnitMFRC522::writeGsN(const uint8_t value) +{ + return writeRegister8(GSN_REG, value); +} + +bool UnitMFRC522::readCWGsP(uint8_t& value) +{ + return readRegister8(CW_GSP_REG, value, 0); +} + +bool UnitMFRC522::writeCWGsP(const uint8_t value) +{ + if (value > 0x3F) { + M5_LIB_LOGE("CWGsP must be 0x00-0x3F (got 0x%02X)", value); + return false; + } + return writeRegister8(CW_GSP_REG, value); +} + +bool UnitMFRC522::readModGsP(uint8_t& value) +{ + return readRegister8(MOD_GSP_REG, value, 0); +} + +bool UnitMFRC522::writeModGsP(const uint8_t value) +{ + if (value > 0x3F) { + M5_LIB_LOGE("ModGsP must be 0x00-0x3F (got 0x%02X)", value); + return false; + } + return writeRegister8(MOD_GSP_REG, value); +} + +bool UnitMFRC522::readRxThreshold(uint8_t& value) +{ + return readRegister8(RX_THRESHOLD_REG, value, 0); +} + +bool UnitMFRC522::writeRxThreshold(const uint8_t value) +{ + if (value & 0x08) { // bit[3] reserved + M5_LIB_LOGE("RxThreshold reserved bit[3] must be 0 (got 0x%02X)", value); + return false; + } + return writeRegister8(RX_THRESHOLD_REG, value); +} + bool UnitMFRC522::calculateCRC(uint16_t& result, const uint8_t* buf, const uint8_t len) { constexpr uint8_t CRC_IRQ{0x04}; @@ -1007,13 +1008,6 @@ bool UnitMFRC522::transceive(uint8_t* rbuf, uint16_t& rx_len, const uint8_t* buf // Wait for command completion if (!wait_comm_irq(RxIRq | IdleIRq, timeout_ms)) { -#if 0 - uint8_t com_irq{}, err_reg{}, fifo_level{}; - readRegister8(COM_IRQ_REG, com_irq, 0); - readRegister8(ERROR_REG, err_reg, 0); - readRegister8(FIFO_LEVEL_REG, fifo_level, 0); - M5_LIB_LOGE("Timeout: COM_IRQ=%02X ERR=%02X FIFO=%u", com_irq, err_reg, fifo_level); -#endif M5_LIB_LOGD("Timeout"); if (error) { *error = ERROR_BIT_TIMEOUT; diff --git a/src/unit/unit_MFRC522.hpp b/src/unit/unit_MFRC522.hpp index b34cd1e..d5b8100 100644 --- a/src/unit/unit_MFRC522.hpp +++ b/src/unit/unit_MFRC522.hpp @@ -95,6 +95,8 @@ public: @brief Settings for begin */ struct config_t { + //! Initial NFC mode applied at begin() + m5::nfc::NFC mode{m5::nfc::NFC::A}; //! mode reg value. See also 9.3.2.2 ModeReg register uint8_t mode_reg{0x3D}; //! Enable antenna on begin if true @@ -159,9 +161,9 @@ public: @brief Configure NFC mode @param mode NFC mode @return True if successful - @warning Only NFC-A + @warning On pure MFRC522 only NFC-A is supported. Derived classes (e.g. UnitWS1850S) override this. */ - inline bool configureNFCMode(const m5::nfc::NFC mode) + inline virtual bool configureNFCMode(const m5::nfc::NFC mode) { // Nop return mode == m5::nfc::NFC::A; @@ -217,6 +219,65 @@ public: bool writeReceiverGain(const mfrc522::ReceiverGain gain); ///@} + ///@name GsN (N-driver conductance) / CWGsP / ModGsP (P-driver conductance) + ///@{ + /*! + @brief Read the GsN register (N-driver conductance: CWGsN[7:4] / ModGsN[3:0]) + @param[out] value Raw 8-bit value (high nibble = CWGsN, low nibble = ModGsN) + @return True if successful + */ + bool readGsN(uint8_t& value); + /*! + @brief Write the GsN register + @param value Raw 8-bit value (high nibble = CWGsN, low nibble = ModGsN) + @return True if successful + */ + bool writeGsN(const uint8_t value); + /*! + @brief Read the CWGsP register (P-driver conductance during no modulation) + @param[out] value 6-bit value (0x00-0x3F) + @return True if successful + */ + bool readCWGsP(uint8_t& value); + /*! + @brief Write the CWGsP register + @param value 6-bit value (0x00-0x3F). Upper 2 bits are reserved. + @return True if successful, false if value > 0x3F or I2C error + */ + bool writeCWGsP(const uint8_t value); + /*! + @brief Read the ModGsP register (P-driver conductance during modulation) + @param[out] value 6-bit value (0x00-0x3F) + @return True if successful + */ + bool readModGsP(uint8_t& value); + /*! + @brief Write the ModGsP register + @param value 6-bit value (0x00-0x3F). Smaller = deeper ASK modulation. + @return True if successful, false if value > 0x3F or I2C error + */ + bool writeModGsP(const uint8_t value); + ///@} + + ///@name RxThreshold (receiver decision levels) + ///@{ + /*! + @brief Read the RxThreshold register + @param[out] value 8-bit raw value (MinLevel[7:4] / CollLevel[2:0]) + @return True if successful + */ + bool readRxThreshold(uint8_t& value); + /*! + @brief Write the RxThreshold register + @param value 8-bit raw value. bit[3] is reserved (must be 0). + @return True if successful, false if reserved bit set or I2C error + @note On WS1850S silicon this write is silently ignored (verified by exhaustive + 0x00-0xFF scan); the chip uses its internal fixed value (reset default 0x84). + Provided here for genuine MFRC522 silicon compatibility. + */ + bool writeRxThreshold(const uint8_t value); + ///@} + ///@name TPrescaler ///@{ /*! diff --git a/src/unit/unit_WS1850S.cpp b/src/unit/unit_WS1850S.cpp index 7cf7bc7..796fc5f 100644 --- a/src/unit/unit_WS1850S.cpp +++ b/src/unit/unit_WS1850S.cpp @@ -8,13 +8,15 @@ @brief WS1850S Unit for M5UnitUnified */ #include "unit_WS1850S.hpp" +#include "pn512_register.hpp" #include +#include +#include namespace { // VERSION_REG value read from actual WS1850S hardware. // Not documented in the WS1850S datasheet (MFRC522 returns 0x91 or 0x92). constexpr uint8_t ws1850s_firmware_version{0x15}; - } // namespace using namespace m5::utility::mmh3; @@ -53,5 +55,172 @@ bool UnitWS1850S::self_test() return false; } +bool UnitWS1850S::configureNFCMode(const m5::nfc::NFC mode) +{ + switch (mode) { + case m5::nfc::NFC::A: + return configure_nfca(); + case m5::nfc::NFC::B: + return configure_nfcb(); + default: + M5_LIB_LOGE("Unsupported NFC mode"); + return false; + } +} + +bool UnitWS1850S::configure_nfca() +{ + if (!turnOffAntenna()) return false; + + // ModeReg: full write of _cfg.mode_reg (default 0x3D includes CRCPreset[1:0]=01 for CRC_A). + // Full write ensures NFC-A state regardless of any prior register modifications. + if (!writeRegister8(MODE_REG, _cfg.mode_reg)) return false; + + // NFC-A framing: TxCRCEn=0, RxCRCEn=0 (M5Unit-NFC appends/validates CRC_A in software). + // TxFraming/RxFraming=00 (Type A). Hardware CRC enabled would double-CRC the frame. + if (!writeRegister8(TX_MODE_REG, (uint8_t)0x00)) return false; + if (!writeRegister8(RX_MODE_REG, (uint8_t)0x00)) return false; + + // TxASKReg: Force100ASK=1 (0x40) — required for ISO/IEC 14443-3 Type A 100% ASK modulation. + // Matches MFRC522::begin() default and the previously-working state. + if (!writeRegister8(TX_ASK_REG, (uint8_t)0x40)) return false; + + // Antenna driver conductance (reset defaults) + if (!writeCWGsP(0x20)) return false; + if (!writeModGsP(0x20)) return false; + // RxThreshold: WS1850S ignores writes to 0x18 (verified by 0x00-0xFF scan). + // Chip uses fixed internal value (reset default 0x84) for the receive decoder. + + // TypeBReg: reset to 0x00 in case NFC-B mode set it (PN512-compatible silicon only). + // Harmless no-op on pure MFRC522 silicon. + if (!writeTypeBReg(0x00)) return false; + + if (!turnOnAntenna()) return false; + _mode = m5::nfc::NFC::A; + return true; +} + +bool UnitWS1850S::configure_nfcb() +{ + // Antenna OFF before reconfiguring + if (!turnOffAntenna()) return false; + + // ModeReg: full write of _cfg.mode_reg with CRCPreset[1:0] forced to 0b11 (CRC_B 0xFFFF + // preset). Full write ensures NFC-B state regardless of any prior register modifications. + if (!writeRegister8(MODE_REG, (uint8_t)((_cfg.mode_reg & 0xFC) | 0x03))) return false; + + // TxModeReg: TxCRCEn=0 (software CRC), TxSpeed=000 (106 kbit), TxFraming=0b11 (Type B) + if (!writeRegister8(TX_MODE_REG, (uint8_t)0x03)) return false; + + // RxModeReg: RxCRCEn=0, RxSpeed=000, RxFraming=0b11 (Type B) + if (!writeRegister8(RX_MODE_REG, (uint8_t)0x03)) return false; + + // Fixed Type B physical layer constants (per ISO/IEC 14443-3 and PN512 datasheet) + constexpr uint8_t TYPE_B_REG_VALUE{0x10}; // EOFSOFWidth=1 (regulation compliant) + constexpr uint8_t CW_GSP_VALUE{0x3F}; // Maximum no-modulation conductance + + // ASK depth (0-15) → ModGsP register value lookup + constexpr uint8_t MOD_GSP_TABLE[16] = { + 0x3F, 0x3A, 0x35, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1E, 0x1C, 0x1B, 0x1A, 0x18, 0x16, 0x14, 0x10, + }; + const uint8_t depth = std::min(_cfg.nfcb_ask_depth, 15); + const uint8_t mod_gsp = MOD_GSP_TABLE[depth]; + + // PN512 TypeBReg (Page 1 address 0xE): EOFSOFWidth/TxEGT/EOFSOFAdjust. + // Effective only if the silicon is PN512-compatible; ignored otherwise. + if (!writeTypeBReg(TYPE_B_REG_VALUE)) return false; + + // TxAutoReg/TxASKReg (0x15): release Force100ASK so the silicon emits 8-30% ASK + if (!writeRegister8(TX_ASK_REG, (uint8_t)0x00)) return false; + + // Antenna driver tuning for Type B (~10% ASK depth) + if (!writeCWGsP(CW_GSP_VALUE)) return false; + if (!writeModGsP(mod_gsp)) return false; + + // RxThresholdReg (0x18) intentionally not written: WS1850S silently ignores writes + // (verified by 0x00-0xFF scan). Chip uses fixed internal value (0x84) for receiver. + +#if 0 + // Diagnostic readback: detect if silicon honored NFC-B framing settings. + // Pure MFRC522 clones reject TxFraming/RxFraming=11 (Type B) and leave 0x80/0x80. + { + uint8_t tx_mode{}, rx_mode{}, mode_reg{}, type_b_reg{}; + readRegister8(TX_MODE_REG, tx_mode, 0); + readRegister8(RX_MODE_REG, rx_mode, 0); + readRegister8(MODE_REG, mode_reg, 0); + readRegister8(m5::unit::pn512::TYPE_B_REG, type_b_reg, 0); + const bool pn512_like = ((tx_mode & 0x03) == 0x03) && ((rx_mode & 0x03) == 0x03); + M5_LIB_LOGD( + "WS1850S NFC-B readback: TxMode=%02X RxMode=%02X Mode=%02X TypeB=%02X => %s", tx_mode, rx_mode, mode_reg, + type_b_reg, + pn512_like ? "PN512-compatible (Type B framing accepted)" : "MFRC522 clone (Type B framing rejected)"); + } +#endif + + if (!turnOnAntenna()) return false; + _mode = m5::nfc::NFC::B; + return true; +} + +bool UnitWS1850S::nfcbTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms) +{ + const uint16_t rx_len_org = rx_len; + rx_len = 0; + + if (!rx || !rx_len_org || !tx || !tx_len || (uint32_t)tx_len + 2 > MAX_FIFO_DEPTH) { + return false; + } + + // CRC_B via PN512 hardware CRC coprocessor (CRCPreset=0xFFFF set by configure_nfcb()). + // PN512 lacks XOR-out; apply CRC_B xorOut=0xFFFF manually. + uint16_t crc = 0; + if (!calculateCRC(crc, tx, tx_len)) { + return false; + } + crc ^= 0xFFFF; + + // Build frame: payload | CRC_B (LSB first) + uint8_t frame[MAX_FIFO_DEPTH]{}; + memcpy(frame, tx, tx_len); + frame[tx_len] = crc & 0xFF; + frame[tx_len + 1] = (crc >> 8) & 0xFF; + + uint8_t discard{}; + uint8_t tmp[MAX_FIFO_DEPTH]{}; + uint16_t rx_tmp = std::min(rx_len_org, (uint16_t)MAX_FIFO_DEPTH); + + // crc=false: keep trailing 2-byte CRC in rx for upper layer to verify + auto ret = transceive(tmp, rx_tmp, frame, tx_len + 2, timeout_ms, discard, 0, false); + + // Diagnostic: dump WS1850S internal state on failure + if (!ret || rx_tmp == 0) { + uint8_t err{}, com_irq{}, fifo{}; + readRegister8(ERROR_REG, err, 0); + readRegister8(COM_IRQ_REG, com_irq, 0); + readRegister8(FIFO_LEVEL_REG, fifo, 0); + M5_LIB_LOGD("Failed: ret:%u rx_tmp:%u tx[%u] ERR=%02X IRQ=%02X FIFO=%u", ret, rx_tmp, tx_len, err, com_irq, + fifo); + } + + rx_len = std::min(rx_len_org, rx_tmp); + memcpy(rx, tmp, rx_len); + return ret && rx_len; +} + +bool UnitWS1850S::readTypeBReg(uint8_t& value) +{ + return readRegister8(m5::unit::pn512::TYPE_B_REG, value, 0); +} + +bool UnitWS1850S::writeTypeBReg(const uint8_t value) +{ + if (value & 0x20) { // bit[5] is RFU per PN512 datasheet 8.2.2.15 + M5_LIB_LOGE("TypeBReg reserved bit[5] must be 0 (got 0x%02X)", value); + return false; + } + return writeRegister8(m5::unit::pn512::TYPE_B_REG, value); +} + } // namespace unit } // namespace m5 diff --git a/src/unit/unit_WS1850S.hpp b/src/unit/unit_WS1850S.hpp index b4c3bde..36cd919 100644 --- a/src/unit/unit_WS1850S.hpp +++ b/src/unit/unit_WS1850S.hpp @@ -18,12 +18,23 @@ namespace unit { /*! @class UnitWS1850S @brief Radio frequency identification unit - @details Functionally compatible with MFRC522 + @details Functionally compatible with MFRC522. Supports NFC-A (always) and NFC-B (if PN512-compatible silicon). */ class UnitWS1850S : public UnitMFRC522 { M5_UNIT_COMPONENT_HPP_BUILDER(UnitWS1850S, 0x28); public: + /*! + @struct config_t + @brief Settings for begin / configureNFCMode + @details Adds NFC-B antenna driver / receiver tuning values on top of UnitMFRC522::config_t. + */ + struct config_t : public UnitMFRC522::config_t { + //! NFC-B ASK modulation depth (0=shallowest, 15=deepest). + //! Mapped internally to ModGsP register. + uint8_t nfcb_ask_depth{11}; + }; + /*! @brief Constructor @param addr I2C address @@ -44,8 +55,83 @@ public: */ virtual bool begin() override; + ///@name Settings for begin / configureNFCMode + ///@{ + /*! @brief Gets the configuration */ + inline config_t config() const + { + return _cfg; + } + /*! @brief Set the configuration */ + inline void config(const config_t& cfg) + { + _cfg = cfg; + UnitMFRC522::config(static_cast(cfg)); + } + ///@} + + ///@name NFC mode + ///@{ + /*! + @brief Gets the current operating mode + @return NFC::A or NFC::B + */ + inline m5::nfc::NFC NFCMode() const + { + return _mode; + } + + /*! + @brief Configure NFC mode + @param mode NFC::A or NFC::B + @return True if successful + @note NFC::B requires PN512-compatible silicon. Antenna tuning values are taken from config_t. + */ + bool configureNFCMode(const m5::nfc::NFC mode) override; + ///@} + + ///@name NFC-B (ISO/IEC 14443 Type B) + ///@{ + /*! + @brief Transceive an NFC-B frame + @param rx Receive buffer (returned with trailing 2-byte CRC_B intact) + @param[in,out] rx_len in:Size of rx buffer out:Actual received length + @param tx Transmit buffer (without CRC; CRC_B is appended internally) + @param tx_len Length of tx + @param timeout_ms Timeout in milliseconds + @return True if successful + @note Software CRC_B is used for transmission. Both software and hardware CRC are + also computed and compared in M5_LIB_LOGI for silicon compatibility diagnosis. + */ + bool nfcbTransceive(uint8_t* rx, uint16_t& rx_len, const uint8_t* tx, const uint16_t tx_len, + const uint32_t timeout_ms); + + /*! + @brief Read the TypeBReg (PN512 Page 1, address 0x1E) + @param[out] value 8-bit raw value (RxSOFReq / EOFSOFWidth / TxEGT / EOFSOFAdjust) + @return True if successful + */ + bool readTypeBReg(uint8_t& value); + /*! + @brief Write the TypeBReg (PN512 Page 1, address 0x1E) + @param value 8-bit raw value. bit[5] is RFU (must be 0) per PN512 datasheet 8.2.2.15. + @return True if successful, false if reserved bit set or I2C error + @note Effective only on PN512-compatible silicon (e.g. WS1850S). + */ + bool writeTypeBReg(const uint8_t value); + ///@} + protected: virtual bool self_test() override; + + /*! @brief Switch chip to NFC-A mode (default) */ + bool configure_nfca(); + /*! @brief Switch chip to NFC-B mode (PN512-compatible silicon only) */ + bool configure_nfcb(); + +protected: + config_t _cfg{}; + m5::nfc::NFC _mode{m5::nfc::NFC::A}; }; } // namespace unit