mirror of
https://github.com/m5stack/M5Unit-RFID.git
synced 2026-05-20 11:28:27 -07:00
Adds NFC-B support for WS1850S and GsN tuning API
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user