From 7fd4600a42d5c1080cdeb0c96eac0dc168b7f532 Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Mon, 23 Feb 2026 15:49:26 -0500 Subject: [PATCH 1/3] cleaning up --- include/Gate.h | 9 +-- include/globals.h | 10 ++- src/DisplayHandler.cpp | 2 - src/Gate.cpp | 167 ++++++++++++++++++++++++++++------------- src/main.cpp | 60 +++++---------- 5 files changed, 143 insertions(+), 105 deletions(-) diff --git a/include/Gate.h b/include/Gate.h index 7b3aba0..58814af 100644 --- a/include/Gate.h +++ b/include/Gate.h @@ -3,16 +3,14 @@ #define Gate_h #include -#include class Gate { private: bool state; - int16_t cycle; uint32_t dur; uint32_t len; - uint16_t div; + uint32_t lastTriggerTick = 0xFFFFFFFF; public: Gate(uint8_t pin); @@ -21,6 +19,8 @@ class Gate { int8_t modifierSelectionIndex; uint8_t divideMode; uint16_t modifier; + uint16_t tickInterval; + bool isEnabled; uint8_t width; uint8_t p; @@ -29,9 +29,6 @@ class Gate { void setLen(uint32_t currentPeriod); void setDiv(uint8_t modifier_selection_index); void setWidth(uint16_t newWidth); - void setP(uint16_t prob); - - bool getState(); }; #endif diff --git a/include/globals.h b/include/globals.h index a81d5ed..478c82d 100644 --- a/include/globals.h +++ b/include/globals.h @@ -6,6 +6,8 @@ #include #include + +// PINS static constexpr uint8_t OUT_1_PIN = 0; static constexpr uint8_t OUT_2_PIN = 2; static constexpr uint8_t OUT_3_PIN = 4; @@ -22,12 +24,12 @@ static constexpr uint8_t ENCODER_CLK_PIN = 20; static constexpr uint8_t ENCODER_DT_PIN = 21; static constexpr uint8_t ENCODER_SW_PIN = 22; +// TIME BASED extern volatile uint8_t BPM; - static constexpr uint32_t MINUTE_US = 60000000; static constexpr uint8_t PPQN = 96; +extern volatile uint32_t MASTER_TICK; -static uint32_t MASTER_TICK = 0; // Modifiers in UI order static std::array MODIFIERS = {8, 4, 2, 0, 1, 2, 3, 4, 8, 16}; @@ -36,11 +38,15 @@ static std::array MOD_TYPES = {0, 0, 0, 0, 1, 1, 1, 1, 1, 1}; // Modifier string static std::array MODIFIER_STRINGS = {"x8", "x4", "x2", "x0", "/1", "/2", "/3", "/4", "/8", "/16"}; + inline uint32_t millis() { return to_ms_since_boot(get_absolute_time()); } + inline uint32_t micros() { return to_us_since_boot(get_absolute_time()); } + + #endif // GLOBALS_H diff --git a/src/DisplayHandler.cpp b/src/DisplayHandler.cpp index b2d0926..64c22fa 100644 --- a/src/DisplayHandler.cpp +++ b/src/DisplayHandler.cpp @@ -10,8 +10,6 @@ #include "pico-ssd1306/shapeRenderer/ShapeRenderer.h" #include "pico-ssd1306/textRenderer/TextRenderer.h" -//TODO: -// the pulses are not even... they occur independently of eachother. pico_ssd1306::SSD1306* display = nullptr; extern void update_BPM(bool up); diff --git a/src/Gate.cpp b/src/Gate.cpp index 3dd0ec9..c69bbcf 100644 --- a/src/Gate.cpp +++ b/src/Gate.cpp @@ -1,20 +1,18 @@ // Gate.cpp -#include "pico/stdlib.h" #include "Gate.h" #include "globals.h" -#include #include + Gate::Gate(uint8_t pin) { this->pin = pin; state = 0; - modifierSelectionIndex = 3; editing = 0; + + modifierSelectionIndex = 3; divideMode = 0; // 1 divison | 0 multiplication modifier = 0; // divide mode modifier (4x, /32, etc) - div = 0; // cycles needed before a pulse based on divide mode and modifier - cycle = 0; // how many cycles have passed since last pulse dur = 0; // how long pulse is on width = 50; // pulse width @@ -23,73 +21,134 @@ Gate::Gate(uint8_t pin) { p = 100; // probability of a pulse } -bool Gate::getState() { - return state; -} void Gate::setLen(uint32_t currentPeriod) { len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0); } + void Gate::setDiv(uint8_t modifier_selecton_index) { - printf("HOW ABOUT HERE\n"); - - uint8_t modifier = MODIFIERS[modifier_selecton_index]; - uint8_t mod_type = MOD_TYPES[modifier_selecton_index]; - if (mod_type == 1) { - div = PPQN * modifier; - } else { - div = PPQN / modifier; - } - divideMode = mod_type; - this->modifier = modifier; - - if (this->modifier == 0) { - turnOff(); + switch(modifier_selecton_index) { + case 0: // x8 (32nd triplets) + tickInterval = 8; // 96 / 12 hits per beat + isEnabled = true; + divideMode = 0; + modifier = 8; + break; + case 1: // x4 (16th triplets) + tickInterval = 16; // 96 / 6 hits per beat + isEnabled = true; + divideMode = 0; + modifier = 4; + break; + case 2: // x2 (8th triplets) + tickInterval = 32; // 96 / 3 hits per beat + isEnabled = true; + divideMode = 0; + modifier = 2; + break; + case 3: // 0 (OFF) + tickInterval = 0; + isEnabled = false; + divideMode = 0; + modifier = 0; + break; + case 4: // /1 (Quarter Notes - The Pulse) + tickInterval = 96; // 96 / 1 hit per beat + isEnabled = true; + divideMode = 1; + modifier = 1; + break; + case 5: // /2 (8th Notes) + tickInterval = 192; // 96 * 2 (1 hit every 2 beats) + isEnabled = true; + divideMode = 1; + modifier = 2; + break; + case 6: // /3 (Dotted Quarter or specialized) + tickInterval = 288; // 96 * 3 (1 hit every 3 beats) + isEnabled = true; + divideMode = 1; + modifier = 3; + break; + case 7: // /4 (Whole Notes) + tickInterval = 384; // 96 * 4 (1 hit every 4 beats) + isEnabled = true; + divideMode = 1; + modifier = 4; + break; + case 8: // /8 (2 Bar phrasing) + tickInterval = 768; // 96 * 8 (1 hit every 8 beats) + isEnabled = true; + divideMode = 1; + modifier = 8; + break; + case 9: // /16 (4 Bar phrasing) + tickInterval = 1536; // 96 * 16 (1 hit every 16 beats) + isEnabled = true; + divideMode = 1; + modifier = 16; + break; + default: + tickInterval = 96; + isEnabled = true; + divideMode = 1; } + // TODO: check if this is actually needed setWidth(this->width); }; -void Gate::setWidth(uint16_t newWidth) { - width = newWidth; - if (divideMode == 1) { - len = (uint32_t)((double)(MINUTE_US / BPM) * (width / 100.0) / 1000.0); - } else { - len = (uint32_t)((double)(MINUTE_US / BPM / modifier) * (width / 100.0) / 1000.0); - } -}; -void Gate::setP(uint16_t prob) { - this->p = prob; +void Gate::setWidth(uint16_t newWidth) { + this->width = newWidth; + + double ms_per_tick = (60000.0 / (double)BPM) / 96.0; + + double ms_between_triggers = ms_per_tick * (double)this->tickInterval; + + this->len = (uint32_t)(ms_between_triggers * (this->width / 100.0)); + + uint32_t max_allowed_len = (ms_between_triggers > 5) ? (uint32_t)ms_between_triggers - 2 : 1; + + if (this->len > max_allowed_len) { + this->len = max_allowed_len; + } + + if (this->len < 1) this->len = 1; } + void Gate::turnOn() { - cycle += 1; - uint8_t pRes = 1; + if (!isEnabled || tickInterval == 0) return; - if (cycle == div) { - if (p < 100) { - uint32_t r = (rand() % 100) + 1; - if (r > p) { - pRes = 0; - } - } + if (MASTER_TICK % tickInterval == 0) { + if (MASTER_TICK != lastTriggerTick) { + lastTriggerTick = MASTER_TICK; - if (pRes == 1) { - state = 1; - gpio_put(pin, state); - dur = millis(); - } - cycle = 0; - }; + uint8_t pRes = 1; + if (p < 100) { + if ((rand() % 100) + 1 > p) pRes = 0; + } + + if (pRes == 1) { + state = 1; + gpio_put(pin, 1); + dur = millis(); + } + } + } + else { + lastTriggerTick = 0xFFFFFFFF; + } } + void Gate::turnOff() { - if (state == 1 && millis() - dur >= len) { - state = 0; - gpio_put(pin, state); - dur = 0; - }; + if (state == 1) { + if (millis() - dur >= len) { + state = 0; + gpio_put(pin, 0); + } + } } - diff --git a/src/main.cpp b/src/main.cpp index 6d0bc56..13a925c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,14 +11,16 @@ #include "DisplayHandler.h" #include "EncoderHandler.h" -struct repeating_timer bpm_timer = {0}; +// Time based operations +struct repeating_timer bpm_timer = {0}; volatile uint8_t BPM = 60; volatile uint8_t PLAY = 1; volatile uint32_t period_us = 0; +volatile uint32_t MASTER_TICK; -volatile bool beatToggle = false; +// Initialize Outputs Gate out1(OUT_1_PIN); Gate out2(OUT_2_PIN); Gate out3(OUT_3_PIN); @@ -30,13 +32,15 @@ Gate out8(OUT_8_PIN); static Gate* outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8}; + +// Initialize Handlers static DisplayHandler display_handler(outputs); -static EncoderHandler encoder_handler(&display_handler); +static EncoderHandler encoder_handler(&display_handler); + bool timer_callback(struct repeating_timer *t) { if (PLAY == 1) { - beatToggle = true; MASTER_TICK += 1; } return true; @@ -86,58 +90,32 @@ void setup_outs() { } // manual setup - out1.setDiv(1); - // out1.setWidth(50); - // - // out2.setDiv(4, 0); - // out2.setWidth(50); - // - // out3.setDiv(2, 0); - // out3.setWidth(50); - // - // out4.setDiv(1); - // out4.setWidth(50); - // - // out5.setDiv(2); - // out5.setWidth(50); - // - // out6.setDiv(4); - // out6.setWidth(50); - // - // out7.setDiv(8); - // out7.setWidth(50); - // - // out8.setDiv(16); - // out8.setWidth(50); + // out1.setDiv(5); + // out1.setWidth(80); } void handle_outs() { - if (beatToggle) { - for (Gate* g: outputs) { - if (g->modifier != 0) { - g->turnOn(); - } - } - - beatToggle = false; - } - for (Gate* g: outputs) { - if (g->modifier != 0) { - g->turnOff(); - } - } + for (Gate* g: outputs) { + g->turnOn(); + g->turnOff(); + } } int main() { + // initialize stdio_init_all(); + + // Seed random srand(rosc_hw->randombit); + // Initialize display and multicore display_handler.setup(); multicore_launch_core1(core1_entry); multicore_fifo_push_blocking(1); + // Initialize Encoder encoder_handler.setup(); setup_outs(); From 347379424cb6100b816478e72a7915f71718487f Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Mon, 23 Feb 2026 21:16:22 -0500 Subject: [PATCH 2/3] improved encoding --- .gitignore | 3 + CMakeLists.txt | 3 + include/EncoderHandler.h | 3 + include/globals.h | 3 + quadrature_encoder.pio | 145 +++++++++++++++++++++++++++++++++++++++ src/DisplayHandler.cpp | 43 ++++++++++-- src/EncoderHandler.cpp | 92 +++++++++++++------------ src/Gate.cpp | 6 +- src/main.cpp | 1 + 9 files changed, 250 insertions(+), 49 deletions(-) create mode 100644 .gitignore create mode 100644 quadrature_encoder.pio diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f593d29 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.clangd +compile_commands.json + diff --git a/CMakeLists.txt b/CMakeLists.txt index 55cb17f..bd176ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,13 @@ target_include_directories(clock PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/lib) add_subdirectory(lib/pico-ssd1306) +pico_generate_pio_header(clock ${CMAKE_CURRENT_LIST_DIR}/quadrature_encoder.pio) + # Pull in standard library and hardware abstraction target_link_libraries(clock pico_stdlib hardware_gpio + hardware_pio hardware_i2c pico_multicore pico_ssd1306 diff --git a/include/EncoderHandler.h b/include/EncoderHandler.h index d44f1ad..c3897d2 100644 --- a/include/EncoderHandler.h +++ b/include/EncoderHandler.h @@ -10,6 +10,8 @@ class EncoderHandler { private: + uint sm; + uint last_count; public: @@ -23,6 +25,7 @@ class EncoderHandler { void setup(); static void gpio_callback(uint gpio, uint32_t events); void moveCursor(bool dir = 1); + void update(); }; #endif diff --git a/include/globals.h b/include/globals.h index 478c82d..4915f1d 100644 --- a/include/globals.h +++ b/include/globals.h @@ -20,6 +20,9 @@ static constexpr uint8_t OUT_8_PIN = 14; static constexpr uint8_t SCREEN_SCL_PIN = 18; static constexpr uint8_t SCREEN_SDA_PIN = 19; +// Modify moves per detent if your encoder is acting weird +// for me, with 20 detents per full rotation, 4 works +static constexpr uint8_t TICKS_PER_DETENT = 4; static constexpr uint8_t ENCODER_CLK_PIN = 20; static constexpr uint8_t ENCODER_DT_PIN = 21; static constexpr uint8_t ENCODER_SW_PIN = 22; diff --git a/quadrature_encoder.pio b/quadrature_encoder.pio new file mode 100644 index 0000000..76966c2 --- /dev/null +++ b/quadrature_encoder.pio @@ -0,0 +1,145 @@ +; +; Copyright (c) 2023 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; +.pio_version 0 // only requires PIO version 0 + +.program quadrature_encoder + +; the code must be loaded at address 0, because it uses computed jumps +.origin 0 + + +; the code works by running a loop that continuously shifts the 2 phase pins into +; ISR and looks at the lower 4 bits to do a computed jump to an instruction that +; does the proper "do nothing" | "increment" | "decrement" action for that pin +; state change (or no change) + +; ISR holds the last state of the 2 pins during most of the code. The Y register +; keeps the current encoder count and is incremented / decremented according to +; the steps sampled + +; the program keeps trying to write the current count to the RX FIFO without +; blocking. To read the current count, the user code must drain the FIFO first +; and wait for a fresh sample (takes ~4 SM cycles on average). The worst case +; sampling loop takes 10 cycles, so this program is able to read step rates up +; to sysclk / 10 (e.g., sysclk 125MHz, max step rate = 12.5 Msteps/sec) + +; 00 state + JMP update ; read 00 + JMP decrement ; read 01 + JMP increment ; read 10 + JMP update ; read 11 + +; 01 state + JMP increment ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP decrement ; read 11 + +; 10 state + JMP decrement ; read 00 + JMP update ; read 01 + JMP update ; read 10 + JMP increment ; read 11 + +; to reduce code size, the last 2 states are implemented in place and become the +; target for the other jumps + +; 11 state + JMP update ; read 00 + JMP increment ; read 01 +decrement: + ; note: the target of this instruction must be the next address, so that + ; the effect of the instruction does not depend on the value of Y. The + ; same is true for the "JMP X--" below. Basically "JMP Y--, " + ; is just a pure "decrement Y" instruction, with no other side effects + JMP Y--, update ; read 10 + + ; this is where the main loop starts +.wrap_target +update: + MOV ISR, Y ; read 11 + PUSH noblock + +sample_pins: + ; we shift into ISR the last state of the 2 input pins (now in OSR) and + ; the new state of the 2 pins, thus producing the 4 bit target for the + ; computed jump into the correct action for this state. Both the PUSH + ; above and the OUT below zero out the other bits in ISR + OUT ISR, 2 + IN PINS, 2 + + ; save the state in the OSR, so that we can use ISR for other purposes + MOV OSR, ISR + ; jump to the correct state machine action + MOV PC, ISR + + ; the PIO does not have a increment instruction, so to do that we do a + ; negate, decrement, negate sequence +increment: + MOV Y, ~Y + JMP Y--, increment_cont +increment_cont: + MOV Y, ~Y +.wrap ; the .wrap here avoids one jump instruction and saves a cycle too + + + +% c-sdk { + +#include "hardware/clocks.h" +#include "hardware/gpio.h" + +// max_step_rate is used to lower the clock of the state machine to save power +// if the application doesn't require a very high sampling rate. Passing zero +// will set the clock to the maximum + +static inline void quadrature_encoder_program_init(PIO pio, uint sm, uint pin, int max_step_rate) +{ + pio_sm_set_consecutive_pindirs(pio, sm, pin, 2, false); + pio_gpio_init(pio, pin); + pio_gpio_init(pio, pin + 1); + + gpio_pull_up(pin); + gpio_pull_up(pin + 1); + + pio_sm_config c = quadrature_encoder_program_get_default_config(0); + + sm_config_set_in_pins(&c, pin); // for WAIT, IN + sm_config_set_jmp_pin(&c, pin); // for JMP + // shift to left, autopull disabled + sm_config_set_in_shift(&c, false, false, 32); + // don't join FIFO's + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); + + // passing "0" as the sample frequency, + if (max_step_rate == 0) { + sm_config_set_clkdiv(&c, 1.0); + } else { + // one state machine loop takes at most 10 cycles + float div = (float)clock_get_hz(clk_sys) / (10 * max_step_rate); + sm_config_set_clkdiv(&c, div); + } + + pio_sm_init(pio, sm, 0, &c); + pio_sm_set_enabled(pio, sm, true); +} + +static inline int32_t quadrature_encoder_get_count(PIO pio, uint sm) +{ + uint ret; + int n; + + // if the FIFO has N entries, we fetch them to drain the FIFO, + // plus one entry which will be guaranteed to not be stale + n = pio_sm_get_rx_fifo_level(pio, sm) + 1; + while (n > 0) { + ret = pio_sm_get_blocking(pio, sm); + n--; + } + return ret; +} + +%} diff --git a/src/DisplayHandler.cpp b/src/DisplayHandler.cpp index 64c22fa..bbd6ed0 100644 --- a/src/DisplayHandler.cpp +++ b/src/DisplayHandler.cpp @@ -78,8 +78,41 @@ void DisplayHandler::moveCursor(bool dir) { outputs[currentOut]->modifierSelectionIndex = std::size(MOD_TYPES) - 1; } - } + } else if (currentScreen == 2) { // width control + outputs[currentOut]->editing = 1; + if (dir == 1) { + outputs[currentOut]->width++; + } else { + outputs[currentOut]->width--; + } + if (outputs[currentOut]->width > 100) { + outputs[currentOut]->width = 100; + } + + if (outputs[currentOut]->width < 1) { + outputs[currentOut]->width = 1; + } + + outputs[currentOut]->setWidth(outputs[currentOut]->width); + + } else if (currentScreen == 3) { + + outputs[currentOut]->editing = 1; + if (dir == 1) { + outputs[currentOut]->p++; + } else { + outputs[currentOut]->p--; + } + + if (outputs[currentOut]->p > 100) { + outputs[currentOut]->p = 100; + } + + if (outputs[currentOut]->p < 0) { + outputs[currentOut]->p = 0; + } + } } } else { @@ -111,16 +144,17 @@ void DisplayHandler::handleClick() { if (onOutScreen) { if (currentScreen == 0) { // exit screen - cursorPosition = currentOut; + cursorPosition = currentOut + 1; currentOut = -1; currentScreen = 0; onOutScreen = 0; + cursorClick = false; } if (currentScreen == 1 && outputs[currentOut]->editing == 1) { outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex); - cursorClick = 0; outputs[currentOut]->editing = 0; + cursorClick = false; } } else { @@ -198,7 +232,8 @@ void DisplayHandler::renderMainPage() { void DisplayHandler::renderOutPage() { - std::string title = std::to_string(currentOut) + "| " + out_pages[currentScreen]; + uint8_t visualOut = currentOut + 1; + std::string title = std::to_string(visualOut) + "| " + out_pages[currentScreen]; pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2); std::string param_string; diff --git a/src/EncoderHandler.cpp b/src/EncoderHandler.cpp index 97be2cb..aa000a6 100644 --- a/src/EncoderHandler.cpp +++ b/src/EncoderHandler.cpp @@ -5,6 +5,8 @@ #include "globals.h" #include #include +#include "hardware/pio.h" +#include "quadrature_encoder.pio.h" static EncoderHandler* self = nullptr; @@ -19,53 +21,59 @@ EncoderHandler::EncoderHandler(DisplayHandler* display_handler) { void EncoderHandler::gpio_callback(uint gpio, uint32_t events) { - static uint64_t last_sw_time = 0; - static uint64_t last_rotate_time = 0; - uint64_t now = to_us_since_boot(get_absolute_time()); + uint64_t now = to_us_since_boot(get_absolute_time()); + static uint64_t last_sw_time = 0; - if (gpio == ENCODER_SW_PIN) { // handle button press - if (now - last_sw_time > 200000) { - self->display_handler->handleClick(); - last_sw_time = now; - } - } else if (gpio == ENCODER_CLK_PIN) { // handle encoder turn - - if (now - last_rotate_time < 5000) return; - - if (events & GPIO_IRQ_EDGE_FALL) { - if (gpio_get(ENCODER_CLK_PIN) == 0) { - - uint16_t dt_state = gpio_get(ENCODER_DT_PIN); - - if (dt_state) { - self->display_handler->moveCursor(); - } else { - self->display_handler->moveCursor(0); - } - } - - last_rotate_time = now; - } - } + if (gpio == ENCODER_SW_PIN) { + if (now - last_sw_time > 200000) { // 200ms debounce + self->display_handler->handleClick(); + last_sw_time = now; + } + } } - void EncoderHandler::setup() { - gpio_init(ENCODER_SW_PIN); - gpio_set_dir(ENCODER_SW_PIN, GPIO_IN); - gpio_pull_up(ENCODER_SW_PIN); + self = this; - gpio_init(ENCODER_CLK_PIN); - gpio_set_dir(ENCODER_CLK_PIN, GPIO_IN); - gpio_pull_up(ENCODER_CLK_PIN); + // 1. Setup Button (Standard GPIO) + gpio_init(ENCODER_SW_PIN); + gpio_set_dir(ENCODER_SW_PIN, GPIO_IN); + gpio_pull_up(ENCODER_SW_PIN); + + // Enable IRQ only for the switch + gpio_set_irq_enabled_with_callback(ENCODER_SW_PIN, GPIO_IRQ_EDGE_FALL, true, &EncoderHandler::gpio_callback); - gpio_init(ENCODER_DT_PIN); - gpio_set_dir(ENCODER_DT_PIN, GPIO_IN); - gpio_pull_up(ENCODER_DT_PIN); + // 2. Setup Rotation (PIO) + // Note: ENCODER_CLK_PIN and ENCODER_DT_PIN must be consecutive! + PIO pio = pio0; + uint offset = pio_add_program(pio, &quadrature_encoder_program); + this->sm = pio_claim_unused_sm(pio, true); + + // Internal pull-ups still needed for the PIO pins + gpio_init(ENCODER_CLK_PIN); + gpio_pull_up(ENCODER_CLK_PIN); + gpio_init(ENCODER_DT_PIN); + gpio_pull_up(ENCODER_DT_PIN); - clk_last_state = gpio_get(ENCODER_CLK_PIN); - - gpio_set_irq_enabled_with_callback(20, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &EncoderHandler::gpio_callback); - gpio_set_irq_enabled(21, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); - gpio_set_irq_enabled(22, GPIO_IRQ_EDGE_FALL, true); + quadrature_encoder_program_init(pio, sm, ENCODER_CLK_PIN, 0); + + this->last_count = 0; +} + +// Call this in your main loop or a timer +void EncoderHandler::update() { + int32_t current_count = quadrature_encoder_get_count(pio0, this->sm); + + int32_t delta = current_count - last_count; + + if (abs(delta) >= TICKS_PER_DETENT) { + + if (delta < 0) { // Changed from > to < to reverse direction + display_handler->moveCursor(); // Clockwise + } else { + display_handler->moveCursor(0); // Counter-clockwise + } + + last_count = current_count - (delta % TICKS_PER_DETENT); + } } diff --git a/src/Gate.cpp b/src/Gate.cpp index c69bbcf..10b3ef5 100644 --- a/src/Gate.cpp +++ b/src/Gate.cpp @@ -30,19 +30,19 @@ void Gate::setLen(uint32_t currentPeriod) { void Gate::setDiv(uint8_t modifier_selecton_index) { switch(modifier_selecton_index) { case 0: // x8 (32nd triplets) - tickInterval = 8; // 96 / 12 hits per beat + tickInterval = 12; // 96 / 12 hits per beat isEnabled = true; divideMode = 0; modifier = 8; break; case 1: // x4 (16th triplets) - tickInterval = 16; // 96 / 6 hits per beat + tickInterval = 24; // 96 / 6 hits per beat isEnabled = true; divideMode = 0; modifier = 4; break; case 2: // x2 (8th triplets) - tickInterval = 32; // 96 / 3 hits per beat + tickInterval = 48; // 96 / 3 hits per beat isEnabled = true; divideMode = 0; modifier = 2; diff --git a/src/main.cpp b/src/main.cpp index 13a925c..daf883a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -123,6 +123,7 @@ int main() { update_period(); while (true) { + encoder_handler.update(); handle_outs(); } } From 150e910ed43859496673e8b595aa3880bc96bffa Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Tue, 24 Feb 2026 19:07:29 -0500 Subject: [PATCH 3/3] updating divisions --- include/globals.h | 6 ++-- src/EncoderHandler.cpp | 12 ++----- src/Gate.cpp | 80 ++++++++++++++++++++++++++++++++---------- 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/include/globals.h b/include/globals.h index 4915f1d..8470f43 100644 --- a/include/globals.h +++ b/include/globals.h @@ -35,11 +35,11 @@ extern volatile uint32_t MASTER_TICK; // Modifiers in UI order -static std::array MODIFIERS = {8, 4, 2, 0, 1, 2, 3, 4, 8, 16}; +static std::array MODIFIERS = {32, 16, 12, 8, 6, 4, 3, 2, 0, 1, 2, 3, 4, 6, 8, 16, 32}; // Modifier type; 0 = multiplicaton, 1 = division; matched with MODIFIERS -static std::array MOD_TYPES = {0, 0, 0, 0, 1, 1, 1, 1, 1, 1}; +static std::array MOD_TYPES = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}; // Modifier string -static std::array MODIFIER_STRINGS = {"x8", "x4", "x2", "x0", "/1", "/2", "/3", "/4", "/8", "/16"}; +static std::array MODIFIER_STRINGS = {"x32", "x16", "x12", "x8", "x6", "x4", "x3", "x2", "x0", "/1", "/2", "/3", "/4", "/6", "/8", "/16", "/32"}; inline uint32_t millis() { diff --git a/src/EncoderHandler.cpp b/src/EncoderHandler.cpp index aa000a6..f6ccf67 100644 --- a/src/EncoderHandler.cpp +++ b/src/EncoderHandler.cpp @@ -35,21 +35,16 @@ void EncoderHandler::gpio_callback(uint gpio, uint32_t events) { void EncoderHandler::setup() { self = this; - // 1. Setup Button (Standard GPIO) gpio_init(ENCODER_SW_PIN); gpio_set_dir(ENCODER_SW_PIN, GPIO_IN); gpio_pull_up(ENCODER_SW_PIN); - // Enable IRQ only for the switch gpio_set_irq_enabled_with_callback(ENCODER_SW_PIN, GPIO_IRQ_EDGE_FALL, true, &EncoderHandler::gpio_callback); - // 2. Setup Rotation (PIO) - // Note: ENCODER_CLK_PIN and ENCODER_DT_PIN must be consecutive! PIO pio = pio0; uint offset = pio_add_program(pio, &quadrature_encoder_program); this->sm = pio_claim_unused_sm(pio, true); - // Internal pull-ups still needed for the PIO pins gpio_init(ENCODER_CLK_PIN); gpio_pull_up(ENCODER_CLK_PIN); gpio_init(ENCODER_DT_PIN); @@ -60,7 +55,6 @@ void EncoderHandler::setup() { this->last_count = 0; } -// Call this in your main loop or a timer void EncoderHandler::update() { int32_t current_count = quadrature_encoder_get_count(pio0, this->sm); @@ -68,10 +62,10 @@ void EncoderHandler::update() { if (abs(delta) >= TICKS_PER_DETENT) { - if (delta < 0) { // Changed from > to < to reverse direction - display_handler->moveCursor(); // Clockwise + if (delta < 0) { + display_handler->moveCursor(); } else { - display_handler->moveCursor(0); // Counter-clockwise + display_handler->moveCursor(0); } last_count = current_count - (delta % TICKS_PER_DETENT); diff --git a/src/Gate.cpp b/src/Gate.cpp index 10b3ef5..d494c62 100644 --- a/src/Gate.cpp +++ b/src/Gate.cpp @@ -29,62 +29,104 @@ void Gate::setLen(uint32_t currentPeriod) { void Gate::setDiv(uint8_t modifier_selecton_index) { switch(modifier_selecton_index) { - case 0: // x8 (32nd triplets) - tickInterval = 12; // 96 / 12 hits per beat + case 0: // x32 + tickInterval = 3; + isEnabled = true; + divideMode = 0; + modifier = 32; + break; + case 1: // x16 + tickInterval = 6; + isEnabled = true; + divideMode = 0; + modifier = 16; + break; + case 2: // x12 + tickInterval = 8; + isEnabled = true; + divideMode = 0; + modifier = 12; + break; + case 3: // x8 + tickInterval = 12; isEnabled = true; divideMode = 0; modifier = 8; break; - case 1: // x4 (16th triplets) - tickInterval = 24; // 96 / 6 hits per beat + case 4: // x6 + tickInterval = 16; + isEnabled = true; + divideMode = 0; + modifier = 6; + break; + case 5: // x4 + tickInterval = 24; isEnabled = true; divideMode = 0; modifier = 4; break; - case 2: // x2 (8th triplets) - tickInterval = 48; // 96 / 3 hits per beat + case 6: // x3 + tickInterval = 32; + isEnabled = true; + divideMode = 0; + modifier = 3; + break; + case 7: // x2 + tickInterval = 48; isEnabled = true; divideMode = 0; modifier = 2; break; - case 3: // 0 (OFF) + case 8: // 0 (OFF) tickInterval = 0; isEnabled = false; divideMode = 0; modifier = 0; break; - case 4: // /1 (Quarter Notes - The Pulse) - tickInterval = 96; // 96 / 1 hit per beat + case 9: // /1 + tickInterval = 96; isEnabled = true; divideMode = 1; modifier = 1; break; - case 5: // /2 (8th Notes) - tickInterval = 192; // 96 * 2 (1 hit every 2 beats) + case 10: // /2 + tickInterval = 192; isEnabled = true; divideMode = 1; modifier = 2; break; - case 6: // /3 (Dotted Quarter or specialized) - tickInterval = 288; // 96 * 3 (1 hit every 3 beats) + case 11: // /3 + tickInterval = 288; isEnabled = true; divideMode = 1; modifier = 3; break; - case 7: // /4 (Whole Notes) - tickInterval = 384; // 96 * 4 (1 hit every 4 beats) + case 12: // /4 + tickInterval = 384; isEnabled = true; divideMode = 1; modifier = 4; break; - case 8: // /8 (2 Bar phrasing) - tickInterval = 768; // 96 * 8 (1 hit every 8 beats) + case 13: // /6 + tickInterval = 576; + isEnabled = true; + divideMode = 1; + modifier = 4; + break; + case 14: // /8 + tickInterval = 768; isEnabled = true; divideMode = 1; modifier = 8; break; - case 9: // /16 (4 Bar phrasing) - tickInterval = 1536; // 96 * 16 (1 hit every 16 beats) + case 15: // /16 + tickInterval = 1536; + isEnabled = true; + divideMode = 1; + modifier = 16; + break; + case 16: // /32 + tickInterval = 3072; isEnabled = true; divideMode = 1; modifier = 16;