out screens

This commit is contained in:
Dominic DiTaranto 2026-02-22 17:28:34 -05:00
parent 90915b89f6
commit 6532b218fe
9 changed files with 453 additions and 138 deletions

View file

@ -17,6 +17,8 @@ include_directories(include)
add_executable(clock
src/main.cpp
src/Gate.cpp
src/DisplayHandler.cpp
src/EncoderHandler.cpp
)
# Enable USB output (useful for later printf debugging)

37
include/DisplayHandler.h Normal file
View file

@ -0,0 +1,37 @@
// DisplayHandler.h
#ifndef DisplayHandler_h
#define DisplayHandler_h
#include <cstdint>
#include <string>
#include <array>
#include "Gate.h"
class DisplayHandler {
private:
char buffer[32];
uint8_t currentScreen;
std::string screens[7];
std::array<std::string, 4> out_pages = {"Exit", "Mod", "Width", "Prob"};
bool onOutScreen = 0;
void renderMainPage();
void renderOutPage();
public:
DisplayHandler(Gate* outputs[]);
Gate** outputs;
bool updateScreen;
int8_t cursorPosition;
uint8_t mainMaxCursorPosition;
uint8_t outMaxCursorPosition;
int8_t currentOut;
bool cursorClick;
void setup();
void render();
void handleClick();
void moveCursor(bool dir = 1);
};
#endif

28
include/EncoderHandler.h Normal file
View file

@ -0,0 +1,28 @@
// EncoderHandler.h
#ifndef EncoderHandler_h
#define EncoderHandler_h
#include <cstdint>
#include <string>
#include "pico/multicore.h"
#include "DisplayHandler.h"
class EncoderHandler {
private:
public:
EncoderHandler(DisplayHandler* display_handler);
DisplayHandler* display_handler;
uint8_t encoder_pos;
bool button_pressed;
uint16_t clk_last_state;
void setup();
static void gpio_callback(uint gpio, uint32_t events);
void moveCursor(bool dir = 1);
};
#endif

View file

@ -12,21 +12,22 @@ class Gate {
int16_t cycle;
uint32_t dur;
uint32_t len;
uint8_t width;
uint8_t divideMode;
uint16_t div;
uint16_t modifier;
std::string divString;
uint8_t p;
public:
Gate(uint8_t pin);
uint8_t pin;
uint8_t editing;
int8_t modifierSelectionIndex;
uint8_t divideMode;
uint16_t modifier;
uint8_t width;
uint8_t p;
void turnOn();
void turnOff();
void setLen(uint32_t currentPeriod);
void setDiv(uint16_t newDiv, uint8_t divide = 1);
void setDiv(uint8_t modifier_selection_index);
void setWidth(uint16_t newWidth);
void setP(uint16_t prob);

View file

@ -3,6 +3,8 @@
#include "pico/stdlib.h"
#include <cstdint>
#include <array>
#include <string>
static constexpr uint8_t OUT_1_PIN = 0;
static constexpr uint8_t OUT_2_PIN = 2;
@ -25,6 +27,15 @@ extern volatile uint8_t BPM;
static constexpr uint32_t MINUTE_US = 60000000;
static constexpr uint8_t PPQN = 96;
static uint32_t MASTER_TICK = 0;
// Modifiers in UI order
static std::array<uint8_t, 10> MODIFIERS = {8, 4, 2, 0, 1, 2, 3, 4, 8, 16};
// Modifier type; 0 = multiplicaton, 1 = division; matched with MODIFIERS
static std::array<uint8_t, 10> MOD_TYPES = {0, 0, 0, 0, 1, 1, 1, 1, 1, 1};
// Modifier string
static std::array<std::string, 10> MODIFIER_STRINGS = {"x8", "x4", "x2", "x0", "/1", "/2", "/3", "/4", "/8", "/16"};
inline uint32_t millis() {
return to_ms_since_boot(get_absolute_time());
}

229
src/DisplayHandler.cpp Normal file
View file

@ -0,0 +1,229 @@
// DisplayHandler.cpp
#include "pico/stdlib.h"
#include "DisplayHandler.h"
#include "globals.h"
#include <string>
#include <cstdlib>
#include "hardware/i2c.h"
#include "pico-ssd1306/ssd1306.h"
#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);
DisplayHandler::DisplayHandler(Gate* outputs[]) {
this->outputs = outputs;
currentScreen = 0;
currentOut = -1;
updateScreen = 1;
cursorPosition = 0;
mainMaxCursorPosition = 8;
outMaxCursorPosition = std::size(out_pages) - 1;
cursorClick = 0;
}
void DisplayHandler::setup() {
i2c_init(i2c1, 400 * 1000);
gpio_set_function(SCREEN_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(SCREEN_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(SCREEN_SDA_PIN);
gpio_pull_up(SCREEN_SCL_PIN);
display = new pico_ssd1306::SSD1306(i2c1, 0x3C, pico_ssd1306::Size::W128xH64);
display->setOrientation(0);
}
void DisplayHandler::moveCursor(bool dir) {
if (onOutScreen) {
if (cursorClick == 0) {
if (dir == 1) {
cursorPosition++;
currentScreen++;
} else {
cursorPosition--;
currentScreen--;
}
if (cursorPosition > outMaxCursorPosition) {
cursorPosition = 0;
currentScreen = 0;
}
if (cursorPosition < 0) {
cursorPosition = outMaxCursorPosition;
currentScreen = outMaxCursorPosition;
}
} else { // click = 1
if (currentScreen == 1) { // mod screen
outputs[currentOut]->editing = 1;
if (dir == 1) {
outputs[currentOut]->modifierSelectionIndex++;
} else {
outputs[currentOut]->modifierSelectionIndex--;
}
if (outputs[currentOut]->modifierSelectionIndex < 0) {
outputs[currentOut]->modifierSelectionIndex = 0;
}
if (outputs[currentOut]->modifierSelectionIndex > std::size(MOD_TYPES) - 1) {
outputs[currentOut]->modifierSelectionIndex = std::size(MOD_TYPES) - 1;
}
}
}
} else {
if (cursorPosition == 0 && cursorClick == 1) { // Engage BPM on Main Screen
update_BPM(dir);
} else {
if (dir == 1) {
cursorPosition++;
} else {
cursorPosition--;
}
if (cursorPosition > mainMaxCursorPosition) {
cursorPosition = 0;
}
if (cursorPosition < 0) {
cursorPosition = mainMaxCursorPosition;
}
}
}
updateScreen = 1;
}
void DisplayHandler::handleClick() {
cursorClick ^= true;
if (onOutScreen) {
if (currentScreen == 0) { // exit screen
cursorPosition = currentOut;
currentOut = -1;
currentScreen = 0;
onOutScreen = 0;
}
if (currentScreen == 1 && outputs[currentOut]->editing == 1) {
outputs[currentOut]->setDiv(outputs[currentOut]->modifierSelectionIndex);
cursorClick = 0;
outputs[currentOut]->editing = 0;
}
} else {
if (currentScreen == 0) { // on main screen
if (cursorPosition == 0) { // Change BPM
} else if (cursorPosition > 0 && cursorPosition < 9) { // go to out screen
currentOut = cursorPosition - 1;
cursorPosition = 1;
currentScreen = 1;
onOutScreen = 1;
cursorClick = 0;
}
}
}
updateScreen = 1;
}
void DisplayHandler::render() {
if (updateScreen) {
display->clear();
if (currentScreen == 0 && currentOut == -1) { // main screen
renderMainPage();
} else if (currentOut != -1) {
renderOutPage();
}
display->sendBuffer();
updateScreen = 0;
}
}
void DisplayHandler::renderMainPage() {
std::string bpm_string = "BPM: " + std::to_string(BPM);
if (cursorPosition == 0) {
if (cursorClick == 1) {
pico_ssd1306::fillRect(display, 0, 0, 100, 18);
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2, pico_ssd1306::WriteMode::SUBTRACT);
} else {
pico_ssd1306::drawRect(display, 0, 0, 100, 18, pico_ssd1306::WriteMode::ADD);
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
}
} else {
pico_ssd1306::drawText(display, font_12x16, bpm_string.c_str(), 1, 2);
}
uint8_t cursor_x = 2;
uint8_t cursor_y = 25;
for (uint8_t i = 1; i < 9; i++) {
if (i == 5) {
cursor_x = 2;
cursor_y += 20;
}
if (cursorPosition == i) {
pico_ssd1306::drawRect(display, cursor_x - 2, cursor_y - 4, cursor_x + 14, cursor_y + 16, pico_ssd1306::WriteMode::ADD);
}
pico_ssd1306::drawText(display, font_12x16, std::to_string(i).c_str(), cursor_x, cursor_y);
cursor_x += 30;
}
}
void DisplayHandler::renderOutPage() {
std::string title = std::to_string(currentOut) + "| " + out_pages[currentScreen];
pico_ssd1306::drawText(display, font_12x16, title.c_str(), 1, 2);
std::string param_string;
if (currentScreen == 0) { // exit screen
param_string = "<--";
} else if (currentScreen == 1) { // modifier screen
uint8_t modifier_selection_index = outputs[currentOut]->modifierSelectionIndex;
param_string = MODIFIER_STRINGS[modifier_selection_index];
} else if (currentScreen == 2) { // Width screen
param_string = std::to_string(outputs[currentOut]->width) + "%";
} else if (currentScreen == 3) { // Probability screen
param_string = std::to_string(outputs[currentOut]->p) + "%";
}
if (cursorClick) {
pico_ssd1306::fillRect(display, 1, 42, 50, 60);
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45, pico_ssd1306::WriteMode::SUBTRACT);
} else {
pico_ssd1306::drawText(display, font_12x16, param_string.c_str(), 1, 45);
}
}

71
src/EncoderHandler.cpp Normal file
View file

@ -0,0 +1,71 @@
// EncoderHandler.cpp
#include "pico/stdlib.h"
#include "EncoderHandler.h"
#include "DisplayHandler.h"
#include "globals.h"
#include <string>
#include <cstdlib>
static EncoderHandler* self = nullptr;
EncoderHandler::EncoderHandler(DisplayHandler* display_handler) {
this->display_handler = display_handler;
self = this;
encoder_pos = 0;
button_pressed = 0;
}
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());
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;
}
}
}
void EncoderHandler::setup() {
gpio_init(ENCODER_SW_PIN);
gpio_set_dir(ENCODER_SW_PIN, GPIO_IN);
gpio_pull_up(ENCODER_SW_PIN);
gpio_init(ENCODER_CLK_PIN);
gpio_set_dir(ENCODER_CLK_PIN, GPIO_IN);
gpio_pull_up(ENCODER_CLK_PIN);
gpio_init(ENCODER_DT_PIN);
gpio_set_dir(ENCODER_DT_PIN, GPIO_IN);
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);
}

View file

@ -9,11 +9,12 @@ Gate::Gate(uint8_t pin) {
this->pin = pin;
state = 0;
divideMode = 1; // 1 divison | 0 multiplication
modifier = 1; // divide mode modifier (4x, /32, etc)
div = 1; // cycles needed before a pulse based on divide mode and modifier
modifierSelectionIndex = 3;
editing = 0;
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
divString = ""; // string for screen .. probably does not belong here
dur = 0; // how long pulse is on
width = 50; // pulse width
@ -30,16 +31,24 @@ void Gate::setLen(uint32_t currentPeriod) {
len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0);
}
void Gate::setDiv(uint16_t modifier, uint8_t divide) {
if (divide == 1) {
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;
divString = "/" + std::to_string(modifier);
} else {
div = PPQN / modifier;
divString = "x" + std::to_string(modifier);
}
divideMode = divide;
divideMode = mod_type;
this->modifier = modifier;
if (this->modifier == 0) {
turnOff();
}
setWidth(this->width);
};
void Gate::setWidth(uint16_t newWidth) {

View file

@ -1,17 +1,15 @@
#include <cstdint>
#include <cstdio>
#include <stdio.h>
#include "hardware/i2c.h"
#include "hardware/structs/rosc.h"
#include "pico/multicore.h"
#include "pico/stdlib.h"
#include "pico/time.h"
#include "pico-ssd1306/ssd1306.h"
#include "pico-ssd1306/textRenderer/TextRenderer.h"
#include "globals.h"
#include "Gate.h"
#include "DisplayHandler.h"
#include "EncoderHandler.h"
struct repeating_timer bpm_timer = {0};
@ -30,17 +28,16 @@ Gate out6(OUT_6_PIN);
Gate out7(OUT_7_PIN);
Gate out8(OUT_8_PIN);
volatile bool updateScreen = 1;
pico_ssd1306::SSD1306* display = nullptr;
static Gate* outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8};
volatile uint8_t encoder_pos = 0;
volatile bool button_pressed = 0;
volatile uint16_t clk_last_state;
static DisplayHandler display_handler(outputs);
static EncoderHandler encoder_handler(&display_handler);
bool timer_callback(struct repeating_timer *t) {
if (PLAY == 1) {
beatToggle = true;
MASTER_TICK += 1;
}
return true;
}
@ -69,117 +66,67 @@ void update_BPM(bool up) {
}
void gpio_callback(uint gpio, uint32_t events) {
if (gpio == ENCODER_SW_PIN) { // handle button press
} else if (gpio == ENCODER_CLK_PIN || gpio == ENCODER_DT_PIN) { // handle encoder turn
uint16_t clk_state = gpio_get(ENCODER_CLK_PIN);
uint16_t dt_state = gpio_get(ENCODER_DT_PIN);
if (clk_state != clk_last_state && clk_state == 1)
if (dt_state != clk_state) {
encoder_pos++; // Clockwise
update_BPM(1);
} else {
encoder_pos--; // Counter-clockwise
update_BPM(0);
}
updateScreen = 1;
clk_last_state = clk_state;
}
}
void setup_encoder() {
gpio_init(ENCODER_SW_PIN);
gpio_set_dir(ENCODER_SW_PIN, GPIO_IN);
gpio_pull_up(ENCODER_SW_PIN);
gpio_init(ENCODER_CLK_PIN);
gpio_set_dir(ENCODER_CLK_PIN, GPIO_IN);
gpio_pull_up(ENCODER_CLK_PIN);
gpio_init(ENCODER_DT_PIN);
gpio_set_dir(ENCODER_DT_PIN, GPIO_IN);
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, &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);
}
void setup_screen() {
i2c_init(i2c1, 400 * 1000);
gpio_set_function(SCREEN_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(SCREEN_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(SCREEN_SDA_PIN);
gpio_pull_up(SCREEN_SCL_PIN);
display = new pico_ssd1306::SSD1306(i2c1, 0x3C, pico_ssd1306::Size::W128xH64);
display->setOrientation(0);
}
void core1_entry() {
multicore_fifo_pop_blocking();
char buffer[32];
while (true) {
if (updateScreen) {
display->clear();
snprintf(buffer, sizeof(buffer), "BPM: %u", BPM);
pico_ssd1306::drawText(display, font_12x16, buffer, 0, 0);
display->sendBuffer();
updateScreen = 0;
}
display_handler.render();
}
}
void setup_outs() {
Gate* my_outputs[] = {&out1, &out2, &out3, &out4, &out5, &out6, &out7, &out8};
for (auto g : my_outputs) {
for (auto g : outputs) {
gpio_init(g->pin);
gpio_set_dir(g->pin, GPIO_OUT);
g->setLen(period_us);
}
// manual setup
out1.setDiv(8, 0);
out1.setWidth(50);
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);
}
out2.setDiv(4, 0);
out2.setWidth(50);
out3.setDiv(2, 0);
out3.setWidth(50);
void handle_outs() {
if (beatToggle) {
for (Gate* g: outputs) {
if (g->modifier != 0) {
g->turnOn();
}
}
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);
beatToggle = false;
}
for (Gate* g: outputs) {
if (g->modifier != 0) {
g->turnOff();
}
}
}
@ -187,37 +134,17 @@ int main() {
stdio_init_all();
srand(rosc_hw->randombit);
setup_screen();
display_handler.setup();
multicore_launch_core1(core1_entry);
multicore_fifo_push_blocking(1);
setup_encoder();
encoder_handler.setup();
setup_outs();
update_period();
while (true) {
if (beatToggle) {
out1.turnOn();
out2.turnOn();
out3.turnOn();
out4.turnOn();
out5.turnOn();
out6.turnOn();
out7.turnOn();
out8.turnOn();
beatToggle = false;
}
out1.turnOff();
out2.turnOff();
out3.turnOff();
out4.turnOff();
out5.turnOff();
out6.turnOff();
out7.turnOff();
out8.turnOff();
handle_outs();
}
}