|
XPoint 0.1.0
Hardware-agnostic crosspoint matrix routing library
|
A small, hardware-agnostic C++11 library for managing crosspoint matrices and signal routing on Arduino and PlatformIO targets. Designed to work on everything from AVR (Uno/Nano) to ESP32 and ARM without pulling in the C++ standard library.
connect(row, col) / disconnect(row, col) / setLevel(row, col, level) APIXPointStatic<ROWS, COLS> — zero-heap variant with compile-time dimensions for AVRdelay())XPointDriver) — swap hardware without touching application codestd::vector, no std::function, no exceptions — compatible with avr-libcAdd to your platformio.ini:
Heap-allocating (recommended for ESP32, STM32, native)
Uses new[]/delete[]. Dimensions are set at runtime. Suitable for any target with adequate heap.
**XPointStatic<ROWS, COLS> (recommended for AVR / zero-heap)**
All state arrays live inside the object — no heap allocation. Dimensions must be known at compile time. Safe to declare globally on an ATmega328P.
User-buffer (maximum control)
Pass your own pre-allocated word array; the object does not own or free it. Use XPoint::poolWords(rows, cols) to compute the required array length.
Total SRAM consumed = class overhead + bit pool.
Bit pool — all three state sections (connection state, interlock map, exclusive columns) are packed into one flat uint32_t array:
| Section | Bits |
|---|---|
| state | rows × cols |
| ilock | rows × rows |
| excl | cols |
| Total bits | rows×cols + rows² + cols |
| Pool words | ceil(total_bits / 32) |
| Pool bytes | pool_words × 4 |
Common matrix sizes after bit-packing:
| Matrix | Total bits | Pool words | Pool bytes |
|---|---|---|---|
| 2×8 | 28 | 1 | 4 |
| 4×4 | 36 | 2 | 8 |
| 1×12 | 25 | 1 | 4 |
| 4×8 | 56 | 2 | 8 |
| 8×8 | 136 | 5 | 20 |
| 8×16 | 208 | 7 | 28 |
| 16×16 | 528 | 17 | 68 |
Class overhead is fixed per XPoint instance, regardless of matrix size:
| Platform | sizeof(XPoint) | sizeof(PulseEvent) |
|---|---|---|
| AVR (ATmega328P, 8-bit, 2-byte pointers) | ~70 B | 7 B (packed) |
| 32-bit ARM / ESP32 | ~80 B | 7 B (packed) |
| 64-bit host (measured) | 96 B | 7 B (packed) |
The pulse table (PulseEvent _pulses[8]) contributes 56 B on all platforms thanks to __attribute__((packed)).
Verify on your target with:
Run ./test_xpoint --sizes on the host to print a full size table (also written to test/TEST_REPORT.md).
Heap vs. XPointStatic: both strategies consume the same total bytes. XPointStatic<R,C> embeds the pool buffer directly in the object (BSS / global storage), so the heap is never touched and there is no fragmentation risk.
setLevel(row, col, level) calls driver->setNodeLevel() instead of setNodeHardware(). Binary drivers (GPIO, shift register) treat level > 0 as on and 0 as off. PWM-capable drivers (TLC59711) set the actual fractional output:
Interlock and exclusive-input protections apply to setLevel the same as connect.
Call matrix.update() in loop(). It de-energizes coils automatically after the pulse duration via driver->releaseNode().
All drivers are in src/drivers/. Arduino-specific drivers compile only when ARDUINO is defined; host-side stubs are provided for testing.
| Driver | File | Use case |
|---|---|---|
ArduinoDirectGPIODriver | ArduinoDirectGPIODriver.* | One MCU pin per node via digitalWrite |
ArduinoShiftRegisterDriver | ArduinoShiftRegisterDriver.* | 74HC595 chain driven by digitalWrite |
MCP23017Driver | MCP23017Driver.* | MCP23017 16-bit I2C GPIO expander |
TLC59711Driver | TLC59711Driver.* | TLC59711 12-channel 16-bit PWM SPI expander |
TCA9548AInterface | TCA9548AInterface.h | I2C bus-mux decorator — transparent channel select |
DirectGPIODriver | DirectGPIODriver.* | Virtual pin-state driver (testing / simulation) |
ShiftRegisterDriver | ShiftRegisterDriver.* | Virtual byte-shadow shift register (testing / simulation) |
begin() configures only the pins your mapper actually returns, so serial/I2C/SPI pins are never disturbed.
commitPhysicalUpdates() shifts bytes MSB-first, last register first (standard 74HC595 daisy-chain order).
Up to 8 MCP23017s can share one I2C bus (address pins A0–A2). Use a transistor stage when driving relay coils.
TCA9548AInterface is a decorator that wraps any I2CInterface and transparently selects a TCA9548A / PCA9548A channel before forwarding each write. Pass it as the I2CInterface* to MCP23017Driver; neither the matrix nor the driver knows a mux is present.
Capacity: one TCA9548A provides 8 segments; up to 8 TCA9548As share one bus (addresses 0x70–0x77), giving 64 segments × 8 MCP23017s per segment = 8192 I/O pins from a single I2C bus.
Performance: a per-mux channel cache (8 bytes of BSS) suppresses the channel-select byte on cache hits. After warm-up, relay operations on the same channel incur zero extra I2C transactions.
No changes to XPoint, MCP23017Driver, or any other class.
commitPhysicalUpdates() assembles the correct 28-byte-per-chip SPI packet (4-byte control word + GS11→GS0 channel order) and transfers it once.
Inherit from XPointDriver and implement begin() and setNodeHardware(). All other methods have default no-op implementations.
| XPoint call | setNodeHardware state | Meaning |
|---|---|---|
connect() | true | Pulse SET coil |
disconnect() | false | Pulse RESET coil |
update() expires | releaseNode() | De-energize coil |
Drivers take a plain C function pointer whose return type matches the driver:
| Driver | MapFn return type | Range |
|---|---|---|
ArduinoDirectGPIODriver | uint8_t | Arduino pin number |
ArduinoShiftRegisterDriver | uint16_t | bit index in SR chain |
MCP23017Driver | uint8_t | pin index 0–15 |
TLC59711Driver | uint16_t | channel index 0–(N×12) |
Non-capturing lambdas convert to function pointers automatically in C++11:
HC595Helper::rowMajorIndex(row, col, cols) computes a row-major shift-register bit index inline.
See test/TESTS.md for the full test suite description, build instructions, and mock infrastructure reference.
The suite has 49 tests across five categories (existing, limit, range, gap, json) plus one opt-in sizes test. Skip categories with --skip-<name> or use --fast to skip the 256×256 full-range sweep. Pass --sizes to add platform size measurements to the run.
Results are written to test/TEST_REPORT.md on every run.
GitHub Actions runs on every push and PR to main/master:
g++ compiles and runs test/test_xpoint.cpp (48 tests by default; 49 with --sizes)TEST_REPORT.md) and deploys to GitHub PagesAGPL-3.0-only — Copyright (c) 2026 Douglas Quigg (dstroy0) <dquig.nosp@m.g123.nosp@m.@gmai.nosp@m.l.co.nosp@m.m>