Files
Shadowtrance dbf850c434 Fixes and new apps (#30)
- M5 Unit Modules library + M5 Unit Test app
- Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout
- Fixed SerialConsole to use the uart controller as it was broken in one of the many updates
- Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake
- Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button
- Epub Reader app
2026-06-07 15:56:32 +02:00

285 lines
12 KiB
C++

/*
* UnitRfid2.h - M5Stack RFID2 Unit (WS1850S, MFRC522-compatible, I2C addr 0x28)
*
* 13.56 MHz RFID reader/writer. The WS1850S chip is a drop-in replacement for
* the MFRC522 and uses the same register map.
*
* Supported standards: ISO/IEC 14443 Type A (MIFARE Classic, NTAG series)
* Read range: < 20mm
*
* Key MFRC522 registers:
* 0x01 CommandReg - issue commands
* 0x04 ComIrqReg - interrupt flags
* 0x05 DivIrqReg - CRC / other irq flags
* 0x06 ErrorReg - error flags
* 0x08 Status2Reg - MFCrypto1On bit
* 0x09 FIFODataReg - FIFO read/write
* 0x0A FIFOLevelReg - bytes in FIFO
* 0x0D BitFramingReg - bit framing for anticollision
* 0x0E CollReg - collision position
* 0x11 ModeReg - TX/RX mode, CRC preset
* 0x14 TxControlReg - antenna Tx1/Tx2 enable
* 0x15 TxASKReg - 100% ASK modulation
* 0x21 CRCResultRegH - CRC result high byte
* 0x22 CRCResultRegL - CRC result low byte
* 0x2A TModeReg - timer mode
* 0x2B TPrescalerReg - timer prescaler
* 0x2C TReloadRegH - timer reload high
* 0x2D TReloadRegL - timer reload low
*
* Usage:
* UnitRfid2 rfid;
* if (rfid.begin(dev)) {
* UnitRfid2::Uid uid;
* if (rfid.readCard(&uid)) {
* auto type = rfid.getCardType(uid);
* if (type == UnitRfid2::CardType::MifareClassic1K) {
* uint8_t block[16];
* rfid.mfReadBlock(4, uid, UnitRfid2::KEY_DEFAULT, block);
* }
* rfid.haltCard();
* }
* }
*/
#pragma once
#include <UnitCommon.h>
#include <cstdint>
class UnitRfid2 {
public:
// ---------------------------------------------------------------------------
// Public types
// ---------------------------------------------------------------------------
enum class CardType : uint8_t {
Unknown,
MifareClassicMini, // SAK 0x09, 5 sectors, 20 blocks
MifareClassic1K, // SAK 0x08, 16 sectors, 64 blocks
MifareClassic4K, // SAK 0x18, 40 sectors, 256 blocks
MifareUltralight, // SAK 0x00, 16 pages
NTAG213, // SAK 0x00, CC[2]=0x12, 45 pages
NTAG215, // SAK 0x00, CC[2]=0x3E, 135 pages
NTAG216, // SAK 0x00, CC[2]=0x6D, 231 pages
};
struct Uid {
uint8_t size = 0;
uint8_t bytes[10] = {};
uint8_t sak = 0;
uint8_t atqa[2] = {};
};
struct MifareKey {
uint8_t k[6] = {};
};
static const MifareKey KEY_DEFAULT; // 0xFF x6
static const MifareKey KNOWN_KEYS[15]; // common keys for auto-auth
static constexpr uint8_t KNOWN_KEY_COUNT = 15;
// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------
static constexpr uint8_t DEFAULT_ADDR = 0x28;
// Pass a I2C controller device
bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
bool isPresent() const { return dev_ != nullptr; }
// ---------------------------------------------------------------------------
// Card detection & UID
// ---------------------------------------------------------------------------
// Detect card, run full anticollision/select (4/7-byte UID), fill uid.
bool readCard(Uid* uid);
// Legacy helpers kept for compatibility.
bool isCardPresent();
uint8_t readUID(uint8_t* uid, uint8_t maxLen);
// ---------------------------------------------------------------------------
// Card type
// ---------------------------------------------------------------------------
CardType getCardType(const Uid& uid);
const char* cardTypeName(CardType t);
// User-accessible page count for UL/NTAG types (first user page = 4).
// Returns 0 for MIFARE Classic types.
uint8_t ultralightPageCount(CardType t);
// ---------------------------------------------------------------------------
// MIFARE Classic
// ---------------------------------------------------------------------------
// Read one 16-byte block; authenticates with keyA first.
bool mfReadBlock(uint8_t block, const Uid& uid,
const MifareKey& keyA, uint8_t out[16]);
// Read one block trying Key A then Key B for a given key.
bool mfReadBlockKeyAB(uint8_t block, const Uid& uid,
const MifareKey& key, uint8_t out[16]);
// Read one block trying all 15 KNOWN_KEYS (both Key A and Key B each).
// keyUsedOut: if non-null, receives the key that succeeded.
// Returns false if no key works.
bool mfReadBlockAuto(uint8_t block, const Uid& uid,
uint8_t out[16], MifareKey* keyUsedOut = nullptr);
// Write one 16-byte block; authenticates with keyA first.
bool mfWriteBlock(uint8_t block, const Uid& uid,
const MifareKey& keyA, const uint8_t data[16]);
// Returns 4 for sectors 0-31, 16 for sectors 32-39, 0 if sector >= 40.
static uint8_t mfSectorBlockCount(uint8_t sector);
// Read all blocks of a sector.
// Sectors 0-31: 4 blocks each (MIFARE Classic 1K/4K/Mini).
// Sectors 32-39: 16 blocks each (MIFARE Classic 4K only).
// out must be at least mfSectorBlockCount(sector)*16 bytes; blockCount is written on success.
bool mfReadSector(uint8_t sector, const Uid& uid,
const MifareKey& keyA, uint8_t* out, uint8_t* blockCount = nullptr);
// Write a custom UID to a magic (gen1a backdoor) MIFARE card.
// Overwrites block 0 including manufacturer data - use with care.
// newUid must be 4 bytes; bcc is computed automatically.
bool mfWriteUid(const uint8_t newUid[4], const Uid& uid);
// Zero all writable data blocks (skips block 0 and sector trailers).
// Authenticates each sector with keyA. Returns count of blocks erased.
uint8_t mfErase(const Uid& uid, const MifareKey& keyA = KEY_DEFAULT);
// ---------------------------------------------------------------------------
// MIFARE Ultralight / NTAG
// ---------------------------------------------------------------------------
// Read one 4-byte page (chip returns 4 pages; we take the first).
bool ulReadPage(uint8_t page, uint8_t out4[4]);
// Read 'count' consecutive pages starting at startPage (count*4 bytes out).
bool ulReadPages(uint8_t startPage, uint8_t count, uint8_t* out);
// Write one 4-byte page. Pages 0-3 are UID/lock/OTP - guarded against write.
// Pass force=true to override the guard (e.g. for clone operations).
bool ulWritePage(uint8_t page, const uint8_t data4[4], bool force = false);
// Zero all user pages (pages 4..4+pageCount-1). Returns count of pages erased.
uint8_t ulErase(CardType t);
// ---------------------------------------------------------------------------
// Housekeeping
// ---------------------------------------------------------------------------
void haltCard();
private:
Device* dev_ = nullptr;
uint8_t addr_ = DEFAULT_ADDR;
// ---------------------------------------------------------------------------
// Low-level register access
// ---------------------------------------------------------------------------
void writeReg(uint8_t reg, uint8_t val);
uint8_t readReg(uint8_t reg);
void setBitMask(uint8_t reg, uint8_t mask);
void clearBitMask(uint8_t reg, uint8_t mask);
void writeFifo(const uint8_t* buf, uint8_t len);
// ---------------------------------------------------------------------------
// Init helpers
// ---------------------------------------------------------------------------
bool softReset();
void antennaOn();
// ---------------------------------------------------------------------------
// CRC & transceive
// ---------------------------------------------------------------------------
bool calcCRC(const uint8_t* data, uint8_t len, uint8_t result[2]);
// Raw transceive - returns bytes received, 0 on error/timeout.
uint8_t transceive(const uint8_t* txBuf, uint8_t txLen,
uint8_t* rxBuf, uint8_t rxMaxLen,
uint8_t* rxValidBits = nullptr);
// Transceive with CRC appended to tx and CRC verified on rx.
bool transceiveCRC(const uint8_t* txBuf, uint8_t txLen,
uint8_t* rxBuf, uint8_t rxMaxLen, uint8_t* rxLen);
// ---------------------------------------------------------------------------
// ISO 14443A anticollision
// ---------------------------------------------------------------------------
// cmd: PICC_REQA (wakes IDLE only) or PICC_WUPA (wakes IDLE + HALT)
bool requestA(uint8_t atqa[2], uint8_t cmd = PICC_REQA);
bool select(Uid* uid);
// ---------------------------------------------------------------------------
// MIFARE internals
// ---------------------------------------------------------------------------
bool mfAuthenticate(uint8_t authCmd, uint8_t block,
const MifareKey& key, const Uid& uid);
bool mfReadBlock16(uint8_t block, uint8_t out[16]);
bool mfWriteBlock16(uint8_t block, const uint8_t data[16]);
bool mfMagicOpen(); // gen1a backdoor sequence
void stopCrypto1();
bool haltA();
// ---------------------------------------------------------------------------
// MFRC522 register addresses
// ---------------------------------------------------------------------------
static constexpr uint8_t REG_COMMAND = 0x01;
static constexpr uint8_t REG_COM_IRQ = 0x04;
static constexpr uint8_t REG_DIV_IRQ = 0x05;
static constexpr uint8_t REG_ERROR = 0x06;
static constexpr uint8_t REG_STATUS2 = 0x08;
static constexpr uint8_t REG_FIFO_DATA = 0x09;
static constexpr uint8_t REG_FIFO_LEVEL = 0x0A;
static constexpr uint8_t REG_CONTROL = 0x0C;
static constexpr uint8_t REG_BIT_FRAMING = 0x0D;
static constexpr uint8_t REG_COLL = 0x0E;
static constexpr uint8_t REG_MODE = 0x11;
static constexpr uint8_t REG_TX_CONTROL = 0x14;
static constexpr uint8_t REG_TX_ASK = 0x15;
static constexpr uint8_t REG_CRC_RESULT_H = 0x21;
static constexpr uint8_t REG_CRC_RESULT_L = 0x22;
static constexpr uint8_t REG_TMODE = 0x2A;
static constexpr uint8_t REG_TPRESCALER = 0x2B;
static constexpr uint8_t REG_TRELOAD_H = 0x2C;
static constexpr uint8_t REG_TRELOAD_L = 0x2D;
// ---------------------------------------------------------------------------
// MFRC522 commands
// ---------------------------------------------------------------------------
static constexpr uint8_t CMD_IDLE = 0x00;
static constexpr uint8_t CMD_CALC_CRC = 0x03;
static constexpr uint8_t CMD_TRANSCEIVE = 0x0C;
static constexpr uint8_t CMD_MF_AUTHENT = 0x0E;
static constexpr uint8_t CMD_SOFT_RESET = 0x0F;
// ---------------------------------------------------------------------------
// ISO 14443A / MIFARE PICC command bytes
// ---------------------------------------------------------------------------
static constexpr uint8_t PICC_REQA = 0x26;
static constexpr uint8_t PICC_WUPA = 0x52; // wakes IDLE + HALT cards
static constexpr uint8_t PICC_HLTA = 0x50;
static constexpr uint8_t PICC_CT = 0x88;
static constexpr uint8_t PICC_SEL_CL1 = 0x93;
static constexpr uint8_t PICC_SEL_CL2 = 0x95;
static constexpr uint8_t PICC_SEL_CL3 = 0x97;
static constexpr uint8_t PICC_ANTICOLL = 0x20;
static constexpr uint8_t PICC_MF_AUTH_KEY_A = 0x60;
static constexpr uint8_t PICC_MF_AUTH_KEY_B = 0x61;
static constexpr uint8_t PICC_MF_READ = 0x30;
static constexpr uint8_t PICC_MF_WRITE = 0xA0;
static constexpr uint8_t PICC_UL_WRITE = 0xA2;
};