From 102cae9d91889ef1fedcb2f533e70ef1dee039ee Mon Sep 17 00:00:00 2001 From: Ashley Strahle Date: Tue, 23 Jun 2026 13:27:24 +0000 Subject: [PATCH] Add PixelStomp MUX driver (DAT=9, LD=10, CLK=11, DI=12) - New PixelStompMux class with shift register read/write - Read 10 buttons via DI (active LOW) - Drive 10 LEDs via DAT (shift out + latch) - LED startup animation uses MUX - Switch polling reads from MUX shift register - MUX instance shared between switch and LED drivers --- include/led_stub.h | 4 ++ include/pixel_stomp_mux.h | 35 ++++++++++++ include/switch_stub.h | 4 ++ src/led_stub.cpp | 36 +++++++------ src/main.cpp | 10 +++- src/pixel_stomp_mux.cpp | 110 ++++++++++++++++++++++++++++++++++++++ src/switch_stub.cpp | 29 ++++------ 7 files changed, 194 insertions(+), 34 deletions(-) create mode 100644 include/pixel_stomp_mux.h create mode 100644 src/pixel_stomp_mux.cpp diff --git a/include/led_stub.h b/include/led_stub.h index cd1a858..b3cf338 100644 --- a/include/led_stub.h +++ b/include/led_stub.h @@ -23,6 +23,8 @@ struct LedState { bool active; }; +class PixelStompMux; + class DefaultLedStub : public LedStub { private: static const uint8_t NUM_LEDS = 10; @@ -34,4 +36,6 @@ public: void begin() override; void set_led_state(uint8_t note, uint8_t channel, uint8_t velocity) override; void clear_all() override; + + void set_mux(PixelStompMux* mux); }; diff --git a/include/pixel_stomp_mux.h b/include/pixel_stomp_mux.h new file mode 100644 index 0000000..905a57c --- /dev/null +++ b/include/pixel_stomp_mux.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +class PixelStompMux { +public: + static const uint8_t NUM_INPUTS = 10; + static const uint8_t NUM_OUTPUTS = 10; + + PixelStompMux(uint8_t dat_pin, uint8_t ld_pin, uint8_t clk_pin, uint8_t di_pin); + + void begin(); + + uint16_t read_buttons(); + bool is_button_pressed(uint8_t index); + + void write_leds(uint16_t led_state); + void set_led(uint8_t index, bool on); + void clear_all(); + + void dump(); + +private: + uint8_t pin_dat; + uint8_t pin_ld; + uint8_t pin_clk; + uint8_t pin_di; + + uint16_t last_button_state; + uint16_t current_led_state; + + void shift_out(uint16_t data, uint8_t bits); + uint16_t shift_in(uint8_t bits); + void pulse_pin(uint8_t pin, uint8_t level, uint16_t duration_us = 1); +}; diff --git a/include/switch_stub.h b/include/switch_stub.h index e7c2400..166fdfd 100644 --- a/include/switch_stub.h +++ b/include/switch_stub.h @@ -21,6 +21,8 @@ struct SwitchState { uint32_t debounce_time; }; +class PixelStompMux; + class DefaultSwitchStub : public SwitchStub { private: static const uint8_t NUM_SWITCHES = 10; @@ -33,4 +35,6 @@ public: bool is_pressed(uint8_t switch_id) override; void configure_switch(uint8_t switch_id, uint8_t gpio_pin) override; void set_debounce_time(uint32_t time_ms) override; + + void set_mux(PixelStompMux* mux); }; diff --git a/src/led_stub.cpp b/src/led_stub.cpp index 5d03a1a..3ff7cd2 100644 --- a/src/led_stub.cpp +++ b/src/led_stub.cpp @@ -1,9 +1,8 @@ #include "led_stub.h" +#include "pixel_stomp_mux.h" #include -static const uint8_t LED_PINS[10] = { - 38, 39, 40, 41, 42, 45, 46, 47, 48, 21 -}; +static PixelStompMux* mux_ptr = nullptr; DefaultLedStub::DefaultLedStub() : initialized(false) { for (int i = 0; i < NUM_LEDS; i++) { @@ -15,26 +14,31 @@ DefaultLedStub::DefaultLedStub() : initialized(false) { } } +void DefaultLedStub::set_mux(PixelStompMux* mux) { + mux_ptr = mux; +} + void DefaultLedStub::begin() { - for (int i = 0; i < NUM_LEDS; i++) { - pinMode(LED_PINS[i], OUTPUT); - digitalWrite(LED_PINS[i], LOW); - } + Serial.println("[LED] Using PixelStomp MUX for LED output"); initialized = true; Serial.println("[LED] Startup animation..."); for (int i = 0; i < NUM_LEDS; i++) { - digitalWrite(LED_PINS[i], HIGH); - Serial.printf("[LED] LED %d ON (GPIO %d)\n", i, LED_PINS[i]); + if (mux_ptr) { + mux_ptr->set_led(i, true); + } + Serial.printf("[LED] LED %d ON\n", i); delay(150); } delay(300); for (int i = NUM_LEDS - 1; i >= 0; i--) { - digitalWrite(LED_PINS[i], LOW); - Serial.printf("[LED] LED %d OFF (GPIO %d)\n", i, LED_PINS[i]); + if (mux_ptr) { + mux_ptr->set_led(i, false); + } + Serial.printf("[LED] LED %d OFF\n", i); delay(100); } @@ -53,7 +57,9 @@ void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t veloci led_states[led_index].active = (velocity > 0); led_states[led_index].timestamp = millis(); - digitalWrite(LED_PINS[led_index], velocity > 0 ? HIGH : LOW); + if (mux_ptr) { + mux_ptr->set_led(led_index, velocity > 0); + } Serial.printf("[LED] Note %d -> LED %d Ch %d Vel %d (%s)\n", note, led_index, channel, velocity, @@ -67,9 +73,9 @@ void DefaultLedStub::clear_all() { for (int i = 0; i < NUM_LEDS; i++) { led_states[i].active = false; led_states[i].velocity = 0; - if (initialized) { - digitalWrite(LED_PINS[i], LOW); - } + } + if (mux_ptr) { + mux_ptr->clear_all(); } Serial.println("[LED] All cleared"); } diff --git a/src/main.cpp b/src/main.cpp index 1d7917a..68fafb8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,10 +3,13 @@ #include #include "midi_transport.h" +#include "pixel_stomp_mux.h" #include "led_stub.h" #include "switch_stub.h" #include "app_task.h" +PixelStompMux mux(9, 10, 11, 12); + DefaultLedStub led_driver; DefaultSwitchStub switch_driver; UsbMidiTransport midi_transport; @@ -34,10 +37,15 @@ void setup() { Serial.println(" Board: ESP32-S3-WROOM-1"); Serial.println("================================="); + Serial.println("[INIT] Initializing PixelStomp MUX..."); + mux.begin(); + Serial.println("[INIT] Starting LED startup animation..."); + led_driver.set_mux(&mux); led_driver.begin(); - Serial.println("[INIT] Initializing switches..."); + Serial.println("[INIT] Initializing switches via MUX..."); + switch_driver.set_mux(&mux); switch_driver.begin(); Serial.println("[INIT] Initializing USB MIDI..."); diff --git a/src/pixel_stomp_mux.cpp b/src/pixel_stomp_mux.cpp new file mode 100644 index 0000000..1069465 --- /dev/null +++ b/src/pixel_stomp_mux.cpp @@ -0,0 +1,110 @@ +#include "pixel_stomp_mux.h" +#include + +static const char* TAG = "MUX"; + +PixelStompMux::PixelStompMux(uint8_t dat, uint8_t ld, uint8_t clk, uint8_t di) + : pin_dat(dat), pin_ld(ld), pin_clk(clk), pin_di(di), + last_button_state(0), current_led_state(0) { +} + +void PixelStompMux::begin() { + pinMode(pin_dat, OUTPUT); + pinMode(pin_ld, OUTPUT); + pinMode(pin_clk, OUTPUT); + pinMode(pin_di, INPUT_PULLUP); + + digitalWrite(pin_dat, LOW); + digitalWrite(pin_ld, LOW); + digitalWrite(pin_clk, LOW); + + Serial.printf("[MUX] Init DAT=%d LD=%d CLK=%d DI=%d\n", + pin_dat, pin_ld, pin_clk, pin_di); +} + +uint16_t PixelStompMux::read_buttons() { + pulse_pin(pin_ld, HIGH); + delayMicroseconds(5); + pulse_pin(pin_ld, LOW); + delayMicroseconds(5); + + uint16_t state = shift_in(NUM_INPUTS); + last_button_state = state; + + return state; +} + +bool PixelStompMux::is_button_pressed(uint8_t index) { + if (index >= NUM_INPUTS) return false; + + uint16_t state = read_buttons(); + return !(state & (1 << index)); +} + +void PixelStompMux::write_leds(uint16_t led_state) { + current_led_state = led_state; + shift_out(led_state, NUM_OUTPUTS); + pulse_pin(pin_ld, HIGH); + delayMicroseconds(5); + pulse_pin(pin_ld, LOW); +} + +void PixelStompMux::set_led(uint8_t index, bool on) { + if (index >= NUM_OUTPUTS) return; + + if (on) { + current_led_state |= (1 << index); + } else { + current_led_state &= ~(1 << index); + } + + write_leds(current_led_state); +} + +void PixelStompMux::clear_all() { + write_leds(0x0000); + Serial.println("[MUX] All LEDs off"); +} + +void PixelStompMux::dump() { + uint16_t buttons = read_buttons(); + Serial.printf("[MUX] Buttons: 0x%03X LEDs: 0x%03X\n", buttons, current_led_state); + + for (int i = 0; i < NUM_INPUTS; i++) { + bool pressed = !(buttons & (1 << i)); + bool led_on = current_led_state & (1 << i); + if (pressed || led_on) { + Serial.printf(" [%d] Button:%s LED:%s\n", i, + pressed ? "PRESS" : "off", + led_on ? "ON" : "off"); + } + } +} + +void PixelStompMux::shift_out(uint16_t data, uint8_t bits) { + for (int i = bits - 1; i >= 0; i--) { + digitalWrite(pin_dat, (data >> i) & 1); + pulse_pin(pin_clk, HIGH); + pulse_pin(pin_clk, LOW); + } +} + +uint16_t PixelStompMux::shift_in(uint8_t bits) { + uint16_t data = 0; + + for (int i = bits - 1; i >= 0; i--) { + bool bit = digitalRead(pin_di); + if (bit) { + data |= (1 << i); + } + pulse_pin(pin_clk, HIGH); + pulse_pin(pin_clk, LOW); + } + + return data; +} + +void PixelStompMux::pulse_pin(uint8_t pin, uint8_t level, uint16_t duration_us) { + digitalWrite(pin, level); + delayMicroseconds(duration_us); +} diff --git a/src/switch_stub.cpp b/src/switch_stub.cpp index f236d8c..2541990 100644 --- a/src/switch_stub.cpp +++ b/src/switch_stub.cpp @@ -1,14 +1,13 @@ #include "switch_stub.h" +#include "pixel_stomp_mux.h" #include -static const uint8_t SWITCH_PINS[10] = { - 2, 3, 4, 5, 6, 7, 15, 16, 17, 18 -}; +static PixelStompMux* mux_ptr = nullptr; DefaultSwitchStub::DefaultSwitchStub() : initialized(false) { for (int i = 0; i < NUM_SWITCHES; i++) { switch_states[i].id = i; - switch_states[i].gpio_pin = SWITCH_PINS[i]; + switch_states[i].gpio_pin = 0; switch_states[i].current_state = false; switch_states[i].previous_state = false; switch_states[i].last_change_time = 0; @@ -17,24 +16,21 @@ DefaultSwitchStub::DefaultSwitchStub() : initialized(false) { } void DefaultSwitchStub::begin() { - for (int i = 0; i < NUM_SWITCHES; i++) { - pinMode(switch_states[i].gpio_pin, INPUT_PULLUP); - } + Serial.println("[SW] Using PixelStomp MUX for button input"); initialized = true; - Serial.printf("[SW] %d switches on GPIOs: ", NUM_SWITCHES); - for (int i = 0; i < NUM_SWITCHES; i++) { - Serial.printf("%d ", switch_states[i].gpio_pin); - } - Serial.println(); +} + +void DefaultSwitchStub::set_mux(PixelStompMux* mux) { + mux_ptr = mux; } bool DefaultSwitchStub::is_pressed(uint8_t switch_id) { - if (!initialized || switch_id >= NUM_SWITCHES) { + if (!initialized || switch_id >= NUM_SWITCHES || !mux_ptr) { return false; } SwitchState& sw = switch_states[switch_id]; - bool raw = (digitalRead(sw.gpio_pin) == LOW); + bool raw = mux_ptr->is_button_pressed(switch_id); uint32_t now = millis(); if (raw != sw.previous_state) { @@ -54,10 +50,7 @@ bool DefaultSwitchStub::is_pressed(uint8_t switch_id) { void DefaultSwitchStub::configure_switch(uint8_t switch_id, uint8_t gpio_pin) { if (switch_id >= NUM_SWITCHES) return; switch_states[switch_id].gpio_pin = gpio_pin; - if (initialized) { - pinMode(gpio_pin, INPUT_PULLUP); - } - Serial.printf("[SW] Switch %d -> GPIO %d\n", switch_id, gpio_pin); + Serial.printf("[SW] Switch %d remapped to GPIO %d\n", switch_id, gpio_pin); } void DefaultSwitchStub::set_debounce_time(uint32_t time_ms) {