diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cde0064 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.13) + +# 1. Generate the map for your Neovim clangd linter +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Include the SDK's CMake entry point +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +project(clock C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +include_directories(include) + +add_executable(clock + src/main.cpp + src/Gate.cpp +) + +# Enable USB output (useful for later printf debugging) +pico_enable_stdio_usb(clock 1) +pico_enable_stdio_uart(clock 0) + +# Pull in standard library and hardware abstraction +target_link_libraries(clock + pico_stdlib + hardware_gpio + hardware_i2c +) + +# Create the .uf2 file +pico_add_extra_outputs(clock) diff --git a/Out.h b/Out.h deleted file mode 100644 index c5e3833..0000000 --- a/Out.h +++ /dev/null @@ -1,33 +0,0 @@ -// Out.h -#ifndef Out_h -#define Out_h - -#include - -class Out { - private: - byte pin; - unsigned char state; - int cycle; - unsigned long dur; - unsigned long len; - byte width; - byte divideMode; - int div; - int modifier; - String divString; - int p; - - public: - Out(byte pin); - void turnOn(); - void turnOff(); - void setLen(unsigned long currentPeriod); - void setDiv(int newDiv, byte divide = 1); - void setWidth(int newWidth); - void setP(int prob); - int getState(); - -}; - -#endif diff --git a/include/Gate.h b/include/Gate.h new file mode 100644 index 0000000..1dedb59 --- /dev/null +++ b/include/Gate.h @@ -0,0 +1,36 @@ +// Gate.h +#ifndef Gate_h +#define Gate_h + +#include +#include + + +class Gate { + private: + bool state; + 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; + + void turnOn(); + void turnOff(); + void setLen(uint32_t currentPeriod); + void setDiv(uint16_t newDiv, uint8_t divide = 1); + void setWidth(uint16_t newWidth); + void setP(uint16_t prob); + + bool getState(); +}; + +#endif diff --git a/globals.h b/include/globals.h similarity index 86% rename from globals.h rename to include/globals.h index d2ea81a..c848cb8 100644 --- a/globals.h +++ b/include/globals.h @@ -1,12 +1,8 @@ #ifndef GLOBALS_H #define GLOBALS_H -#include +#include -extern byte BPM; -extern unsigned long minute; -extern unsigned long period; -extern byte ppqn; #endif // GLOBALS_H /* diff --git a/master_clock.ino b/master_clock.ino index 2e43ef2..c424f8f 100644 --- a/master_clock.ino +++ b/master_clock.ino @@ -45,6 +45,10 @@ unsigned long screenLastUpdate = 0; const unsigned long screenThrottle = 200; byte forceScreenUpdate = 1; +byte currentScreen = 0; +byte editMode = 0; +int mainScreenCursorPos = 0; +byte maxMainScreenPos = 8; void setup() { Serial.begin(9600); @@ -58,6 +62,7 @@ void setup() { // screen setup u8g2.begin(); + u8g2.setFontMode(1); // display.clearDisplay(); // display.setTextSize(2); @@ -150,16 +155,39 @@ void updateScreen() { if (currentTime - screenLastUpdate >= screenThrottle && forceScreenUpdate == 1) { screenLastUpdate = currentTime; forceScreenUpdate = 0; - // The firstPage()/nextPage() loop manages where the drawing commands go u8g2.firstPage(); do { - // --- Drawing Commands for the CURRENT Page --- - // All drawing commands must live INSIDE this do/while loop. - u8g2.setFont(u8g2_font_helvR08_tf); - u8g2.setCursor(0, 10); - u8g2.print(F("BPM: ")); - u8g2.print(BPM); // Example of dynamic data + // 12px - nonbold + if (mainScreenCursorPos == 0) { + u8g2.setFont(u8g2_font_7x14B_mf); + } else { + u8g2.setFont(u8g2_font_7x14_mf); + } + u8g2.setCursor(0, 15); + u8g2.print(F("BPM: ")); + u8g2.print(BPM); + + u8g2.setFont(u8g2_font_7x14_mf); + u8g2.setCursor(0, 45); + + char buffer[5]; + for (byte i = 1; i < 9; i++) { + if (i == 5) { + u8g2.setCursor(0, 60); + } + + if (mainScreenCursorPos == i) { + u8g2.setFont(u8g2_font_7x14B_mf); + } + + sprintf(buffer, "[%d] ", i); + u8g2.print(buffer); + + if (mainScreenCursorPos == i) { + u8g2.setFont(u8g2_font_7x14_mf); + } + }; } while (u8g2.nextPage()); } } @@ -186,11 +214,22 @@ void checkRPot() { if (currentPos != previousPos) { Serial.print("Encoder Position: "); Serial.println(currentPos); - - if (currentPos > previousPos) { - BPM++; - } else { - BPM--; + + // on home screen + if (currentScreen == 0) { + if (editMode == 0) { + if (currentPos > previousPos) { + mainScreenCursorPos++; + if (mainScreenCursorPos > maxMainScreenPos) { + mainScreenCursorPos = 0; + } + } else { + mainScreenCursorPos--; + if (mainScreenCursorPos < 0) { + mainScreenCursorPos = maxMainScreenPos; + } + } + } } forceScreenUpdate = 1; @@ -202,6 +241,17 @@ void checkRPot() { } } +void handleBPMChange(byte increase) { + if (increase == 1) { + BPM++; + } else { + BPM--; + } + + period = (minute / BPM) / ppqn; + Timer1.setPeriod(period); +} + void updateEncoder() { // The ISR should be as short and fast as possible // Check the state of the DT pin to determine direction diff --git a/Out.cpp b/src/Gate.cpp similarity index 54% rename from Out.cpp rename to src/Gate.cpp index 33beeb2..432ac49 100644 --- a/Out.cpp +++ b/src/Gate.cpp @@ -1,11 +1,13 @@ -// Out.cpp -#include "Arduino.h" -#include "Out.h" +// Gate.cpp +#include "pico/stdlib.h" +#include "Gate.h" #include "globals.h" +#include +#include -Out::Out(byte pin) { +Gate::Gate(uint8_t pin) { this->pin = pin; - state = LOW; + state = 0; divideMode = 1; // 1 divison | 0 multiplication modifier = 1; // divide mode modifier (4x, /32, etc) @@ -18,58 +20,55 @@ Out::Out(byte pin) { len = 0; // max len a pulse can be on, as determined by width p = 100; // probability of a pulse - - - pinMode(pin, OUTPUT); } -int Out::getState() { +bool Gate::getState() { return state; } -void Out::setLen(unsigned long currentPeriod) { - len = (unsigned long)((double)currentPeriod * (width / 100.0) / 1000.0); +void Gate::setLen(uint32_t currentPeriod) { + len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0); } -void Out::setDiv(int modifier, byte divide = 1) { +void Gate::setDiv(uint16_t modifier, uint8_t divide) { if (divide == 1) { div = ppqn * modifier; - divString = "/" + modifier; + divString = "/" + std::to_string(modifier); } else { div = ppqn / modifier; - divString = "x" + modifier; + divString = "x" + std::to_string(modifier); } divideMode = divide; this->modifier = modifier; }; -void Out::setWidth(int newWidth) { +void Gate::setWidth(uint16_t newWidth) { width = newWidth; if (divideMode == 1) { - len = (unsigned long)((double)(minute / BPM) * (width / 100.0) / 1000.0); + len = (uint32_t)((double)(minute / BPM) * (width / 100.0) / 1000.0); } else { - len = (unsigned long)((double)(minute / BPM / modifier) * (width / 100.0) / 1000.0); + len = (uint32_t)((double)(minute / BPM / modifier) * (width / 100.0) / 1000.0); } }; -void Out::setP(int prob) { +void Gate::setP(uint16_t prob) { this->p = prob; } -void Out::turnOn() { +void Gate::turnOn() { cycle += 1; - byte pRes = 1; + uint8_t pRes = 1; if (cycle == div) { if (p < 100) { - long r = random(1, 101); + uint32_t r = (rand() % 100) + 1; if (r > p) { pRes = 0; } } if (pRes == 1) { - state = HIGH; + state = 1; digitalWrite(pin, state); dur = millis(); } @@ -77,9 +76,9 @@ void Out::turnOn() { }; } -void Out::turnOff() { - if (state == HIGH && millis() - dur >= len) { - state = LOW; +void Gate::turnOff() { + if (state == 1 && millis() - dur >= len) { + state = 0; digitalWrite(pin, state); dur = 0; }; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f6f1451 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,96 @@ +#include +#include "hardware/structs/rosc.h" +#include "pico/stdlib.h" +#include "pico/time.h" +#include + +#include "globals.h" +#include "Gate.h" + +static constexpr uint8_t OUT_1_PIN = 0; +static constexpr uint8_t OUT_2_PIN = 2; +static constexpr uint8_t OUT_3_PIN = 4; +static constexpr uint8_t OUT_4_PIN = 6; +static constexpr uint8_t OUT_5_PIN = 8; +static constexpr uint8_t OUT_6_PIN = 10; +static constexpr uint8_t OUT_7_PIN = 12; +static constexpr uint8_t OUT_8_PIN = 14; + +static constexpr uint8_t SCREEN_SCL_PIN = 18; +static constexpr uint8_t SCREEN_SDA_PIN = 19; + +static constexpr uint8_t ENCODER_CLK_PIN = 20; +static constexpr uint8_t ENCODER_DT_PIN = 21; +static constexpr uint8_t ENCODER_SW_PIN = 22; + +volatile uint8_t PLAY = 1; +volatile uint8_t BPM = 100; + +static constexpr uint32_t MINUTE_US = 60000000; +static constexpr uint8_t PPQN = 96; +volatile uint32_t period_us = 0; + +struct repeating_timer bpm_timer; + +volatile bool beatToggle = false; + +Gate out1(OUT_1_PIN); +Gate out2(OUT_2_PIN); +Gate out3(OUT_3_PIN); +Gate out4(OUT_4_PIN); +Gate out5(OUT_5_PIN); +Gate out6(OUT_6_PIN); +Gate out7(OUT_7_PIN); +Gate out8(OUT_8_PIN); + + +bool timer_callback(struct repeating_timer *t) { + if (PLAY == 1) { + beatToggle = true; + } + return true; +} + + +void init_timer(uint32_t period_us) { + cancel_repeating_timer(&bpm_timer); + add_repeating_timer_us(-(int64_t)period_us, timer_callback, NULL, &bpm_timer); +} + + +void update_period() { + period_us = (uint32_t)(MINUTE_US / (uint32_t)BPM / PPQN); + init_timer(period_us); +} + + +int main() { + stdio_init_all(); + + srand(rosc_hw->randombit); + + gpio_init(out1.pin); + gpio_init(out2.pin); + gpio_init(out3.pin); + gpio_init(out4.pin); + gpio_init(out5.pin); + gpio_init(out6.pin); + gpio_init(out7.pin); + gpio_init(out8.pin); + + gpio_set_dir(out1.pin, GPIO_OUT); + gpio_set_dir(out2.pin, GPIO_OUT); + gpio_set_dir(out3.pin, GPIO_OUT); + gpio_set_dir(out4.pin, GPIO_OUT); + gpio_set_dir(out5.pin, GPIO_OUT); + gpio_set_dir(out6.pin, GPIO_OUT); + gpio_set_dir(out7.pin, GPIO_OUT); + gpio_set_dir(out8.pin, GPIO_OUT); + + update_period(); + + while (true) { + + } + +}