Adds NFC-B support for WS1850S and GsN tuning API

This commit is contained in:
GOB
2026-05-14 15:27:11 +09:00
parent 5f38bbce00
commit f3c027a5b9
6 changed files with 479 additions and 64 deletions
@@ -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 <nfc/layer/b/nfc_layer_b.hpp>
#include <nfc/layer/ndef_layer.hpp>
#include "unit/unit_WS1850S.hpp"
#include <M5Utility.hpp>
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<NFCLayerB::Adapter> make_ws1850s_b_adapter(UnitWS1850S& u)
{
return std::unique_ptr<NFCLayerB::Adapter>(new AdapterWS1850SForB(u));
}
} // namespace
NFCLayerB::NFCLayerB(UnitWS1850S& u) : _ndef{*this}, _isoDEP{*this}, _impl(make_ws1850s_b_adapter(u))
{
}
} // namespace nfc
} // namespace m5
+36
View File
@@ -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 <cstdint>
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
+54 -60
View File
@@ -128,19 +128,6 @@ uint16_t calculate_reload(const uint32_t timeout_ms, const uint16_t tprescaler)
return static_cast<uint16_t>(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<uint16_t>(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;
+63 -2
View File
@@ -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
///@{
/*!
+170 -1
View File
@@ -8,13 +8,15 @@
@brief WS1850S Unit for M5UnitUnified
*/
#include "unit_WS1850S.hpp"
#include "pn512_register.hpp"
#include <M5Utility.hpp>
#include <nfc/b/nfcb.hpp>
#include <algorithm>
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<uint8_t>(_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<uint16_t>(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<uint16_t>(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
+87 -1
View File
@@ -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<const UnitMFRC522::config_t&>(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