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
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Unit8Encoder.h - M5Stack 8Encoder Unit (STM32, I2C addr 0x41)
|
||||
*
|
||||
* 8 rotary encoders with push buttons, individual RGB LEDs, plus one toggle
|
||||
* switch with its own RGB LED.
|
||||
*
|
||||
* Register map:
|
||||
* 0x00 + i*4 : int32_t absolute counter for encoder i (0-3) [R/W]
|
||||
* 0x10 + i*4 : int32_t absolute counter for encoder i (4-7) [R/W]
|
||||
* 0x20 + i*4 : int32_t increment (delta) for encoder i (0-3), resets after read [R]
|
||||
* 0x30 + i*4 : int32_t increment (delta) for encoder i (4-7), resets after read [R]
|
||||
* 0x40 + i : uint8_t write 1 to reset counter i to 0 (i = 0-7) [W]
|
||||
* 0x50 + i : uint8_t button state for encoder i (0=released, 1=pressed) [R]
|
||||
* 0x60 : uint8_t toggle switch state (0=off, 1=on) [R]
|
||||
* 0x70 + i*3 : [R, G, B] LED colour for encoder i (i = 0-7, 0x00RRGGBB) [R/W]
|
||||
* 0x88 : [R, G, B] LED colour for the toggle switch (LED8) [R/W]
|
||||
* 0xF0 : uint8_t firmware version [R]
|
||||
* 0xFF : uint8_t I2C address (R/W)
|
||||
*
|
||||
* LED indices: 0-7 = encoder LEDs, 8 = switch LED (total 9 LEDs).
|
||||
*
|
||||
* Usage:
|
||||
* Unit8Encoder enc;
|
||||
* if (enc.begin(dev)) { ... } // dev = device_find_by_name("i2c1")
|
||||
* enc.readAll(deltas, buttons); // deltas in detents (÷4), buttons 0/1
|
||||
* bool sw = false;
|
||||
* if (enc.readSwitch(sw)) { sw holds switch state } // false = read failed
|
||||
* enc.setAllLeds(0xFF0000); // set all 9 LEDs (encoders + switch) to red
|
||||
* enc.setSwitchLed(0x00FF00); // set switch LED colour only
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <cstdint>
|
||||
|
||||
class Unit8Encoder {
|
||||
public:
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x41;
|
||||
static constexpr uint8_t LED_COUNT = 9; // 8 encoder LEDs + 1 switch LED
|
||||
static constexpr uint8_t ENCODER_LED_COUNT = 8;
|
||||
|
||||
Unit8Encoder() = default;
|
||||
Unit8Encoder(const Unit8Encoder&) = delete;
|
||||
Unit8Encoder& operator=(const Unit8Encoder&) = delete;
|
||||
|
||||
// Pass a I2C controller device
|
||||
// Probe and initialise. Returns false if the unit is not present.
|
||||
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
|
||||
bool isPresent() const { return dev_ != nullptr; }
|
||||
|
||||
// Read all 8 encoder deltas (in detents, hardware counts ÷4) and button states.
|
||||
// Returns false on I2C error.
|
||||
bool readAll(int32_t deltas[8], uint8_t buttons[8]);
|
||||
|
||||
// Read the toggle switch state into `state`. Returns false on I2C error.
|
||||
// On error, `state` is left unchanged. Use the return value to distinguish
|
||||
// "switch off" (returns true, state=false) from read failure (returns false).
|
||||
bool readSwitch(bool& state);
|
||||
|
||||
// Set one LED immediately (0x00RRGGBB). Updates cached colour.
|
||||
// idx 0-7 = encoder LEDs, idx 8 = switch LED. Silently ignores idx > 8.
|
||||
void setLed(uint8_t idx, uint32_t rgb);
|
||||
|
||||
// Set the switch LED colour (0x00RRGGBB) immediately.
|
||||
void setSwitchLed(uint32_t rgb);
|
||||
|
||||
// Flush pending[LED_COUNT] to hardware if anything changed.
|
||||
// pending[0-7] = encoder LEDs, pending[8] = switch LED.
|
||||
// Call every poll with desired colours; only sends if dirty.
|
||||
void flushLeds(const uint32_t pending[LED_COUNT]);
|
||||
|
||||
// Set all 9 LEDs (encoder + switch) to one colour immediately.
|
||||
void setAllLeds(uint32_t rgb);
|
||||
|
||||
// Cached LED colours (0x00RRGGBB). Read-only - updated by set*/flush*.
|
||||
// Index 0-7 = encoder LEDs, index 8 = switch LED.
|
||||
const uint32_t* ledColors() const { return ledColor_; }
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
uint32_t ledColor_[LED_COUNT] = {};
|
||||
|
||||
static constexpr uint8_t REG_INCREMENT = 0x20;
|
||||
static constexpr uint8_t REG_BUTTON = 0x50;
|
||||
static constexpr uint8_t REG_SWITCH = 0x60;
|
||||
static constexpr uint8_t REG_LED = 0x70; // encoder LEDs 0-7 (24 bytes)
|
||||
static constexpr uint8_t REG_SWITCH_LED = 0x88; // switch LED (3 bytes)
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* UnitByteButton.h - M5Stack ByteButton Unit (STM32, I2C addr 0x47)
|
||||
*
|
||||
* 8 momentary buttons with individual RGB LEDs.
|
||||
*
|
||||
* Register map:
|
||||
* 0x00 : uint8_t all 8 button states as a bitmask (bit i = button i)
|
||||
* 0x10 + i : uint8_t LED brightness for button i (0-255)
|
||||
* 0x19 : uint8_t LED show mode (0=user-defined, 1=system-default)
|
||||
* 0x20 + i*4 : uint32_t LED RGB colour for button i (LE: bytes = [B,G,R,0x00])
|
||||
* 0x50 + i : uint8_t LED colour in RGB233 compressed format
|
||||
* 0x60 + i : uint8_t individual button state (0=released, non-zero=pressed)
|
||||
* 0x70 + i*4 : uint32_t system "off" colour for button i
|
||||
* 0x90 + i*4 : uint32_t system "on" colour for button i
|
||||
* 0xFE : uint8_t firmware version
|
||||
* 0xFF : uint8_t I2C address (R/W)
|
||||
*
|
||||
* LED colour format: write uint32_t 0x00RRGGBB as little-endian → wire sees [BB, GG, RR, 00]
|
||||
*
|
||||
* Usage:
|
||||
* UnitByteButton bb;
|
||||
* if (bb.begin(dev)) { ... }
|
||||
* uint8_t mask = bb.readButtons(); // bitmask, bit 0 = button 0
|
||||
* bb.setLed(i, 0xFF4400); // per-button
|
||||
* bb.flushLeds(pending); // burst write if dirty
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitByteButton {
|
||||
public:
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x47;
|
||||
static constexpr uint8_t BUTTON_COUNT = 8;
|
||||
|
||||
UnitByteButton() = default;
|
||||
UnitByteButton(const UnitByteButton&) = delete;
|
||||
UnitByteButton& operator=(const UnitByteButton&) = delete;
|
||||
|
||||
// Pass a I2C controller device
|
||||
// Probe and initialise. Returns false if not present.
|
||||
// Sets show mode to user-defined so setLed/flushLeds take effect.
|
||||
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
|
||||
bool isPresent() const { return dev_ != nullptr; }
|
||||
|
||||
// Read all 8 buttons as a bitmask. Bit i = button i (1=pressed).
|
||||
// Returns the bitmask via value; sets *ok=false on I2C error (ok may be nullptr).
|
||||
uint8_t readButtons(bool* ok = nullptr);
|
||||
|
||||
// Read individual button state (idx 0-7). Returns true if pressed.
|
||||
bool readButton(uint8_t idx);
|
||||
|
||||
// Set one LED (0x00RRGGBB, idx 0-7). Updates cache and sends immediately.
|
||||
void setLed(uint8_t idx, uint32_t rgb);
|
||||
|
||||
// Flush pending[BUTTON_COUNT] to hardware in one burst if anything changed
|
||||
// since the last flush. pending[] entries are 0x00RRGGBB.
|
||||
void flushLeds(const uint32_t pending[BUTTON_COUNT]);
|
||||
|
||||
// Set all 8 LEDs to one colour immediately.
|
||||
void setAllLeds(uint32_t rgb);
|
||||
|
||||
const uint32_t* ledColors() const { return ledColor_; }
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
uint32_t ledColor_[BUTTON_COUNT] = {};
|
||||
|
||||
static constexpr uint8_t REG_STATUS = 0x00;
|
||||
static constexpr uint8_t REG_SHOW_MODE = 0x19;
|
||||
static constexpr uint8_t REG_RGB888 = 0x20; // + i*4, LE uint32_t
|
||||
static constexpr uint8_t REG_STATUS_8 = 0x60; // + i, individual
|
||||
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* UnitCardKB2.h - M5Stack CardKB V2 keyboard unit
|
||||
*
|
||||
* Supports two connection modes:
|
||||
* I2C (addr 0x5F, factory default) - via GROVE port
|
||||
* UART (115200-8N-1) - via GROVE port after Fn+Sym+2 mode switch
|
||||
*
|
||||
* I2C: plain 1-byte read returns ASCII of held key (0 = none).
|
||||
*
|
||||
* UART frame: AA [DATA_LEN=0x03] [KEY_ID] [KEY_STATE] [checksum]
|
||||
* KEY_ID = row*11 + col (0-43, rows 0-3, cols 0-10)
|
||||
* KEY_STATE = 0x01 pressed, 0x02 released
|
||||
* checksum = (DATA_LEN + KEY_ID + KEY_STATE) & 0xFF
|
||||
*
|
||||
* Special keys (UART translation by this driver; NOT available in I2C mode):
|
||||
* Esc=0x1B Del=0x08 Enter=0x0A Space=0x20
|
||||
* Fn+D=0x1E(up) Fn+X=0x1F(down) Fn+Z=0x1D(left) Fn+C=0x1C(right)
|
||||
*
|
||||
* I2C mode: Fn+D/X/Z/C and Fn+1 (Esc) produce no output - the firmware only
|
||||
* pushes regular ASCII into the I2C queue; Fn combos go to BLE HID directly.
|
||||
* UART mode: Fn combos work; firmware sends raw KEY_ID, driver translates.
|
||||
* BLE HID mode: USB HID keycodes (ESC=0x29, arrows=0x4F-0x52), handled by
|
||||
* BluetoothHidHost → LV_KEY_*.
|
||||
*
|
||||
* Mode switch: Fn+Sym+1 = I2C, Fn+Sym+2 = UART (persists across reboot)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <tactility/drivers/uart_controller.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitCardKB2 {
|
||||
public:
|
||||
enum class Mode { I2C, Uart };
|
||||
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x5F;
|
||||
|
||||
UnitCardKB2() = default;
|
||||
~UnitCardKB2() { end(); }
|
||||
UnitCardKB2(const UnitCardKB2&) = delete;
|
||||
UnitCardKB2& operator=(const UnitCardKB2&) = delete;
|
||||
|
||||
// Pass a I2C controller device
|
||||
// I2C mode - probe and initialise. Returns false if not present.
|
||||
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
|
||||
// Pass a UART controller device
|
||||
// UART mode - set_config then open uart_controller.
|
||||
// Returns false if the device is null or open fails.
|
||||
[[nodiscard]] bool beginUart(Device* dev);
|
||||
|
||||
void end();
|
||||
|
||||
bool isPresent() const { return mode_ == Mode::I2C ? i2cDev_ != nullptr : uartDev_ != nullptr; }
|
||||
// Only meaningful when isPresent() returns true.
|
||||
// Call end() before switching modes (begin→beginUart or vice versa).
|
||||
Mode mode() const { return mode_; }
|
||||
|
||||
// Returns the ASCII value of the currently/last pressed key, or 0 if none.
|
||||
// In UART mode: drains available bytes from the UART RX buffer, parses frames,
|
||||
// returns the ASCII of the most recently pressed key (releases are ignored).
|
||||
// In I2C mode: behaviour unchanged from before.
|
||||
char getKey();
|
||||
|
||||
// Returns true if a key is currently held (I2C mode only; always false in UART mode).
|
||||
bool hasKey();
|
||||
|
||||
private:
|
||||
// I2C
|
||||
Device* i2cDev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
char cachedKey_ = 0;
|
||||
|
||||
// UART
|
||||
Device* uartDev_ = nullptr;
|
||||
|
||||
// Frame parser state
|
||||
enum class FrameState : uint8_t { WaitAA, WaitLen, WaitId, WaitState, WaitCsum };
|
||||
FrameState frameState_ = FrameState::WaitAA;
|
||||
uint8_t frameId_ = 0;
|
||||
uint8_t frameKs_ = 0;
|
||||
|
||||
// UART modifier state (tracked from key events)
|
||||
bool capsLock_ = false;
|
||||
bool symMode_ = false;
|
||||
bool fnHeld_ = false;
|
||||
bool oneShiftPending_ = false; // Aa single-tap one-shot uppercase
|
||||
uint32_t lastAATimestamp_ = 0; // ms timestamp of the Aa press that set oneShiftPending_
|
||||
|
||||
Mode mode_ = Mode::I2C;
|
||||
|
||||
char readFromI2C();
|
||||
char pollUart();
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* UnitCommon.h - shared I2C helpers for M5Stack STM32-based units
|
||||
*
|
||||
* All M5Stack Grove units with STM32 firmware require a STOP condition between
|
||||
* the register-pointer write and the data read (the STM32 HAL receive callback
|
||||
* populates tx_buffer after STOP, before the next START). Using a repeated-START
|
||||
* (the default i2c_controller_read_register) causes bus errors on every read.
|
||||
*
|
||||
* Pattern: write(reg) → STOP → vTaskDelay(2ms) → read(data)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <cstring>
|
||||
|
||||
static constexpr uint32_t UNIT_I2C_TIMEOUT_MS = 20;
|
||||
static constexpr uint32_t UNIT_I2C_PROBE_TIMEOUT_MS = 10; // shorter timeout for presence checks
|
||||
static constexpr uint32_t UNIT_STM32_READ_DELAY_MS = 2; // STM32 needs STOP + delay before read
|
||||
static constexpr uint16_t UNIT_MAX_WRITE_PAYLOAD = 32; // max bytes in a single I2C write
|
||||
|
||||
// Probe whether a device exists at the given address on the given I2C bus.
|
||||
inline bool unitProbe(Device* dev, uint8_t addr) {
|
||||
return i2c_controller_has_device_at_address(
|
||||
dev, addr, pdMS_TO_TICKS(UNIT_I2C_PROBE_TIMEOUT_MS)) == ERROR_NONE;
|
||||
}
|
||||
|
||||
// Write [reg, buf...] as a single transaction (STOP at end).
|
||||
inline bool unitWriteReg(Device* dev, uint8_t addr, uint8_t reg,
|
||||
const uint8_t* buf, uint16_t len) {
|
||||
if (len > UNIT_MAX_WRITE_PAYLOAD) return false;
|
||||
if (len > 0 && buf == nullptr) return false;
|
||||
uint8_t pkt[33];
|
||||
pkt[0] = reg;
|
||||
if (len) memcpy(pkt + 1, buf, len);
|
||||
return i2c_controller_write(dev, addr, pkt, (uint16_t)(len + 1),
|
||||
pdMS_TO_TICKS(UNIT_I2C_TIMEOUT_MS)) == ERROR_NONE;
|
||||
}
|
||||
|
||||
// Write register pointer only (no data payload) - convenience wrapper.
|
||||
inline bool unitWriteRegPtr(Device* dev, uint8_t addr, uint8_t reg) {
|
||||
return i2c_controller_write(dev, addr, ®, 1,
|
||||
pdMS_TO_TICKS(UNIT_I2C_TIMEOUT_MS)) == ERROR_NONE;
|
||||
}
|
||||
|
||||
// Read from a register: write pointer → STOP → 2ms delay → read.
|
||||
inline bool unitReadReg(Device* dev, uint8_t addr, uint8_t reg,
|
||||
uint8_t* buf, uint16_t len) {
|
||||
if (len > 0 && buf == nullptr) return false;
|
||||
if (!unitWriteRegPtr(dev, addr, reg)) return false;
|
||||
vTaskDelay(pdMS_TO_TICKS(UNIT_STM32_READ_DELAY_MS));
|
||||
return i2c_controller_read(dev, addr, buf, len,
|
||||
pdMS_TO_TICKS(UNIT_I2C_TIMEOUT_MS)) == ERROR_NONE;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* UnitDualButton.h - M5Stack Dual Button Unit (GPIO, Port B)
|
||||
*
|
||||
* Two independent momentary push buttons connected to GPIO pins.
|
||||
* Buttons are active-low - pressing pulls the pin to GND.
|
||||
* Internal pull-up resistors are enabled by the driver.
|
||||
*
|
||||
* Typical Port B pin assignment (M5Stack Core2):
|
||||
* Button A (red) - GPIO 36
|
||||
* Button B (blue) - GPIO 26
|
||||
*
|
||||
* Usage:
|
||||
* UnitDualButton btn;
|
||||
* Device* gpioCtrl = device_find_by_name("gpio0");
|
||||
* if (btn.begin(gpioCtrl, 36, 26)) {
|
||||
* bool a = btn.isButtonAPressed();
|
||||
* bool b = btn.isButtonBPressed();
|
||||
* }
|
||||
* // When done: btn.end() or let destructor release pins
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/gpio_controller.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitDualButton {
|
||||
public:
|
||||
~UnitDualButton();
|
||||
|
||||
UnitDualButton() = default;
|
||||
UnitDualButton(const UnitDualButton&) = delete;
|
||||
UnitDualButton& operator=(const UnitDualButton&) = delete;
|
||||
UnitDualButton(UnitDualButton&&) = delete;
|
||||
UnitDualButton& operator=(UnitDualButton&&) = delete;
|
||||
|
||||
// Pass a GPIO controller device
|
||||
// Acquire and configure both GPIO pins as inputs with pull-up.
|
||||
// controller: GPIO controller device (e.g. device_find_by_name("gpio0"))
|
||||
// pinA / pinB: physical GPIO pin numbers
|
||||
bool begin(Device* controller, gpio_pin_t pinA, gpio_pin_t pinB);
|
||||
|
||||
// Release GPIO descriptors. Safe to call multiple times.
|
||||
void end();
|
||||
|
||||
bool isPresent() const { return ready_; }
|
||||
|
||||
// True when button A is pressed (active-low: pin reads low).
|
||||
bool isButtonAPressed() const;
|
||||
|
||||
// True when button B is pressed (active-low: pin reads low).
|
||||
bool isButtonBPressed() const;
|
||||
|
||||
private:
|
||||
GpioDescriptor* descA_ = nullptr;
|
||||
GpioDescriptor* descB_ = nullptr;
|
||||
bool ready_ = false;
|
||||
|
||||
static bool readPin(GpioDescriptor* desc);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* UnitJoystick2.h - M5Stack Joystick2 Unit (STM32, I2C addr 0x63)
|
||||
*
|
||||
* XY analogue joystick with push button and one RGB LED.
|
||||
*
|
||||
* Register map:
|
||||
* 0x00 : uint16_t[2] X, Y raw 12-bit ADC (4 bytes LE, 0-4095)
|
||||
* 0x10 : uint8_t[2] X, Y 8-bit ADC (2 bytes)
|
||||
* 0x20 : uint8_t button (0=pressed, 1=released - inverted!)
|
||||
* 0x30 : uint32_t RGB LED colour (LE, 0x00RRGGBB)
|
||||
* 0x40 : uint16_t[8] calibration data (16 bytes)
|
||||
* 0x50 : int16_t[2] offset-corrected 12-bit X, Y
|
||||
* 0x60 : int8_t[2] offset-corrected 8-bit X, Y
|
||||
* 0xFE : uint8_t firmware version
|
||||
* 0xFF : uint8_t I2C address (R/W)
|
||||
*
|
||||
* Coordinate conventions after read:
|
||||
* Raw 12-bit: 0-4095, centre ~2048
|
||||
* Offset value: signed, centre = 0
|
||||
* Button: isPressed() returns true when button is pressed (inverts the 0=pressed register)
|
||||
*
|
||||
* Usage:
|
||||
* UnitJoystick2 joy;
|
||||
* if (joy.begin(dev)) { ... }
|
||||
* int16_t x, y; joy.readXY12(&x, &y); // offset-corrected, centre=0
|
||||
* bool btn = joy.isPressed();
|
||||
* joy.setLed(0xFF0000);
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitJoystick2 {
|
||||
public:
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x63;
|
||||
|
||||
// Pass a I2C controller device
|
||||
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
bool isPresent() const { return dev_ != nullptr; }
|
||||
|
||||
// Read offset-corrected 12-bit XY. Centre = 0, range ~±2048.
|
||||
bool readXY12(int16_t* x, int16_t* y);
|
||||
|
||||
// Read raw 12-bit XY (0-4095, unsigned). Centre ~2048.
|
||||
bool readXY12Raw(uint16_t* x, uint16_t* y);
|
||||
|
||||
// Read offset-corrected 8-bit XY. Centre = 0, range ~±128.
|
||||
bool readXY8(int8_t* x, int8_t* y);
|
||||
|
||||
// Button state. True = pressed (inverts hardware 0=pressed convention).
|
||||
bool isPressed() const;
|
||||
|
||||
// Set the RGB LED colour (0x00RRGGBB). Returns false on I2C error.
|
||||
bool setLed(uint32_t rgb);
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
|
||||
static constexpr uint8_t REG_ADC_12BIT = 0x00;
|
||||
static constexpr uint8_t REG_ADC_8BIT = 0x10;
|
||||
static constexpr uint8_t REG_BUTTON = 0x20;
|
||||
static constexpr uint8_t REG_RGB = 0x30;
|
||||
static constexpr uint8_t REG_OFFSET_12BIT = 0x50;
|
||||
static constexpr uint8_t REG_OFFSET_8BIT = 0x60;
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* UnitLcd.h - M5Stack LCD Unit (ESP32-PICO inside, I2C addr 0x3E)
|
||||
*
|
||||
* 135×240 IPS display driven by an internal ESP32 that bridges I2C commands
|
||||
* to an ST7789V2. Unlike the STM32-based units there is no register map -
|
||||
* instead you send drawing commands into a command buffer. The internal ESP32
|
||||
* processes them independently, so writes are fire-and-forget for most ops.
|
||||
*
|
||||
* Key differences from STM32 units:
|
||||
* - No STOP+delay+read pattern - standard repeated-START reads work fine
|
||||
* - No 2ms inter-transaction delay needed
|
||||
* - Pixel data goes in a single uninterrupted I2C transaction (WRITE_RAW)
|
||||
* - Check READ_BUFCOUNT (0x09) before large writes to avoid overflow
|
||||
* - Max I2C clock: 400 kHz
|
||||
*
|
||||
* Physical display: 135 wide × 240 tall (portrait, rotation 0/2)
|
||||
* 240 wide × 135 tall (landscape, rotation 1/3)
|
||||
*
|
||||
* Coordinate system: origin top-left, X right, Y down, in the current
|
||||
* rotation's logical space. Use width()/height() for safe bounds.
|
||||
*
|
||||
* Command quick-reference:
|
||||
* 0x22 [brightness] Set backlight (0=off, 255=max)
|
||||
* 0x36 [rotation] Set rotation (0-3 = 0/90/180/270°)
|
||||
* 0x2A [x0] [x1] Set X window (column range, inclusive)
|
||||
* 0x2B [y0] [y1] Set Y window (row range, inclusive)
|
||||
* 0x68 [x0][y0][x1][y1] Fill rect with stored colour
|
||||
* 0x6A [x0][y0][x1][y1][hi][lo] Fill rect with RGB565 colour inline
|
||||
* 0x62 [x][y][hi][lo] Draw pixel RGB565
|
||||
* 0x42 [pixels...] Write raw RGB565 stream (STOP ends it)
|
||||
* 0x09 Read buffer remaining count (1 byte)
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitLcd {
|
||||
public:
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x3E;
|
||||
static constexpr uint16_t PHYS_WIDTH = 135;
|
||||
static constexpr uint16_t PHYS_HEIGHT = 240;
|
||||
|
||||
// Legacy constants - equal to physical portrait dimensions.
|
||||
static constexpr uint16_t WIDTH = PHYS_WIDTH;
|
||||
static constexpr uint16_t HEIGHT = PHYS_HEIGHT;
|
||||
|
||||
UnitLcd() = default;
|
||||
UnitLcd(const UnitLcd&) = delete;
|
||||
UnitLcd& operator=(const UnitLcd&) = delete;
|
||||
|
||||
// Pass a I2C controller device
|
||||
// Probe and initialise. Sets brightness to 128, rotation to 0.
|
||||
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
|
||||
bool isPresent() const { return dev_ != nullptr; }
|
||||
|
||||
// Logical dimensions in the current rotation's coordinate space.
|
||||
uint16_t width() const { return (rotation_ & 1) ? PHYS_HEIGHT : PHYS_WIDTH; }
|
||||
uint16_t height() const { return (rotation_ & 1) ? PHYS_WIDTH : PHYS_HEIGHT; }
|
||||
uint8_t rotation() const { return rotation_; }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Control
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void setBrightness(uint8_t brightness);
|
||||
|
||||
// Rotation: 0=portrait(135×240), 1=landscape(240×135),
|
||||
// 2=portrait flipped, 3=landscape flipped.
|
||||
void setRotation(uint8_t rotation);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Filled primitives (fast hardware commands)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
void fillScreen(uint16_t rgb565);
|
||||
void fillRect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint16_t rgb565);
|
||||
void drawPixel(uint8_t x, uint8_t y, uint16_t rgb565);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Raw pixel streaming
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
bool setWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1);
|
||||
void writePixels(const uint16_t* pixels, uint32_t len);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Software-rendered shapes (Bresenham / midpoint algorithms)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Horizontal / vertical lines (faster than drawLine for axis-aligned).
|
||||
void drawHLine(uint8_t x, uint8_t y, uint8_t len, uint16_t rgb565);
|
||||
void drawVLine(uint8_t x, uint8_t y, uint8_t len, uint16_t rgb565);
|
||||
|
||||
// Arbitrary line (Bresenham).
|
||||
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t rgb565);
|
||||
|
||||
// Rectangle outline.
|
||||
void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t rgb565);
|
||||
|
||||
// Filled / outline circle (midpoint algorithm).
|
||||
void fillCircle(int16_t cx, int16_t cy, int16_t r, uint16_t rgb565);
|
||||
void drawCircle(int16_t cx, int16_t cy, int16_t r, uint16_t rgb565);
|
||||
|
||||
// Rounded rectangle (filled / outline).
|
||||
void fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t rgb565);
|
||||
void drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t rgb565);
|
||||
|
||||
// Triangle (filled / outline).
|
||||
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
|
||||
int16_t x2, int16_t y2, uint16_t rgb565);
|
||||
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
|
||||
int16_t x2, int16_t y2, uint16_t rgb565);
|
||||
|
||||
// Arc - angles in degrees (0=right, 90=down, 180=left, 270=up).
|
||||
// r0 = outer radius, r1 = inner radius (0 for solid filled wedge).
|
||||
// fillArc draws a filled arc/wedge; drawArc draws the outline only.
|
||||
void fillArc(int16_t cx, int16_t cy, int16_t r0, int16_t r1,
|
||||
float startDeg, float endDeg, uint16_t rgb565);
|
||||
void drawArc(int16_t cx, int16_t cy, int16_t r0, int16_t r1,
|
||||
float startDeg, float endDeg, uint16_t rgb565);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Text rendering - 5×7 bitmap font (ASCII 32-126)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// Draw a single character at (x, y). scale 1=5×7 px, 2=10×14 px, …
|
||||
void drawChar(uint8_t x, uint8_t y, char c, uint16_t fg, uint16_t bg, uint8_t scale = 1);
|
||||
|
||||
// Draw a null-terminated string starting at (x, y), advancing 6*scale px per char.
|
||||
void drawText(uint8_t x, uint8_t y, const char* str, uint16_t fg, uint16_t bg, uint8_t scale = 1);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Misc
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
uint8_t bufferRemaining();
|
||||
|
||||
static uint16_t rgb888to565(uint32_t rgb888) {
|
||||
uint8_t r = (rgb888 >> 16) & 0xFF;
|
||||
uint8_t g = (rgb888 >> 8) & 0xFF;
|
||||
uint8_t b = rgb888 & 0xFF;
|
||||
return (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
|
||||
}
|
||||
|
||||
// color565 alias used by M5GFX-style code
|
||||
static uint16_t color565(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
|
||||
}
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
uint8_t rotation_ = 0;
|
||||
|
||||
bool sendCmd(const uint8_t* data, uint16_t len);
|
||||
|
||||
// Helper: draw pixel only if in bounds (used by circle/line algorithms).
|
||||
void plotPixel(int16_t x, int16_t y, uint16_t rgb565);
|
||||
|
||||
// Helper: fill horizontal span, clamped to screen.
|
||||
void hspan(int16_t x, int16_t y, int16_t len, uint16_t rgb565);
|
||||
|
||||
// Octant helper for circle algorithms.
|
||||
void circleOctants(int16_t cx, int16_t cy, int16_t x, int16_t y,
|
||||
uint16_t rgb565, bool fill);
|
||||
|
||||
// Arc pixel helper - plots or fills one pixel/column depending on fill flag.
|
||||
void arcImpl(int16_t cx, int16_t cy, int16_t r0, int16_t r1,
|
||||
float startDeg, float endDeg, uint16_t rgb565, bool fill);
|
||||
|
||||
// ESP32 I2C driver buffer is 256 bytes. With 1 command byte, 255 bytes remain.
|
||||
// 255 / 2 bytes per pixel = 127, rounded down to 126 for alignment.
|
||||
static constexpr uint16_t CHUNK_PIXELS = 126;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* UnitMidi.h - M5Stack MIDI Unit / Synth Unit (SAM2695, UART 31250 bps)
|
||||
*
|
||||
* Both the MIDI Unit and the Synth Unit use the SAM2695 audio synthesizer IC
|
||||
* connected via a standard MIDI UART link. This driver covers both.
|
||||
*
|
||||
* SAM2695 specs:
|
||||
* - 64-voice polyphony (38 with effects)
|
||||
* - 16 MIDI channels
|
||||
* - MIDI 1.0 protocol, 31250 bps, 8N1
|
||||
*
|
||||
* Usage:
|
||||
* Device* uart = device_find_by_name("uart1");
|
||||
* UnitMidi midi;
|
||||
* if (midi.begin(uart)) {
|
||||
* midi.programChange(0, 0); // channel 0, Grand Piano
|
||||
* midi.noteOn(0, 60, 100); // middle C, velocity 100
|
||||
* vTaskDelay(pdMS_TO_TICKS(500));
|
||||
* midi.noteOff(0, 60);
|
||||
* }
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitMidi {
|
||||
public:
|
||||
UnitMidi() = default;
|
||||
~UnitMidi() { end(); }
|
||||
UnitMidi(const UnitMidi&) = delete;
|
||||
UnitMidi& operator=(const UnitMidi&) = delete;
|
||||
UnitMidi(UnitMidi&&) = delete;
|
||||
UnitMidi& operator=(UnitMidi&&) = delete;
|
||||
|
||||
// Pass a UART controller device
|
||||
// begin() opens the controller, sets baud rate to 31250, and issues a system reset.
|
||||
[[nodiscard]] bool begin(Device* dev);
|
||||
|
||||
void end();
|
||||
|
||||
bool isPresent() const { return ready_; }
|
||||
|
||||
// Send raw MIDI bytes.
|
||||
void send(const uint8_t* data, size_t len);
|
||||
|
||||
// MIDI 1.0 message helpers
|
||||
void reset(); // 0xFF - system reset
|
||||
void noteOn(uint8_t channel, uint8_t note, uint8_t velocity); // 0x90|ch, note, vel
|
||||
void noteOff(uint8_t channel, uint8_t note); // 0x80|ch, note, 0
|
||||
void programChange(uint8_t channel, uint8_t program); // 0xC0|ch, program
|
||||
void controlChange(uint8_t channel, uint8_t controller, uint8_t value); // 0xB0|ch, ctrl, val
|
||||
void pitchBend(uint8_t channel, int16_t value); // 0xE0|ch, LSB, MSB (-8192..+8191)
|
||||
void allNotesOff(uint8_t channel); // CC 123 = 0
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
bool ready_ = false;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* UnitPaHub.h - M5Stack PaHub v2 I2C multiplexer (TCA9548A, addr 0x70)
|
||||
*
|
||||
* 6-channel I2C mux. Selecting a channel routes the downstream bus to that
|
||||
* port only. Selecting channel 255 (or calling deselect()) disables all ports.
|
||||
*
|
||||
* Usage:
|
||||
* UnitPaHub hub;
|
||||
* if (hub.begin(dev)) {
|
||||
* hub.select(0); // enable channel 0
|
||||
* enc.begin(dev); // devices on channel 0 now reachable
|
||||
* hub.deselect(); // disable all channels when done
|
||||
* }
|
||||
*
|
||||
* Note: for devices that stay selected for extended periods (e.g. polling),
|
||||
* leave the channel selected - only call deselect() if you need to prevent
|
||||
* address conflicts between units on different channels.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitPaHub {
|
||||
public:
|
||||
UnitPaHub() = default;
|
||||
UnitPaHub(const UnitPaHub&) = delete;
|
||||
UnitPaHub& operator=(const UnitPaHub&) = delete;
|
||||
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x70;
|
||||
static constexpr uint8_t NUM_CHANNELS = 6;
|
||||
static constexpr uint8_t NO_CHANNEL = 0xFF;
|
||||
|
||||
// Pass a I2C controller device
|
||||
bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
bool isPresent() const { return dev_ != nullptr; }
|
||||
|
||||
// Select a single channel (0-5). Returns false on error.
|
||||
bool select(uint8_t channel);
|
||||
|
||||
// Disable all channels.
|
||||
bool deselect();
|
||||
|
||||
uint8_t currentChannel() const { return channel_; }
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
uint8_t channel_ = NO_CHANNEL;
|
||||
};
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 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;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* UnitScroll.h - M5Stack Scroll Unit (STM32, I2C addr 0x40)
|
||||
*
|
||||
* Rotary encoder with push button and one RGB LED.
|
||||
*
|
||||
* Register map:
|
||||
* 0x10 : int16_t absolute encoder value
|
||||
* 0x20 : uint8_t button (0=pressed, 1=released - inverted!)
|
||||
* 0x30 : 4 bytes LED: [index(0), R, G, B] - index byte always 0
|
||||
* 0x40 : uint8_t write 1 to reset encoder to 0
|
||||
* 0x50 : int16_t increment (delta) since last read - resets after read
|
||||
* 0xFE : uint8_t firmware version
|
||||
* 0xFF : uint8_t I2C address (R/W)
|
||||
*
|
||||
* Usage:
|
||||
* UnitScroll scroll;
|
||||
* if (scroll.begin(dev)) { ... }
|
||||
* int16_t delta = scroll.readDelta(); // ±detents since last call
|
||||
* bool btn = scroll.isPressed();
|
||||
* scroll.setLed(0x00FF00);
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <UnitCommon.h>
|
||||
#include <cstdint>
|
||||
|
||||
class UnitScroll {
|
||||
public:
|
||||
static constexpr uint8_t DEFAULT_ADDR = 0x40;
|
||||
|
||||
UnitScroll() = default;
|
||||
UnitScroll(const UnitScroll&) = delete;
|
||||
UnitScroll& operator=(const UnitScroll&) = delete;
|
||||
|
||||
// Pass a I2C controller device
|
||||
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
|
||||
bool isPresent() const { return dev_ != nullptr; }
|
||||
|
||||
// Read increment (delta) since last call. Resets hardware counter.
|
||||
int16_t readDelta();
|
||||
|
||||
// Read absolute encoder value.
|
||||
int16_t readAbsolute() const;
|
||||
|
||||
// Button state. True = pressed (register value 0 = pressed, inverted hardware).
|
||||
bool isPressed() const;
|
||||
|
||||
// Set the RGB LED colour (0x00RRGGBB).
|
||||
void setLed(uint32_t rgb);
|
||||
|
||||
// Reset absolute encoder to 0.
|
||||
void resetEncoder();
|
||||
|
||||
private:
|
||||
Device* dev_ = nullptr;
|
||||
uint8_t addr_ = DEFAULT_ADDR;
|
||||
|
||||
static constexpr uint8_t REG_ENCODER = 0x10;
|
||||
static constexpr uint8_t REG_BUTTON = 0x20;
|
||||
static constexpr uint8_t REG_LED = 0x30;
|
||||
static constexpr uint8_t REG_RESET = 0x40;
|
||||
static constexpr uint8_t REG_INC_ENCODER = 0x50;
|
||||
};
|
||||
Reference in New Issue
Block a user