From 898e98bbfe34422552679b19f0709668d2dc3f7d Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Mon, 17 Nov 2025 23:16:23 +0100 Subject: [PATCH 01/17] migrsated switch reading to PIO --- .gitignore | 3 +- .vscode/c_cpp_properties.json | 6 +- src/IOBoardController.cpp | 9 +-- src/IODevices/Switches.cpp | 135 +++++++++++++++------------------- src/IODevices/Switches.h | 48 ++++++++---- src/IODevices/Switches.pio | 37 ++++++++++ 6 files changed, 137 insertions(+), 101 deletions(-) create mode 100644 src/IODevices/Switches.pio diff --git a/.gitignore b/.gitignore index bf10959..5286d05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea .pio -.DS_Store \ No newline at end of file +.DS_Store +*.pio.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 1fb4880..f6141a2 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,8 +3,10 @@ { "name": "Mac", "includePath": [ - "${workspaceFolder}/**", - "${env:HOME}/.platformio/packages/framework-arduinopico/**" + "${workspaceFolder}/src", + "${workspaceFolder}/.pio/libdeps/IO_16_8_1/**", + "${env:HOME}/.platformio/packages/framework-arduinopico/**", + "/Library/Developer/CommandLineTools/usr/lib/clang/*/include" ], "defines": [], "intelliSenseMode": "macos-clang-arm64" diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index 858bff1..fc5d58f 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -31,9 +31,6 @@ IOBoardController::IOBoardController(int cT) { void IOBoardController::update() { if (running) { - if (activeSwitches) { - switches()->update(); - } if (activeSwitchMatrix) { switchMatrix()->update(); } @@ -75,7 +72,6 @@ void IOBoardController::handleEvent(Event *event) { case EVENT_RESET: // Clear all configurations or reboot the device. _pwmDevices->reset(); - _switches->reset(); _switchMatrix->reset(); // Issue a delayed reset of the board. @@ -95,10 +91,7 @@ void IOBoardController::handleEvent(ConfigEvent *event) { port = event->value; break; case CONFIG_TOPIC_NUMBER: - // Ports 15-18 (labeled as 13-16) of IO_16_8_1 are stateful. - _switches->registerSwitch((byte)port, event->value, - (controllerType == CONTROLLER_16_8_1 && - port >= 15 && port <= 18)); + _switches->registerSwitch((byte)port, event->value); activeSwitches = true; break; } diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index fc38531..e2726f7 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -1,98 +1,85 @@ #include "Switches.h" -void Switches::registerSwitch(byte p, byte n, bool s) { - if (last < (MAX_SWITCHES - 1)) { - if (s) { - resetStatefulPort(p); - } +#include "Switches.pio.h" - pinMode(p, INPUT); +Switches* Switches::instance = nullptr; + +void Switches::registerSwitch(byte p, byte n) { + if (last < (MAX_SWITCHES - 1)) { port[++last] = p; number[last] = n; - toggled[last] = false; - stateful[last] = s; - delayMicroseconds(10); - // Note, we have active LOW! - state[last] = !digitalRead(p); + active = true; } } -void Switches::resetStatefulPort(byte p) { - // Set mid power output as input. - pinMode(p, OUTPUT); - digitalWrite(p, HIGH); - pinMode(p, INPUT); -} +void Switches::handleSwitchChanges(uint16_t raw) { + absolute_time_t now = get_absolute_time(); + uint16_t changed = raw ^ lastStable; -void Switches::reset() { - for (uint8_t i = 0; i < MAX_SWITCHES; i++) { - if (stateful[i]) resetStatefulPort(port[i]); - } + for (int i = 0; i < MAX_SWITCHES; i++) { + uint16_t mask = 1u << i; - for (uint8_t i = 0; i < MAX_SWITCHES; i++) { - port[i] = 0; - number[i] = 0; - state[i] = 0; - toggled[i] = false; - stateful[i] = false; - } - - last = -1; -} - -void Switches::update() { - // Wait for SWITCH_DEBOUNCE milliseconds to debounce the switches. That covers - // the edge case that a switch was hit right before the last polling of - // events. After SWITCH_DEBOUNCE milliseconds every switch is allowed to - // toggle once until the events get polled again. - if (millis() - _ms >= SWITCH_DEBOUNCE) { - for (int i = 0; i <= last; i++) { - if (!toggled[i]) { - // Note, we have active LOW! - bool new_state = !digitalRead(port[i]); - if (new_state != state[i]) { - state[i] = new_state; - toggled[i] = true; - // Dispatch all switch events as "local fast". - // If a PWM output registered to it, we have "fast flip". Useful for - // flippers, kick backs, jets and sling shots. - _eventDispatcher->dispatch(new Event( - EVENT_SOURCE_SWITCH, word(0, number[i]), state[i], true)); - } + if (changed & mask) { + // Debounce + if (absolute_time_diff_us(debounceTime[i], now) >= + SWITCH_DEBOUNCE * 1000) { + bool newState = !(raw & mask); // active-low + debounceTime[i] = now; + lastStable = (lastStable & ~mask) | (newState ? mask : 0); + // Dispatch all switch events as "local fast". + // If a PWM output registered to it, we have "fast flip". Useful for + // flippers, kick backs, jets and sling shots. + _eventDispatcher->dispatch( + new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), newState, true)); } } } } -void Switches::handleEvent(Event *event) { +void Switches::handleEvent(Event* event) { switch (event->sourceId) { - case EVENT_POLL_EVENTS: - if (running && boardId == (byte)event->value) { - // This I/O board has been polled for events, so all current switch - // states are transmitted. Reset switch debounce timer and toggles. - _ms = millis(); + case EVENT_READ_SWITCHES: + // The CPU requested all current states. Usually this event is sent when + // the game gets started. + if (active) { + // First, send OFF for all switches then ON for the active ones using + // the IRQ handler. for (int i = 0; i <= last; i++) { - toggled[i] = false; - if (stateful[i]) resetStatefulPort(port[i]); + _eventDispatcher->dispatch( + new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), 0)); } - } - break; - case EVENT_READ_SWITCHES: - // The CPU requested all current states. - for (int i = 0; i <= last; i++) { - // Send all states of switches that haven't been toggled since last poll - // (and dispatched their event already). - if (!toggled[i]) { - _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), state[i])); - } else { - toggled[i] = false; - if (stateful[i]) resetStatefulPort(port[i]); + if (!running) { + instance = this; + running = true; + + extern const pio_program_t switches_pio_program; + uint offset = pio_add_program(pio, &switches_pio_program); + pio_sm_config c = switches_pio_program_get_default_config(offset); + + sm_config_set_in_pins(&c, SWITCHES_BASE_PIN); + sm_config_set_sideset_pins(&c, 15); // Side-set begins at GPIO 15 + sm_config_set_set_pins(&c, 15, 4); // Set begins at GPIO 15 + + // Connect 16 GPIOs to this PIO block + for (uint i = 0; i < 16; i++) { + pio_gpio_init(pio, SWITCHES_BASE_PIN + i); + } + + // Set the pin direction at the PIO + pio_sm_set_consecutive_pindirs(pio, sm, SWITCHES_BASE_PIN, 16, false); + + sm_config_set_in_shift(&c, true, false, 16); + + pio_sm_init(pio, sm, offset, &c); + + irq_set_exclusive_handler(PIO0_IRQ_0, onSwitchCanges); + irq_set_enabled(PIO0_IRQ_0, true); + pio_set_irq0_source_enabled(pio, pis_interrupt0, true); + + pio_sm_set_enabled(pio, sm, true); } } - _ms = millis(); - running = true; break; } } diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index 213ae2c..01430a7 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -12,49 +12,65 @@ #include "../EventDispatcher/Event.h" #include "../EventDispatcher/EventDispatcher.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" -#ifndef MAX_SWITCHES +#define SWITCHES_BASE_PIN 3 #define MAX_SWITCHES 16 -#endif - -#ifndef SWITCH_DEBOUNCE #define SWITCH_DEBOUNCE 2 -#endif class Switches : public EventListener { public: Switches(byte bId, EventDispatcher* eD) { boardId = bId; - _ms = millis(); _eventDispatcher = eD; _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); - } - void registerSwitch(byte p, byte n, bool stateful = false); + pio = pio0; + sm = 0; - void update(); - void reset(); + lastStable = 0; + for (int i = 0; i < MAX_SWITCHES; i++) { + debounceTime[i] = 0; + } + } + + void registerSwitch(byte p, byte n); void handleEvent(Event* event); void handleEvent(ConfigEvent* event) {} - private: - void resetStatefulPort(byte p); + void handleSwitchChanges(uint16_t raw); + PIO pio; + int sm; + + private: byte boardId; - unsigned long _ms; bool running = false; + bool active = false; byte port[MAX_SWITCHES] = {0}; byte number[MAX_SWITCHES] = {0}; - bool state[MAX_SWITCHES] = {0}; - bool toggled[MAX_SWITCHES] = {0}; - bool stateful[MAX_SWITCHES] = {0}; int last = -1; + uint16_t lastStable; + absolute_time_t debounceTime[MAX_SWITCHES]; + EventDispatcher* _eventDispatcher; + + static Switches* instance; + + static void __not_in_flash_func(onSwitchCanges)() { + // IRQ0 clear + pio0_hw->irq = 1u << 0; + + // Get 16 bit from FIFO + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm); + instance->handleSwitchChanges(raw & 0xFFFF); + } }; #endif diff --git a/src/IODevices/Switches.pio b/src/IODevices/Switches.pio new file mode 100644 index 0000000..6dfe53b --- /dev/null +++ b/src/IODevices/Switches.pio @@ -0,0 +1,37 @@ +.program switches_pio +.side_set 4 opt + + ; Initialize X to all pins HIGH. Max. 16 switches, so 16 bit, so 0xFFFF: + ; But "set" can only set 5 bits, so a max value of 31 (0x1F). + set x, 31 ; X = 0x1F + in x, 5 ; shift 5 bits from X into ISR, now ISR = 0x1F + in x, 5 ; shift 5 bits from X into ISR, now ISR = 0x3FF + in x, 5 ; shift 5 bits from X into ISR, now ISR = 0x7FFF + in x, 1 ; shift 1 bit from X into ISR, now ISR = 0xFFFF + mov x, isr ; copy ISR to X, now X = 0xFFFF + set y, 0 ; Y = 0x0 + +loop: + mov isr, y ; ISR = 0x0 + in pins, 16 ; read 16 switches to ISR (filling lower 16 bits of the 32bit ISR) + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a switch state has changed + mov x, y ; update X to the new switch states + set y, 0 ; Y = 0x0 + + ; Reset stateful pins (GPIO 15-18) + set pindirs, 0b1111 side 0 ; change direction of 4 pins (15-18) to output, set LOW + nop side 0b1111 ; set 4 pins to HIGH + nop side 0b1111 ; short delay + nop side 0b1111 ; short delay + nop side 0b1111 ; short delay + nop side 0 ; set LOW + set pindirs, 0 side 0 ; change direction all pins back to input + + jmp loop From a5c8e3fbe181f57f694ac800f398a441c0f8ff3a Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Wed, 19 Nov 2025 12:55:37 +0100 Subject: [PATCH 02/17] migrated 8x8 matrix to PIOs --- src/IODevices/SwitchMatrix.pio | 149 +++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 src/IODevices/SwitchMatrix.pio diff --git a/src/IODevices/SwitchMatrix.pio b/src/IODevices/SwitchMatrix.pio new file mode 100644 index 0000000..eeb6014 --- /dev/null +++ b/src/IODevices/SwitchMatrix.pio @@ -0,0 +1,149 @@ +.program columns_active_high_pio +.wrap_target + set x, 1 + mov isr, x ; initialize ISR with 0x00000001 + set x, 0 ; X = 0 + set y, 7 ; 8 columns (0-7) + +loop: + mov osr, isr ; copy ISR to OSR for output + out pins, 8 ; set 8 pins for columns, one is HIGH, others LOW + in x, 1 ; shift left ISR by 1 bit + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap + + +.program odd_rows_active_high_pio + ; Initialize X to all pins LOW. Max. 4x8 rows, so 32 bit, so 0x0: + set x, 0 ; X = 0x0 + mov y, x ; Y = 0x0 + mov isr, y ; ISR = 0x0 + +loop: + wait 0 PIN 8 ; wait for odd column 1 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 10 ; wait for odd column 3 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 12 ; wait for odd column 5 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 14 ; wait for odd column 7 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed + mov x, y ; update X to the new odd columns state + jmp loop + + +.program even_rows_active_high_pio + ; Initialize X to all pins LOW. Max. 4x8 rows, so 32 bit, so 0x0: + set x, 0 ; X = 0x0 + mov y, x ; Y = 0x0 + mov isr, y ; ISR = 0x0 + +loop: + wait 0 PIN 9 ; wait for even column 2 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 11 ; wait for even column 4 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 13 ; wait for even column 6 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 15 ; wait for even column 8 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed + mov x, y ; update X to the new switch states + jmp loop + + +; ---------------------------------------------------------------------------------------------------------------------------- +; ---------------------------------------------------------------------------------------------------------------------------- + + +.program columns_active_low_pio +.wrap_target + set y, 1 + mov isr, ~y ; initialize ISR with 0xFFFFFFFE + set y, 0 ; Y = 0 + mov x, ~y ; X = 0xFFFFFFFF + set y, 7 ; 8 columns (0-7) + +loop: + mov osr, isr ; copy ISR to OSR for output + out pins, 8 ; set 8 pins for columns, one is HIGH, others LOW + in x, 1 ; shift left ISR by 1 bit + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap + + +.program odd_rows_active_low_pio + ; Initialize X to all pins HIGH. Max. 4x8 rows, so 32 bit: + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 PIN 8 ; wait for odd column 1 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 10 ; wait for odd column 3 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 12 ; wait for odd column 5 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 14 ; wait for odd column 7 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed + mov x, y ; update X to the new odd columns state + jmp loop + + +.program even_rows_active_low_pio + ; Initialize X to all pins HIGH. Max. 4x8 rows, so 32 bit: + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 PIN 9 ; wait for even column 2 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 11 ; wait for even column 4 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 13 ; wait for even column 6 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 15 ; wait for even column 8 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed + mov x, y ; update X to the new switch states + jmp loop From b8d73549aecf8d5e1bdb8d7f72769b4dcafce783 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Wed, 19 Nov 2025 12:56:26 +0100 Subject: [PATCH 03/17] 8x8 matrix PIO --- .vscode/c_cpp_properties.json | 463 ++++++++++++++++++++++++++++++++- src/EventDispatcher/Event.h | 3 - src/IOBoardController.cpp | 26 +- src/IOBoardController.h | 4 +- src/IODevices/SwitchMatrix.cpp | 240 ++++++++++------- src/IODevices/SwitchMatrix.h | 81 +++--- src/IODevices/Switches.cpp | 26 +- src/IODevices/Switches.h | 11 +- 8 files changed, 678 insertions(+), 176 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index f6141a2..106f88c 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,16 +1,463 @@ +// +// !!! WARNING !!! AUTO-GENERATED FILE! +// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": +// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags +// { "configurations": [ { - "name": "Mac", + "name": "PlatformIO", "includePath": [ - "${workspaceFolder}/src", - "${workspaceFolder}/.pio/libdeps/IO_16_8_1/**", - "${env:HOME}/.platformio/packages/framework-arduinopico/**", - "/Library/Developer/CommandLineTools/usr/lib/clang/*/include" + "/Volumes/data/workspace/PPUC/io-boards/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/RPI_PICO_TimerInterrupt/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Bounce2/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WS2812FX/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Adafruit NeoPixel", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WavePWM/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated-avr-comp", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040/pico_base", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_regs/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_structs/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/pico_platform/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_btstack/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_arch/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_driver/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/cyw43-driver/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/decoder/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/encoder/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/yxml", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/platform/embedded", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RP2040/Include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/tinyusb/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/boards/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/hardware_claim/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_base_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_binary_info/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_sync/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_time/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_util/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_stdlib_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_usb_reset_interface_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/boot_bootrom_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Core/Include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_adc/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_base/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_boot_lock/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_clocks/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_divider/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_dma/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_exception/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_flash/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_gpio/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_i2c/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_interp/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_irq/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_rtc/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pio/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pll/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pwm/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_resets/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_spi/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_timer/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_uart/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_vreg/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_watchdog/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_xosc/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_aon_timer/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_async_context/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_bootrom/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_double/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_flash/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_float/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_int64_ops/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_lwip/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_multicore/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_common/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_compiler/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_sections/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_panic/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_printf/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime_init/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_rand/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio_uart/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_unique_id/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/lwip/src/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/freertos", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/variants/rpipico", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ADCInput/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ArduinoOTA/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AsyncUDP/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AudioBufferManager/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BTstackLib/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothAudio/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHCI/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHIDMaster/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/DNSServer/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/EEPROM/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ESPHost/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFSUSB/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Bluetooth/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Joystick/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Keyboard/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Mouse/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPClient/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdate/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdateServer/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Hash/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/I2S/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Joystick/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBLE/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Keyboard/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBLE/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LEAmDNS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LittleFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MD5Builder/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MIDIUSB/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Mouse/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseAbsolute/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBLE/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/NetBIOS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PDM/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PWMAudio/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PicoOTA/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SD/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SDFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPI/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPISlave/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SdFat/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SerialBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Servo/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SimpleMDNS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SingleFileDrive/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SoftwareSPI/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Ticker/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Updater/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/VFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WebServer/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WiFi/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Wire/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/http-parser/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_CYW43/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_ESPHost/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_Ethernet/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_WINC1500/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_enc28j60/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5100/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5500/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w55rp20/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6100/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6300/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2040", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2350", + "" ], - "defines": [], - "intelliSenseMode": "macos-clang-arm64" + "browse": { + "limitSymbolsToIncludedHeaders": true, + "path": [ + "/Volumes/data/workspace/PPUC/io-boards/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/RPI_PICO_TimerInterrupt/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Bounce2/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WS2812FX/src", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Adafruit NeoPixel", + "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WavePWM/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated-avr-comp", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040/pico_base", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_regs/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_structs/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/pico_platform/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_btstack/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_arch/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_driver/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/cyw43-driver/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/decoder/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/encoder/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/yxml", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/platform/embedded", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RP2040/Include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/tinyusb/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/boards/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/hardware_claim/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_base_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_binary_info/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_sync/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_time/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_util/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_stdlib_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_usb_reset_interface_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/boot_bootrom_headers/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Core/Include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_adc/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_base/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_boot_lock/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_clocks/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_divider/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_dma/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_exception/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_flash/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_gpio/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_i2c/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_interp/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_irq/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_rtc/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pio/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pll/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pwm/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_resets/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_spi/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_timer/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_uart/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_vreg/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_watchdog/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_xosc/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_aon_timer/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_async_context/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_bootrom/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_double/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_flash/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_float/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_int64_ops/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_lwip/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_multicore/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_common/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_compiler/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_sections/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_panic/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_printf/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime_init/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_rand/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio_uart/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_unique_id/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/lwip/src/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/freertos", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/variants/rpipico", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ADCInput/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ArduinoOTA/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AsyncUDP/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AudioBufferManager/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BTstackLib/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothAudio/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHCI/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHIDMaster/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/DNSServer/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/EEPROM/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ESPHost/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFSUSB/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Bluetooth/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Joystick/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Keyboard/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Mouse/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPClient/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdate/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdateServer/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Hash/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/I2S/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Joystick/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBLE/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Keyboard/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBLE/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LEAmDNS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LittleFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MD5Builder/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MIDIUSB/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Mouse/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseAbsolute/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBLE/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/NetBIOS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PDM/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PWMAudio/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PicoOTA/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SD/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SDFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPI/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPISlave/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SdFat/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SerialBT/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Servo/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SimpleMDNS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SingleFileDrive/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SoftwareSPI/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Ticker/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Updater/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/VFS/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WebServer/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WiFi/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Wire/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/http-parser/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_CYW43/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_ESPHost/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_Ethernet/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_WINC1500/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_enc28j60/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5100/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5500/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w55rp20/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6100/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6300/src", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2040", + "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2350", + "" + ] + }, + "defines": [ + "PLATFORMIO=60118", + "ARDUINO_RASPBERRY_PI_PICO", + "ARDUINO_ARCH_RP2040", + "USBD_MAX_POWER_MA=500", + "PICO_STDIO_USB", + "CFG_TUSB_MCU=OPT_MCU_RP2040", + "CFG_TUSB_OS=OPT_OS_PICO", + "CYW43_DEFAULT_PIN_WL_CLOCK=29u", + "CYW43_DEFAULT_PIN_WL_CS=25u", + "CYW43_DEFAULT_PIN_WL_DATA_IN=24u", + "CYW43_DEFAULT_PIN_WL_DATA_OUT=24u", + "CYW43_DEFAULT_PIN_WL_HOST_WAKE=24u", + "CYW43_DEFAULT_PIN_WL_REG_ON=23u", + "CYW43_PIN_WL_DYNAMIC=1", + "CYW43_PIO_CLOCK_DIV_DYNAMIC=1", + "CYW43_WARN=//", + "LIB_BOOT_STAGE2_HEADERS=1", + "LIB_PICO_AON_TIMER=1", + "LIB_PICO_ATOMIC=1", + "LIB_PICO_BIT_OPS=1", + "LIB_PICO_BIT_OPS_PICO=1", + "LIB_PICO_BOOTSEL_VIA_DOUBLE_RESET=1", + "LIB_PICO_CLIB_INTERFACE=1", + "LIB_PICO_CRT0=1", + "LIB_PICO_CXX_OPTIONS=1", + "LIB_PICO_DIVIDER=1", + "LIB_PICO_DIVIDER_COMPILER=1", + "LIB_PICO_DOUBLE=1", + "LIB_PICO_FIX_RP2040_USB_DEVICE_ENUMERATION=1", + "LIB_PICO_FLASH=1", + "LIB_PICO_FLOAT=1", + "LIB_PICO_FLOAT_PICO=1", + "LIB_PICO_INT64_OPS=1", + "LIB_PICO_INT64_OPS_COMPILER=1", + "LIB_PICO_MALLOC=1", + "LIB_PICO_MEM_OPS=1", + "LIB_PICO_MEM_OPS_COMPILER=1", + "LIB_PICO_MULTICORE=1", + "LIB_PICO_NEWLIB_INTERFACE=1", + "LIB_PICO_PLATFORM=1", + "LIB_PICO_PLATFORM_COMMON=1", + "LIB_PICO_PLATFORM_COMPILER=1", + "LIB_PICO_PLATFORM_PANIC=1", + "LIB_PICO_PLATFORM_SECTIONS=1", + "LIB_PICO_PRINTF=1", + "LIB_PICO_PRINTF_PICO=1", + "LIB_PICO_RAND=1", + "LIB_PICO_RUNTIME=1", + "LIB_PICO_RUNTIME_INIT=1", + "LIB_PICO_STANDARD_BINARY_INFO=1", + "LIB_PICO_STANDARD_LINK=1", + "LIB_PICO_STDIO=0", + "LIB_PICO_STDIO_UART=0", + "LIB_PICO_STDLIB=1", + "LIB_PICO_SYNC=1", + "LIB_PICO_SYNC_CRITICAL_SECTION=1", + "LIB_PICO_SYNC_MUTEX=1", + "LIB_PICO_SYNC_SEM=1", + "LIB_PICO_TIME=1", + "LIB_PICO_TIME_ADAPTER=1", + "LIB_PICO_UNIQUE_ID=1", + "LIB_PICO_UTIL=1", + "LIB_TINYUSB_BOARD=1", + "LIB_TINYUSB_DEVICE=1", + "PICO_32BIT=1", + "PICO_BUILD=1", + "PICO_COPY_TO_RAM=0", + "PICO_NO_BINARY_INFO=1", + "PICO_NO_FLASH=0", + "PICO_NO_HARDWARE=0", + "PICO_ON_DEVICE=1", + "PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1", + "PICO_RP2040_USB_DEVICE_UFRAME_FIX=1", + "PICO_USE_BLOCKED_RAM=0", + "PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64", + "PICO_MAX_SHARED_IRQ_HANDLERS=6", + "PICO_CYW43_ARCH_HEADER=stdint.h", + "CYW43_TASK_STACK_SIZE=1024", + "LIB_PICO_DIVIDER_HARDWARE=1", + "LIB_PICO_DOUBLE_PICO=1", + "LIB_PICO_INT64_OPS_PICO=1", + "LIB_PICO_MEM_OPS_PICO=1", + "PICO_DOUBLE_SUPPORT_ROM_V1=1", + "PICO_FLOAT_SUPPORT_ROM_V1=1", + "PICO_PLATFORM=rp2040", + "PICO_RP2040=1", + "PICO_RP2040_B0_SUPPORTED=1", + "PICO_RP2040_B1_SUPPORTED=1", + "PICO_RP2040_B2_SUPPORTED=1", + "ARM_MATH_CM0_FAMILY", + "ARM_MATH_CM0_PLUS", + "TARGET_RP2040", + "ARDUINO=10810", + "ARDUINO_ARCH_RP2040", + "F_CPU=133000000L", + "BOARD_NAME=\"pico\"", + "PICO_FLASH_SIZE_BYTES=2097152", + "FILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC", + "USE_UTF8_LONG_NAMES=1", + "DISABLE_FS_H_WARNING=1", + "CFG_TUSB_MCU=OPT_MCU_RP2040", + "USB_VID=0x2e8a", + "USB_PID=0x000a", + "USBD_VID=0x2e8a", + "USBD_PID=0x000a", + "USB_MANUFACTURER=\"Raspberry Pi\"", + "USB_PRODUCT=\"Pico\"", + "PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1", + "CYW43_LWIP=1", + "CYW43_PIO_CLOCK_DIV_DYNAMIC=1", + "LWIP_IPV4=1", + "LWIP_IGMP=1", + "LWIP_CHECKSUM_CTRL_PER_NETIF=1", + "LWIP_IPV6=0", + "ARDUINO_VARIANT=\"rpipico\"", + "" + ], + "cStandard": "gnu17", + "cppStandard": "gnu++17", + "compilerPath": "/Users/markus.kalkbrenner/.platformio/packages/toolchain-rp2040-earlephilhower/bin/arm-none-eabi-gcc", + "compilerArgs": [ + "-march=armv6-m", + "-mcpu=cortex-m0plus", + "-mthumb", + "" + ] } ], "version": 4 -} \ No newline at end of file +} diff --git a/src/EventDispatcher/Event.h b/src/EventDispatcher/Event.h index 56381b6..7c3eb8e 100644 --- a/src/EventDispatcher/Event.h +++ b/src/EventDispatcher/Event.h @@ -91,9 +91,6 @@ #define LED_TYPE_FLASHER 2 // Flasher #define LED_TYPE_LAMP 3 // Lamp -#define MATRIX_TYPE_COLUMN 1 // Column -#define MATRIX_TYPE_ROW 2 // Row - #define PWM_EFFECT_SINE 1 #define PWM_EFFECT_RAMP_DOWN_STOP 2 #define PWM_EFFECT_IMPULSE 3 diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index fc5d58f..db6f622 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -31,9 +31,6 @@ IOBoardController::IOBoardController(int cT) { void IOBoardController::update() { if (running) { - if (activeSwitchMatrix) { - switchMatrix()->update(); - } if (activePwmDevices) { pwmDevices()->update(); } @@ -72,7 +69,6 @@ void IOBoardController::handleEvent(Event *event) { case EVENT_RESET: // Clear all configurations or reboot the device. _pwmDevices->reset(); - _switchMatrix->reset(); // Issue a delayed reset of the board. // Core 1 should have enough time to turn off it's devices. @@ -92,7 +88,6 @@ void IOBoardController::handleEvent(ConfigEvent *event) { break; case CONFIG_TOPIC_NUMBER: _switches->registerSwitch((byte)port, event->value); - activeSwitches = true; break; } break; @@ -104,28 +99,13 @@ void IOBoardController::handleEvent(ConfigEvent *event) { _switchMatrix->setActiveLow(); } break; - case CONFIG_TOPIC_MAX_PULSE_TIME: - _switchMatrix->setPulseTime((byte)event->value); - break; - case CONFIG_TOPIC_TYPE: - type = event->value; - number = 0; - port = 0; - break; - case CONFIG_TOPIC_NUMBER: - number = event->value; - break; case CONFIG_TOPIC_PORT: port = event->value; - if (MATRIX_TYPE_COLUMN == type) { - _switchMatrix->registerColumn(port, number); - } else { - _switchMatrix->registerRow(port, number); - } - activeSwitchMatrix = true; + break; + case CONFIG_TOPIC_NUMBER: + _switchMatrix->registerSwitch((byte)port, event->value); break; } - break; case CONFIG_TOPIC_PWM: diff --git a/src/IOBoardController.h b/src/IOBoardController.h index b07f627..17af7d6 100644 --- a/src/IOBoardController.h +++ b/src/IOBoardController.h @@ -7,7 +7,7 @@ GPIO16,17,18: UART TX, UART RX, RS485 Direction GPIO19-24, 26, 27: Power Out (PWM) GPIO25: Status-LED - GPIO28: ADC für Adressierung + GPIO28: ADC for Board ID GPIO29: Reserve (z.B. für einen LED-Strip oder zweite Status-LED) */ @@ -49,8 +49,6 @@ class IOBoardController : public EventListener { bool running = false; bool activePwmDevices = false; - bool activeSwitches = false; - bool activeSwitchMatrix = false; bool m_debug = false; int controllerType; diff --git a/src/IODevices/SwitchMatrix.cpp b/src/IODevices/SwitchMatrix.cpp index ecf466b..0469904 100644 --- a/src/IODevices/SwitchMatrix.cpp +++ b/src/IODevices/SwitchMatrix.cpp @@ -1,113 +1,173 @@ #include "SwitchMatrix.h" -void SwitchMatrix::setActiveLow() { activeLow = true; } +#include "SwitchMatrix.pio.h" -void SwitchMatrix::setPulseTime(byte pT) { pulseTime = pT; } +SwitchMatrix* SwitchMatrix::instance = nullptr; -void SwitchMatrix::registerColumn(byte p, byte n) { - if (n > 0 && n < MAX_COLUMNS) { - columns[n - 1] = p; - pinMode(p, OUTPUT); - } -} +void SwitchMatrix::setActiveLow() { activeLow = true; } -void SwitchMatrix::registerRow(byte p, byte n) { - if (n > 0 && n < MAX_ROWS) { - rows[n - 1] = p; - pinMode(p, INPUT); +void SwitchMatrix::registerSwitch(byte p, byte n) { + if (p < MATRIX_SWITCHES) { + mapping[p] = n; + active = true; } } -void SwitchMatrix::reset() { - pulseTime = 2; - pauseTime = 2; - activeLow = false; - active = false; - column = 0; - - for (uint8_t col = 0; col < MAX_COLUMNS; col++) { - columns[col] = -1; - for (uint8_t row = 0; row < MAX_ROWS; row++) { - rows[row] = -1; - state[col][row] = 0; - toggled[col][row] = 0; - } - } -} +void SwitchMatrix::handleRowChanges(uint32_t raw, uint8_t even) { + absolute_time_t now = get_absolute_time(); + uint32_t changed = raw ^ lastStable[even]; // raw to raw comparison -void SwitchMatrix::update() { - unsigned long ms = millis(); - if (active) { - if (ms > (_ms + (int)(pulseTime / 2))) { - for (int row = 0; row < MAX_ROWS; row++) { - if (rows[row] != -1 && !toggled[column][row]) { - bool new_state = digitalRead(rows[row]); - if (new_state != state[column][row]) { - state[column][row] = new_state; - toggled[column][row] = true; - - word number = (column + 1) * (row + 1); - if (platform != PLATFORM_DATA_EAST) { - number = ((column + 1) * 10) + (row + 1); - } - // Dispatch all switch events as "local fast". - // If a PWM output registered to it, we have "fast flip". Useful for - // flippers, kick backs, jets and sling shots. - _eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, number, - state[column][row], true)); - } - } - } - } + for (int column = 0; column < 4; column++) { + for (int row = 0; row < 8; row++) { + uint8_t pos = ((column * 2) + even) * 8 + row; + if (mapping[pos] == 0) continue; // Not registered - if (ms > (_ms + pulseTime)) { - digitalWrite(columns[column], activeLow); - active = false; - _ms = ms; - } - } else if (!active && (ms > (_ms + pauseTime))) { - column++; - if (column >= MAX_COLUMNS) { - column = 0; - } + uint32_t mask = 0x80000000 >> (column * 8 + row); + + if (changed & mask) { + // Debounce + if (absolute_time_diff_us(debounceTime[pos], now) >= + MATRIX_SWITCH_DEBOUNCE * 1000) { + debounceTime[pos] = now; - // If column is not in use (-1), the next update will increase the column. - if (columns[column] != -1) { - digitalWrite(columns[column], !activeLow); - active = true; - _ms = ms; + // Convert RAW to logical pressed/released + // ----------------------------------------- + // activeLow : pressed = raw_bit == 0 + // activeHigh: pressed = raw_bit == 1 + bool rawBit = (raw & mask) != 0; + + bool newState = activeLow ? (!rawBit) // active-low: 0 = pressed + : rawBit; // active-high: 1 = pressed + + // Store the *raw* stable state + if (rawBit) + lastStable[even] |= mask; // raw=1 + else + lastStable[even] &= ~mask; // raw=0 + + // Dispatch all switch events as "local fast". + // If a PWM output registered to it, we have "fast flip". Useful for + // flippers, kick backs, jets and sling shots. + _eventDispatcher->dispatch(new Event( + EVENT_SOURCE_SWITCH, word(0, mapping[pos]), newState, true)); + } + } } } } -void SwitchMatrix::handleEvent(Event *event) { +void SwitchMatrix::handleEvent(Event* event) { switch (event->sourceId) { - case EVENT_POLL_EVENTS: - if (boardId == (byte)event->value) { - // This I/O board has been polled for events, so all current switch - // states are transmitted. Reset switch toggles. - for (int col = 0; col < MAX_COLUMNS; col++) { - for (int row = 0; row < MAX_ROWS; row++) { - toggled[col][row] = false; + case EVENT_READ_SWITCHES: + // The CPU requested all current states. Usually this event is sent when + // the game gets started. + if (active) { + // First, send OFF for all switches then ON for the active ones using + // the IRQ handler. + for (int i = 0; i < MATRIX_SWITCHES; i++) { + if (mapping[i] != 0) { + _eventDispatcher->dispatch( + new Event(EVENT_SOURCE_SWITCH, word(0, mapping[i]), 0)); } } - } - break; - case EVENT_READ_SWITCHES: - // The CPU requested all current states. - for (int col = 0; col < MAX_COLUMNS; col++) { - if (columns[col] != -1) { - for (int row = 0; row < MAX_ROWS; row++) { - if (rows[row] != -1) { - word number = (column + 1) * (row + 1); - if (platform != PLATFORM_DATA_EAST) { - number = ((column + 1) * 10) + (row + 1); - } - _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_SWITCH, number, state[column][row])); - } + if (!running) { + instance = this; + running = true; + + uint columns_offset; + pio_sm_config c_columns_pio; + uint odd_rows_offset; + pio_sm_config c_odd_rows; + uint even_rows_offset; + pio_sm_config c_even_rows; + + if (activeLow) { + extern const pio_program_t columns_active_low_pio_program; + columns_offset = + pio_add_program(pio, &columns_active_low_pio_program); + c_columns_pio = columns_active_low_pio_program_get_default_config( + columns_offset); + + extern const pio_program_t odd_rows_active_low_pio_program; + uint odd_rows_offset = + pio_add_program(pio, &odd_rows_active_low_pio_program); + pio_sm_config c_odd_rows = + odd_rows_active_low_pio_program_get_default_config( + odd_rows_offset); + + extern const pio_program_t even_rows_active_low_pio_program; + uint even_rows_offset = + pio_add_program(pio, &even_rows_active_low_pio_program); + pio_sm_config c_even_rows = + even_rows_active_low_pio_program_get_default_config( + even_rows_offset); + } else { + extern const pio_program_t columns_active_high_pio_program; + columns_offset = + pio_add_program(pio, &columns_active_high_pio_program); + c_columns_pio = columns_active_high_pio_program_get_default_config( + columns_offset); + + extern const pio_program_t odd_rows_active_high_pio_program; + uint odd_rows_offset = + pio_add_program(pio, &odd_rows_active_high_pio_program); + pio_sm_config c_odd_rows = + odd_rows_active_high_pio_program_get_default_config( + odd_rows_offset); + + extern const pio_program_t even_rows_active_high_pio_program; + uint even_rows_offset = + pio_add_program(pio, &even_rows_active_high_pio_program); + pio_sm_config c_even_rows = + even_rows_active_high_pio_program_get_default_config( + even_rows_offset); + } + + // Columns + sm_config_set_in_pins(&c_columns_pio, COLUMNS_BASE_PIN); + // Connect 8 GPIOs to this PIO block + for (uint i = 0; i < 8; i++) { + pio_gpio_init(pio, COLUMNS_BASE_PIN + i); + } + // Set the pin direction at the PIO + pio_sm_set_consecutive_pindirs(pio, sm_even_rows, COLUMNS_BASE_PIN, 8, + true); + sm_config_set_out_shift(&c_columns_pio, true, false, 8); + pio_sm_init(pio, sm_columns, columns_offset, &c_columns_pio); + pio_sm_set_enabled(pio, sm_columns, true); + + // Odd Rows + sm_config_set_in_pins(&c_odd_rows, SWITCH_MATRIX_BASE_PIN); + // Connect 16 GPIOs to this PIO block + for (uint i = 0; i < 16; i++) { + pio_gpio_init(pio, SWITCH_MATRIX_BASE_PIN + i); + } + // Set the pin direction at the PIO + pio_sm_set_consecutive_pindirs(pio, sm_odd_rows, + SWITCH_MATRIX_BASE_PIN, 16, false); + sm_config_set_in_shift(&c_odd_rows, true, false, 8); + pio_sm_init(pio, sm_odd_rows, odd_rows_offset, &c_odd_rows); + irq_set_exclusive_handler(PIO0_IRQ_0, onOddRowChanges); + irq_set_enabled(PIO0_IRQ_0, true); + pio_set_irq0_source_enabled(pio, pis_interrupt0, true); + pio_sm_set_enabled(pio, sm_odd_rows, true); + + // Even Rows + sm_config_set_in_pins(&c_even_rows, SWITCH_MATRIX_BASE_PIN); + // Connect 16 GPIOs to this PIO block + for (uint i = 0; i < 16; i++) { + pio_gpio_init(pio, SWITCH_MATRIX_BASE_PIN + i); } + // Set the pin direction at the PIO + pio_sm_set_consecutive_pindirs(pio, sm_even_rows, + SWITCH_MATRIX_BASE_PIN, 16, false); + sm_config_set_in_shift(&c_even_rows, true, false, 8); + pio_sm_init(pio, sm_even_rows, even_rows_offset, &c_even_rows); + irq_set_exclusive_handler(PIO0_IRQ_1, onEvenRowChanges); + irq_set_enabled(PIO0_IRQ_1, true); + pio_set_irq0_source_enabled(pio, pis_interrupt1, true); + pio_sm_set_enabled(pio, sm_even_rows, true); } } break; diff --git a/src/IODevices/SwitchMatrix.h b/src/IODevices/SwitchMatrix.h index 221a8c8..994da12 100644 --- a/src/IODevices/SwitchMatrix.h +++ b/src/IODevices/SwitchMatrix.h @@ -1,6 +1,6 @@ /* SwitchMatrix_h. - Created by Markus Kalkbrenner, 2023. + Created by Markus Kalkbrenner, 2023-2025. */ #ifndef SwitchMatrix_h @@ -8,60 +8,71 @@ #include "../EventDispatcher/Event.h" #include "../EventDispatcher/EventDispatcher.h" -#include "../PPUC.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" -#ifndef MAX_COLUMNS -#define MAX_COLUMNS 10 -#endif - -#ifndef MAX_ROWS -#define MAX_ROWS 8 -#endif +#define SWITCH_MATRIX_BASE_PIN 0 +#define COLUMNS_BASE_PIN (SWITCH_MATRIX_BASE_PIN + 8) +#define MATRIX_SWITCHES 64 +#define MATRIX_SWITCH_DEBOUNCE 2 class SwitchMatrix : public EventListener { public: - SwitchMatrix(byte bId, EventDispatcher *eD) { + SwitchMatrix(byte bId, EventDispatcher* eD) { boardId = bId; - platform = PLATFORM_LIBPINMAME; - - reset(); - - _ms = millis(); _eventDispatcher = eD; _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); - } - void registerColumn(byte p, byte n); - void registerRow(byte p, byte n); + pio = pio0; + sm_columns = 0; + sm_odd_rows = 2; + sm_even_rows = 1; + } void setActiveLow(); - void setPulseTime(byte pT); + void registerSwitch(byte p, byte n); - void update(); - void reset(); + void handleEvent(Event* event); - void handleEvent(Event *event); + void handleEvent(ConfigEvent* event) {} - void handleEvent(ConfigEvent *event) {} + void handleRowChanges(uint32_t raw, uint8_t even); + + PIO pio; + int sm_columns; + int sm_even_rows; + int sm_odd_rows; private: byte boardId; - byte platform; - byte pulseTime; - byte pauseTime; - bool activeLow; - bool active; + bool activeLow = false; + bool running = false; + bool active = false; + + byte mapping[MATRIX_SWITCHES] = {0}; + uint32_t lastStable[2] = {0}; + absolute_time_t debounceTime[MATRIX_SWITCHES] = {0}; - unsigned long _ms; + EventDispatcher* _eventDispatcher; - int8_t columns[MAX_COLUMNS]; - int8_t rows[MAX_ROWS]; - bool state[MAX_COLUMNS][MAX_ROWS] = {0}; - bool toggled[MAX_COLUMNS][MAX_ROWS] = {0}; - byte column = 0; + static SwitchMatrix* instance; - EventDispatcher *_eventDispatcher; + static void __not_in_flash_func(onOddRowChanges)() { + // IRQ0 clear + pio0_hw->irq = 1u << 0; + + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_odd_rows); + instance->handleRowChanges(raw, 0); + } + + static void __not_in_flash_func(onEvenRowChanges)() { + // IRQ1 clear + pio0_hw->irq = 1u << 1; + + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_even_rows); + instance->handleRowChanges(raw, 1); + } }; #endif diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index e2726f7..cefebe1 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -14,18 +14,30 @@ void Switches::registerSwitch(byte p, byte n) { void Switches::handleSwitchChanges(uint16_t raw) { absolute_time_t now = get_absolute_time(); - uint16_t changed = raw ^ lastStable; + uint16_t changed = raw ^ lastStable; // raw to raw comparison for (int i = 0; i < MAX_SWITCHES; i++) { + if (number[i] == 0) continue; // Not registered + uint16_t mask = 1u << i; if (changed & mask) { // Debounce if (absolute_time_diff_us(debounceTime[i], now) >= SWITCH_DEBOUNCE * 1000) { - bool newState = !(raw & mask); // active-low debounceTime[i] = now; - lastStable = (lastStable & ~mask) | (newState ? mask : 0); + + bool rawBit = (raw & mask) != 0; + + // logical pressed state for active-low + bool newState = !rawBit; + + // store raw stable bit (not logical) + if (rawBit) + lastStable |= mask; // raw = 1 + else + lastStable &= ~mask; // raw = 0 + // Dispatch all switch events as "local fast". // If a PWM output registered to it, we have "fast flip". Useful for // flippers, kick backs, jets and sling shots. @@ -45,8 +57,10 @@ void Switches::handleEvent(Event* event) { // First, send OFF for all switches then ON for the active ones using // the IRQ handler. for (int i = 0; i <= last; i++) { - _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), 0)); + if (number[i] != 0) { + _eventDispatcher->dispatch( + new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), 0)); + } } if (!running) { @@ -73,7 +87,7 @@ void Switches::handleEvent(Event* event) { pio_sm_init(pio, sm, offset, &c); - irq_set_exclusive_handler(PIO0_IRQ_0, onSwitchCanges); + irq_set_exclusive_handler(PIO0_IRQ_0, onSwitchChanges); irq_set_enabled(PIO0_IRQ_0, true); pio_set_irq0_source_enabled(pio, pis_interrupt0, true); diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index 01430a7..982c0e0 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -29,11 +29,6 @@ class Switches : public EventListener { pio = pio0; sm = 0; - - lastStable = 0; - for (int i = 0; i < MAX_SWITCHES; i++) { - debounceTime[i] = 0; - } } void registerSwitch(byte p, byte n); @@ -56,14 +51,14 @@ class Switches : public EventListener { byte number[MAX_SWITCHES] = {0}; int last = -1; - uint16_t lastStable; - absolute_time_t debounceTime[MAX_SWITCHES]; + uint16_t lastStable = 0; + absolute_time_t debounceTime[MAX_SWITCHES] = {0}; EventDispatcher* _eventDispatcher; static Switches* instance; - static void __not_in_flash_func(onSwitchCanges)() { + static void __not_in_flash_func(onSwitchChanges)() { // IRQ0 clear pio0_hw->irq = 1u << 0; From 9a02486ca3be20ca494ae4c9d3db324a67979457 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Wed, 19 Nov 2025 19:13:25 +0100 Subject: [PATCH 04/17] migrated switch matrix support to PIOs --- src/EventDispatcher/Event.h | 1 + src/IOBoardController.cpp | 23 ++- src/IOBoardController.h | 1 + src/IODevices/SwitchMatrix.cpp | 183 ++++++++---------- src/IODevices/SwitchMatrix.h | 45 ++--- src/IODevices/SwitchMatrix.pio | 149 -------------- .../SwitchMatrixPIO/ActiveHigh4Columns.pio | 15 ++ .../SwitchMatrixPIO/ActiveHigh4Rows.pio | 35 ++++ .../SwitchMatrixPIO/ActiveHigh8Rows.pio | 35 ++++ .../SwitchMatrixPIO/ActiveLow4Columns.pio | 16 ++ .../SwitchMatrixPIO/ActiveLow4Rows.pio | 37 ++++ .../SwitchMatrixPIO/ActiveLow8Rows.pio | 37 ++++ src/IODevices/Switches.cpp | 89 +++++---- src/IODevices/Switches.h | 17 +- src/IODevices/Switches.pio | 37 ---- .../SwitchesPIO/ActiveLow16Switches.pio | 48 +++++ .../SwitchesPIO/ActiveLow4Switches.pio | 31 +++ .../SwitchesPIO/ActiveLow8Switches.pio | 31 +++ 18 files changed, 462 insertions(+), 368 deletions(-) delete mode 100644 src/IODevices/SwitchMatrix.pio create mode 100644 src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio create mode 100644 src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio create mode 100644 src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio create mode 100644 src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio create mode 100644 src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio create mode 100644 src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio delete mode 100644 src/IODevices/Switches.pio create mode 100644 src/IODevices/SwitchesPIO/ActiveLow16Switches.pio create mode 100644 src/IODevices/SwitchesPIO/ActiveLow4Switches.pio create mode 100644 src/IODevices/SwitchesPIO/ActiveLow8Switches.pio diff --git a/src/EventDispatcher/Event.h b/src/EventDispatcher/Event.h index 7c3eb8e..9850437 100644 --- a/src/EventDispatcher/Event.h +++ b/src/EventDispatcher/Event.h @@ -65,6 +65,7 @@ #define CONFIG_TOPIC_MIN_INTENSITY 77 // "M" #define CONFIG_TOPIC_NUMBER 78 // "N" #define CONFIG_TOPIC_AMOUNT_LEDS 79 // "O" +#define CONFIG_TOPIC_NUM_ROWS 79 // "O" #define CONFIG_TOPIC_PORT 80 // "P" #define CONFIG_TOPIC_SPEED 83 // "S" #define CONFIG_TOPIC_SOURCE 83 // "S" diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index db6f622..beee24a 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -81,29 +81,34 @@ void IOBoardController::handleEvent(Event *event) { void IOBoardController::handleEvent(ConfigEvent *event) { if (event->boardId == boardId) { switch (event->topic) { - case CONFIG_TOPIC_SWITCHES: + case CONFIG_TOPIC_SWITCH_MATRIX: switch (event->key) { + case CONFIG_TOPIC_ACTIVE_LOW: + if (event->value) { + _switchMatrix->setActiveLow(); + } + break; + case CONFIG_TOPIC_NUM_ROWS: + rows = (uint8_t)event->value; + _switchMatrix->setNumRows(rows); + _switches->setNumSwitches(MAX_SWITCHES - NUM_COLUMNS - rows); + break; case CONFIG_TOPIC_PORT: port = event->value; break; case CONFIG_TOPIC_NUMBER: - _switches->registerSwitch((byte)port, event->value); + _switchMatrix->registerSwitch((byte)port, event->value); break; } break; - case CONFIG_TOPIC_SWITCH_MATRIX: + case CONFIG_TOPIC_SWITCHES: switch (event->key) { - case CONFIG_TOPIC_ACTIVE_LOW: - if (event->value) { - _switchMatrix->setActiveLow(); - } - break; case CONFIG_TOPIC_PORT: port = event->value; break; case CONFIG_TOPIC_NUMBER: - _switchMatrix->registerSwitch((byte)port, event->value); + _switches->registerSwitch((byte)port, event->value); break; } break; diff --git a/src/IOBoardController.h b/src/IOBoardController.h index 17af7d6..1dcfc24 100644 --- a/src/IOBoardController.h +++ b/src/IOBoardController.h @@ -56,6 +56,7 @@ class IOBoardController : public EventListener { byte port = 0; byte number = 0; byte power = 0; + byte rows = 0; uint16_t minPulseTime = 0; uint16_t maxPulseTime = 0; byte holdPower = 0; diff --git a/src/IODevices/SwitchMatrix.cpp b/src/IODevices/SwitchMatrix.cpp index 0469904..7a8eec2 100644 --- a/src/IODevices/SwitchMatrix.cpp +++ b/src/IODevices/SwitchMatrix.cpp @@ -1,55 +1,55 @@ #include "SwitchMatrix.h" -#include "SwitchMatrix.pio.h" +#include "SwitchMatrixPIO/ActiveHigh4Columns.pio.h" +#include "SwitchMatrixPIO/ActiveHigh4Rows.pio.h" +#include "SwitchMatrixPIO/ActiveHigh8Rows.pio.h" +#include "SwitchMatrixPIO/ActiveLow4Columns.pio.h" +#include "SwitchMatrixPIO/ActiveLow4Rows.pio.h" +#include "SwitchMatrixPIO/ActiveLow8Rows.pio.h" SwitchMatrix* SwitchMatrix::instance = nullptr; -void SwitchMatrix::setActiveLow() { activeLow = true; } - void SwitchMatrix::registerSwitch(byte p, byte n) { - if (p < MATRIX_SWITCHES) { + if (p < (NUM_COLUMNS * numRows)) { mapping[p] = n; active = true; } } -void SwitchMatrix::handleRowChanges(uint32_t raw, uint8_t even) { +void SwitchMatrix::handleRowChanges(uint32_t raw) { absolute_time_t now = get_absolute_time(); - uint32_t changed = raw ^ lastStable[even]; // raw to raw comparison + uint32_t changed = raw ^ lastStable; // raw to raw comparison - for (int column = 0; column < 4; column++) { - for (int row = 0; row < 8; row++) { - uint8_t pos = ((column * 2) + even) * 8 + row; + for (int column = 0; column < NUM_COLUMNS; column++) { + for (int row = 0; row < numRows; row++) { + uint8_t pos = column * numRows + row; if (mapping[pos] == 0) continue; // Not registered - uint32_t mask = 0x80000000 >> (column * 8 + row); + uint32_t mask = 1u << ((NUM_COLUMNS - 1 - column) * numRows + row); if (changed & mask) { + // Convert RAW to logical pressed/released + // ----------------------------------------- + // activeLow : pressed = raw_bit == 0 + // activeHigh: pressed = raw_bit == 1 + bool rawBit = (raw & mask) != 0; + bool switchState = activeLow ? (!rawBit) // active-low: 0 = pressed + : rawBit; // active-high: 1 = pressed // Debounce - if (absolute_time_diff_us(debounceTime[pos], now) >= + if (absolute_time_diff_us(debounceTime[pos][switchState], now) >= MATRIX_SWITCH_DEBOUNCE * 1000) { - debounceTime[pos] = now; - - // Convert RAW to logical pressed/released - // ----------------------------------------- - // activeLow : pressed = raw_bit == 0 - // activeHigh: pressed = raw_bit == 1 - bool rawBit = (raw & mask) != 0; - - bool newState = activeLow ? (!rawBit) // active-low: 0 = pressed - : rawBit; // active-high: 1 = pressed - + debounceTime[pos][switchState] = now; // Store the *raw* stable state if (rawBit) - lastStable[even] |= mask; // raw=1 + lastStable |= mask; // raw=1 else - lastStable[even] &= ~mask; // raw=0 + lastStable &= ~mask; // raw=0 // Dispatch all switch events as "local fast". // If a PWM output registered to it, we have "fast flip". Useful for // flippers, kick backs, jets and sling shots. _eventDispatcher->dispatch(new Event( - EVENT_SOURCE_SWITCH, word(0, mapping[pos]), newState, true)); + EVENT_SOURCE_SWITCH, word(0, mapping[pos]), switchState, true)); } } } @@ -64,7 +64,7 @@ void SwitchMatrix::handleEvent(Event* event) { if (active) { // First, send OFF for all switches then ON for the active ones using // the IRQ handler. - for (int i = 0; i < MATRIX_SWITCHES; i++) { + for (int i = 0; i < (NUM_COLUMNS * numRows); i++) { if (mapping[i] != 0) { _eventDispatcher->dispatch( new Event(EVENT_SOURCE_SWITCH, word(0, mapping[i]), 0)); @@ -76,98 +76,83 @@ void SwitchMatrix::handleEvent(Event* event) { running = true; uint columns_offset; - pio_sm_config c_columns_pio; - uint odd_rows_offset; - pio_sm_config c_odd_rows; - uint even_rows_offset; - pio_sm_config c_even_rows; + pio_sm_config c_columns; + uint rows_offset; + pio_sm_config c_rows; if (activeLow) { - extern const pio_program_t columns_active_low_pio_program; + extern const pio_program_t active_low_4_columns_pio_program; columns_offset = - pio_add_program(pio, &columns_active_low_pio_program); - c_columns_pio = columns_active_low_pio_program_get_default_config( + pio_add_program(pio, &active_low_4_columns_pio_program); + c_columns = active_low_4_columns_pio_program_get_default_config( columns_offset); - extern const pio_program_t odd_rows_active_low_pio_program; - uint odd_rows_offset = - pio_add_program(pio, &odd_rows_active_low_pio_program); - pio_sm_config c_odd_rows = - odd_rows_active_low_pio_program_get_default_config( - odd_rows_offset); - - extern const pio_program_t even_rows_active_low_pio_program; - uint even_rows_offset = - pio_add_program(pio, &even_rows_active_low_pio_program); - pio_sm_config c_even_rows = - even_rows_active_low_pio_program_get_default_config( - even_rows_offset); - } else { - extern const pio_program_t columns_active_high_pio_program; + if (4 == numRows) { + extern const pio_program_t active_low_4_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_low_4_rows_pio_program); + pio_sm_config c_rows = + active_low_4_rows_pio_program_get_default_config(rows_offset); + } else { // 8 rows + extern const pio_program_t active_low_8_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_low_8_rows_pio_program); + pio_sm_config c_rows = + active_low_8_rows_pio_program_get_default_config(rows_offset); + } + } else { // active high + extern const pio_program_t active_high_4_columns_pio_program; columns_offset = - pio_add_program(pio, &columns_active_high_pio_program); - c_columns_pio = columns_active_high_pio_program_get_default_config( + pio_add_program(pio, &active_high_4_columns_pio_program); + c_columns = active_high_4_columns_pio_program_get_default_config( columns_offset); - extern const pio_program_t odd_rows_active_high_pio_program; - uint odd_rows_offset = - pio_add_program(pio, &odd_rows_active_high_pio_program); - pio_sm_config c_odd_rows = - odd_rows_active_high_pio_program_get_default_config( - odd_rows_offset); - - extern const pio_program_t even_rows_active_high_pio_program; - uint even_rows_offset = - pio_add_program(pio, &even_rows_active_high_pio_program); - pio_sm_config c_even_rows = - even_rows_active_high_pio_program_get_default_config( - even_rows_offset); + if (4 == numRows) { + extern const pio_program_t active_high_4_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_high_4_rows_pio_program); + pio_sm_config c_rows = + active_high_4_rows_pio_program_get_default_config( + rows_offset); + } else { // 8 rows + extern const pio_program_t active_high_8_rows_pio_program; + uint rows_offset = + pio_add_program(pio, &active_high_8_rows_pio_program); + pio_sm_config c_rows = + active_high_8_rows_pio_program_get_default_config( + rows_offset); + } } // Columns - sm_config_set_in_pins(&c_columns_pio, COLUMNS_BASE_PIN); - // Connect 8 GPIOs to this PIO block - for (uint i = 0; i < 8; i++) { + sm_config_set_in_pins(&c_columns, COLUMNS_BASE_PIN); + // Connect GPIOs to this PIO block + for (uint i = 0; i < NUM_COLUMNS; i++) { pio_gpio_init(pio, COLUMNS_BASE_PIN + i); } // Set the pin direction at the PIO - pio_sm_set_consecutive_pindirs(pio, sm_even_rows, COLUMNS_BASE_PIN, 8, - true); - sm_config_set_out_shift(&c_columns_pio, true, false, 8); - pio_sm_init(pio, sm_columns, columns_offset, &c_columns_pio); + pio_sm_set_consecutive_pindirs(pio, sm_rows, COLUMNS_BASE_PIN, + NUM_COLUMNS, true); + sm_config_set_out_shift(&c_columns, false, false, 0); + pio_sm_init(pio, sm_columns, columns_offset, &c_columns); pio_sm_set_enabled(pio, sm_columns, true); - // Odd Rows - sm_config_set_in_pins(&c_odd_rows, SWITCH_MATRIX_BASE_PIN); - // Connect 16 GPIOs to this PIO block - for (uint i = 0; i < 16; i++) { - pio_gpio_init(pio, SWITCH_MATRIX_BASE_PIN + i); + // Rows + sm_config_set_in_pins(&c_rows, COLUMNS_BASE_PIN - numRows); + // Connect GPIOs to this PIO block + for (uint i = 0; i < (numRows + NUM_COLUMNS); i++) { + pio_gpio_init(pio, COLUMNS_BASE_PIN - numRows + i); } - // Set the pin direction at the PIO - pio_sm_set_consecutive_pindirs(pio, sm_odd_rows, - SWITCH_MATRIX_BASE_PIN, 16, false); - sm_config_set_in_shift(&c_odd_rows, true, false, 8); - pio_sm_init(pio, sm_odd_rows, odd_rows_offset, &c_odd_rows); - irq_set_exclusive_handler(PIO0_IRQ_0, onOddRowChanges); + // Set the pin direction at the PIO, we also read the 4 column pins + pio_sm_set_consecutive_pindirs(pio, sm_rows, + COLUMNS_BASE_PIN - numRows, + numRows + NUM_COLUMNS, false); + sm_config_set_in_shift(&c_rows, false, false, 0); + pio_sm_init(pio, sm_rows, rows_offset, &c_rows); + irq_set_exclusive_handler(PIO0_IRQ_0, onRowChanges); irq_set_enabled(PIO0_IRQ_0, true); pio_set_irq0_source_enabled(pio, pis_interrupt0, true); - pio_sm_set_enabled(pio, sm_odd_rows, true); - - // Even Rows - sm_config_set_in_pins(&c_even_rows, SWITCH_MATRIX_BASE_PIN); - // Connect 16 GPIOs to this PIO block - for (uint i = 0; i < 16; i++) { - pio_gpio_init(pio, SWITCH_MATRIX_BASE_PIN + i); - } - // Set the pin direction at the PIO - pio_sm_set_consecutive_pindirs(pio, sm_even_rows, - SWITCH_MATRIX_BASE_PIN, 16, false); - sm_config_set_in_shift(&c_even_rows, true, false, 8); - pio_sm_init(pio, sm_even_rows, even_rows_offset, &c_even_rows); - irq_set_exclusive_handler(PIO0_IRQ_1, onEvenRowChanges); - irq_set_enabled(PIO0_IRQ_1, true); - pio_set_irq0_source_enabled(pio, pis_interrupt1, true); - pio_sm_set_enabled(pio, sm_even_rows, true); + pio_sm_set_enabled(pio, sm_rows, true); } } break; diff --git a/src/IODevices/SwitchMatrix.h b/src/IODevices/SwitchMatrix.h index 994da12..8739b52 100644 --- a/src/IODevices/SwitchMatrix.h +++ b/src/IODevices/SwitchMatrix.h @@ -11,9 +11,9 @@ #include "hardware/gpio.h" #include "hardware/pio.h" -#define SWITCH_MATRIX_BASE_PIN 0 -#define COLUMNS_BASE_PIN (SWITCH_MATRIX_BASE_PIN + 8) -#define MATRIX_SWITCHES 64 +#define COLUMNS_BASE_PIN 13 // GPIO 13-16 for columns, the only pins with required hardware on IO_16_8_1 board +#define NUM_COLUMNS 4 +#define MAX_ROWS 8 #define MATRIX_SWITCH_DEBOUNCE 2 class SwitchMatrix : public EventListener { @@ -23,55 +23,42 @@ class SwitchMatrix : public EventListener { _eventDispatcher = eD; _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); - - pio = pio0; - sm_columns = 0; - sm_odd_rows = 2; - sm_even_rows = 1; } - void setActiveLow(); + void setActiveLow() { activeLow = true; } + void setNumRows(uint8_t n) { numRows = n; } void registerSwitch(byte p, byte n); void handleEvent(Event* event); void handleEvent(ConfigEvent* event) {} - void handleRowChanges(uint32_t raw, uint8_t even); + void handleRowChanges(uint32_t raw); - PIO pio; - int sm_columns; - int sm_even_rows; - int sm_odd_rows; + PIO pio = pio0; + int sm_columns = 0; + int sm_rows = 1; private: byte boardId; bool activeLow = false; + uint8_t numRows = 4; bool running = false; bool active = false; - byte mapping[MATRIX_SWITCHES] = {0}; - uint32_t lastStable[2] = {0}; - absolute_time_t debounceTime[MATRIX_SWITCHES] = {0}; - + byte mapping[NUM_COLUMNS * MAX_ROWS] = {0}; + uint32_t lastStable = 0; + absolute_time_t debounceTime[NUM_COLUMNS * MAX_ROWS][2] = {0}; EventDispatcher* _eventDispatcher; static SwitchMatrix* instance; - static void __not_in_flash_func(onOddRowChanges)() { + static void __not_in_flash_func(onRowChanges)() { // IRQ0 clear pio0_hw->irq = 1u << 0; - uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_odd_rows); - instance->handleRowChanges(raw, 0); - } - - static void __not_in_flash_func(onEvenRowChanges)() { - // IRQ1 clear - pio0_hw->irq = 1u << 1; - - uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_even_rows); - instance->handleRowChanges(raw, 1); + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_rows); + instance->handleRowChanges(raw); } }; diff --git a/src/IODevices/SwitchMatrix.pio b/src/IODevices/SwitchMatrix.pio deleted file mode 100644 index eeb6014..0000000 --- a/src/IODevices/SwitchMatrix.pio +++ /dev/null @@ -1,149 +0,0 @@ -.program columns_active_high_pio -.wrap_target - set x, 1 - mov isr, x ; initialize ISR with 0x00000001 - set x, 0 ; X = 0 - set y, 7 ; 8 columns (0-7) - -loop: - mov osr, isr ; copy ISR to OSR for output - out pins, 8 ; set 8 pins for columns, one is HIGH, others LOW - in x, 1 ; shift left ISR by 1 bit - nop [16] ; short delay to let other state machines read rows - jmp y-- loop ; decrement Y, loop until Y < 0 -.wrap - - -.program odd_rows_active_high_pio - ; Initialize X to all pins LOW. Max. 4x8 rows, so 32 bit, so 0x0: - set x, 0 ; X = 0x0 - mov y, x ; Y = 0x0 - mov isr, y ; ISR = 0x0 - -loop: - wait 0 PIN 8 ; wait for odd column 1 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 10 ; wait for odd column 3 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 12 ; wait for odd column 5 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 14 ; wait for odd column 7 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - - mov y, isr ; copy ISR to Y for comparison - jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - jmp loop - -changed: - mov osr, y - push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it - irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed - mov x, y ; update X to the new odd columns state - jmp loop - - -.program even_rows_active_high_pio - ; Initialize X to all pins LOW. Max. 4x8 rows, so 32 bit, so 0x0: - set x, 0 ; X = 0x0 - mov y, x ; Y = 0x0 - mov isr, y ; ISR = 0x0 - -loop: - wait 0 PIN 9 ; wait for even column 2 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 11 ; wait for even column 4 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 13 ; wait for even column 6 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 15 ; wait for even column 8 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - - mov y, isr ; copy ISR to Y for comparison - jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - jmp loop - -changed: - mov osr, y - push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it - irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed - mov x, y ; update X to the new switch states - jmp loop - - -; ---------------------------------------------------------------------------------------------------------------------------- -; ---------------------------------------------------------------------------------------------------------------------------- - - -.program columns_active_low_pio -.wrap_target - set y, 1 - mov isr, ~y ; initialize ISR with 0xFFFFFFFE - set y, 0 ; Y = 0 - mov x, ~y ; X = 0xFFFFFFFF - set y, 7 ; 8 columns (0-7) - -loop: - mov osr, isr ; copy ISR to OSR for output - out pins, 8 ; set 8 pins for columns, one is HIGH, others LOW - in x, 1 ; shift left ISR by 1 bit - nop [16] ; short delay to let other state machines read rows - jmp y-- loop ; decrement Y, loop until Y < 0 -.wrap - - -.program odd_rows_active_low_pio - ; Initialize X to all pins HIGH. Max. 4x8 rows, so 32 bit: - set x, 0 ; X = 0x0 - mov y, ~x ; Y = 0xFFFFFFFF - mov x, y ; X = 0xFFFFFFFF - mov isr, y ; ISR = 0xFFFFFFFF - -loop: - wait 0 PIN 8 ; wait for odd column 1 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 10 ; wait for odd column 3 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 12 ; wait for odd column 5 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 14 ; wait for odd column 7 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - - mov y, isr ; copy ISR to Y for comparison - jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - jmp loop - -changed: - mov osr, y - push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it - irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed - mov x, y ; update X to the new odd columns state - jmp loop - - -.program even_rows_active_low_pio - ; Initialize X to all pins HIGH. Max. 4x8 rows, so 32 bit: - set x, 0 ; X = 0x0 - mov y, ~x ; Y = 0xFFFFFFFF - mov x, y ; X = 0xFFFFFFFF - mov isr, y ; ISR = 0xFFFFFFFF - -loop: - wait 0 PIN 9 ; wait for even column 2 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 11 ; wait for even column 4 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 13 ; wait for even column 6 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 15 ; wait for even column 8 - in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - - mov y, isr ; copy ISR to Y for comparison - jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - jmp loop - -changed: - mov osr, y - push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it - irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed - mov x, y ; update X to the new switch states - jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio new file mode 100644 index 0000000..c054f12 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Columns.pio @@ -0,0 +1,15 @@ +.program active_high_4_columns_pio +.wrap_target + set x, 1 + mov isr, x ; initialize ISR with 0x00000001 + set x, 0 ; X = 0 + set y, 3 ; 4 columns (0-3) + nop [16] ; short delay to let other state machines perform debouncing + +loop: + mov osr, isr ; copy ISR to OSR for output + out pins, 4 ; set 4 pins for columns, one is HIGH, others LOW + in x, 1 ; shift left ISR by 1 bit + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio new file mode 100644 index 0000000..2adad9d --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh4Rows.pio @@ -0,0 +1,35 @@ +.program active_high_4_rows_pio + ; Initialize X to all pins LOW + set x, 0 ; X = 0x0 + mov y, x ; Y = 0x0 + mov isr, y ; ISR = 0x0 + mov osr, y ; ISR = 0x0 + +loop: + wait 0 PIN 4 ; wait for column 1 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 5 ; wait for column 2 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 6 ; wait for odd column 3 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 7 ; wait for odd column 4 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio new file mode 100644 index 0000000..2bd87cb --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio @@ -0,0 +1,35 @@ +.program active_high_8_rows_pio + ; Initialize X to all pins LOW + set x, 0 ; X = 0x0 + mov y, x ; Y = 0x0 + mov isr, y ; ISR = 0x0 + mov osr, y ; ISR = 0x0 + +loop: + wait 0 PIN 4 ; wait for column 1 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 5 ; wait for column 2 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 6 ; wait for odd column 3 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 7 ; wait for odd column 4 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio new file mode 100644 index 0000000..ae3fa59 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow4Columns.pio @@ -0,0 +1,16 @@ +.program active_low_4_columns_pio +.wrap_target + set y, 1 + mov isr, ~y ; initialize ISR with 0xFFFFFFFE + set y, 0 ; Y = 0 + mov x, ~y ; X = 0xFFFFFFFF + set y, 3 ; 4 columns (0-3) + nop [16] ; short delay to let other state machines perform debouncing + +loop: + mov osr, isr ; copy ISR to OSR for output + out pins, 4 ; set 4 pins for columns, one is LOW, others HIGH + in x, 1 ; shift left ISR by 1 bit + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio new file mode 100644 index 0000000..cdcc6b8 --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow4Rows.pio @@ -0,0 +1,37 @@ +.program active_low_4_rows_pio + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + wait 0 PIN 4 ; wait for column 1 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 5 ; wait for column 2 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 6 ; wait for odd column 3 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + wait 0 PIN 7 ; wait for odd column 4 + in pins, 4 ; read 4 rows to ISR (shifting into lower 4 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio new file mode 100644 index 0000000..a0b05bf --- /dev/null +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio @@ -0,0 +1,37 @@ +.program active_low_8_rows_pio + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + wait 0 PIN 4 ; wait for column 1 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 5 ; wait for column 2 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 6 ; wait for odd column 3 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + wait 0 PIN 7 ; wait for odd column 4 + in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to X for comparison + jmp x!=y changed + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 0 ; trigger interrupt 0 to notify the main CPU that matrix changed + jmp loop diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index cefebe1..a2a462f 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -1,11 +1,13 @@ #include "Switches.h" -#include "Switches.pio.h" +#include "SwitchesPIO/ActiveLow4Switches.pio.h" +#include "SwitchesPIO/ActiveLow8Switches.pio.h" +#include "SwitchesPIO/ActiveLow16Switches.pio.h" Switches* Switches::instance = nullptr; void Switches::registerSwitch(byte p, byte n) { - if (last < (MAX_SWITCHES - 1)) { + if (last < (numSwitches - 1) && p < numSwitches) { port[++last] = p; number[last] = n; active = true; @@ -16,33 +18,30 @@ void Switches::handleSwitchChanges(uint16_t raw) { absolute_time_t now = get_absolute_time(); uint16_t changed = raw ^ lastStable; // raw to raw comparison - for (int i = 0; i < MAX_SWITCHES; i++) { + for (int i = 0; i < numSwitches; i++) { if (number[i] == 0) continue; // Not registered - uint16_t mask = 1u << i; + uint16_t mask = 1u << (numSwitches - 1 - i); // the switches are in inverted order if (changed & mask) { + bool rawBit = (raw & mask) != 0; + bool switchState = !rawBit; // active-low: 0 = pressed + // Debounce - if (absolute_time_diff_us(debounceTime[i], now) >= + if (absolute_time_diff_us(debounceTime[i][switchState], now) >= SWITCH_DEBOUNCE * 1000) { - debounceTime[i] = now; - - bool rawBit = (raw & mask) != 0; - - // logical pressed state for active-low - bool newState = !rawBit; - - // store raw stable bit (not logical) - if (rawBit) - lastStable |= mask; // raw = 1 - else - lastStable &= ~mask; // raw = 0 + debounceTime[i][switchState] = now; + // Store the *raw* stable state + if (rawBit) + lastStable |= mask; // raw=1 + else + lastStable &= ~mask; // raw=0 // Dispatch all switch events as "local fast". // If a PWM output registered to it, we have "fast flip". Useful for // flippers, kick backs, jets and sling shots. _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), newState, true)); + new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), switchState, true)); } } } @@ -67,30 +66,48 @@ void Switches::handleEvent(Event* event) { instance = this; running = true; - extern const pio_program_t switches_pio_program; - uint offset = pio_add_program(pio, &switches_pio_program); - pio_sm_config c = switches_pio_program_get_default_config(offset); + uint offset; + pio_sm_config c; + + switch (numSwitches) + { + case 4: + extern const pio_program_t active_low_4_switches_pio_program; + offset = pio_add_program(pio, &active_low_4_switches_pio_program); + c = active_low_4_switches_pio_program_get_default_config(offset); + break; + + case 8: + extern const pio_program_t active_low_8_switches_pio_program; + offset = pio_add_program(pio, &active_low_8_switches_pio_program); + c = active_low_8_switches_pio_program_get_default_config(offset); + break; + + case MAX_SWITCHES: + default: + extern const pio_program_t active_low_16_switches_pio_program; + offset = pio_add_program(pio, &active_low_16_switches_pio_program); + c = active_low_16_switches_pio_program_get_default_config(offset); + break; + } sm_config_set_in_pins(&c, SWITCHES_BASE_PIN); - sm_config_set_sideset_pins(&c, 15); // Side-set begins at GPIO 15 - sm_config_set_set_pins(&c, 15, 4); // Set begins at GPIO 15 - - // Connect 16 GPIOs to this PIO block - for (uint i = 0; i < 16; i++) { + if (MAX_SWITCHES == numSwitches) { + // Using GPIO 12-15 as switch inputs on IO_16_8_1 board requires resetting the sateful input after reading. + sm_config_set_sideset_pins(&c, 13); // Side-set begins at GPIO 13 + sm_config_set_set_pins(&c, 13, 4); // Set begins at GPIO 13 + } + // Connect GPIOs to this PIO block + for (uint i = 0; i < numSwitches; i++) { pio_gpio_init(pio, SWITCHES_BASE_PIN + i); } - // Set the pin direction at the PIO - pio_sm_set_consecutive_pindirs(pio, sm, SWITCHES_BASE_PIN, 16, false); - - sm_config_set_in_shift(&c, true, false, 16); - + pio_sm_set_consecutive_pindirs(pio, sm, SWITCHES_BASE_PIN, numSwitches, false); + sm_config_set_in_shift(&c, false, false, 0); pio_sm_init(pio, sm, offset, &c); - - irq_set_exclusive_handler(PIO0_IRQ_0, onSwitchChanges); - irq_set_enabled(PIO0_IRQ_0, true); - pio_set_irq0_source_enabled(pio, pis_interrupt0, true); - + irq_set_exclusive_handler(PIO0_IRQ_1, onSwitchChanges); + irq_set_enabled(PIO0_IRQ_1, true); + pio_set_irq0_source_enabled(pio, pis_interrupt1, true); pio_sm_set_enabled(pio, sm, true); } } diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index 982c0e0..1a8e1e8 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -15,7 +15,7 @@ #include "hardware/gpio.h" #include "hardware/pio.h" -#define SWITCHES_BASE_PIN 3 +#define SWITCHES_BASE_PIN 0 #define MAX_SWITCHES 16 #define SWITCH_DEBOUNCE 2 @@ -26,11 +26,9 @@ class Switches : public EventListener { _eventDispatcher = eD; _eventDispatcher->addListener(this, EVENT_POLL_EVENTS); _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); - - pio = pio0; - sm = 0; } + void setNumSwitches(uint8_t n) { numSwitches = n; } void registerSwitch(byte p, byte n); void handleEvent(Event* event); @@ -39,11 +37,12 @@ class Switches : public EventListener { void handleSwitchChanges(uint16_t raw); - PIO pio; - int sm; + PIO pio = pio0; + int sm = 2; // State machine 0 and 1 are used by SwitchMatrix private: byte boardId; + uint8_t numSwitches = MAX_SWITCHES; bool running = false; bool active = false; @@ -52,15 +51,15 @@ class Switches : public EventListener { int last = -1; uint16_t lastStable = 0; - absolute_time_t debounceTime[MAX_SWITCHES] = {0}; + absolute_time_t debounceTime[MAX_SWITCHES][2] = {0}; EventDispatcher* _eventDispatcher; static Switches* instance; static void __not_in_flash_func(onSwitchChanges)() { - // IRQ0 clear - pio0_hw->irq = 1u << 0; + // IRQ1 clear + pio0_hw->irq = 1u << 1; // Get 16 bit from FIFO uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm); diff --git a/src/IODevices/Switches.pio b/src/IODevices/Switches.pio deleted file mode 100644 index 6dfe53b..0000000 --- a/src/IODevices/Switches.pio +++ /dev/null @@ -1,37 +0,0 @@ -.program switches_pio -.side_set 4 opt - - ; Initialize X to all pins HIGH. Max. 16 switches, so 16 bit, so 0xFFFF: - ; But "set" can only set 5 bits, so a max value of 31 (0x1F). - set x, 31 ; X = 0x1F - in x, 5 ; shift 5 bits from X into ISR, now ISR = 0x1F - in x, 5 ; shift 5 bits from X into ISR, now ISR = 0x3FF - in x, 5 ; shift 5 bits from X into ISR, now ISR = 0x7FFF - in x, 1 ; shift 1 bit from X into ISR, now ISR = 0xFFFF - mov x, isr ; copy ISR to X, now X = 0xFFFF - set y, 0 ; Y = 0x0 - -loop: - mov isr, y ; ISR = 0x0 - in pins, 16 ; read 16 switches to ISR (filling lower 16 bits of the 32bit ISR) - mov y, isr ; copy ISR to Y for comparison - jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - jmp loop - -changed: - mov osr, y - push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it - irq 0 ; trigger interrupt 0 to notify the main CPU that a switch state has changed - mov x, y ; update X to the new switch states - set y, 0 ; Y = 0x0 - - ; Reset stateful pins (GPIO 15-18) - set pindirs, 0b1111 side 0 ; change direction of 4 pins (15-18) to output, set LOW - nop side 0b1111 ; set 4 pins to HIGH - nop side 0b1111 ; short delay - nop side 0b1111 ; short delay - nop side 0b1111 ; short delay - nop side 0 ; set LOW - set pindirs, 0 side 0 ; change direction all pins back to input - - jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio new file mode 100644 index 0000000..c2da94e --- /dev/null +++ b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio @@ -0,0 +1,48 @@ +.program active_low_16_switches_pio +.side_set 4 opt + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + in pins, 16 ; read 16 switches to ISR (filling lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + nop ; short delay for debouncing + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + + ; Reset stateful pins (GPIO 15-18) + set pindirs, 0b1111 side 0 ; change direction of 4 pins (15-18) to output, set LOW + nop side 0b1111 ; set 4 pins to HIGH + nop side 0b1111 ; short delay + nop side 0b1111 ; short delay + nop side 0b1111 ; short delay + nop side 0 ; set LOW + set pindirs, 0 side 0 ; change direction all pins back to input + + jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio new file mode 100644 index 0000000..371b6c6 --- /dev/null +++ b/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio @@ -0,0 +1,31 @@ +.program active_low_4_switches_pio + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + in pins, 4 ; read 4 switches to ISR (filling lower 4 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + nop [16] ; short delay for debouncing + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio new file mode 100644 index 0000000..245b535 --- /dev/null +++ b/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio @@ -0,0 +1,31 @@ +.program active_low_8_switches_pio + ; Initialize X to all pins HIGH + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov osr, y ; OSR = 0xFFFFFFFF + +loop: + set y, 0 ; X = 0x0 + mov isr, ~y ; ISR = 0xFFFFFFFF + in pins, 8 ; read 8 switches to ISR (filling lower 8 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + + ; two consecutive identical reads required for debouncing + mov y, osr ; copy OSR to Y for comparison + jmp x!=y new_stable_state + + jmp loop + +changed: + mov x, isr ; update X to the new state + nop [16] ; short delay for debouncing + jmp loop + +new_stable_state: + mov osr, isr ; update OSR to the new state, used for debouncing + push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + jmp loop From 34a81b6db19b87cabb570656dcfe73643c02f7dd Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Wed, 19 Nov 2025 20:28:34 +0100 Subject: [PATCH 05/17] added flicker on config transmission --- src/EffectsController.cpp | 28 ++++++---------------------- src/EffectsController.h | 5 +---- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/EffectsController.cpp b/src/EffectsController.cpp index 169deb0..218e029 100644 --- a/src/EffectsController.cpp +++ b/src/EffectsController.cpp @@ -83,10 +83,6 @@ void EffectsController::addEffect(EffectContainer *container) { stackEffectContainers[++stackCounter] = container; } -void EffectsController::attachBrightnessControl(byte port, byte poti) { - brightnessControl[--port] = poti; -} - void EffectsController::setBrightness(byte port, byte brightness) { ws2812FXDevices[--port][0]->setBrightness(brightness); ws2812FXbrightness[port] = brightness; @@ -142,6 +138,12 @@ void EffectsController::handleEvent(Event *event) { void EffectsController::handleEvent(ConfigEvent *event) { if (event->boardId == boardId) { + if (flickerState) + _ledBuiltInDevice->on(); + else + _ledBuiltInDevice->off(); + flickerState = !flickerState; + switch (event->topic) { case CONFIG_TOPIC_PLATFORM: platform = event->value; @@ -517,24 +519,6 @@ void EffectsController::update() { } } } - - if (brightnessControlBasePin > 0) { - if (millis() - brightnessUpdateInterval > - UPDATE_INTERVAL_WS2812FX_BRIGHTNESS) { - // Don't update the brightness too often. - brightnessUpdateInterval = millis(); - for (byte i = 0; i < PPUC_MAX_BRIGHTNESS_CONTROLS; i++) { - brightnessControlReads[i] = - analogRead(brightnessControlBasePin + i) / 4; - } - for (byte i = 0; i < PPUC_MAX_WS2812FX_DEVICES; i++) { - if (brightnessControl[i] > 0) { - setBrightness(i + 1, - brightnessControlReads[brightnessControl[i - 1]]); - } - } - } - } } void EffectsController::start() { diff --git a/src/EffectsController.h b/src/EffectsController.h index 280997b..410bd43 100644 --- a/src/EffectsController.h +++ b/src/EffectsController.h @@ -133,10 +133,7 @@ class EffectsController : public EventListener { byte ws2812FXbrightness[PPUC_MAX_WS2812FX_DEVICES] = {0}; EffectContainer* stackEffectContainers[EFFECT_STACK_SIZE]; int stackCounter = -1; - byte brightnessControl[PPUC_MAX_WS2812FX_DEVICES] = {0}; - byte brightnessControlReads[PPUC_MAX_BRIGHTNESS_CONTROLS] = {0}; - byte brightnessControlBasePin = 0; - + bool flickerState = false; int mode = 0; byte platform; From c213354c1d57959db729fe8b9b76c50d2c722572 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Thu, 20 Nov 2025 10:27:02 +0100 Subject: [PATCH 06/17] fixed switches base pins --- src/IODevices/SwitchMatrix.h | 2 +- src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio | 8 ++++---- src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio | 8 ++++---- src/IODevices/Switches.h | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/IODevices/SwitchMatrix.h b/src/IODevices/SwitchMatrix.h index 8739b52..3aa0005 100644 --- a/src/IODevices/SwitchMatrix.h +++ b/src/IODevices/SwitchMatrix.h @@ -11,7 +11,7 @@ #include "hardware/gpio.h" #include "hardware/pio.h" -#define COLUMNS_BASE_PIN 13 // GPIO 13-16 for columns, the only pins with required hardware on IO_16_8_1 board +#define COLUMNS_BASE_PIN 15 // GPIO 15-18 for columns, the only pins with required hardware on IO_16_8_1 board #define NUM_COLUMNS 4 #define MAX_ROWS 8 #define MATRIX_SWITCH_DEBOUNCE 2 diff --git a/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio index 2bd87cb..160ac0e 100644 --- a/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio +++ b/src/IODevices/SwitchMatrixPIO/ActiveHigh8Rows.pio @@ -6,13 +6,13 @@ mov osr, y ; ISR = 0x0 loop: - wait 0 PIN 4 ; wait for column 1 + wait 0 PIN 8 ; wait for column 1 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 5 ; wait for column 2 + wait 0 PIN 9 ; wait for column 2 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 6 ; wait for odd column 3 + wait 0 PIN 10 ; wait for odd column 3 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 7 ; wait for odd column 4 + wait 0 PIN 11 ; wait for odd column 4 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) mov y, isr ; copy ISR to X for comparison diff --git a/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio b/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio index a0b05bf..607d270 100644 --- a/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio +++ b/src/IODevices/SwitchMatrixPIO/ActiveLow8Rows.pio @@ -8,13 +8,13 @@ loop: set y, 0 ; X = 0x0 mov isr, ~y ; ISR = 0xFFFFFFFF - wait 0 PIN 4 ; wait for column 1 + wait 0 PIN 8 ; wait for column 1 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 5 ; wait for column 2 + wait 0 PIN 9 ; wait for column 2 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 6 ; wait for odd column 3 + wait 0 PIN 10 ; wait for odd column 3 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) - wait 0 PIN 7 ; wait for odd column 4 + wait 0 PIN 11 ; wait for odd column 4 in pins, 8 ; read 8 rows to ISR (shifting into lower 8 bits of the 32bit ISR) mov y, isr ; copy ISR to X for comparison diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index 1a8e1e8..ac90349 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -15,7 +15,7 @@ #include "hardware/gpio.h" #include "hardware/pio.h" -#define SWITCHES_BASE_PIN 0 +#define SWITCHES_BASE_PIN 3 #define MAX_SWITCHES 16 #define SWITCH_DEBOUNCE 2 From 0f62b16f39aa329369f430d86d518d846d063ffe Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Thu, 20 Nov 2025 18:57:39 +0100 Subject: [PATCH 07/17] fixed interrupt 1, made interrupt handler public --- src/IODevices/SwitchMatrix.h | 22 +++++++++++----------- src/IODevices/Switches.cpp | 2 +- src/IODevices/Switches.h | 22 +++++++++++----------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/IODevices/SwitchMatrix.h b/src/IODevices/SwitchMatrix.h index 3aa0005..8465def 100644 --- a/src/IODevices/SwitchMatrix.h +++ b/src/IODevices/SwitchMatrix.h @@ -39,7 +39,17 @@ class SwitchMatrix : public EventListener { int sm_columns = 0; int sm_rows = 1; - private: + static SwitchMatrix* instance; + + static void __not_in_flash_func(onRowChanges)() { + // IRQ0 clear + pio0_hw->irq = 1u << 0; + + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_rows); + instance->handleRowChanges(raw); + } + + private: byte boardId; bool activeLow = false; uint8_t numRows = 4; @@ -50,16 +60,6 @@ class SwitchMatrix : public EventListener { uint32_t lastStable = 0; absolute_time_t debounceTime[NUM_COLUMNS * MAX_ROWS][2] = {0}; EventDispatcher* _eventDispatcher; - - static SwitchMatrix* instance; - - static void __not_in_flash_func(onRowChanges)() { - // IRQ0 clear - pio0_hw->irq = 1u << 0; - - uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm_rows); - instance->handleRowChanges(raw); - } }; #endif diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index a2a462f..b824577 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -107,7 +107,7 @@ void Switches::handleEvent(Event* event) { pio_sm_init(pio, sm, offset, &c); irq_set_exclusive_handler(PIO0_IRQ_1, onSwitchChanges); irq_set_enabled(PIO0_IRQ_1, true); - pio_set_irq0_source_enabled(pio, pis_interrupt1, true); + pio_set_irq1_source_enabled(pio, pis_interrupt1, true); pio_sm_set_enabled(pio, sm, true); } } diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index ac90349..6bbabc8 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -40,6 +40,17 @@ class Switches : public EventListener { PIO pio = pio0; int sm = 2; // State machine 0 and 1 are used by SwitchMatrix + static Switches* instance; + + static void __not_in_flash_func(onSwitchChanges)() { + // IRQ1 clear + pio0_hw->irq = 1u << 1; + + // Get 16 bit from FIFO + uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm); + instance->handleSwitchChanges(raw & 0xFFFF); + } + private: byte boardId; uint8_t numSwitches = MAX_SWITCHES; @@ -54,17 +65,6 @@ class Switches : public EventListener { absolute_time_t debounceTime[MAX_SWITCHES][2] = {0}; EventDispatcher* _eventDispatcher; - - static Switches* instance; - - static void __not_in_flash_func(onSwitchChanges)() { - // IRQ1 clear - pio0_hw->irq = 1u << 1; - - // Get 16 bit from FIFO - uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm); - instance->handleSwitchChanges(raw & 0xFFFF); - } }; #endif From 6ffe07530f3e710f742972339ee1699b9a2f412f Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Thu, 20 Nov 2025 20:15:45 +0100 Subject: [PATCH 08/17] fixed GPIO positions --- src/IODevices/Switches.cpp | 10 +++++----- src/IODevices/Switches.h | 2 +- src/IODevices/SwitchesPIO/ActiveLow16Switches.pio | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index b824577..59028e9 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -18,10 +18,10 @@ void Switches::handleSwitchChanges(uint16_t raw) { absolute_time_t now = get_absolute_time(); uint16_t changed = raw ^ lastStable; // raw to raw comparison - for (int i = 0; i < numSwitches; i++) { + for (int i = 0; i <= last; i++) { if (number[i] == 0) continue; // Not registered - uint16_t mask = 1u << (numSwitches - 1 - i); // the switches are in inverted order + uint16_t mask = 1u << (port[i] - SWITCHES_BASE_PIN); if (changed & mask) { bool rawBit = (raw & mask) != 0; @@ -93,9 +93,9 @@ void Switches::handleEvent(Event* event) { sm_config_set_in_pins(&c, SWITCHES_BASE_PIN); if (MAX_SWITCHES == numSwitches) { - // Using GPIO 12-15 as switch inputs on IO_16_8_1 board requires resetting the sateful input after reading. - sm_config_set_sideset_pins(&c, 13); // Side-set begins at GPIO 13 - sm_config_set_set_pins(&c, 13, 4); // Set begins at GPIO 13 + // Using GPIO 15-18 as switch inputs on IO_16_8_1 board requires resetting the sateful input after reading. + sm_config_set_set_pins(&c, 15, 4); // Set begins at GPIO 15 for 4 pins + sm_config_set_sideset_pins(&c, 15); // Side-set begins at GPIO 15 } // Connect GPIOs to this PIO block for (uint i = 0; i < numSwitches; i++) { diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index 6bbabc8..da51581 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -17,7 +17,7 @@ #define SWITCHES_BASE_PIN 3 #define MAX_SWITCHES 16 -#define SWITCH_DEBOUNCE 2 +#define SWITCH_DEBOUNCE 20 class Switches : public EventListener { public: diff --git a/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio index c2da94e..4aaf7b9 100644 --- a/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio +++ b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio @@ -28,7 +28,6 @@ changed: nop ; short delay for debouncing nop ; short delay for debouncing nop ; short delay for debouncing - nop ; short delay for debouncing jmp loop new_stable_state: @@ -37,12 +36,13 @@ new_stable_state: irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed ; Reset stateful pins (GPIO 15-18) - set pindirs, 0b1111 side 0 ; change direction of 4 pins (15-18) to output, set LOW + set pindirs, 0b1111 ; change direction of 4 pins (15-18) to output + nop side 0 ; set LOW nop side 0b1111 ; set 4 pins to HIGH nop side 0b1111 ; short delay nop side 0b1111 ; short delay nop side 0b1111 ; short delay nop side 0 ; set LOW - set pindirs, 0 side 0 ; change direction all pins back to input + set pindirs, 0 ; change direction all pins back to input jmp loop From 1cd44001da11ecbe1537e0ce417cbef4b515b76b Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Thu, 27 Nov 2025 12:13:52 +0100 Subject: [PATCH 09/17] fixed swiztch reading, use std quque for events, remove event stack limit, limit IO boards to 8, use DIP switch 4 for debug on/off --- src/EffectsController.h | 2 +- src/EventDispatcher/CrossLinkDebugger.cpp | 2 +- src/EventDispatcher/Event.h | 1 + src/EventDispatcher/EventDispatcher.cpp | 44 +++---- src/EventDispatcher/EventDispatcher.h | 5 +- src/IOBoardController.cpp | 20 ++- src/IOBoardController.h | 4 +- src/IODevices/SwitchMatrix8x16.pio | 114 +++++++++++++++++ src/IODevices/Switches.cpp | 121 ++++++++++-------- src/IODevices/Switches.h | 30 +++-- .../SwitchesPIO/ActiveLow16Switches.pio | 24 ++-- .../SwitchesPIO/ActiveLow4Switches.pio | 23 ++-- .../SwitchesPIO/ActiveLow8Switches.pio | 23 ++-- src/PPUCTimings.h | 6 +- src/main.cpp | 55 ++++---- 15 files changed, 308 insertions(+), 166 deletions(-) create mode 100644 src/IODevices/SwitchMatrix8x16.pio diff --git a/src/EffectsController.h b/src/EffectsController.h index 410bd43..f27b579 100644 --- a/src/EffectsController.h +++ b/src/EffectsController.h @@ -59,7 +59,7 @@ class EffectsController : public EventListener { if (controllerType == CONTROLLER_16_8_1) { // Read bordID. Ideal value at 10bit resolution: (DIP+1)*1023*2/35 // -> 58.46 to 935.3 - boardId = 16 - ((int)((analogRead(28) + 29.23) / 58.46)); + boardId = (16 - ((int)((analogRead(28) + 29.23) / 58.46))) & 0b0111; _ledBuiltInDevice = new LedBuiltInDevice(); _ledBuiltInDevice->on(); diff --git a/src/EventDispatcher/CrossLinkDebugger.cpp b/src/EventDispatcher/CrossLinkDebugger.cpp index b731403..6a7c287 100644 --- a/src/EventDispatcher/CrossLinkDebugger.cpp +++ b/src/EventDispatcher/CrossLinkDebugger.cpp @@ -9,7 +9,7 @@ CrossLinkDebugger::CrossLinkDebugger() { Serial.print("PPUC board #"); // Read bordID. Ideal value at 10bit resolution: (DIP+1)*1023*2/35 -> 58.46 // to 935.3 - Serial.println(16 - ((int)((analogRead(28) + 29.23) / 58.46))); + Serial.println((16 - ((int)((analogRead(28) + 29.23) / 58.46))) & 0b0111); Serial.println("PPUC core #0 started"); Serial.println("PPUC CrossLinkDebugger"); Serial.println("----------------------"); diff --git a/src/EventDispatcher/Event.h b/src/EventDispatcher/Event.h index 9850437..87d1dbb 100644 --- a/src/EventDispatcher/Event.h +++ b/src/EventDispatcher/Event.h @@ -63,6 +63,7 @@ #define CONFIG_TOPIC_MIN_PULSE_TIME 77 // "M" #define CONFIG_TOPIC_FROM 77 // "M" #define CONFIG_TOPIC_MIN_INTENSITY 77 // "M" +#define CONFIG_TOPIC_DEBOUNCE_TIME 77 // "M" #define CONFIG_TOPIC_NUMBER 78 // "N" #define CONFIG_TOPIC_AMOUNT_LEDS 79 // "O" #define CONFIG_TOPIC_NUM_ROWS 79 // "O" diff --git a/src/EventDispatcher/EventDispatcher.cpp b/src/EventDispatcher/EventDispatcher.cpp index f2a6b03..9b6434e 100644 --- a/src/EventDispatcher/EventDispatcher.cpp +++ b/src/EventDispatcher/EventDispatcher.cpp @@ -36,28 +36,22 @@ void EventDispatcher::addListener(EventListener *eventListener, char sourceId) { void EventDispatcher::dispatch(Event *event) { if (EVENT_RESET == event->sourceId) { // Force immediate handling of the reset event. Forget about the others. - for (int i = 0; i <= stackCounter; i++) { - if (stackEvents[i]) { - delete stackEvents[i]; - } + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + delete e; } - stackCounter = -1; } - if (stackCounter < (EVENT_STACK_SIZE - 1)) { - stackEvents[++stackCounter] = event; + eventQueue.push(event); - if (event->localFast) { - for (byte i = 0; i <= numListeners; i++) { - if (event->sourceId == eventListenerFilters[i] || - EVENT_SOURCE_ANY == eventListenerFilters[i]) { - eventListeners[i]->handleEvent(event); - } + if (event->localFast) { + for (byte i = 0; i <= numListeners; i++) { + if (event->sourceId == eventListenerFilters[i] || + EVENT_SOURCE_ANY == eventListenerFilters[i]) { + eventListeners[i]->handleEvent(event); } } - } else { - // Too many events stacked, delete the event and free the memory. - delete event; } } @@ -111,12 +105,11 @@ void EventDispatcher::callListeners(ConfigEvent *event, bool sendToOtherCore) { void EventDispatcher::update() { if (!rs485) { // We're on Core1, the EffectController. Transmit stacked // events to Core0. - for (int i = 0; i <= stackCounter; i++) { - Event *event = stackEvents[i]; - callListeners(event, true, false); + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, false); } - // -1 means empty. - stackCounter = -1; } else { if (hwSerial->available() >= 7) { bool success = false; @@ -165,12 +158,11 @@ void EventDispatcher::update() { // Wait until the RS485 converter switched to write mode. delayMicroseconds(RS485_MODE_SWITCH_DELAY); - for (int k = 0; k <= stackCounter; k++) { - Event *event = stackEvents[k]; - callListeners(event, true, true); + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, true); } - // -1 means empty. - stackCounter = -1; // Send NULL event to indicate that transmission is // complete. diff --git a/src/EventDispatcher/EventDispatcher.h b/src/EventDispatcher/EventDispatcher.h index 5abd4b1..bcdabb7 100644 --- a/src/EventDispatcher/EventDispatcher.h +++ b/src/EventDispatcher/EventDispatcher.h @@ -10,6 +10,8 @@ #include +#include + #include "Event.h" #include "EventListener.h" #include "MultiCoreCrossLink.h" @@ -51,8 +53,7 @@ class EventDispatcher { void callListeners(ConfigEvent* event, bool sendToOtherCore); - Event* stackEvents[EVENT_STACK_SIZE]; - int stackCounter = -1; + std::queue eventQueue; EventListener* eventListeners[MAX_EVENT_LISTENERS]; char eventListenerFilters[MAX_EVENT_LISTENERS]; diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index 40906ff..abe4724 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -2,6 +2,8 @@ #include "EventDispatcher/CrossLinkDebugger.h" +#define SWITCH_DEBOUNCE 10 + IOBoardController::IOBoardController(int cT) { _eventDispatcher = new EventDispatcher(); _eventDispatcher->addListener(this, EVENT_CONFIGURATION); @@ -15,6 +17,11 @@ IOBoardController::IOBoardController(int cT) { // Read bordID. Ideal value at 10bit resolution: (DIP+1)*1023*2/35 -> 58.46 // to 935.3 boardId = 16 - ((int)((analogRead(28) + 29.23) / 58.46)); + m_debug = (boardId & 0b1000) != 0; + if (m_debug) { + boardId -= 8; + } + _eventDispatcher->setBoard(boardId); _eventDispatcher->setRS485ModePin(RS485_MODE_PIN); _eventDispatcher->setCrossLinkSerial(Serial1); @@ -31,6 +38,12 @@ IOBoardController::IOBoardController(int cT) { void IOBoardController::update() { if (running) { + if (activeSwitches) { + // nop + } + if (activeSwitchMatrix) { + //switchMatrix()->update(); + } if (activePwmDevices) { pwmDevices()->update(); } @@ -98,6 +111,7 @@ void IOBoardController::handleEvent(ConfigEvent *event) { break; case CONFIG_TOPIC_NUMBER: _switchMatrix->registerSwitch((byte)port, event->value); + activeSwitchMatrix = true; break; } break; @@ -108,7 +122,11 @@ void IOBoardController::handleEvent(ConfigEvent *event) { port = event->value; break; case CONFIG_TOPIC_NUMBER: - _switches->registerSwitch((byte)port, event->value); + number = event->value; + break; + case CONFIG_TOPIC_DEBOUNCE_TIME: + _switches->registerSwitch((byte)port, number, event->value); + activeSwitches = true; break; } break; diff --git a/src/IOBoardController.h b/src/IOBoardController.h index 1dcfc24..b7414c5 100644 --- a/src/IOBoardController.h +++ b/src/IOBoardController.h @@ -40,7 +40,7 @@ class IOBoardController : public EventListener { void update(); - void debug() { m_debug = true; } + bool isDebug() { return m_debug; } private: PwmDevices *_pwmDevices; @@ -49,6 +49,8 @@ class IOBoardController : public EventListener { bool running = false; bool activePwmDevices = false; + bool activeSwitches = false; + bool activeSwitchMatrix = false; bool m_debug = false; int controllerType; diff --git a/src/IODevices/SwitchMatrix8x16.pio b/src/IODevices/SwitchMatrix8x16.pio new file mode 100644 index 0000000..9bd416c --- /dev/null +++ b/src/IODevices/SwitchMatrix8x16.pio @@ -0,0 +1,114 @@ +.program columns8x16_pio +.wrap_target + set y, 7 ; 8 columns (0-7) + +loop: + mov osr, y ; copy Y to OSR for output + out pins, 8 ; set 8 pins for columns, one is active LOW, others HIGH + nop [16] ; short delay to let other state machines read rows + jmp y-- loop ; decrement Y, loop until Y < 0 +.wrap + + +.program even_rows_high_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 10 ; wait for even column 8 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 8 ; wait for even column 6 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed + mov x, y ; update X to the new switch states + jmp loop + + +.program odd_rows_high_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 9 ; wait for odd column 7 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 7 ; wait for odd column 5 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed + mov x, y ; update X to the new odd columns state + jmp loop + + +; IMPORTANT: +; The "low" PIO programs must run on a separate PIO instance from the "high" PIO programs, +; because of the limitation of interrupts per PIO instance (only 2). + +.program even_rows_low_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 6 ; wait for even column 4 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 4 ; wait for even column 2 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 1 ; trigger interrupt 1 to notify the main CPU that a even columns state has changed + mov x, y ; update X to the new switch states + jmp loop + + +.program odd_rows_low_pio + ; Initialize X to all pins HIGH. + set x, 0 ; X = 0x0 + mov y, ~x ; Y = 0xFFFFFFFF + mov x, y ; X = 0xFFFFFFFF + mov isr, y ; ISR = 0xFFFFFFFF + +loop: + wait 0 GPIO 5 ; wait for odd column 3 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + wait 0 GPIO 3 ; wait for odd column 1 + in pins, 16 ; read 16 rows to ISR (shifting into lower 16 bits of the 32bit ISR) + + mov y, isr ; copy ISR to Y for comparison + jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) + jmp loop + +changed: + mov osr, y + push block ; push OSR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it + irq 0 ; trigger interrupt 0 to notify the main CPU that a odd columns state has changed + mov x, y ; update X to the new odd columns state + jmp loop diff --git a/src/IODevices/Switches.cpp b/src/IODevices/Switches.cpp index 59028e9..4e30711 100644 --- a/src/IODevices/Switches.cpp +++ b/src/IODevices/Switches.cpp @@ -1,50 +1,57 @@ #include "Switches.h" +#include "SwitchesPIO/ActiveLow16Switches.pio.h" #include "SwitchesPIO/ActiveLow4Switches.pio.h" #include "SwitchesPIO/ActiveLow8Switches.pio.h" -#include "SwitchesPIO/ActiveLow16Switches.pio.h" Switches* Switches::instance = nullptr; -void Switches::registerSwitch(byte p, byte n) { +void Switches::registerSwitch(byte p, byte n, uint8_t debounceTimeMs) { if (last < (numSwitches - 1) && p < numSwitches) { port[++last] = p; number[last] = n; + debounceSetting[last] = debounceTimeMs; active = true; } } -void Switches::handleSwitchChanges(uint16_t raw) { - absolute_time_t now = get_absolute_time(); - uint16_t changed = raw ^ lastStable; // raw to raw comparison - - for (int i = 0; i <= last; i++) { - if (number[i] == 0) continue; // Not registered - - uint16_t mask = 1u << (port[i] - SWITCHES_BASE_PIN); - - if (changed & mask) { - bool rawBit = (raw & mask) != 0; - bool switchState = !rawBit; // active-low: 0 = pressed - - // Debounce - if (absolute_time_diff_us(debounceTime[i][switchState], now) >= - SWITCH_DEBOUNCE * 1000) { - debounceTime[i][switchState] = now; - // Store the *raw* stable state - if (rawBit) - lastStable |= mask; // raw=1 +void Switches::handleSwitchChanges(uint32_t raw) { + uint32_t now = millis(); + uint32_t changed = raw ^ currentStable; + if (changed > 0) { + uint32_t allSwitchesMask = 0; + + for (int i = 0; i <= last; i++) { + uint32_t mask = 1u << (port[i] - SWITCHES_BASE_PIN); + allSwitchesMask |= mask; + + if (changed & mask) { + // Debounce + if ((debounceTime[i] + debounceSetting[i]) < now) { + debounceTime[i] = now; + bool switchState = ((raw & mask) != 0); + if (switchState) + currentStable |= mask; // set bit in lastStable to 1 else - lastStable &= ~mask; // raw=0 - - // Dispatch all switch events as "local fast". - // If a PWM output registered to it, we have "fast flip". Useful for - // flippers, kick backs, jets and sling shots. - _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), switchState, true)); + currentStable &= ~mask; // set bit in lastStable to 0 + // Dispatch all switch events as "local fast". + // If a PWM output registered to it, we have "fast flip". Useful for + // flippers, kick backs, jets and sling shots. + _eventDispatcher->dispatch(new Event(EVENT_SOURCE_SWITCH, + word(0, number[i]), + switchState ? 1 : 0, true)); + // digitalWrite(LED_BUILTIN, switchState); + } } } + + // Set unregistered switches to raw value to for next comparison + currentStable = + (currentStable & allSwitchesMask) + (raw & ~allSwitchesMask); } + + // Push debounced state to PIO for next detection + pio_sm_put(pio, sm, ~currentStable); } void Switches::handleEvent(Event* event) { @@ -57,8 +64,10 @@ void Switches::handleEvent(Event* event) { // the IRQ handler. for (int i = 0; i <= last; i++) { if (number[i] != 0) { + uint16_t mask = 1u << (port[i] - SWITCHES_BASE_PIN); _eventDispatcher->dispatch( - new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), 0)); + new Event(EVENT_SOURCE_SWITCH, word(0, number[i]), + ((currentStable & mask) > 0) ? 1 : 0)); } } @@ -69,40 +78,44 @@ void Switches::handleEvent(Event* event) { uint offset; pio_sm_config c; - switch (numSwitches) - { - case 4: - extern const pio_program_t active_low_4_switches_pio_program; - offset = pio_add_program(pio, &active_low_4_switches_pio_program); - c = active_low_4_switches_pio_program_get_default_config(offset); - break; - - case 8: - extern const pio_program_t active_low_8_switches_pio_program; - offset = pio_add_program(pio, &active_low_8_switches_pio_program); - c = active_low_8_switches_pio_program_get_default_config(offset); - break; - - case MAX_SWITCHES: - default: - extern const pio_program_t active_low_16_switches_pio_program; - offset = pio_add_program(pio, &active_low_16_switches_pio_program); - c = active_low_16_switches_pio_program_get_default_config(offset); - break; + switch (numSwitches) { + case 4: + extern const pio_program_t active_low_4_switches_pio_program; + offset = pio_add_program(pio, &active_low_4_switches_pio_program); + c = active_low_4_switches_pio_program_get_default_config(offset); + break; + + case 8: + extern const pio_program_t active_low_8_switches_pio_program; + offset = pio_add_program(pio, &active_low_8_switches_pio_program); + c = active_low_8_switches_pio_program_get_default_config(offset); + break; + + case MAX_SWITCHES: + default: + extern const pio_program_t active_low_16_switches_pio_program; + offset = + pio_add_program(pio, &active_low_16_switches_pio_program); + c = active_low_16_switches_pio_program_get_default_config(offset); + break; } sm_config_set_in_pins(&c, SWITCHES_BASE_PIN); if (MAX_SWITCHES == numSwitches) { - // Using GPIO 15-18 as switch inputs on IO_16_8_1 board requires resetting the sateful input after reading. - sm_config_set_set_pins(&c, 15, 4); // Set begins at GPIO 15 for 4 pins - sm_config_set_sideset_pins(&c, 15); // Side-set begins at GPIO 15 + // Using GPIO 15-18 as switch inputs on IO_16_8_1 board requires + // resetting the sateful input after reading. + // Set begins at GPIO 15 for 4 pins. + sm_config_set_set_pins(&c, 15, 4); + // Side-set begins at GPIO 15. + sm_config_set_sideset_pins(&c, 15); } // Connect GPIOs to this PIO block for (uint i = 0; i < numSwitches; i++) { pio_gpio_init(pio, SWITCHES_BASE_PIN + i); } // Set the pin direction at the PIO - pio_sm_set_consecutive_pindirs(pio, sm, SWITCHES_BASE_PIN, numSwitches, false); + pio_sm_set_consecutive_pindirs(pio, sm, SWITCHES_BASE_PIN, + numSwitches, false); sm_config_set_in_shift(&c, false, false, 0); pio_sm_init(pio, sm, offset, &c); irq_set_exclusive_handler(PIO0_IRQ_1, onSwitchChanges); diff --git a/src/IODevices/Switches.h b/src/IODevices/Switches.h index da51581..e79e98d 100644 --- a/src/IODevices/Switches.h +++ b/src/IODevices/Switches.h @@ -17,7 +17,6 @@ #define SWITCHES_BASE_PIN 3 #define MAX_SWITCHES 16 -#define SWITCH_DEBOUNCE 20 class Switches : public EventListener { public: @@ -28,41 +27,46 @@ class Switches : public EventListener { _eventDispatcher->addListener(this, EVENT_READ_SWITCHES); } - void setNumSwitches(uint8_t n) { numSwitches = n; } - void registerSwitch(byte p, byte n); + void setNumSwitches(uint8_t n) { + numSwitches = n; + validSwitchMask = (1u << numSwitches) - 1; + } + void registerSwitch(byte p, byte n, uint8_t debounceTimeMs); void handleEvent(Event* event); void handleEvent(ConfigEvent* event) {} - void handleSwitchChanges(uint16_t raw); + void handleSwitchChanges(uint32_t raw); PIO pio = pio0; - int sm = 2; // State machine 0 and 1 are used by SwitchMatrix - + int sm = 2; // State machine 0 and 1 are used by SwitchMatrix + uint16_t validSwitchMask = (1u << MAX_SWITCHES) - 1; static Switches* instance; + uint8_t numSwitches = MAX_SWITCHES; static void __not_in_flash_func(onSwitchChanges)() { - // IRQ1 clear + // re-enable IRQ1 for next switch change (clear IRQ 1) pio0_hw->irq = 1u << 1; - // Get 16 bit from FIFO - uint32_t raw = pio_sm_get_blocking(instance->pio, instance->sm); - instance->handleSwitchChanges(raw & 0xFFFF); + // Get 32 bit from FIFO + uint32_t raw = pio_sm_get(instance->pio, instance->sm); + instance->handleSwitchChanges((~raw) & instance->validSwitchMask); } private: byte boardId; - uint8_t numSwitches = MAX_SWITCHES; + bool running = false; bool active = false; byte port[MAX_SWITCHES] = {0}; byte number[MAX_SWITCHES] = {0}; + uint8_t debounceSetting[MAX_SWITCHES] = {0}; + uint32_t debounceTime[MAX_SWITCHES] = {0}; int last = -1; - uint16_t lastStable = 0; - absolute_time_t debounceTime[MAX_SWITCHES][2] = {0}; + uint16_t currentStable = 0; EventDispatcher* _eventDispatcher; }; diff --git a/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio index 4aaf7b9..3f4bdbe 100644 --- a/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio +++ b/src/IODevices/SwitchesPIO/ActiveLow16Switches.pio @@ -1,40 +1,39 @@ .program active_low_16_switches_pio .side_set 4 opt - ; Initialize X to all pins HIGH set x, 0 ; X = 0x0 - mov y, ~x ; Y = 0xFFFFFFFF - mov x, y ; X = 0xFFFFFFFF - mov osr, y ; OSR = 0xFFFFFFFF + mov osr, ~x + jmp reset_stateful_pins loop: - set y, 0 ; X = 0x0 - mov isr, ~y ; ISR = 0xFFFFFFFF - in pins, 16 ; read 16 switches to ISR (filling lower 16 bits of the 32bit ISR) - + mov isr, null ; ISR is 0x0 + in pins, 16 ; read 16 switches to ISR mov y, isr ; copy ISR to Y for comparison jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - ; two consecutive identical reads required for debouncing + ; two consecutive identical reads required for first level of debouncing mov y, osr ; copy OSR to Y for comparison jmp x!=y new_stable_state jmp loop changed: - mov x, isr ; update X to the new state + mov x, y ; update X to the new state nop ; short delay for debouncing nop ; short delay for debouncing nop ; short delay for debouncing nop ; short delay for debouncing nop ; short delay for debouncing nop ; short delay for debouncing + jmp loop new_stable_state: - mov osr, isr ; update OSR to the new state, used for debouncing - push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + push ; push ISR to RX FIFO irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + pull ; pull debounced state from TX FIFO to OSR + mov x, osr ; update X to the new state from OSR +reset_stateful_pins: ; Reset stateful pins (GPIO 15-18) set pindirs, 0b1111 ; change direction of 4 pins (15-18) to output nop side 0 ; set LOW @@ -44,5 +43,4 @@ new_stable_state: nop side 0b1111 ; short delay nop side 0 ; set LOW set pindirs, 0 ; change direction all pins back to input - jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio index 371b6c6..de3aa17 100644 --- a/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio +++ b/src/IODevices/SwitchesPIO/ActiveLow4Switches.pio @@ -1,31 +1,28 @@ .program active_low_4_switches_pio - ; Initialize X to all pins HIGH set x, 0 ; X = 0x0 - mov y, ~x ; Y = 0xFFFFFFFF - mov x, y ; X = 0xFFFFFFFF - mov osr, y ; OSR = 0xFFFFFFFF + mov osr, ~x loop: - set y, 0 ; X = 0x0 - mov isr, ~y ; ISR = 0xFFFFFFFF - in pins, 4 ; read 4 switches to ISR (filling lower 4 bits of the 32bit ISR) - + mov isr, null ; ISR is 0x0 + in pins, 8 ; read 4 switches to ISR mov y, isr ; copy ISR to Y for comparison jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - ; two consecutive identical reads required for debouncing + ; two consecutive identical reads required for first level of debouncing mov y, osr ; copy OSR to Y for comparison jmp x!=y new_stable_state jmp loop changed: - mov x, isr ; update X to the new state - nop [16] ; short delay for debouncing + mov x, y ; update X to the new state + jmp loop new_stable_state: - mov osr, isr ; update OSR to the new state, used for debouncing - push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + push ; push ISR to RX FIFO irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + pull ; pull debounced state from TX FIFO to OSR + mov x, osr ; update X to the new state from OSR + jmp loop diff --git a/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio b/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio index 245b535..19c140a 100644 --- a/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio +++ b/src/IODevices/SwitchesPIO/ActiveLow8Switches.pio @@ -1,31 +1,28 @@ .program active_low_8_switches_pio - ; Initialize X to all pins HIGH set x, 0 ; X = 0x0 - mov y, ~x ; Y = 0xFFFFFFFF - mov x, y ; X = 0xFFFFFFFF - mov osr, y ; OSR = 0xFFFFFFFF + mov osr, ~x loop: - set y, 0 ; X = 0x0 - mov isr, ~y ; ISR = 0xFFFFFFFF - in pins, 8 ; read 8 switches to ISR (filling lower 8 bits of the 32bit ISR) - + mov isr, null ; ISR is 0x0 + in pins, 8 ; read 8 switches to ISR mov y, isr ; copy ISR to Y for comparison jmp x!=y changed ; jump to "changed" if X (old state) != Y (new state) - ; two consecutive identical reads required for debouncing + ; two consecutive identical reads required for first level of debouncing mov y, osr ; copy OSR to Y for comparison jmp x!=y new_stable_state jmp loop changed: - mov x, isr ; update X to the new state - nop [16] ; short delay for debouncing + mov x, y ; update X to the new state + jmp loop new_stable_state: - mov osr, isr ; update OSR to the new state, used for debouncing - push block ; push ISR to FIFO, "block" waits until the CPU handled enough data in the FIFO, so we don't overflow it, ISR is empty after that! + push ; push ISR to RX FIFO irq 1 ; trigger interrupt 1 to notify the main CPU that switches changed + pull ; pull debounced state from TX FIFO to OSR + mov x, osr ; update X to the new state from OSR + jmp loop diff --git a/src/PPUCTimings.h b/src/PPUCTimings.h index 91b5929..821bc01 100644 --- a/src/PPUCTimings.h +++ b/src/PPUCTimings.h @@ -6,9 +6,9 @@ #ifndef PPUC_TIMINGS_h #define PPUC_TIMINGS_h -#define WAIT_FOR_EFFECT_CONTROLLER_RESET 3000 // 3 seconds -#define WAIT_FOR_SERIAL_DEBUGGER_TIMEOUT 1000 // 1 second -#define WAIT_FOR_IO_BOARD_BOOT 1000 // 1 second +#define WAIT_FOR_EFFECT_CONTROLLER_RESET 3000 // 3 seconds +#define WAIT_FOR_SERIAL_DEBUGGER_TIMEOUT 1000 // 1 second +#define WAIT_FOR_IO_BOARD_BOOT 1000 // 1 second #define WAIT_FOR_IO_BOARD_RESET \ (WAIT_FOR_SERIAL_DEBUGGER_TIMEOUT + WAIT_FOR_EFFECT_CONTROLLER_RESET + \ diff --git a/src/main.cpp b/src/main.cpp index efaf71e..ab2ba51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,29 +44,6 @@ void setup() { // Overclock according to Raspberry Pi Pico SDK recommendations. set_sys_clock_khz(SYS_CLK_KHZ, true); - uint32_t timeout = millis() + WAIT_FOR_SERIAL_DEBUGGER_TIMEOUT; - - Serial.begin(115200); - // Wait for a serial connection of a debugger via USB. Serial us USB CDC - // The Pico implements USB itself so special care must be taken. Use - // while(!Serial){} in the setup() code before printing anything so that it - // waits for the USB connection to be established. - // https://community.platformio.org/t/serial-monitor-not-working/1512/25 - while (!Serial && millis() < timeout) { - } - - if (Serial) { - usb_debugging = true; - ioBoardController.debug(); - delay(10); - ioBoardController.eventDispatcher()->addListener(new CrossLinkDebugger()); - } else { - Serial.end(); - } - - core_0_initilized = true; - rp2040.restartCore1(); - // RS485 connection. Serial1.end(); // Deactivete UART to empty TX FIFO after reboot delay(5); @@ -79,12 +56,40 @@ void setup() { Serial1.read(); } - // The watchdog interferes with the USB debuging. - if (!usb_debugging) { + usb_debugging = ioBoardController.isDebug(); + + if (usb_debugging) { + pinMode(LED_BUILTIN, OUTPUT); + Serial.begin(115200); + delay(100); + // Wait for a serial connection of a debugger via USB CDC. + // The Pico implements USB itself so special care must be taken. Use + // while(!Serial){} in the setup() code before printing anything so that + // it waits for the USB connection to be established. + // https://community.platformio.org/t/serial-monitor-not-working/1512/25 + while (!Serial) { + digitalWrite(LED_BUILTIN, HIGH); + delay(100); + digitalWrite(LED_BUILTIN, LOW); + delay(100); + digitalWrite(LED_BUILTIN, HIGH); + delay(100); + digitalWrite(LED_BUILTIN, LOW); + delay(1000); + } + + Serial.println("USB Serial debugging active."); + // ioBoardController.eventDispatcher()->addListener(new CrossLinkDebugger()); + } else { + // The watchdog interferes with the USB debuging, so only start it + // if USB debugging is not active. if (!ITimer.attachInterruptInterval(1000000, watchdog)) { // @todo } } + + core_0_initilized = true; + rp2040.restartCore1(); } void setup1() { From 3980436beb7b2bdf6acd1323c3aea0e99c039c28 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Wed, 28 Jan 2026 22:07:00 +0100 Subject: [PATCH 10/17] fixed typos --- .github/workflows/io-bords.yml | 111 --------------------------------- src/IOBoardController.h | 8 --- src/main.cpp | 6 +- 3 files changed, 3 insertions(+), 122 deletions(-) delete mode 100644 .github/workflows/io-bords.yml diff --git a/.github/workflows/io-bords.yml b/.github/workflows/io-bords.yml deleted file mode 100644 index d1a42d3..0000000 --- a/.github/workflows/io-bords.yml +++ /dev/null @@ -1,111 +0,0 @@ -name: PPUC IO Boards - -on: - push: - pull_request: - schedule: - - cron: '0 9 * * *' # run at 08:00 UTC - -jobs: - version: - name: Detect version - runs-on: ubuntu-latest - outputs: - tag: ${{ steps.version.outputs.tag }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - id: version - run: | - VERSION_MAJOR=$(grep -Eo "FIRMWARE_VERSION_MAJOR\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") - VERSION_MINOR=$(grep -Eo "FIRMWARE_VERSION_MINOR\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") - VERSION_PATCH=$(grep -Eo "FIRMWARE_VERSION_PATCH\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") - TAG="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" - echo "${TAG}" - echo "tag=${TAG}" >> $GITHUB_OUTPUT - - name: Check git tag - if: startsWith(github.ref, 'refs/tags/v') - run: | - GIT_TAG="${GITHUB_REF#refs/tags/}" - EXPECTED_TAG="v${{ steps.version.outputs.tag }}" - if [[ "${GIT_TAG}" != "${EXPECTED_TAG}" ]]; then - echo "Error: Git tag (${GIT_TAG}) does not match version from PPUC.h (v${{ steps.version.outputs.tag }})" - exit 1 - fi - - pio-run: - name: Build and upload firmware - runs-on: ubuntu-latest - needs: [ version ] - - strategy: - fail-fast: false - matrix: - controller: ['IO_16_8_1'] - - steps: - - name: PPUC ${{ matrix.controller }} - uses: actions/checkout@v4 - - - name: Cache pip - uses: actions/cache@v4 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Cache PlatformIO - uses: actions/cache@v4 - with: - path: ~/.platformio - key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - - - name: Set up Python - uses: actions/setup-python@v5 - - - name: Install PlatformIO - run: | - python -m pip install --upgrade pip - pip install --upgrade platformio - - - name: Build elf2uf2 from source - run: | - sudo apt update && sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi - git clone https://github.com/ckormanyos/elf2uf2.git - cd elf2uf2 - make all - sudo cp bin/elf2uf2 /usr/local/bin/ - cd .. - - - name: Run PlatformIO - run: | - pio run - - - name: Create UF2 file (using elf2uf2) - run: | - ELF_FILE=$(find .pio/build/ -name "*.elf" | head -n 1) - echo "Found ELF file: $ELF_FILE" - elf2uf2 "$ELF_FILE" "${ELF_FILE%.elf}.uf2" - cp ${ELF_FILE%.elf}.uf2 ${{ matrix.controller }}-${{ needs.version.outputs.tag }}.uf2 - - name: Upload UF2 artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.controller }}-firmware - path: | - ${{ matrix.controller }}-${{ needs.version.outputs.tag }}.uf2 - - post-build: - runs-on: ubuntu-latest - needs: [ version, pio-run ] - name: Release - steps: - - uses: actions/download-artifact@v4 - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/v') - with: - draft: true - files: | - */*.uf2 diff --git a/src/IOBoardController.h b/src/IOBoardController.h index b7414c5..9e7240b 100644 --- a/src/IOBoardController.h +++ b/src/IOBoardController.h @@ -1,14 +1,6 @@ /* IOBoardController.h Created by Markus Kalkbrenner. - - GPIO0-7: Input (Switches) or low power output - GPIO8-15: Input (Switches) - GPIO16,17,18: UART TX, UART RX, RS485 Direction - GPIO19-24, 26, 27: Power Out (PWM) - GPIO25: Status-LED - GPIO28: ADC for Board ID - GPIO29: Reserve (z.B. für einen LED-Strip oder zweite Status-LED) */ #ifndef IOBOARDCONTROLLER_h diff --git a/src/main.cpp b/src/main.cpp index ab2ba51..a8381cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,7 +32,7 @@ bool watchdog(struct repeating_timer *t) { } bool usb_debugging = false; -bool core_0_initilized = false; +bool core_0_initialized = false; // Each controller will be bound to its own core and has it's own // EventDispatcher. Only the EventDispatcher of IOBoardController @@ -88,12 +88,12 @@ void setup() { } } - core_0_initilized = true; + core_0_initialized = true; rp2040.restartCore1(); } void setup1() { - while (!core_0_initilized) { + while (!core_0_initialized) { } if (usb_debugging) { From 608951e6a5075440facaf4e48ed245eb1e65a7da Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Wed, 28 Jan 2026 23:12:49 +0100 Subject: [PATCH 11/17] pios --- .github/workflows/io-boards.yml | 111 ++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 .github/workflows/io-boards.yml diff --git a/.github/workflows/io-boards.yml b/.github/workflows/io-boards.yml new file mode 100644 index 0000000..d1a42d3 --- /dev/null +++ b/.github/workflows/io-boards.yml @@ -0,0 +1,111 @@ +name: PPUC IO Boards + +on: + push: + pull_request: + schedule: + - cron: '0 9 * * *' # run at 08:00 UTC + +jobs: + version: + name: Detect version + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - id: version + run: | + VERSION_MAJOR=$(grep -Eo "FIRMWARE_VERSION_MAJOR\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") + VERSION_MINOR=$(grep -Eo "FIRMWARE_VERSION_MINOR\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") + VERSION_PATCH=$(grep -Eo "FIRMWARE_VERSION_PATCH\s+[0-9]+" src/PPUC.h | grep -Eo "[0-9]+") + TAG="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" + echo "${TAG}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + - name: Check git tag + if: startsWith(github.ref, 'refs/tags/v') + run: | + GIT_TAG="${GITHUB_REF#refs/tags/}" + EXPECTED_TAG="v${{ steps.version.outputs.tag }}" + if [[ "${GIT_TAG}" != "${EXPECTED_TAG}" ]]; then + echo "Error: Git tag (${GIT_TAG}) does not match version from PPUC.h (v${{ steps.version.outputs.tag }})" + exit 1 + fi + + pio-run: + name: Build and upload firmware + runs-on: ubuntu-latest + needs: [ version ] + + strategy: + fail-fast: false + matrix: + controller: ['IO_16_8_1'] + + steps: + - name: PPUC ${{ matrix.controller }} + uses: actions/checkout@v4 + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Cache PlatformIO + uses: actions/cache@v4 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + + - name: Set up Python + uses: actions/setup-python@v5 + + - name: Install PlatformIO + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + + - name: Build elf2uf2 from source + run: | + sudo apt update && sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi + git clone https://github.com/ckormanyos/elf2uf2.git + cd elf2uf2 + make all + sudo cp bin/elf2uf2 /usr/local/bin/ + cd .. + + - name: Run PlatformIO + run: | + pio run + + - name: Create UF2 file (using elf2uf2) + run: | + ELF_FILE=$(find .pio/build/ -name "*.elf" | head -n 1) + echo "Found ELF file: $ELF_FILE" + elf2uf2 "$ELF_FILE" "${ELF_FILE%.elf}.uf2" + cp ${ELF_FILE%.elf}.uf2 ${{ matrix.controller }}-${{ needs.version.outputs.tag }}.uf2 + - name: Upload UF2 artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.controller }}-firmware + path: | + ${{ matrix.controller }}-${{ needs.version.outputs.tag }}.uf2 + + post-build: + runs-on: ubuntu-latest + needs: [ version, pio-run ] + name: Release + steps: + - uses: actions/download-artifact@v4 + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/v') + with: + draft: true + files: | + */*.uf2 From 6abb7902171d27510de42477d874e7555cbc80d3 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Sun, 15 Feb 2026 13:23:49 +0100 Subject: [PATCH 12/17] prepared v2 --- src/EventDispatcher/EventDispatcher.cpp | 481 ++++++++++++++++++------ src/EventDispatcher/EventDispatcher.h | 21 ++ src/PPUCProtocolV2.h | 198 ++++++++++ 3 files changed, 579 insertions(+), 121 deletions(-) create mode 100644 src/PPUCProtocolV2.h diff --git a/src/EventDispatcher/EventDispatcher.cpp b/src/EventDispatcher/EventDispatcher.cpp index 9b6434e..4659d0d 100644 --- a/src/EventDispatcher/EventDispatcher.cpp +++ b/src/EventDispatcher/EventDispatcher.cpp @@ -1,6 +1,18 @@ #include "EventDispatcher.h" -EventDispatcher::EventDispatcher() {} +#include + +EventDispatcher::EventDispatcher() { + for (uint16_t i = 0; i < ppuc::v2::kMaxCoilBits; ++i) { + coilIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < ppuc::v2::kMaxLampBits; ++i) { + lampIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < ppuc::v2::kMaxSwitchBits; ++i) { + switchIndexToNumber[i] = i; + } +} void EventDispatcher::setRS485ModePin(int pin) { rs485 = true; @@ -82,6 +94,10 @@ void EventDispatcher::callListeners(Event *event, bool sendToOtherCore, multiCoreCrossLink->pushEvent(event); } + if (event->sourceId == EVENT_SOURCE_SWITCH) { + updateSwitchBitmap(event); + } + // delete the event and free the memory delete event; } @@ -102,148 +118,371 @@ void EventDispatcher::callListeners(ConfigEvent *event, bool sendToOtherCore) { delete event; } -void EventDispatcher::update() { - if (!rs485) { // We're on Core1, the EffectController. Transmit stacked - // events to Core0. - while (!eventQueue.empty()) { - Event *e = eventQueue.front(); - eventQueue.pop(); - callListeners(e, true, false); +bool EventDispatcher::readBytes(byte *buffer, size_t len) { + size_t offset = 0; + uint32_t start = micros(); + while (offset < len) { + if (hwSerial->available() > 0) { + buffer[offset++] = hwSerial->read(); + continue; } - } else { - if (hwSerial->available() >= 7) { - bool success = false; - - byte startByte = hwSerial->read(); - if (startByte == 255) { - byte sourceId = hwSerial->read(); - if (sourceId != 0) { - if (sourceId == EVENT_CONFIGURATION) { - // Config Event has 12 bytes, 2 bytes are already parsed above. - while (hwSerial->available() < 10) { - } - // We have a ConfigEvent. - byte boardId = hwSerial->read(); - byte topic = hwSerial->read(); - byte index = hwSerial->read(); - byte key = hwSerial->read(); - uint32_t value = (((uint32_t)hwSerial->read()) << 24) + - (((uint32_t)hwSerial->read()) << 16) + - (((uint32_t)hwSerial->read()) << 8) + - hwSerial->read(); - byte stopByte = hwSerial->read(); - if (stopByte == 0b10101010) { - stopByte = hwSerial->read(); - if (stopByte == 0b01010101) { - success = true; - callListeners( - new ConfigEvent(boardId, topic, index, key, value), true); - } - } - } else { - word eventId = word(hwSerial->read(), hwSerial->read()); - if (eventId != 0) { - byte value = hwSerial->read(); - byte stopByte = hwSerial->read(); - if (stopByte == 0b10101010) { - stopByte = hwSerial->read(); - if (stopByte == 0b01010101) { - success = true; - callListeners(new Event((char)sourceId, eventId, value), true, - false); - - if (sourceId == EVENT_POLL_EVENTS && board == value) { - digitalWrite(rs485Pin, HIGH); // Write. - // Wait until the RS485 converter switched to write mode. - delayMicroseconds(RS485_MODE_SWITCH_DELAY); - - while (!eventQueue.empty()) { - Event *e = eventQueue.front(); - eventQueue.pop(); - callListeners(e, true, true); - } - - // Send NULL event to indicate that transmission is - // complete. - callListeners(new Event(EVENT_NULL, 1, board), false, true); - - lastPoll = millis(); - - // Flush the serial buffer and wait until done. - hwSerial->flush(); - digitalWrite(rs485Pin, LOW); // Read. - // Wait until the RS485 converter switched back to read - // mode. - delayMicroseconds(RS485_MODE_SWITCH_DELAY); - } else if (sourceId == EVENT_RUN) { - running = true; - } - - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received wrong second stop byte "); - Serial.println(stopByte, DEC); - rp2040.resumeOtherCore(); - } - } - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received wrong first stop byte "); - Serial.println(stopByte, DEC); - rp2040.resumeOtherCore(); + if ((micros() - start) > 8000) { + return false; + } + } + + return true; +} + +int16_t EventDispatcher::findMappedIndex(const uint16_t* table, uint16_t count, + uint16_t number) { + for (uint16_t i = 0; i < count; ++i) { + if (table[i] == number) { + return (int16_t)i; + } + } + return -1; +} + +void EventDispatcher::updateSwitchBitmap(Event *event) { + int16_t mappedIndex = + findMappedIndex(switchIndexToNumber, runtimeConfig.switchBits, + event->eventId); + if (mappedIndex < 0) { + return; + } + + ppuc::v2::SetBitmapBit(switchStates, (uint16_t)mappedIndex, + event->value != 0); +} + +void EventDispatcher::applyOutputStates(const byte *coils, size_t coilBytes, + const byte *lamps, size_t lampBytes) { + for (uint16_t n = 0; n < runtimeConfig.coilBits; ++n) { + bool oldState = ppuc::v2::GetBitmapBit(outputCoils, n); + bool newState = ppuc::v2::GetBitmapBit(coils, n); + if (oldState != newState) { + callListeners( + new Event(EVENT_SOURCE_SOLENOID, coilIndexToNumber[n], + newState ? 1 : 0), + true, false); + } + } + memcpy(outputCoils, coils, coilBytes); + + for (uint16_t n = 0; n < runtimeConfig.lampBits; ++n) { + bool oldState = ppuc::v2::GetBitmapBit(outputLamps, n); + bool newState = ppuc::v2::GetBitmapBit(lamps, n); + if (oldState != newState) { + callListeners( + new Event(EVENT_SOURCE_LIGHT, lampIndexToNumber[n], newState ? 1 : 0), + true, false); + } + } + memcpy(outputLamps, lamps, lampBytes); +} + +void EventDispatcher::sendSwitchStateFrame(byte nextBoard) { + const size_t switchBytes = ppuc::v2::BitsToBytes(runtimeConfig.switchBits); + const size_t frameBytes = + ppuc::v2::kHeaderBytes + switchBytes + ppuc::v2::kCrcBytes; + + byte frame[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxSwitchBytes + + ppuc::v2::kCrcBytes]; + frame[0] = ppuc::v2::kSyncByte; + frame[1] = ppuc::v2::ComposeTypeAndFlags(ppuc::v2::kFrameSwitchState, + ppuc::v2::kFlagKeyframe); + frame[2] = nextBoard; + frame[3] = txSequence++; + memcpy(&frame[4], switchStates, switchBytes); + + uint16_t crc = + ppuc::v2::Crc16Ccitt(frame, ppuc::v2::kHeaderBytes + switchBytes); + frame[4 + switchBytes] = highByte(crc); + frame[5 + switchBytes] = lowByte(crc); + + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + hwSerial->write(frame, frameBytes); + hwSerial->flush(); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + lastPoll = millis(); +} + +bool EventDispatcher::handleV2Frame() { + if (hwSerial->available() < (int)ppuc::v2::kHeaderBytes) { + return false; + } + + if (hwSerial->peek() != ppuc::v2::kSyncByte) { + return false; + } + + if (!readBytes(v2Buffer, ppuc::v2::kHeaderBytes)) { + return false; + } + + ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(v2Buffer[1]); + size_t payloadBytes = 0; + switch (frameType) { + case ppuc::v2::kFrameSetup: + payloadBytes = ppuc::v2::kSetupPayloadBytes; + break; + case ppuc::v2::kFrameMapping: + payloadBytes = ppuc::v2::kMappingPayloadBytes; + break; + case ppuc::v2::kFrameOutputState: + payloadBytes = ppuc::v2::BitsToBytes(runtimeConfig.coilBits) + + ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + break; + case ppuc::v2::kFrameHeartbeat: + case ppuc::v2::kFrameError: + payloadBytes = 0; + break; + default: + return false; + } + + if (!readBytes(&v2Buffer[ppuc::v2::kHeaderBytes], + payloadBytes + ppuc::v2::kCrcBytes)) { + return false; + } + + const size_t crcOffset = ppuc::v2::kHeaderBytes + payloadBytes; + uint16_t receivedCrc = + word(v2Buffer[crcOffset], v2Buffer[crcOffset + 1]); + uint16_t expectedCrc = + ppuc::v2::Crc16Ccitt(v2Buffer, ppuc::v2::kHeaderBytes + payloadBytes); + if (receivedCrc != expectedCrc) { + return true; + } + + if (frameType == ppuc::v2::kFrameSetup) { + ppuc::v2::RuntimeConfig newConfig; + newConfig.coilBits = word(v2Buffer[4], v2Buffer[5]); + newConfig.lampBits = word(v2Buffer[6], v2Buffer[7]); + newConfig.switchBits = word(v2Buffer[8], v2Buffer[9]); + if (ppuc::v2::IsValidRuntimeConfig(newConfig)) { + runtimeConfig = newConfig; + memset(outputCoils, 0, sizeof(outputCoils)); + memset(outputLamps, 0, sizeof(outputLamps)); + memset(switchStates, 0, sizeof(switchStates)); + for (uint16_t i = 0; i < runtimeConfig.coilBits; ++i) { + coilIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < runtimeConfig.lampBits; ++i) { + lampIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < runtimeConfig.switchBits; ++i) { + switchIndexToNumber[i] = i; + } + } + return true; + } + + if (frameType == ppuc::v2::kFrameMapping) { + const uint8_t domain = v2Buffer[4]; + const uint16_t index = word(v2Buffer[6], v2Buffer[7]); + const uint16_t number = word(v2Buffer[8], v2Buffer[9]); + + if (domain == ppuc::v2::kDomainCoil && index < runtimeConfig.coilBits) { + coilIndexToNumber[index] = number; + } else if (domain == ppuc::v2::kDomainLamp && + index < runtimeConfig.lampBits) { + lampIndexToNumber[index] = number; + } else if (domain == ppuc::v2::kDomainSwitch && + index < runtimeConfig.switchBits) { + switchIndexToNumber[index] = number; + } + + return true; + } + + if (frameType == ppuc::v2::kFrameOutputState) { + const size_t coilBytes = ppuc::v2::BitsToBytes(runtimeConfig.coilBits); + const size_t lampBytes = ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + applyOutputStates(&v2Buffer[4], coilBytes, &v2Buffer[4 + coilBytes], + lampBytes); + + if (v2Buffer[2] == board) { + sendSwitchStateFrame((byte)((board + 1) % ppuc::v2::kMaxBoards)); + } + return true; + } + + return true; +} + +bool EventDispatcher::handleLegacyFrame() { + if (hwSerial->available() < 7) { + return false; + } + + bool success = false; + + byte startByte = hwSerial->read(); + if (startByte == 255) { + byte sourceId = hwSerial->read(); + if (sourceId != 0) { + if (sourceId == EVENT_CONFIGURATION) { + // Config Event has 12 bytes, 2 bytes are already parsed above. + while (hwSerial->available() < 10) { + } + + // We have a ConfigEvent. + byte boardId = hwSerial->read(); + byte topic = hwSerial->read(); + byte index = hwSerial->read(); + byte key = hwSerial->read(); + uint32_t value = (((uint32_t)hwSerial->read()) << 24) + + (((uint32_t)hwSerial->read()) << 16) + + (((uint32_t)hwSerial->read()) << 8) + + hwSerial->read(); + byte stopByte = hwSerial->read(); + if (stopByte == 0b10101010) { + stopByte = hwSerial->read(); + if (stopByte == 0b01010101) { + success = true; + callListeners(new ConfigEvent(boardId, topic, index, key, value), + true); + } + } + } else { + word eventId = word(hwSerial->read(), hwSerial->read()); + if (eventId != 0) { + byte value = hwSerial->read(); + byte stopByte = hwSerial->read(); + if (stopByte == 0b10101010) { + stopByte = hwSerial->read(); + if (stopByte == 0b01010101) { + success = true; + callListeners(new Event((char)sourceId, eventId, value), true, + false); + + if (sourceId == EVENT_POLL_EVENTS && board == value) { + digitalWrite(rs485Pin, HIGH); // Write. + // Wait until the RS485 converter switched to write mode. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, true); } + + // Send NULL event to indicate that transmission is complete. + callListeners(new Event(EVENT_NULL, 1, board), false, true); + + lastPoll = millis(); + + // Flush the serial buffer and wait until done. + hwSerial->flush(); + digitalWrite(rs485Pin, LOW); // Read. + // Wait until the RS485 converter switched back to read mode. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + } else if (sourceId == EVENT_RUN) { + running = true; } + } else { if (Serial) { rp2040.idleOtherCore(); - Serial.print("Received invalid event id "); - Serial.println(eventId, DEC); + Serial.print("Received wrong second stop byte "); + Serial.println(stopByte, DEC); rp2040.resumeOtherCore(); } } + } else { + if (Serial) { + rp2040.idleOtherCore(); + Serial.print("Received wrong first stop byte "); + Serial.println(stopByte, DEC); + rp2040.resumeOtherCore(); + } } } else { if (Serial) { rp2040.idleOtherCore(); - Serial.print("Received invalid source id "); - Serial.println(sourceId, DEC); + Serial.print("Received invalid event id "); + Serial.println(eventId, DEC); rp2040.resumeOtherCore(); } } - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received wrong start byte "); - Serial.println(startByte, DEC); - rp2040.resumeOtherCore(); + } + } else { + if (Serial) { + rp2040.idleOtherCore(); + Serial.print("Received invalid source id "); + Serial.println(sourceId, DEC); + rp2040.resumeOtherCore(); + } + } + } else { + if (Serial) { + rp2040.idleOtherCore(); + Serial.print("Received wrong start byte "); + Serial.println(startByte, DEC); + rp2040.resumeOtherCore(); + } + // We didn't receive a start byte. Fake "success" to start over with the + // next byte. + success = true; + } + + if (success) { + if (error) { + error = false; + dispatch(new Event(EVENT_NO_ERROR, 1, board)); + } + } else { + error = true; + dispatch(new Event(EVENT_ERROR, 1, board)); + + while (hwSerial->available()) { + byte bits = hwSerial->read(); + if (bits == 0b10101010 && hwSerial->available()) { + bits = hwSerial->read(); + if (bits == 0b01010101) { + // Now we should be back in sync. + break; } - // We didn't receive a start byte. Fake "success" to start over with the - // next byte. - success = true; } + } + } - if (success) { - if (error) { - error = false; - dispatch(new Event(EVENT_NO_ERROR, 1, board)); + return success; +} + +void EventDispatcher::update() { + if (!rs485) { // We're on Core1, the EffectController. Transmit stacked + // events to Core0. + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, false); + } + } else { + while (!eventQueue.empty()) { + Event *e = eventQueue.front(); + eventQueue.pop(); + callListeners(e, true, false); + } + + while (hwSerial->available() > 0) { + int firstByte = hwSerial->peek(); + if (firstByte == ppuc::v2::kSyncByte) { + if (!handleV2Frame()) { + break; } - } else { - error = true; - dispatch(new Event(EVENT_ERROR, 1, board)); - - while (hwSerial->available()) { - byte bits = hwSerial->read(); - if (bits == 0b10101010 && hwSerial->available()) { - bits = hwSerial->read(); - if (bits == 0b01010101) { - // Now we should be back in sync. - break; - } - } + } else if (firstByte == 255) { + if (!handleLegacyFrame()) { + break; } + } else { + // Desync/noise, consume one byte and continue. + hwSerial->read(); } } } diff --git a/src/EventDispatcher/EventDispatcher.h b/src/EventDispatcher/EventDispatcher.h index bcdabb7..33ee12f 100644 --- a/src/EventDispatcher/EventDispatcher.h +++ b/src/EventDispatcher/EventDispatcher.h @@ -15,6 +15,7 @@ #include "Event.h" #include "EventListener.h" #include "MultiCoreCrossLink.h" +#include "../PPUCProtocolV2.h" #ifndef MAX_EVENT_LISTENERS #define MAX_EVENT_LISTENERS 32 @@ -49,6 +50,16 @@ class EventDispatcher { uint32_t getLastPoll(); private: + bool readBytes(byte* buffer, size_t len); + bool handleLegacyFrame(); + bool handleV2Frame(); + void sendSwitchStateFrame(byte nextBoard); + void applyOutputStates(const byte* coils, size_t coilBytes, const byte* lamps, + size_t lampBytes); + void updateSwitchBitmap(Event* event); + int16_t findMappedIndex(const uint16_t* table, uint16_t count, + uint16_t number); + void callListeners(Event* event, bool sendToOtherCore, bool sendToRS485); void callListeners(ConfigEvent* event, bool sendToOtherCore); @@ -60,6 +71,16 @@ class EventDispatcher { int numListeners = -1; byte msg[12]; + byte v2Buffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxCoilBytes + + ppuc::v2::kMaxLampBytes + ppuc::v2::kCrcBytes]; + byte outputCoils[ppuc::v2::kMaxCoilBytes] = {0}; + byte outputLamps[ppuc::v2::kMaxLampBytes] = {0}; + byte switchStates[ppuc::v2::kMaxSwitchBytes] = {0}; + uint16_t coilIndexToNumber[ppuc::v2::kMaxCoilBits]; + uint16_t lampIndexToNumber[ppuc::v2::kMaxLampBits]; + uint16_t switchIndexToNumber[ppuc::v2::kMaxSwitchBits]; + byte txSequence = 0; + ppuc::v2::RuntimeConfig runtimeConfig; bool rs485 = false; uint8_t rs485Pin = 0; diff --git a/src/PPUCProtocolV2.h b/src/PPUCProtocolV2.h new file mode 100644 index 0000000..2fc2db8 --- /dev/null +++ b/src/PPUCProtocolV2.h @@ -0,0 +1,198 @@ +#pragma once + +#include +#include + +namespace ppuc { +namespace v2 { + +constexpr uint32_t kBaudRate = 250000; + +constexpr uint8_t kSyncByte = 0xA5; +constexpr uint8_t kNoBoard = 0xFF; +constexpr uint8_t kMaxBoards = 8; + +// Bitmaps are indexed by global device number: bit N => device number N. +// Runtime counts are configured per game and announced with SetupFrame. +constexpr uint16_t kDefaultCoilBits = 24; +constexpr uint16_t kDefaultLampBits = 64; +constexpr uint16_t kDefaultSwitchBits = 64; +constexpr uint16_t kMaxCoilBits = 256; +constexpr uint16_t kMaxLampBits = 256; +constexpr uint16_t kMaxSwitchBits = 256; + +constexpr size_t BitsToBytes(uint16_t bits) { return (bits + 7u) / 8u; } + +constexpr size_t kDefaultCoilBytes = BitsToBytes(kDefaultCoilBits); +constexpr size_t kDefaultLampBytes = BitsToBytes(kDefaultLampBits); +constexpr size_t kDefaultSwitchBytes = BitsToBytes(kDefaultSwitchBits); +constexpr size_t kMaxCoilBytes = BitsToBytes(kMaxCoilBits); +constexpr size_t kMaxLampBytes = BitsToBytes(kMaxLampBits); +constexpr size_t kMaxSwitchBytes = BitsToBytes(kMaxSwitchBits); + +constexpr size_t kHeaderBytes = 4; +constexpr size_t kCrcBytes = 2; + +enum FrameType : uint8_t { + kFrameOutputState = 0x01, + kFrameSwitchState = 0x02, + kFrameHeartbeat = 0x03, + kFrameError = 0x04, + kFrameSetup = 0x05, + kFrameMapping = 0x06, +}; + +enum MappingDomain : uint8_t { + kDomainCoil = 0x01, + kDomainLamp = 0x02, + kDomainSwitch = 0x03, +}; + +enum FrameFlag : uint8_t { + kFlagNone = 0x00, + kFlagKeyframe = 0x10, + kFlagDelta = 0x20, + kFlagError = 0x80, +}; + +struct FrameHeader { + uint8_t sync; + uint8_t typeAndFlags; + uint8_t nextBoard; + uint8_t sequence; +}; + +struct SetupPayload { + uint16_t coilBits; + uint16_t lampBits; + uint16_t switchBits; +}; + +struct MappingPayload { + uint8_t domain; + uint8_t reserved; + uint16_t index; + uint16_t number; +}; + +struct OutputPayload { + // Only first BitsToBytes(coilBits/lampBits) bytes are used at runtime. + uint8_t coils[kMaxCoilBytes]; + uint8_t lamps[kMaxLampBytes]; +}; + +struct SwitchPayload { + // Only first BitsToBytes(switchBits) bytes are used at runtime. + uint8_t switches[kMaxSwitchBytes]; +}; + +struct SetupFrame { + FrameHeader header; + SetupPayload payload; + uint16_t crc; +}; + +struct MappingFrame { + FrameHeader header; + MappingPayload payload; + uint16_t crc; +}; + +struct OutputStateFrame { + FrameHeader header; + OutputPayload payload; + uint16_t crc; +}; + +struct SwitchStateFrame { + FrameHeader header; + SwitchPayload payload; + uint16_t crc; +}; + +constexpr size_t kSetupPayloadBytes = sizeof(SetupPayload); +constexpr size_t kMappingPayloadBytes = sizeof(MappingPayload); +constexpr size_t kOutputPayloadBytes = sizeof(OutputPayload); +constexpr size_t kSwitchPayloadBytes = sizeof(SwitchPayload); +constexpr size_t kSetupFrameBytes = sizeof(SetupFrame); +constexpr size_t kMappingFrameBytes = sizeof(MappingFrame); +constexpr size_t kOutputFrameBytes = sizeof(OutputStateFrame); +constexpr size_t kSwitchFrameBytes = sizeof(SwitchStateFrame); + +struct RuntimeConfig { + uint16_t coilBits = kDefaultCoilBits; + uint16_t lampBits = kDefaultLampBits; + uint16_t switchBits = kDefaultSwitchBits; +}; + +inline bool IsValidRuntimeConfig(const RuntimeConfig& cfg) { + return cfg.coilBits > 0 && cfg.coilBits <= kMaxCoilBits && cfg.lampBits > 0 && + cfg.lampBits <= kMaxLampBits && cfg.switchBits > 0 && + cfg.switchBits <= kMaxSwitchBits; +} + +inline size_t OutputPayloadBytes(const RuntimeConfig& cfg) { + return BitsToBytes(cfg.coilBits) + BitsToBytes(cfg.lampBits); +} + +inline size_t SwitchPayloadBytes(const RuntimeConfig& cfg) { + return BitsToBytes(cfg.switchBits); +} + +inline size_t OutputFrameBytes(const RuntimeConfig& cfg) { + return kHeaderBytes + OutputPayloadBytes(cfg) + kCrcBytes; +} + +inline size_t SwitchFrameBytes(const RuntimeConfig& cfg) { + return kHeaderBytes + SwitchPayloadBytes(cfg) + kCrcBytes; +} + +inline uint16_t Crc16Ccitt(const uint8_t* data, size_t len) { + uint16_t crc = 0xFFFF; + for (size_t i = 0; i < len; ++i) { + crc ^= static_cast(data[i]) << 8; + for (uint8_t bit = 0; bit < 8; ++bit) { + if (crc & 0x8000) { + crc = static_cast((crc << 1) ^ 0x1021); + } else { + crc <<= 1; + } + } + } + return crc; +} + +inline uint8_t ComposeTypeAndFlags(FrameType type, uint8_t flags) { + return static_cast(static_cast(type) | flags); +} + +inline FrameType ExtractType(uint8_t typeAndFlags) { + return static_cast(typeAndFlags & 0x0F); +} + +inline uint8_t ExtractFlags(uint8_t typeAndFlags) { + return static_cast(typeAndFlags & 0xF0); +} + +inline bool IsValidBoard(uint8_t board) { + return board == kNoBoard || board < kMaxBoards; +} + +inline void SetBitmapBit(uint8_t* bitmap, uint16_t number, bool on) { + const uint16_t byteIndex = number / 8u; + const uint8_t bitMask = static_cast(1u << (number % 8u)); + if (on) { + bitmap[byteIndex] |= bitMask; + } else { + bitmap[byteIndex] &= static_cast(~bitMask); + } +} + +inline bool GetBitmapBit(const uint8_t* bitmap, uint16_t number) { + const uint16_t byteIndex = number / 8u; + const uint8_t bitMask = static_cast(1u << (number % 8u)); + return (bitmap[byteIndex] & bitMask) != 0; +} + +} // namespace v2 +} // namespace ppuc From a79b044f773a5acd68808a4720fc04a9f8ef66bd Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Sun, 15 Feb 2026 13:46:44 +0100 Subject: [PATCH 13/17] adjusted baudrate --- src/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index a8381cf..ae525ed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "EventDispatcher/CrossLinkDebugger.h" #include "IOBoardController.h" #include "PPUC.h" +#include "PPUCProtocolV2.h" #include "RPi_Pico_TimerInterrupt.h" IOBoardController ioBoardController(CONTROLLER_16_8_1); @@ -50,7 +51,7 @@ void setup() { pinMode(RS485_MODE_PIN, OUTPUT); digitalWrite(RS485_MODE_PIN, LOW); // Read mode delay(5); - Serial1.begin(115200); + Serial1.begin(ppuc::v2::kBaudRate); // Empty RX FIFO after reboot while (Serial1.available()) { Serial1.read(); From 36e197b71b6de0784044090b218e77699fefc705 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Mon, 16 Feb 2026 18:21:24 +0100 Subject: [PATCH 14/17] migrated config events to v2 --- src/EventDispatcher/EventDispatcher.cpp | 421 ++++++++++++++++++------ src/EventDispatcher/EventDispatcher.h | 30 ++ src/IOBoardController.cpp | 1 + src/PPUCProtocolV2.h | 19 ++ 4 files changed, 370 insertions(+), 101 deletions(-) diff --git a/src/EventDispatcher/EventDispatcher.cpp b/src/EventDispatcher/EventDispatcher.cpp index 4659d0d..f803ef6 100644 --- a/src/EventDispatcher/EventDispatcher.cpp +++ b/src/EventDispatcher/EventDispatcher.cpp @@ -1,7 +1,12 @@ #include "EventDispatcher.h" +#include "hardware/uart.h" #include +namespace { +constexpr uint32_t kV2RxTimeoutUs = 8000; +} + EventDispatcher::EventDispatcher() { for (uint16_t i = 0; i < ppuc::v2::kMaxCoilBits; ++i) { coilIndexToNumber[i] = i; @@ -34,6 +39,8 @@ void EventDispatcher::setCrossLinkSerial(HardwareSerial &reference) { hwSerial = (HardwareSerial *)&reference; } +void EventDispatcher::setDebug(bool enabled) { debugEnabled = enabled; } + void EventDispatcher::addListener(EventListener *eventListener) { addListener(eventListener, EVENT_SOURCE_ANY); } @@ -135,6 +142,245 @@ bool EventDispatcher::readBytes(byte *buffer, size_t len) { return true; } +size_t EventDispatcher::getV2PayloadBytes(ppuc::v2::FrameType frameType) { + switch (frameType) { + case ppuc::v2::kFrameSetup: + return ppuc::v2::kSetupPayloadBytes; + case ppuc::v2::kFrameMapping: + return ppuc::v2::kMappingPayloadBytes; + case ppuc::v2::kFrameConfig: + return ppuc::v2::kConfigPayloadBytes; + case ppuc::v2::kFrameOutputState: + return ppuc::v2::BitsToBytes(runtimeConfig.coilBits) + + ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + case ppuc::v2::kFrameHeartbeat: + case ppuc::v2::kFrameError: + case ppuc::v2::kFrameReset: + return 0; + default: + return 0; + } +} + +bool EventDispatcher::processV2Frame(const byte* frame, size_t payloadBytes) { + const ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(frame[1]); + const size_t crcOffset = ppuc::v2::kHeaderBytes + payloadBytes; + uint16_t receivedCrc = word(frame[crcOffset], frame[crcOffset + 1]); + uint16_t expectedCrc = + ppuc::v2::Crc16Ccitt(frame, ppuc::v2::kHeaderBytes + payloadBytes); + if (receivedCrc != expectedCrc) { + v2RxCrcFail++; + return false; + } + v2RxFrames++; + + if (frameType == ppuc::v2::kFrameSetup) { + ppuc::v2::RuntimeConfig newConfig; + newConfig.coilBits = word(frame[4], frame[5]); + newConfig.lampBits = word(frame[6], frame[7]); + newConfig.switchBits = word(frame[8], frame[9]); + if (ppuc::v2::IsValidRuntimeConfig(newConfig)) { + runtimeConfig = newConfig; + memset(outputCoils, 0, sizeof(outputCoils)); + memset(outputLamps, 0, sizeof(outputLamps)); + memset(switchStates, 0, sizeof(switchStates)); + for (uint16_t i = 0; i < runtimeConfig.coilBits; ++i) { + coilIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < runtimeConfig.lampBits; ++i) { + lampIndexToNumber[i] = i; + } + for (uint16_t i = 0; i < runtimeConfig.switchBits; ++i) { + switchIndexToNumber[i] = i; + } + if (!v2UartDmaActive) { + if (startV2UartDmaTransport()) { + v2CutoverOk++; + } else { + v2CutoverFail++; + } + } + } + return true; + } + + if (frameType == ppuc::v2::kFrameMapping) { + const uint8_t domain = frame[4]; + const uint16_t index = word(frame[6], frame[7]); + const uint16_t number = word(frame[8], frame[9]); + + if (domain == ppuc::v2::kDomainCoil && index < runtimeConfig.coilBits) { + coilIndexToNumber[index] = number; + } else if (domain == ppuc::v2::kDomainLamp && + index < runtimeConfig.lampBits) { + lampIndexToNumber[index] = number; + } else if (domain == ppuc::v2::kDomainSwitch && + index < runtimeConfig.switchBits) { + switchIndexToNumber[index] = number; + } + return true; + } + + if (frameType == ppuc::v2::kFrameOutputState) { + const size_t coilBytes = ppuc::v2::BitsToBytes(runtimeConfig.coilBits); + const size_t lampBytes = ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + applyOutputStates(&frame[4], coilBytes, &frame[4 + coilBytes], lampBytes); + if (frame[2] == board) { + sendSwitchStateFrame((byte)((board + 1) % ppuc::v2::kMaxBoards)); + } + return true; + } + + if (frameType == ppuc::v2::kFrameReset) { + dispatch(new Event(EVENT_RESET)); + return true; + } + + if (frameType == ppuc::v2::kFrameConfig) { + callListeners( + new ConfigEvent(frame[4], frame[5], frame[6], frame[7], + (((uint32_t)frame[8]) << 24) | + (((uint32_t)frame[9]) << 16) | + (((uint32_t)frame[10]) << 8) | + ((uint32_t)frame[11])), + true); + return true; + } + + return true; +} + +bool EventDispatcher::startV2UartDmaTransport() { + if (v2UartDmaActive) { + return true; + } + + int rxDma = dma_claim_unused_channel(false); + if (rxDma < 0) { + return false; + } + + int txDma = dma_claim_unused_channel(false); + if (txDma < 0) { + dma_channel_unclaim(rxDma); + return false; + } + v2RxDmaChannel = rxDma; + v2TxDmaChannel = txDma; + v2UartDmaActive = true; + v2RxState = V2_RX_IDLE; + v2RxPayloadBytes = 0; + v2RxStateStartUs = micros(); + + return true; +} + +void EventDispatcher::stopV2UartDmaTransport() { + if (!v2UartDmaActive) { + return; + } + + dma_channel_abort(v2RxDmaChannel); + dma_channel_abort(v2TxDmaChannel); + dma_channel_unclaim(v2RxDmaChannel); + dma_channel_unclaim(v2TxDmaChannel); + + v2UartDmaActive = false; + v2RxState = V2_RX_IDLE; + v2RxDmaChannel = -1; + v2TxDmaChannel = -1; +} + +bool EventDispatcher::sendV2FrameUartDma(const byte* frame, size_t frameBytes) { + if (!v2UartDmaActive || !frame || frameBytes == 0) { + return false; + } + + memcpy(v2DmaTxBuffer, frame, frameBytes); + dma_channel_config txConfig = dma_channel_get_default_config(v2TxDmaChannel); + channel_config_set_transfer_data_size(&txConfig, DMA_SIZE_8); + channel_config_set_dreq(&txConfig, uart_get_dreq(uart1, true)); + channel_config_set_read_increment(&txConfig, true); + channel_config_set_write_increment(&txConfig, false); + + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + dma_channel_configure(v2TxDmaChannel, &txConfig, &uart1_hw->dr, + v2DmaTxBuffer, frameBytes, true); + dma_channel_wait_for_finish_blocking(v2TxDmaChannel); + uart_tx_wait_blocking(uart1); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + v2TxFrames++; + return true; +} + +void EventDispatcher::serviceV2UartDmaRx() { + if (!v2UartDmaActive) { + return; + } + + if (v2RxState == V2_RX_IDLE) { + dma_channel_config rxConfig = dma_channel_get_default_config(v2RxDmaChannel); + channel_config_set_transfer_data_size(&rxConfig, DMA_SIZE_8); + channel_config_set_dreq(&rxConfig, uart_get_dreq(uart1, false)); + channel_config_set_read_increment(&rxConfig, false); + channel_config_set_write_increment(&rxConfig, true); + dma_channel_configure(v2RxDmaChannel, &rxConfig, v2DmaRxBuffer, + &uart1_hw->dr, ppuc::v2::kHeaderBytes, true); + v2RxState = V2_RX_HEADER; + v2RxStateStartUs = micros(); + v2RxDmaRestarts++; + return; + } + + if (v2RxState == V2_RX_HEADER && dma_channel_is_busy(v2RxDmaChannel)) { + if ((micros() - v2RxStateStartUs) > kV2RxTimeoutUs) { + dma_channel_abort(v2RxDmaChannel); + v2RxState = V2_RX_IDLE; + v2RxDmaTimeouts++; + } + return; + } + + if (v2RxState == V2_RX_HEADER) { + if (v2DmaRxBuffer[0] != ppuc::v2::kSyncByte) { + v2RxSyncFail++; + v2RxState = V2_RX_IDLE; + return; + } + ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(v2DmaRxBuffer[1]); + v2RxPayloadBytes = getV2PayloadBytes(frameType); + + dma_channel_config rxConfig = dma_channel_get_default_config(v2RxDmaChannel); + channel_config_set_transfer_data_size(&rxConfig, DMA_SIZE_8); + channel_config_set_dreq(&rxConfig, uart_get_dreq(uart1, false)); + channel_config_set_read_increment(&rxConfig, false); + channel_config_set_write_increment(&rxConfig, true); + dma_channel_configure(v2RxDmaChannel, &rxConfig, + &v2DmaRxBuffer[ppuc::v2::kHeaderBytes], + &uart1_hw->dr, + v2RxPayloadBytes + ppuc::v2::kCrcBytes, true); + v2RxState = V2_RX_BODY; + v2RxStateStartUs = micros(); + return; + } + + if (v2RxState == V2_RX_BODY && dma_channel_is_busy(v2RxDmaChannel)) { + if ((micros() - v2RxStateStartUs) > kV2RxTimeoutUs) { + dma_channel_abort(v2RxDmaChannel); + v2RxState = V2_RX_IDLE; + v2RxDmaTimeouts++; + } + return; + } + + if (v2RxState == V2_RX_BODY) { + processV2Frame(v2DmaRxBuffer, v2RxPayloadBytes); + v2RxState = V2_RX_IDLE; + } +} + int16_t EventDispatcher::findMappedIndex(const uint16_t* table, uint16_t count, uint16_t number) { for (uint16_t i = 0; i < count; ++i) { @@ -146,6 +392,10 @@ int16_t EventDispatcher::findMappedIndex(const uint16_t* table, uint16_t count, } void EventDispatcher::updateSwitchBitmap(Event *event) { + // V2 switch reporting is bitmap-based. Legacy switch events still originate + // from the existing switch devices/listeners; this method mirrors those + // events into the dense V2 switch-state RAM bitmap. On token/poll, the board + // sends this bitmap back to the CPU in one V2 switch frame. int16_t mappedIndex = findMappedIndex(switchIndexToNumber, runtimeConfig.switchBits, event->eventId); @@ -159,6 +409,10 @@ void EventDispatcher::updateSwitchBitmap(Event *event) { void EventDispatcher::applyOutputStates(const byte *coils, size_t coilBytes, const byte *lamps, size_t lampBytes) { + // V2 output frames carry full RAM snapshots. To preserve existing + // EventListener behavior, we synthesize legacy events only for changed bits + // (edge detection old snapshot -> new snapshot). This keeps the rest of the + // firmware event-driven without requiring listener rewrites. for (uint16_t n = 0; n < runtimeConfig.coilBits; ++n) { bool oldState = ppuc::v2::GetBitmapBit(outputCoils, n); bool newState = ppuc::v2::GetBitmapBit(coils, n); @@ -184,12 +438,15 @@ void EventDispatcher::applyOutputStates(const byte *coils, size_t coilBytes, } void EventDispatcher::sendSwitchStateFrame(byte nextBoard) { + // Switch updates are transmitted as a compact V2 frame containing the full + // dense switch bitmap. The CPU selects the responding board via token + // (header.nextBoard in output frame). This board answers once and then + // returns RS485 direction to RX mode. const size_t switchBytes = ppuc::v2::BitsToBytes(runtimeConfig.switchBits); const size_t frameBytes = ppuc::v2::kHeaderBytes + switchBytes + ppuc::v2::kCrcBytes; - byte frame[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxSwitchBytes + - ppuc::v2::kCrcBytes]; + byte* frame = v2DmaTxBuffer; frame[0] = ppuc::v2::kSyncByte; frame[1] = ppuc::v2::ComposeTypeAndFlags(ppuc::v2::kFrameSwitchState, ppuc::v2::kFlagKeyframe); @@ -202,12 +459,16 @@ void EventDispatcher::sendSwitchStateFrame(byte nextBoard) { frame[4 + switchBytes] = highByte(crc); frame[5 + switchBytes] = lowByte(crc); - digitalWrite(rs485Pin, HIGH); // Write. - delayMicroseconds(RS485_MODE_SWITCH_DELAY); - hwSerial->write(frame, frameBytes); - hwSerial->flush(); - digitalWrite(rs485Pin, LOW); // Read. - delayMicroseconds(RS485_MODE_SWITCH_DELAY); + if (!v2UartDmaActive || !sendV2FrameUartDma(frame, frameBytes)) { + v2TxFallback++; + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + hwSerial->write(frame, frameBytes); + hwSerial->flush(); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + } + lastPoll = millis(); } @@ -225,24 +486,12 @@ bool EventDispatcher::handleV2Frame() { } ppuc::v2::FrameType frameType = ppuc::v2::ExtractType(v2Buffer[1]); - size_t payloadBytes = 0; - switch (frameType) { - case ppuc::v2::kFrameSetup: - payloadBytes = ppuc::v2::kSetupPayloadBytes; - break; - case ppuc::v2::kFrameMapping: - payloadBytes = ppuc::v2::kMappingPayloadBytes; - break; - case ppuc::v2::kFrameOutputState: - payloadBytes = ppuc::v2::BitsToBytes(runtimeConfig.coilBits) + - ppuc::v2::BitsToBytes(runtimeConfig.lampBits); - break; - case ppuc::v2::kFrameHeartbeat: - case ppuc::v2::kFrameError: - payloadBytes = 0; - break; - default: - return false; + size_t payloadBytes = getV2PayloadBytes(frameType); + if (frameType != ppuc::v2::kFrameHeartbeat && + frameType != ppuc::v2::kFrameError && + frameType != ppuc::v2::kFrameReset && payloadBytes == 0 && + frameType != ppuc::v2::kFrameOutputState) { + return false; } if (!readBytes(&v2Buffer[ppuc::v2::kHeaderBytes], @@ -250,69 +499,7 @@ bool EventDispatcher::handleV2Frame() { return false; } - const size_t crcOffset = ppuc::v2::kHeaderBytes + payloadBytes; - uint16_t receivedCrc = - word(v2Buffer[crcOffset], v2Buffer[crcOffset + 1]); - uint16_t expectedCrc = - ppuc::v2::Crc16Ccitt(v2Buffer, ppuc::v2::kHeaderBytes + payloadBytes); - if (receivedCrc != expectedCrc) { - return true; - } - - if (frameType == ppuc::v2::kFrameSetup) { - ppuc::v2::RuntimeConfig newConfig; - newConfig.coilBits = word(v2Buffer[4], v2Buffer[5]); - newConfig.lampBits = word(v2Buffer[6], v2Buffer[7]); - newConfig.switchBits = word(v2Buffer[8], v2Buffer[9]); - if (ppuc::v2::IsValidRuntimeConfig(newConfig)) { - runtimeConfig = newConfig; - memset(outputCoils, 0, sizeof(outputCoils)); - memset(outputLamps, 0, sizeof(outputLamps)); - memset(switchStates, 0, sizeof(switchStates)); - for (uint16_t i = 0; i < runtimeConfig.coilBits; ++i) { - coilIndexToNumber[i] = i; - } - for (uint16_t i = 0; i < runtimeConfig.lampBits; ++i) { - lampIndexToNumber[i] = i; - } - for (uint16_t i = 0; i < runtimeConfig.switchBits; ++i) { - switchIndexToNumber[i] = i; - } - } - return true; - } - - if (frameType == ppuc::v2::kFrameMapping) { - const uint8_t domain = v2Buffer[4]; - const uint16_t index = word(v2Buffer[6], v2Buffer[7]); - const uint16_t number = word(v2Buffer[8], v2Buffer[9]); - - if (domain == ppuc::v2::kDomainCoil && index < runtimeConfig.coilBits) { - coilIndexToNumber[index] = number; - } else if (domain == ppuc::v2::kDomainLamp && - index < runtimeConfig.lampBits) { - lampIndexToNumber[index] = number; - } else if (domain == ppuc::v2::kDomainSwitch && - index < runtimeConfig.switchBits) { - switchIndexToNumber[index] = number; - } - - return true; - } - - if (frameType == ppuc::v2::kFrameOutputState) { - const size_t coilBytes = ppuc::v2::BitsToBytes(runtimeConfig.coilBits); - const size_t lampBytes = ppuc::v2::BitsToBytes(runtimeConfig.lampBits); - applyOutputStates(&v2Buffer[4], coilBytes, &v2Buffer[4 + coilBytes], - lampBytes); - - if (v2Buffer[2] == board) { - sendSwitchStateFrame((byte)((board + 1) % ppuc::v2::kMaxBoards)); - } - return true; - } - - return true; + return processV2Frame(v2Buffer, payloadBytes); } bool EventDispatcher::handleLegacyFrame() { @@ -470,19 +657,23 @@ void EventDispatcher::update() { callListeners(e, true, false); } - while (hwSerial->available() > 0) { - int firstByte = hwSerial->peek(); - if (firstByte == ppuc::v2::kSyncByte) { - if (!handleV2Frame()) { - break; - } - } else if (firstByte == 255) { - if (!handleLegacyFrame()) { - break; + if (v2UartDmaActive) { + serviceV2UartDmaRx(); + } else { + while (hwSerial->available() > 0) { + int firstByte = hwSerial->peek(); + if (firstByte == ppuc::v2::kSyncByte) { + if (!handleV2Frame()) { + break; + } + } else if (firstByte == 255) { + if (!handleLegacyFrame()) { + break; + } + } else { + // Desync/noise, consume one byte and continue. + hwSerial->read(); } - } else { - // Desync/noise, consume one byte and continue. - hwSerial->read(); } } } @@ -498,6 +689,34 @@ void EventDispatcher::update() { callListeners(configEvent, false); } } + + if (debugEnabled && Serial && (millis() - debugLastPrintMs) >= 1000) { + debugLastPrintMs = millis(); + rp2040.idleOtherCore(); + Serial.print("V2DBG board="); + Serial.print(board); + Serial.print(" active="); + Serial.print(v2UartDmaActive ? 1 : 0); + Serial.print(" cutover_ok="); + Serial.print(v2CutoverOk); + Serial.print(" cutover_fail="); + Serial.print(v2CutoverFail); + Serial.print(" rx="); + Serial.print(v2RxFrames); + Serial.print(" rx_crc_fail="); + Serial.print(v2RxCrcFail); + Serial.print(" rx_sync_fail="); + Serial.print(v2RxSyncFail); + Serial.print(" rx_dma_restart="); + Serial.print(v2RxDmaRestarts); + Serial.print(" rx_dma_timeout="); + Serial.print(v2RxDmaTimeouts); + Serial.print(" tx="); + Serial.print(v2TxFrames); + Serial.print(" tx_fallback="); + Serial.println(v2TxFallback); + rp2040.resumeOtherCore(); + } } uint32_t EventDispatcher::getLastPoll() { diff --git a/src/EventDispatcher/EventDispatcher.h b/src/EventDispatcher/EventDispatcher.h index 33ee12f..46d6522 100644 --- a/src/EventDispatcher/EventDispatcher.h +++ b/src/EventDispatcher/EventDispatcher.h @@ -12,6 +12,7 @@ #include +#include "hardware/dma.h" #include "Event.h" #include "EventListener.h" #include "MultiCoreCrossLink.h" @@ -38,6 +39,7 @@ class EventDispatcher { MultiCoreCrossLink* getMultiCoreCrossLink(); void setCrossLinkSerial(HardwareSerial& reference); + void setDebug(bool enabled); void addListener(EventListener* eventListener, char sourceId); @@ -53,6 +55,12 @@ class EventDispatcher { bool readBytes(byte* buffer, size_t len); bool handleLegacyFrame(); bool handleV2Frame(); + bool startV2UartDmaTransport(); + void stopV2UartDmaTransport(); + void serviceV2UartDmaRx(); + bool sendV2FrameUartDma(const byte* frame, size_t frameBytes); + size_t getV2PayloadBytes(ppuc::v2::FrameType frameType); + bool processV2Frame(const byte* frame, size_t payloadBytes); void sendSwitchStateFrame(byte nextBoard); void applyOutputStates(const byte* coils, size_t coilBytes, const byte* lamps, size_t lampBytes); @@ -73,6 +81,10 @@ class EventDispatcher { byte msg[12]; byte v2Buffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxCoilBytes + ppuc::v2::kMaxLampBytes + ppuc::v2::kCrcBytes]; + byte v2DmaRxBuffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxCoilBytes + + ppuc::v2::kMaxLampBytes + ppuc::v2::kCrcBytes]; + byte v2DmaTxBuffer[ppuc::v2::kHeaderBytes + ppuc::v2::kMaxSwitchBytes + + ppuc::v2::kCrcBytes]; byte outputCoils[ppuc::v2::kMaxCoilBytes] = {0}; byte outputLamps[ppuc::v2::kMaxLampBytes] = {0}; byte switchStates[ppuc::v2::kMaxSwitchBytes] = {0}; @@ -81,6 +93,24 @@ class EventDispatcher { uint16_t switchIndexToNumber[ppuc::v2::kMaxSwitchBits]; byte txSequence = 0; ppuc::v2::RuntimeConfig runtimeConfig; + bool v2UartDmaActive = false; + enum V2RxState { V2_RX_IDLE = 0, V2_RX_HEADER, V2_RX_BODY }; + V2RxState v2RxState = V2_RX_IDLE; + size_t v2RxPayloadBytes = 0; + uint32_t v2RxStateStartUs = 0; + int v2RxDmaChannel = -1; + int v2TxDmaChannel = -1; + bool debugEnabled = false; + uint32_t debugLastPrintMs = 0; + uint32_t v2CutoverOk = 0; + uint32_t v2CutoverFail = 0; + uint32_t v2RxFrames = 0; + uint32_t v2RxCrcFail = 0; + uint32_t v2RxSyncFail = 0; + uint32_t v2RxDmaRestarts = 0; + uint32_t v2RxDmaTimeouts = 0; + uint32_t v2TxFrames = 0; + uint32_t v2TxFallback = 0; bool rs485 = false; uint8_t rs485Pin = 0; diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index abe4724..7c604a9 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -23,6 +23,7 @@ IOBoardController::IOBoardController(int cT) { } _eventDispatcher->setBoard(boardId); + _eventDispatcher->setDebug(m_debug); _eventDispatcher->setRS485ModePin(RS485_MODE_PIN); _eventDispatcher->setCrossLinkSerial(Serial1); _multiCoreCrossLink = new MultiCoreCrossLink(); diff --git a/src/PPUCProtocolV2.h b/src/PPUCProtocolV2.h index 2fc2db8..d795e31 100644 --- a/src/PPUCProtocolV2.h +++ b/src/PPUCProtocolV2.h @@ -40,6 +40,8 @@ enum FrameType : uint8_t { kFrameError = 0x04, kFrameSetup = 0x05, kFrameMapping = 0x06, + kFrameReset = 0x07, + kFrameConfig = 0x08, }; enum MappingDomain : uint8_t { @@ -75,6 +77,14 @@ struct MappingPayload { uint16_t number; }; +struct ConfigPayload { + uint8_t boardId; + uint8_t topic; + uint8_t index; + uint8_t key; + uint32_t value; +}; + struct OutputPayload { // Only first BitsToBytes(coilBits/lampBits) bytes are used at runtime. uint8_t coils[kMaxCoilBytes]; @@ -98,6 +108,12 @@ struct MappingFrame { uint16_t crc; }; +struct ConfigFrame { + FrameHeader header; + ConfigPayload payload; + uint16_t crc; +}; + struct OutputStateFrame { FrameHeader header; OutputPayload payload; @@ -112,10 +128,13 @@ struct SwitchStateFrame { constexpr size_t kSetupPayloadBytes = sizeof(SetupPayload); constexpr size_t kMappingPayloadBytes = sizeof(MappingPayload); +constexpr size_t kConfigPayloadBytes = sizeof(ConfigPayload); constexpr size_t kOutputPayloadBytes = sizeof(OutputPayload); constexpr size_t kSwitchPayloadBytes = sizeof(SwitchPayload); +constexpr size_t kResetFrameBytes = kHeaderBytes + kCrcBytes; constexpr size_t kSetupFrameBytes = sizeof(SetupFrame); constexpr size_t kMappingFrameBytes = sizeof(MappingFrame); +constexpr size_t kConfigFrameBytes = sizeof(ConfigFrame); constexpr size_t kOutputFrameBytes = sizeof(OutputStateFrame); constexpr size_t kSwitchFrameBytes = sizeof(SwitchStateFrame); From 6898dd9ce38e670001df14589e12f0c37e73f606 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Mon, 16 Feb 2026 19:23:25 +0100 Subject: [PATCH 15/17] removed vscode --- .gitignore | 1 + .vscode/c_cpp_properties.json | 463 ---------------------------------- 2 files changed, 1 insertion(+), 463 deletions(-) delete mode 100644 .vscode/c_cpp_properties.json diff --git a/.gitignore b/.gitignore index 5286d05..155326f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea +.vscode .pio .DS_Store *.pio.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 106f88c..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,463 +0,0 @@ -// -// !!! WARNING !!! AUTO-GENERATED FILE! -// PLEASE DO NOT MODIFY IT AND USE "platformio.ini": -// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags -// -{ - "configurations": [ - { - "name": "PlatformIO", - "includePath": [ - "/Volumes/data/workspace/PPUC/io-boards/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/RPI_PICO_TimerInterrupt/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Bounce2/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WS2812FX/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Adafruit NeoPixel", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WavePWM/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated-avr-comp", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040/pico_base", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_regs/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_structs/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/pico_platform/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_btstack/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_arch/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_driver/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/cyw43-driver/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/decoder/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/encoder/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/yxml", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/platform/embedded", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RP2040/Include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/tinyusb/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/boards/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/hardware_claim/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_base_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_binary_info/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_sync/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_time/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_util/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_stdlib_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_usb_reset_interface_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/boot_bootrom_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Core/Include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_adc/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_base/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_boot_lock/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_clocks/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_divider/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_dma/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_exception/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_flash/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_gpio/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_i2c/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_interp/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_irq/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_rtc/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pio/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pll/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pwm/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_resets/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_spi/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_timer/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_uart/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_vreg/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_watchdog/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_xosc/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_aon_timer/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_async_context/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_bootrom/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_double/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_flash/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_float/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_int64_ops/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_lwip/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_multicore/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_common/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_compiler/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_sections/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_panic/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_printf/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime_init/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_rand/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio_uart/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_unique_id/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/lwip/src/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/freertos", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/variants/rpipico", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ADCInput/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ArduinoOTA/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AsyncUDP/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AudioBufferManager/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BTstackLib/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothAudio/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHCI/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHIDMaster/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/DNSServer/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/EEPROM/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ESPHost/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFSUSB/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Bluetooth/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Joystick/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Keyboard/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Mouse/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPClient/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdate/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdateServer/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Hash/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/I2S/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Joystick/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBLE/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Keyboard/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBLE/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LEAmDNS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LittleFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MD5Builder/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MIDIUSB/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Mouse/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseAbsolute/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBLE/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/NetBIOS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PDM/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PWMAudio/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PicoOTA/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SD/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SDFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPI/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPISlave/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SdFat/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SerialBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Servo/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SimpleMDNS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SingleFileDrive/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SoftwareSPI/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Ticker/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Updater/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/VFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WebServer/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WiFi/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Wire/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/http-parser/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_CYW43/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_ESPHost/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_Ethernet/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_WINC1500/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_enc28j60/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5100/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5500/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w55rp20/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6100/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6300/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2040", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2350", - "" - ], - "browse": { - "limitSymbolsToIncludedHeaders": true, - "path": [ - "/Volumes/data/workspace/PPUC/io-boards/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/RPI_PICO_TimerInterrupt/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Bounce2/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WS2812FX/src", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/Adafruit NeoPixel", - "/Volumes/data/workspace/PPUC/io-boards/.pio/libdeps/IO_16_8_1/WavePWM/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/api/deprecated-avr-comp", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include/rp2040/pico_base", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_regs/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/hardware_structs/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2040/pico_platform/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_btstack/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_arch/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_cyw43_driver/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/cyw43-driver/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/decoder/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/bluedroid/encoder/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/3rd-party/yxml", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/btstack/platform/embedded", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Device/RP2040/Include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/tinyusb/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/boards/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/hardware_claim/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_base_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_binary_info/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_sync/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_time/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_util/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_stdlib_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/common/pico_usb_reset_interface_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/boot_bootrom_headers/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/cmsis/stub/CMSIS/Core/Include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_adc/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_base/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_boot_lock/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_clocks/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_divider/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_dma/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_exception/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_flash/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_gpio/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_i2c/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_interp/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_irq/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_rtc/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pio/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pll/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_pwm/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_resets/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_spi/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_sync_spin_lock/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_timer/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_uart/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_vreg/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_watchdog/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/hardware_xosc/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_aon_timer/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_async_context/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_bootrom/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_double/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_fix/rp2040_usb_device_enumeration/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_flash/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_float/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_int64_ops/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_lwip/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_multicore/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_common/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_compiler/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_sections/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_platform_panic/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_printf/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_runtime_init/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_rand/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_stdio_uart/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/src/rp2_common/pico_unique_id/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/pico-sdk/lib/lwip/src/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/cores/rp2040/freertos", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/include", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/variants/rpipico", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ADCInput/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ArduinoOTA/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AsyncUDP/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/AudioBufferManager/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BTstackLib/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothAudio/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHCI/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/BluetoothHIDMaster/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/DNSServer/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/EEPROM/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/ESPHost/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/FatFSUSB/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Bluetooth/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Joystick/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Keyboard/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HID_Mouse/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPClient/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdate/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/HTTPUpdateServer/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Hash/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/I2S/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Joystick/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBLE/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/JoystickBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Keyboard/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBLE/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/KeyboardBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LEAmDNS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/LittleFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MD5Builder/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MIDIUSB/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Mouse/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseAbsolute/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBLE/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/MouseBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/NetBIOS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PDM/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PWMAudio/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/PicoOTA/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SD/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SDFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPI/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SPISlave/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SdFat/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SerialBT/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Servo/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SimpleMDNS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SingleFileDrive/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/SoftwareSPI/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Ticker/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Updater/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/VFS/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WebServer/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/WiFi/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/Wire/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/http-parser/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_CYW43/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_ESPHost/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_Ethernet/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_WINC1500/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_enc28j60/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5100/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w5500/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w55rp20/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6100/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/lwIP_w6300/src", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2040", - "/Users/markus.kalkbrenner/.platformio/packages/framework-arduinopico/libraries/rp2350", - "" - ] - }, - "defines": [ - "PLATFORMIO=60118", - "ARDUINO_RASPBERRY_PI_PICO", - "ARDUINO_ARCH_RP2040", - "USBD_MAX_POWER_MA=500", - "PICO_STDIO_USB", - "CFG_TUSB_MCU=OPT_MCU_RP2040", - "CFG_TUSB_OS=OPT_OS_PICO", - "CYW43_DEFAULT_PIN_WL_CLOCK=29u", - "CYW43_DEFAULT_PIN_WL_CS=25u", - "CYW43_DEFAULT_PIN_WL_DATA_IN=24u", - "CYW43_DEFAULT_PIN_WL_DATA_OUT=24u", - "CYW43_DEFAULT_PIN_WL_HOST_WAKE=24u", - "CYW43_DEFAULT_PIN_WL_REG_ON=23u", - "CYW43_PIN_WL_DYNAMIC=1", - "CYW43_PIO_CLOCK_DIV_DYNAMIC=1", - "CYW43_WARN=//", - "LIB_BOOT_STAGE2_HEADERS=1", - "LIB_PICO_AON_TIMER=1", - "LIB_PICO_ATOMIC=1", - "LIB_PICO_BIT_OPS=1", - "LIB_PICO_BIT_OPS_PICO=1", - "LIB_PICO_BOOTSEL_VIA_DOUBLE_RESET=1", - "LIB_PICO_CLIB_INTERFACE=1", - "LIB_PICO_CRT0=1", - "LIB_PICO_CXX_OPTIONS=1", - "LIB_PICO_DIVIDER=1", - "LIB_PICO_DIVIDER_COMPILER=1", - "LIB_PICO_DOUBLE=1", - "LIB_PICO_FIX_RP2040_USB_DEVICE_ENUMERATION=1", - "LIB_PICO_FLASH=1", - "LIB_PICO_FLOAT=1", - "LIB_PICO_FLOAT_PICO=1", - "LIB_PICO_INT64_OPS=1", - "LIB_PICO_INT64_OPS_COMPILER=1", - "LIB_PICO_MALLOC=1", - "LIB_PICO_MEM_OPS=1", - "LIB_PICO_MEM_OPS_COMPILER=1", - "LIB_PICO_MULTICORE=1", - "LIB_PICO_NEWLIB_INTERFACE=1", - "LIB_PICO_PLATFORM=1", - "LIB_PICO_PLATFORM_COMMON=1", - "LIB_PICO_PLATFORM_COMPILER=1", - "LIB_PICO_PLATFORM_PANIC=1", - "LIB_PICO_PLATFORM_SECTIONS=1", - "LIB_PICO_PRINTF=1", - "LIB_PICO_PRINTF_PICO=1", - "LIB_PICO_RAND=1", - "LIB_PICO_RUNTIME=1", - "LIB_PICO_RUNTIME_INIT=1", - "LIB_PICO_STANDARD_BINARY_INFO=1", - "LIB_PICO_STANDARD_LINK=1", - "LIB_PICO_STDIO=0", - "LIB_PICO_STDIO_UART=0", - "LIB_PICO_STDLIB=1", - "LIB_PICO_SYNC=1", - "LIB_PICO_SYNC_CRITICAL_SECTION=1", - "LIB_PICO_SYNC_MUTEX=1", - "LIB_PICO_SYNC_SEM=1", - "LIB_PICO_TIME=1", - "LIB_PICO_TIME_ADAPTER=1", - "LIB_PICO_UNIQUE_ID=1", - "LIB_PICO_UTIL=1", - "LIB_TINYUSB_BOARD=1", - "LIB_TINYUSB_DEVICE=1", - "PICO_32BIT=1", - "PICO_BUILD=1", - "PICO_COPY_TO_RAM=0", - "PICO_NO_BINARY_INFO=1", - "PICO_NO_FLASH=0", - "PICO_NO_HARDWARE=0", - "PICO_ON_DEVICE=1", - "PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1", - "PICO_RP2040_USB_DEVICE_UFRAME_FIX=1", - "PICO_USE_BLOCKED_RAM=0", - "PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64", - "PICO_MAX_SHARED_IRQ_HANDLERS=6", - "PICO_CYW43_ARCH_HEADER=stdint.h", - "CYW43_TASK_STACK_SIZE=1024", - "LIB_PICO_DIVIDER_HARDWARE=1", - "LIB_PICO_DOUBLE_PICO=1", - "LIB_PICO_INT64_OPS_PICO=1", - "LIB_PICO_MEM_OPS_PICO=1", - "PICO_DOUBLE_SUPPORT_ROM_V1=1", - "PICO_FLOAT_SUPPORT_ROM_V1=1", - "PICO_PLATFORM=rp2040", - "PICO_RP2040=1", - "PICO_RP2040_B0_SUPPORTED=1", - "PICO_RP2040_B1_SUPPORTED=1", - "PICO_RP2040_B2_SUPPORTED=1", - "ARM_MATH_CM0_FAMILY", - "ARM_MATH_CM0_PLUS", - "TARGET_RP2040", - "ARDUINO=10810", - "ARDUINO_ARCH_RP2040", - "F_CPU=133000000L", - "BOARD_NAME=\"pico\"", - "PICO_FLASH_SIZE_BYTES=2097152", - "FILE_COPY_CONSTRUCTOR_SELECT=FILE_COPY_CONSTRUCTOR_PUBLIC", - "USE_UTF8_LONG_NAMES=1", - "DISABLE_FS_H_WARNING=1", - "CFG_TUSB_MCU=OPT_MCU_RP2040", - "USB_VID=0x2e8a", - "USB_PID=0x000a", - "USBD_VID=0x2e8a", - "USBD_PID=0x000a", - "USB_MANUFACTURER=\"Raspberry Pi\"", - "USB_PRODUCT=\"Pico\"", - "PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1", - "CYW43_LWIP=1", - "CYW43_PIO_CLOCK_DIV_DYNAMIC=1", - "LWIP_IPV4=1", - "LWIP_IGMP=1", - "LWIP_CHECKSUM_CTRL_PER_NETIF=1", - "LWIP_IPV6=0", - "ARDUINO_VARIANT=\"rpipico\"", - "" - ], - "cStandard": "gnu17", - "cppStandard": "gnu++17", - "compilerPath": "/Users/markus.kalkbrenner/.platformio/packages/toolchain-rp2040-earlephilhower/bin/arm-none-eabi-gcc", - "compilerArgs": [ - "-march=armv6-m", - "-mcpu=cortex-m0plus", - "-mthumb", - "" - ] - } - ], - "version": 4 -} From 88e85145b64133d0c5f6ec713a08b993cb21154d Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Mon, 16 Feb 2026 19:40:17 +0100 Subject: [PATCH 16/17] removed v1 support --- src/EventDispatcher/EventDispatcher.cpp | 147 +----------------------- src/EventDispatcher/EventDispatcher.h | 2 - 2 files changed, 3 insertions(+), 146 deletions(-) diff --git a/src/EventDispatcher/EventDispatcher.cpp b/src/EventDispatcher/EventDispatcher.cpp index f803ef6..0d4223e 100644 --- a/src/EventDispatcher/EventDispatcher.cpp +++ b/src/EventDispatcher/EventDispatcher.cpp @@ -502,146 +502,6 @@ bool EventDispatcher::handleV2Frame() { return processV2Frame(v2Buffer, payloadBytes); } -bool EventDispatcher::handleLegacyFrame() { - if (hwSerial->available() < 7) { - return false; - } - - bool success = false; - - byte startByte = hwSerial->read(); - if (startByte == 255) { - byte sourceId = hwSerial->read(); - if (sourceId != 0) { - if (sourceId == EVENT_CONFIGURATION) { - // Config Event has 12 bytes, 2 bytes are already parsed above. - while (hwSerial->available() < 10) { - } - - // We have a ConfigEvent. - byte boardId = hwSerial->read(); - byte topic = hwSerial->read(); - byte index = hwSerial->read(); - byte key = hwSerial->read(); - uint32_t value = (((uint32_t)hwSerial->read()) << 24) + - (((uint32_t)hwSerial->read()) << 16) + - (((uint32_t)hwSerial->read()) << 8) + - hwSerial->read(); - byte stopByte = hwSerial->read(); - if (stopByte == 0b10101010) { - stopByte = hwSerial->read(); - if (stopByte == 0b01010101) { - success = true; - callListeners(new ConfigEvent(boardId, topic, index, key, value), - true); - } - } - } else { - word eventId = word(hwSerial->read(), hwSerial->read()); - if (eventId != 0) { - byte value = hwSerial->read(); - byte stopByte = hwSerial->read(); - if (stopByte == 0b10101010) { - stopByte = hwSerial->read(); - if (stopByte == 0b01010101) { - success = true; - callListeners(new Event((char)sourceId, eventId, value), true, - false); - - if (sourceId == EVENT_POLL_EVENTS && board == value) { - digitalWrite(rs485Pin, HIGH); // Write. - // Wait until the RS485 converter switched to write mode. - delayMicroseconds(RS485_MODE_SWITCH_DELAY); - - while (!eventQueue.empty()) { - Event *e = eventQueue.front(); - eventQueue.pop(); - callListeners(e, true, true); - } - - // Send NULL event to indicate that transmission is complete. - callListeners(new Event(EVENT_NULL, 1, board), false, true); - - lastPoll = millis(); - - // Flush the serial buffer and wait until done. - hwSerial->flush(); - digitalWrite(rs485Pin, LOW); // Read. - // Wait until the RS485 converter switched back to read mode. - delayMicroseconds(RS485_MODE_SWITCH_DELAY); - } else if (sourceId == EVENT_RUN) { - running = true; - } - - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received wrong second stop byte "); - Serial.println(stopByte, DEC); - rp2040.resumeOtherCore(); - } - } - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received wrong first stop byte "); - Serial.println(stopByte, DEC); - rp2040.resumeOtherCore(); - } - } - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received invalid event id "); - Serial.println(eventId, DEC); - rp2040.resumeOtherCore(); - } - } - } - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received invalid source id "); - Serial.println(sourceId, DEC); - rp2040.resumeOtherCore(); - } - } - } else { - if (Serial) { - rp2040.idleOtherCore(); - Serial.print("Received wrong start byte "); - Serial.println(startByte, DEC); - rp2040.resumeOtherCore(); - } - // We didn't receive a start byte. Fake "success" to start over with the - // next byte. - success = true; - } - - if (success) { - if (error) { - error = false; - dispatch(new Event(EVENT_NO_ERROR, 1, board)); - } - } else { - error = true; - dispatch(new Event(EVENT_ERROR, 1, board)); - - while (hwSerial->available()) { - byte bits = hwSerial->read(); - if (bits == 0b10101010 && hwSerial->available()) { - bits = hwSerial->read(); - if (bits == 0b01010101) { - // Now we should be back in sync. - break; - } - } - } - } - - return success; -} - void EventDispatcher::update() { if (!rs485) { // We're on Core1, the EffectController. Transmit stacked // events to Core0. @@ -660,16 +520,15 @@ void EventDispatcher::update() { if (v2UartDmaActive) { serviceV2UartDmaRx(); } else { + // Fallback parser is still needed for V2 bootstrap and fault handling: + // - bootstrap: receive initial V2 setup frame before DMA cutover + // - fault path: continue operating if UART DMA transport cannot start while (hwSerial->available() > 0) { int firstByte = hwSerial->peek(); if (firstByte == ppuc::v2::kSyncByte) { if (!handleV2Frame()) { break; } - } else if (firstByte == 255) { - if (!handleLegacyFrame()) { - break; - } } else { // Desync/noise, consume one byte and continue. hwSerial->read(); diff --git a/src/EventDispatcher/EventDispatcher.h b/src/EventDispatcher/EventDispatcher.h index 46d6522..1e788bb 100644 --- a/src/EventDispatcher/EventDispatcher.h +++ b/src/EventDispatcher/EventDispatcher.h @@ -53,7 +53,6 @@ class EventDispatcher { private: bool readBytes(byte* buffer, size_t len); - bool handleLegacyFrame(); bool handleV2Frame(); bool startV2UartDmaTransport(); void stopV2UartDmaTransport(); @@ -115,7 +114,6 @@ class EventDispatcher { bool rs485 = false; uint8_t rs485Pin = 0; byte board = 255; - bool error = false; uint32_t lastPoll; bool running = false; From 6b36be6300a58454c66b19f8ed78168e44105934 Mon Sep 17 00:00:00 2001 From: Markus Kalkbrenner Date: Mon, 16 Feb 2026 23:19:08 +0100 Subject: [PATCH 17/17] optimized --- src/EventDispatcher/Event.h | 2 + src/EventDispatcher/EventDispatcher.cpp | 76 ++++++++++++++++++++++++- src/EventDispatcher/EventDispatcher.h | 7 +++ src/IOBoardController.cpp | 6 ++ src/PPUCProtocolV2.h | 1 + 5 files changed, 90 insertions(+), 2 deletions(-) diff --git a/src/EventDispatcher/Event.h b/src/EventDispatcher/Event.h index 87d1dbb..b024f1d 100644 --- a/src/EventDispatcher/Event.h +++ b/src/EventDispatcher/Event.h @@ -48,6 +48,7 @@ #define CONFIG_TOPIC_SWITCHES 115 // "s" #define CONFIG_TOPIC_TRIGGER 116 // "t" #define CONFIG_TOPIC_SWITCH_MATRIX 120 // "x" +#define CONFIG_TOPIC_SWITCH_CHAIN 121 // "y" #define CONFIG_TOPIC_HOLD_POWER_ACTIVATION_TIME 65 // "A" #define CONFIG_TOPIC_DURATION 65 // "A" @@ -76,6 +77,7 @@ #define CONFIG_TOPIC_LIGHT_UP 85 // "U" #define CONFIG_TOPIC_ACTIVE_LOW 86 // "V" #define CONFIG_TOPIC_POWER 87 // "W" +#define CONFIG_TOPIC_NEXT_BOARD 88 // "X" #define CONFIG_TOPIC_TYPE 89 // "Y" #define CONFIG_TOPIC_EFFECT 89 // "Y" #define CONFIG_TOPIC_MODE 90 // "Z" diff --git a/src/EventDispatcher/EventDispatcher.cpp b/src/EventDispatcher/EventDispatcher.cpp index 0d4223e..50bfe26 100644 --- a/src/EventDispatcher/EventDispatcher.cpp +++ b/src/EventDispatcher/EventDispatcher.cpp @@ -41,6 +41,10 @@ void EventDispatcher::setCrossLinkSerial(HardwareSerial &reference) { void EventDispatcher::setDebug(bool enabled) { debugEnabled = enabled; } +void EventDispatcher::setNextSwitchBoard(byte boardId) { + nextSwitchBoard = boardId; +} + void EventDispatcher::addListener(EventListener *eventListener) { addListener(eventListener, EVENT_SOURCE_ANY); } @@ -153,6 +157,10 @@ size_t EventDispatcher::getV2PayloadBytes(ppuc::v2::FrameType frameType) { case ppuc::v2::kFrameOutputState: return ppuc::v2::BitsToBytes(runtimeConfig.coilBits) + ppuc::v2::BitsToBytes(runtimeConfig.lampBits); + case ppuc::v2::kFrameSwitchState: + return ppuc::v2::BitsToBytes(runtimeConfig.switchBits); + case ppuc::v2::kFrameSwitchNoChange: + return 0; case ppuc::v2::kFrameHeartbeat: case ppuc::v2::kFrameError: case ppuc::v2::kFrameReset: @@ -226,11 +234,26 @@ bool EventDispatcher::processV2Frame(const byte* frame, size_t payloadBytes) { const size_t lampBytes = ppuc::v2::BitsToBytes(runtimeConfig.lampBits); applyOutputStates(&frame[4], coilBytes, &frame[4 + coilBytes], lampBytes); if (frame[2] == board) { - sendSwitchStateFrame((byte)((board + 1) % ppuc::v2::kMaxBoards)); + if (switchDirty) { + sendSwitchStateFrame(nextSwitchBoard); + switchDirty = false; + } else { + sendSwitchNoChangeFrame(nextSwitchBoard); + } } return true; } + if (frameType == ppuc::v2::kFrameSwitchState) { + const size_t switchBytes = ppuc::v2::BitsToBytes(runtimeConfig.switchBits); + applySwitchStates(&frame[4], switchBytes); + return true; + } + + if (frameType == ppuc::v2::kFrameSwitchNoChange) { + return true; + } + if (frameType == ppuc::v2::kFrameReset) { dispatch(new Event(EVENT_RESET)); return true; @@ -405,6 +428,9 @@ void EventDispatcher::updateSwitchBitmap(Event *event) { ppuc::v2::SetBitmapBit(switchStates, (uint16_t)mappedIndex, event->value != 0); + if (!applyingRemoteSwitchState) { + switchDirty = true; + } } void EventDispatcher::applyOutputStates(const byte *coils, size_t coilBytes, @@ -437,6 +463,24 @@ void EventDispatcher::applyOutputStates(const byte *coils, size_t coilBytes, memcpy(outputLamps, lamps, lampBytes); } +void EventDispatcher::applySwitchStates(const byte* switches, + size_t switchBytes) { + // Global switch state is board-to-board on the RS485 bus. CPU/libppuc never + // broadcasts switch states. Every board consumes incoming switch frames and + // emits local switch events for fast-flip/effect listeners. + applyingRemoteSwitchState = true; + for (uint16_t n = 0; n < runtimeConfig.switchBits; ++n) { + bool oldState = ppuc::v2::GetBitmapBit(switchStates, n); + bool newState = ppuc::v2::GetBitmapBit(switches, n); + if (oldState != newState) { + dispatch(new Event(EVENT_SOURCE_SWITCH, switchIndexToNumber[n], + newState ? 1 : 0, true)); + } + } + applyingRemoteSwitchState = false; + memcpy(switchStates, switches, switchBytes); +} + void EventDispatcher::sendSwitchStateFrame(byte nextBoard) { // Switch updates are transmitted as a compact V2 frame containing the full // dense switch bitmap. The CPU selects the responding board via token @@ -472,6 +516,31 @@ void EventDispatcher::sendSwitchStateFrame(byte nextBoard) { lastPoll = millis(); } +void EventDispatcher::sendSwitchNoChangeFrame(byte nextBoard) { + byte* frame = v2DmaTxBuffer; + frame[0] = ppuc::v2::kSyncByte; + frame[1] = ppuc::v2::ComposeTypeAndFlags(ppuc::v2::kFrameSwitchNoChange, + ppuc::v2::kFlagNone); + frame[2] = nextBoard; + frame[3] = txSequence++; + uint16_t crc = ppuc::v2::Crc16Ccitt(frame, ppuc::v2::kHeaderBytes); + frame[4] = highByte(crc); + frame[5] = lowByte(crc); + + if (!v2UartDmaActive || !sendV2FrameUartDma(frame, ppuc::v2::kResetFrameBytes)) { + v2TxFallback++; + digitalWrite(rs485Pin, HIGH); // Write. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + hwSerial->write(frame, ppuc::v2::kResetFrameBytes); + hwSerial->flush(); + digitalWrite(rs485Pin, LOW); // Read. + delayMicroseconds(RS485_MODE_SWITCH_DELAY); + } + + v2SwitchNoChangeTx++; + lastPoll = millis(); +} + bool EventDispatcher::handleV2Frame() { if (hwSerial->available() < (int)ppuc::v2::kHeaderBytes) { return false; @@ -490,7 +559,8 @@ bool EventDispatcher::handleV2Frame() { if (frameType != ppuc::v2::kFrameHeartbeat && frameType != ppuc::v2::kFrameError && frameType != ppuc::v2::kFrameReset && payloadBytes == 0 && - frameType != ppuc::v2::kFrameOutputState) { + frameType != ppuc::v2::kFrameOutputState && + frameType != ppuc::v2::kFrameSwitchNoChange) { return false; } @@ -572,6 +642,8 @@ void EventDispatcher::update() { Serial.print(v2RxDmaTimeouts); Serial.print(" tx="); Serial.print(v2TxFrames); + Serial.print(" tx_nochange="); + Serial.print(v2SwitchNoChangeTx); Serial.print(" tx_fallback="); Serial.println(v2TxFallback); rp2040.resumeOtherCore(); diff --git a/src/EventDispatcher/EventDispatcher.h b/src/EventDispatcher/EventDispatcher.h index 1e788bb..c3a0050 100644 --- a/src/EventDispatcher/EventDispatcher.h +++ b/src/EventDispatcher/EventDispatcher.h @@ -40,6 +40,7 @@ class EventDispatcher { void setCrossLinkSerial(HardwareSerial& reference); void setDebug(bool enabled); + void setNextSwitchBoard(byte boardId); void addListener(EventListener* eventListener, char sourceId); @@ -61,8 +62,10 @@ class EventDispatcher { size_t getV2PayloadBytes(ppuc::v2::FrameType frameType); bool processV2Frame(const byte* frame, size_t payloadBytes); void sendSwitchStateFrame(byte nextBoard); + void sendSwitchNoChangeFrame(byte nextBoard); void applyOutputStates(const byte* coils, size_t coilBytes, const byte* lamps, size_t lampBytes); + void applySwitchStates(const byte* switches, size_t switchBytes); void updateSwitchBitmap(Event* event); int16_t findMappedIndex(const uint16_t* table, uint16_t count, uint16_t number); @@ -109,11 +112,15 @@ class EventDispatcher { uint32_t v2RxDmaRestarts = 0; uint32_t v2RxDmaTimeouts = 0; uint32_t v2TxFrames = 0; + uint32_t v2SwitchNoChangeTx = 0; uint32_t v2TxFallback = 0; + bool switchDirty = false; + bool applyingRemoteSwitchState = false; bool rs485 = false; uint8_t rs485Pin = 0; byte board = 255; + byte nextSwitchBoard = ppuc::v2::kNoBoard; uint32_t lastPoll; bool running = false; diff --git a/src/IOBoardController.cpp b/src/IOBoardController.cpp index 7c604a9..7eeb9de 100644 --- a/src/IOBoardController.cpp +++ b/src/IOBoardController.cpp @@ -117,6 +117,12 @@ void IOBoardController::handleEvent(ConfigEvent *event) { } break; + case CONFIG_TOPIC_SWITCH_CHAIN: + if (event->key == CONFIG_TOPIC_NEXT_BOARD) { + _eventDispatcher->setNextSwitchBoard((byte)event->value); + } + break; + case CONFIG_TOPIC_SWITCHES: switch (event->key) { case CONFIG_TOPIC_PORT: diff --git a/src/PPUCProtocolV2.h b/src/PPUCProtocolV2.h index d795e31..ef0e1f3 100644 --- a/src/PPUCProtocolV2.h +++ b/src/PPUCProtocolV2.h @@ -42,6 +42,7 @@ enum FrameType : uint8_t { kFrameMapping = 0x06, kFrameReset = 0x07, kFrameConfig = 0x08, + kFrameSwitchNoChange = 0x09, }; enum MappingDomain : uint8_t {