From 53419fa89cb311dc13ef0e5d7717e91923cde5a4 Mon Sep 17 00:00:00 2001 From: doctea Date: Wed, 31 Jan 2024 20:59:16 +0000 Subject: [PATCH 01/13] initial working proof-of-concept of rp2040 support --- .../RP2040ClockBlink/RP2040ClockBlink.ino | 92 ++++++++++++++++++ .../RP2040UsbMasterMidiClock.ino | 96 +++++++++++++++++++ src/platforms/esp32.h | 1 + src/platforms/rp2040.h | 57 +++++++++++ src/uClock.cpp | 6 ++ 5 files changed, 252 insertions(+) create mode 100644 examples/RP2040ClockBlink/RP2040ClockBlink.ino create mode 100644 examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino create mode 100644 src/platforms/rp2040.h diff --git a/examples/RP2040ClockBlink/RP2040ClockBlink.ino b/examples/RP2040ClockBlink/RP2040ClockBlink.ino new file mode 100644 index 0000000..2a6dc0c --- /dev/null +++ b/examples/RP2040ClockBlink/RP2040ClockBlink.ino @@ -0,0 +1,92 @@ +/* USB MIDI Sync Box - RP2040 example that just blinks LED + * + * + * This example code is in the public domain. + * + */ + +//#define LED_BUILTIN PIN_LED_B + +#include "Adafruit_TinyUSB.h" + +#include + +uint8_t bpm_blink_timer = 1; +void handle_bpm_led(uint32_t tick) +{ + // BPM led indicator + if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer + bpm_blink_timer = 8; + digitalWrite(LED_BUILTIN, LOW); + } else if ( !(tick % (24)) ) { // each quarter led on + bpm_blink_timer = 1; + digitalWrite(LED_BUILTIN, LOW); + } else if ( !(tick % bpm_blink_timer) ) { // get led off + digitalWrite(LED_BUILTIN, HIGH); + } +} + + +// Internal clock handlers +void onSync24Callback(uint32_t tick) { + handle_bpm_led(tick); +} + +void onClockStart() { + //MIDI_USB.sendRealTime(midi::Start); +} + +void onClockStop() { + //MIDI_USB.sendRealTime(midi::Stop); +} + +void setup() { +/*#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) + // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040 + TinyUSB_Device_Init(0); +#endif*/ + + //MIDI_USB.begin(MIDI_CHANNEL_OMNI); + + // A led to count bpms + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + + + Serial.begin(115200); + /*while (!Serial) + delay(1);*/ + + // Setup our clock system + + // Inits the clock + uClock.init(); + // Set the callback function for the clock output to send MIDI Sync message. + uClock.setOnSync24(onSync24Callback); + // Set the callback function for MIDI Start and Stop messages. + uClock.setOnClockStart(onClockStart); + uClock.setOnClockStop(onClockStop); + // Set the clock BPM to 126 BPM + uClock.setTempo(60); + // Starts the clock, tick-tac-tick-tac.. + //Serial.println("about to uClock.start()..."); Serial.flush(); + uClock.start(); + //Serial.println("uClock.start()ed!"); Serial.flush(); +} + +uint32_t count = 0; + +// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... +void loop() { + //MIDI_USB.read(); + //count++; + //if (millis()%1000==0) + // Serial.println("looped!"); +} diff --git a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino b/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino new file mode 100644 index 0000000..cf1092a --- /dev/null +++ b/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino @@ -0,0 +1,96 @@ +/* USB MIDI Sync Box - example that also prints to serial and sends USB midi as well as blinking LED + * (usb part still needs testing to make sure it works!) + * + * This example code is in the public domain. + * + */ +#include +#include + +Adafruit_USBD_MIDI usb_midi; +MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); + +//#define LED_BUILTIN PIN_LED_B + +//MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); +#include + +uint8_t bpm_blink_timer = 1; +void handle_bpm_led(uint32_t tick) +{ + // BPM led indicator + if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer + bpm_blink_timer = 8; + digitalWrite(LED_BUILTIN, LOW); + } else if ( !(tick % (24)) ) { // each quarter led on + bpm_blink_timer = 1; + digitalWrite(LED_BUILTIN, LOW); + } else if ( !(tick % bpm_blink_timer) ) { // get led off + digitalWrite(LED_BUILTIN, HIGH); + } +} + + +// Internal clock handlers +void onSync24Callback(uint32_t tick) { + // Send MIDI_CLOCK to external gears + MIDI_USB.sendRealTime(midi::Clock); + handle_bpm_led(tick); +} + +void onClockStart() { + MIDI_USB.sendRealTime(midi::Start); +} + +void onClockStop() { + MIDI_USB.sendRealTime(midi::Stop); +} + +void setup() { +#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) + // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040 + TinyUSB_Device_Init(0); +#endif + + MIDI_USB.begin(MIDI_CHANNEL_OMNI); + + // A led to count bpms + pinMode(LED_BUILTIN, OUTPUT); + + Serial.begin(115200); + while (!Serial) + delay(1); + + // wait until device mounted + /*while( !TinyUSBDevice.mounted() ) { + Serial.println("waiting for usb.."); + Serial.flush(); + delay(1); + }*/ + + // Setup our clock system + // Inits the clock + Serial.println("about to uClock.init()..."); Serial.flush(); + uClock.init(); + // Set the callback function for the clock output to send MIDI Sync message. + uClock.setOnSync24(onSync24Callback); + // Set the callback function for MIDI Start and Stop messages. + uClock.setOnClockStart(onClockStart); + uClock.setOnClockStop(onClockStop); + // Set the clock BPM to 126 BPM + uClock.setTempo(126); + // Starts the clock, tick-tac-tick-tac.. + Serial.println("about to uClock.start()..."); Serial.flush(); + uClock.start(); + Serial.println("uClock.start()ed!"); Serial.flush(); +} + +uint32_t count = 0; + +// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... +void loop() { + MIDI_USB.read(); + count++; + if (millis()%1000==0) + Serial.println("looped!!!"); +} diff --git a/src/platforms/esp32.h b/src/platforms/esp32.h index 1344923..22e80b0 100644 --- a/src/platforms/esp32.h +++ b/src/platforms/esp32.h @@ -2,6 +2,7 @@ #include #include +// esp32-specific timer #define TIMER_ID 0 hw_timer_t * _uclockTimer = NULL; // mutex control for ISR diff --git a/src/platforms/rp2040.h b/src/platforms/rp2040.h new file mode 100644 index 0000000..1c5a37d --- /dev/null +++ b/src/platforms/rp2040.h @@ -0,0 +1,57 @@ +#include +#include "FreeRTOS.h" +#include +#include +#include "pico/sync.h" + +// RPi-specific timer +struct repeating_timer timer; + +// FreeRTOS main clock task size in bytes +#define CLOCK_STACK_SIZE 5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size +TaskHandle_t taskHandle; +// mutex to protect the shared resource +SemaphoreHandle_t _mutex; +// mutex control for task +#define ATOMIC(X) xSemaphoreTake(_mutex, portMAX_DELAY); X; xSemaphoreGive(_mutex); + +// forward declaration of uClockHandler +void uClockHandler(); + +// ISR handler -- called when tick happens +bool handlerISR(repeating_timer *timer) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // Send a notification to task1 + vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + + return true; +} + +// task for user clock process +void clockTask(void *pvParameters) +{ + while (1) { + // wait for a notification from ISR + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + uClockHandler(); + } +} + +void initTimer(uint32_t init_clock) +{ + // initialize the mutex for shared resource access + _mutex = xSemaphoreCreateMutex(); + + // create the clockTask + xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); + + // set up RPi interrupt timer + add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); +} + +void setTimer(uint32_t us_interval) { + cancel_repeating_timer(&timer); + add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); +} \ No newline at end of file diff --git a/src/uClock.cpp b/src/uClock.cpp index 3ad2473..18c6883 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -57,6 +57,12 @@ #if defined(ARDUINO_ARCH_STM32) #include "platforms/stm32.h" #endif +// +// RP2040 (Raspberry Pico) family +// +#if defined(ARDUINO_ARCH_RP2040) + #include "platforms/rp2040.h" +#endif // // Platform specific timer setup/control From 99d18452016981fa8c8993ca6dca468705782325 Mon Sep 17 00:00:00 2001 From: doctea Date: Thu, 1 Feb 2024 23:13:40 +0000 Subject: [PATCH 02/13] use interrupts instead of FreeRTOS tasks -- seems to work with both cores? --- .../RP2040ClockBlink/RP2040ClockBlink.ino | 8 +- .../RP2040UsbMasterMidiClock.ino | 34 ++++- src/platforms/rp2040.h | 137 ++++++++++++------ 3 files changed, 120 insertions(+), 59 deletions(-) diff --git a/examples/RP2040ClockBlink/RP2040ClockBlink.ino b/examples/RP2040ClockBlink/RP2040ClockBlink.ino index 2a6dc0c..63c0650 100644 --- a/examples/RP2040ClockBlink/RP2040ClockBlink.ino +++ b/examples/RP2040ClockBlink/RP2040ClockBlink.ino @@ -11,6 +11,8 @@ #include +#define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } + uint8_t bpm_blink_timer = 1; void handle_bpm_led(uint32_t tick) { @@ -50,14 +52,14 @@ void setup() { // A led to count bpms pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, HIGH); + /*digitalWrite(LED_BUILTIN, HIGH); delay(500); digitalWrite(LED_BUILTIN, LOW); delay(500); digitalWrite(LED_BUILTIN, HIGH); delay(500); digitalWrite(LED_BUILTIN, LOW); - delay(500); + delay(500);*/ Serial.begin(115200); @@ -88,5 +90,5 @@ void loop() { //MIDI_USB.read(); //count++; //if (millis()%1000==0) - // Serial.println("looped!"); + // ATOMIC(Serial.println("looped!")); } diff --git a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino b/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino index cf1092a..f3445c0 100644 --- a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino +++ b/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino @@ -9,11 +9,16 @@ Adafruit_USBD_MIDI usb_midi; MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); +//MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); + +#include +#define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } //#define LED_BUILTIN PIN_LED_B +//#define WAIT_FOR_SERIAL +#define ENABLE_MULTICORE -//MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); -#include +volatile uint32_t count = 0; uint8_t bpm_blink_timer = 1; void handle_bpm_led(uint32_t tick) @@ -36,6 +41,8 @@ void onSync24Callback(uint32_t tick) { // Send MIDI_CLOCK to external gears MIDI_USB.sendRealTime(midi::Clock); handle_bpm_led(tick); + + Serial.printf("ticked with %u\n", tick); } void onClockStart() { @@ -46,6 +53,16 @@ void onClockStop() { MIDI_USB.sendRealTime(midi::Stop); } +#ifdef ENABLE_MULTICORE + void setup1() { + } + + void loop1() { + if (count%1000==0) + ATOMIC(Serial.println("loop1()!"); Serial.flush()); + } +#endif + void setup() { #if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040 @@ -58,8 +75,10 @@ void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); - while (!Serial) - delay(1); + #ifdef WAIT_FOR_SERIAL + while (!Serial) + delay(1); + #endif // wait until device mounted /*while( !TinyUSBDevice.mounted() ) { @@ -78,19 +97,18 @@ void setup() { uClock.setOnClockStart(onClockStart); uClock.setOnClockStop(onClockStop); // Set the clock BPM to 126 BPM - uClock.setTempo(126); + uClock.setTempo(60); // Starts the clock, tick-tac-tick-tac.. Serial.println("about to uClock.start()..."); Serial.flush(); uClock.start(); - Serial.println("uClock.start()ed!"); Serial.flush(); + ATOMIC(Serial.println("uClock.start()ed!"); Serial.flush();) } -uint32_t count = 0; // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... void loop() { MIDI_USB.read(); count++; if (millis()%1000==0) - Serial.println("looped!!!"); + ATOMIC(Serial.println("loop()!"); Serial.flush();); } diff --git a/src/platforms/rp2040.h b/src/platforms/rp2040.h index 1c5a37d..af94e12 100644 --- a/src/platforms/rp2040.h +++ b/src/platforms/rp2040.h @@ -1,57 +1,98 @@ #include -#include "FreeRTOS.h" -#include -#include #include "pico/sync.h" -// RPi-specific timer -struct repeating_timer timer; - -// FreeRTOS main clock task size in bytes -#define CLOCK_STACK_SIZE 5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size -TaskHandle_t taskHandle; -// mutex to protect the shared resource -SemaphoreHandle_t _mutex; -// mutex control for task -#define ATOMIC(X) xSemaphoreTake(_mutex, portMAX_DELAY); X; xSemaphoreGive(_mutex); - -// forward declaration of uClockHandler -void uClockHandler(); - -// ISR handler -- called when tick happens -bool handlerISR(repeating_timer *timer) -{ - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - // Send a notification to task1 - vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - - return true; -} - -// task for user clock process -void clockTask(void *pvParameters) -{ - while (1) { - // wait for a notification from ISR - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); +#define MULTICORE + +#ifdef MULTICORE + // use interrupt version -- works for 2 cores ie can run loop1() and loop() simultaneously as well as the clock callback? + + // RPi-specific timer + struct repeating_timer timer; + + #define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } + + // forward declaration of uClockHandler + void uClockHandler(); + + // ISR handler -- called when tick happens + bool handlerISR(repeating_timer *timer) + { uClockHandler(); + + return true; + } + + void initTimer(uint32_t init_clock) + { + // set up RPi interrupt timer + // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); + } + + void setTimer(uint32_t us_interval) { + cancel_repeating_timer(&timer); + // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); + } +#else + // use FreeRTOS scheduling/mutex version -- doesn't work (task starts but does not run) if using loop1() ie core 2 + + #include "FreeRTOS.h" + #include + #include + + // RPi-specific timer + struct repeating_timer timer; + + // FreeRTOS main clock task size in bytes + #define CLOCK_STACK_SIZE 5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size + TaskHandle_t taskHandle; + // mutex to protect the shared resource + SemaphoreHandle_t _mutex; + // mutex control for task + #define ATOMIC(X) xSemaphoreTake(_mutex, portMAX_DELAY); X; xSemaphoreGive(_mutex); + + // forward declaration of uClockHandler + void uClockHandler(); + + // ISR handler -- called when tick happens + bool handlerISR(repeating_timer *timer) + { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + // Send a notification to task1 + vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken); + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); + + return true; } -} -void initTimer(uint32_t init_clock) -{ - // initialize the mutex for shared resource access - _mutex = xSemaphoreCreateMutex(); + // task for user clock process + void clockTask(void *pvParameters) + { + while (1) { + // wait for a notification from ISR + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + uClockHandler(); + } + } + + void initTimer(uint32_t init_clock) + { + // initialize the mutex for shared resource access + _mutex = xSemaphoreCreateMutex(); - // create the clockTask - xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); + // create the clockTask + xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); - // set up RPi interrupt timer - add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); -} + // set up RPi interrupt timer + // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); + } + + void setTimer(uint32_t us_interval) { + cancel_repeating_timer(&timer); + // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); + } -void setTimer(uint32_t us_interval) { - cancel_repeating_timer(&timer); - add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); -} \ No newline at end of file +#endif \ No newline at end of file From fadff608037bd9db3e2bd2f5f32cdf9654cfbc54 Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 3 Feb 2024 19:49:12 -0300 Subject: [PATCH 03/13] Update RP2040UsbMasterMidiClock.ino remove usb read from loop since its a clock sync only example. --- .../RP2040UsbMasterMidiClock.ino | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino b/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino index cf1092a..c579d76 100644 --- a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino +++ b/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino @@ -10,8 +10,6 @@ Adafruit_USBD_MIDI usb_midi; MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); -//#define LED_BUILTIN PIN_LED_B - //MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); #include @@ -62,11 +60,11 @@ void setup() { delay(1); // wait until device mounted - /*while( !TinyUSBDevice.mounted() ) { + while( !TinyUSBDevice.mounted() ) { Serial.println("waiting for usb.."); Serial.flush(); delay(1); - }*/ + } // Setup our clock system // Inits the clock @@ -85,12 +83,6 @@ void setup() { Serial.println("uClock.start()ed!"); Serial.flush(); } -uint32_t count = 0; - // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... void loop() { - MIDI_USB.read(); - count++; - if (millis()%1000==0) - Serial.println("looped!!!"); } From bb68563c55d9af312a9179a0bb422e957e5c4bab Mon Sep 17 00:00:00 2001 From: doctea Date: Sun, 4 Feb 2024 21:42:22 +0000 Subject: [PATCH 04/13] update comment only --- src/platforms/rp2040.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platforms/rp2040.h b/src/platforms/rp2040.h index af94e12..fa6b69e 100644 --- a/src/platforms/rp2040.h +++ b/src/platforms/rp2040.h @@ -31,7 +31,7 @@ void setTimer(uint32_t us_interval) { cancel_repeating_timer(&timer); - // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + // todo: actually should be -us_interval so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); } #else @@ -91,7 +91,7 @@ void setTimer(uint32_t us_interval) { cancel_repeating_timer(&timer); - // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + // todo: actually should be -us_interval so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); } From 98cb3335f4602760c90d60ef1f889a9abe494175 Mon Sep 17 00:00:00 2001 From: doctea Date: Sun, 4 Feb 2024 21:56:03 +0000 Subject: [PATCH 05/13] updated README to mention RP2040 experimental report and current known problems with it. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 68115fd..ec05509 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interruption. It is designed to be multi-architecture, portable, and easy to use within the Opensource community universe. -We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, and Seedstudio XIAO M0)**. +We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, and Seedstudio XIAO M0)**. It has experimental support for **RP2040 boards (Raspberry Pico, Seeed XIAO RP2040)** (see notes). The absence of real-time features necessary for creating professional-level embedded devices for music and video on Opensource community-based platforms like Arduino led to the development of uClock. By leveraging the use of timer hardware interruptions, the library can schedule and manage real-time-like processing with safe shared resource access through its API. @@ -373,3 +373,11 @@ void loop() //processYourPots(); } ``` + +## Known problems + +### RP2040 support + +- Doing a 'soft reboot' (eg from reflashing) seems to crash on startup, but starting from cold and powering on works fine. +- Using FreeRTOS multithreading fails if the second core is used (via setup1() and loop1()) - using the 'interrupts-based' version of the RP2040 uClock support seems to solve this. +- Tick ticking may be off due to repeating_timer following from the end of previous tick, rather than following the start of the previous tick. From 08e68385caf304d8e3efc4f4f501154882b547e4 Mon Sep 17 00:00:00 2001 From: doctea Date: Sun, 4 Feb 2024 22:55:57 +0000 Subject: [PATCH 06/13] updated readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ec05509..72e322a 100755 --- a/README.md +++ b/README.md @@ -378,6 +378,7 @@ void loop() ### RP2040 support +- Uses the [earlephilhower core](https://github.com/earlephilhower/arduino-pico) - Doing a 'soft reboot' (eg from reflashing) seems to crash on startup, but starting from cold and powering on works fine. - Using FreeRTOS multithreading fails if the second core is used (via setup1() and loop1()) - using the 'interrupts-based' version of the RP2040 uClock support seems to solve this. - Tick ticking may be off due to repeating_timer following from the end of previous tick, rather than following the start of the previous tick. From 6a8451f8d9be02204bb8a61d097c8d02f666eb1c Mon Sep 17 00:00:00 2001 From: doctea Date: Fri, 1 Mar 2024 22:44:08 +0000 Subject: [PATCH 07/13] add rp2040 to library.properties; minor comment --- library.properties | 2 +- src/platforms/rp2040.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library.properties b/library.properties index fa4b2e8..69b5647 100755 --- a/library.properties +++ b/library.properties @@ -6,5 +6,5 @@ sentence=BPM clock generator for Arduino platform. paragraph=A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, Seedstudio XIAO M0. ESP32 and STM32) category=Timing url=https://github.com/midilab/uClock -architectures=avr,arm,samd,stm32,esp32 +architectures=avr,arm,samd,stm32,esp32,rp2040 includes=uClock.h diff --git a/src/platforms/rp2040.h b/src/platforms/rp2040.h index fa6b69e..7cf1069 100644 --- a/src/platforms/rp2040.h +++ b/src/platforms/rp2040.h @@ -1,6 +1,7 @@ #include #include "pico/sync.h" +// todo: make this a build flag, so user can choose which method to use? #define MULTICORE #ifdef MULTICORE @@ -22,8 +23,7 @@ return true; } - void initTimer(uint32_t init_clock) - { + void initTimer(uint32_t init_clock) { // set up RPi interrupt timer // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); From e41fb3157a9d9b0fe13f1f86f6ba9fed06592d00 Mon Sep 17 00:00:00 2001 From: midilab Date: Thu, 11 Apr 2024 11:10:05 -0300 Subject: [PATCH 08/13] update version to 2.1.0: rpi2040 support --- library.json | 6 +++--- library.properties | 4 ++-- src/uClock.cpp | 4 ++-- src/uClock.h | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library.json b/library.json index e64bcf2..1952300 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "uClock", - "version": "2.0.0", - "description": "A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, Seedstudio XIAO M0. ESP32 and STM32)", + "version": "2.1.0", + "description": "A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32)", "keywords": "bpm, clock, timing, tick, music, generator", "repository": { @@ -22,5 +22,5 @@ "headers": "uClock.h", "dependencies": {}, "frameworks": "Arduino", - "platforms": "atmelavr,atmelmegaavr,espressif32,ststm32,teensy,atmelsam" + "platforms": "atmelavr,atmelmegaavr,espressif32,ststm32,teensy,atmelsam,raspberrypi" } diff --git a/library.properties b/library.properties index 69b5647..c03f701 100755 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=uClock -version=2.0.0 +version=2.1.0 author=Romulo Silva maintainer=Romulo Silva sentence=BPM clock generator for Arduino platform. -paragraph=A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, Seedstudio XIAO M0. ESP32 and STM32) +paragraph=A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) category=Timing url=https://github.com/midilab/uClock architectures=avr,arm,samd,stm32,esp32,rp2040 diff --git a/src/uClock.cpp b/src/uClock.cpp index 18c6883..8196f49 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -1,8 +1,8 @@ /*! * @file uClock.cpp * Project BPM clock generator for Arduino - * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.0.0 + * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) + * @version 2.1.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co diff --git a/src/uClock.h b/src/uClock.h index 722b385..9b438c2 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -1,8 +1,8 @@ /*! * @file uClock.h * Project BPM clock generator for Arduino - * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.0.0 + * @brief A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) + * @version 2.1.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co From 9d8865b024ac83abe7652292d7a4205caa0f449c Mon Sep 17 00:00:00 2001 From: midilab Date: Thu, 11 Apr 2024 11:10:47 -0300 Subject: [PATCH 09/13] remove freertos support(unstable so far) and make interrupted version the default only. update main example --- .../RP2040ClockBlink/RP2040ClockBlink.ino | 94 --------------- .../RP2040UsbUartMasterClock.ino} | 76 +++++------- .../RP2040UsbUartMasterClock/builtin_led.ino | 37 ++++++ src/platforms/rp2040.h | 110 ++++-------------- 4 files changed, 84 insertions(+), 233 deletions(-) delete mode 100644 examples/RP2040ClockBlink/RP2040ClockBlink.ino rename examples/{RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino => RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino} (52%) create mode 100644 examples/RP2040UsbUartMasterClock/builtin_led.ino diff --git a/examples/RP2040ClockBlink/RP2040ClockBlink.ino b/examples/RP2040ClockBlink/RP2040ClockBlink.ino deleted file mode 100644 index 63c0650..0000000 --- a/examples/RP2040ClockBlink/RP2040ClockBlink.ino +++ /dev/null @@ -1,94 +0,0 @@ -/* USB MIDI Sync Box - RP2040 example that just blinks LED - * - * - * This example code is in the public domain. - * - */ - -//#define LED_BUILTIN PIN_LED_B - -#include "Adafruit_TinyUSB.h" - -#include - -#define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } - -uint8_t bpm_blink_timer = 1; -void handle_bpm_led(uint32_t tick) -{ - // BPM led indicator - if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer - bpm_blink_timer = 8; - digitalWrite(LED_BUILTIN, LOW); - } else if ( !(tick % (24)) ) { // each quarter led on - bpm_blink_timer = 1; - digitalWrite(LED_BUILTIN, LOW); - } else if ( !(tick % bpm_blink_timer) ) { // get led off - digitalWrite(LED_BUILTIN, HIGH); - } -} - - -// Internal clock handlers -void onSync24Callback(uint32_t tick) { - handle_bpm_led(tick); -} - -void onClockStart() { - //MIDI_USB.sendRealTime(midi::Start); -} - -void onClockStop() { - //MIDI_USB.sendRealTime(midi::Stop); -} - -void setup() { -/*#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) - // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040 - TinyUSB_Device_Init(0); -#endif*/ - - //MIDI_USB.begin(MIDI_CHANNEL_OMNI); - - // A led to count bpms - pinMode(LED_BUILTIN, OUTPUT); - /*digitalWrite(LED_BUILTIN, HIGH); - delay(500); - digitalWrite(LED_BUILTIN, LOW); - delay(500); - digitalWrite(LED_BUILTIN, HIGH); - delay(500); - digitalWrite(LED_BUILTIN, LOW); - delay(500);*/ - - - Serial.begin(115200); - /*while (!Serial) - delay(1);*/ - - // Setup our clock system - - // Inits the clock - uClock.init(); - // Set the callback function for the clock output to send MIDI Sync message. - uClock.setOnSync24(onSync24Callback); - // Set the callback function for MIDI Start and Stop messages. - uClock.setOnClockStart(onClockStart); - uClock.setOnClockStop(onClockStop); - // Set the clock BPM to 126 BPM - uClock.setTempo(60); - // Starts the clock, tick-tac-tick-tac.. - //Serial.println("about to uClock.start()..."); Serial.flush(); - uClock.start(); - //Serial.println("uClock.start()ed!"); Serial.flush(); -} - -uint32_t count = 0; - -// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... -void loop() { - //MIDI_USB.read(); - //count++; - //if (millis()%1000==0) - // ATOMIC(Serial.println("looped!")); -} diff --git a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino b/examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino similarity index 52% rename from examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino rename to examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino index 666bd38..e571da9 100644 --- a/examples/RP2040UsbMasterMidiClock/RP2040UsbMasterMidiClock.ino +++ b/examples/RP2040UsbUartMasterClock/RP2040UsbUartMasterClock.ino @@ -1,95 +1,74 @@ -/* USB MIDI Sync Box - example that also prints to serial and sends USB midi as well as blinking LED - * (usb part still needs testing to make sure it works!) +/* + * USB/Uart MIDI Sync Box * * This example code is in the public domain. * */ + #include #include -Adafruit_USBD_MIDI usb_midi; -MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); -//MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); - #include -#define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } -//#define LED_BUILTIN PIN_LED_B -//#define WAIT_FOR_SERIAL -#define ENABLE_MULTICORE +// Instantiate the MIDI interfaces +Adafruit_USBD_MIDI usb_midi; +MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI_USB); +MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); -volatile uint32_t count = 0; +// Do your rpi 2040 has a ws2812 RGB LED? set the pin! +// otherwise keep it commented for normal LED_BUILTIN led blinking +#define WS2812_BUILTIN_LED 16 uint8_t bpm_blink_timer = 1; void handle_bpm_led(uint32_t tick) { // BPM led indicator - if ( !(tick % (96)) || (tick == 1) ) { // first compass step will flash longer + if ( !(tick % (96)) || (tick == 1) ) { // first of 4 quarter pulse will flash longer bpm_blink_timer = 8; - digitalWrite(LED_BUILTIN, LOW); + ledOn(); } else if ( !(tick % (24)) ) { // each quarter led on bpm_blink_timer = 1; - digitalWrite(LED_BUILTIN, LOW); + ledOn(); } else if ( !(tick % bpm_blink_timer) ) { // get led off - digitalWrite(LED_BUILTIN, HIGH); + ledOff(); } } - // Internal clock handlers void onSync24Callback(uint32_t tick) { // Send MIDI_CLOCK to external gears + MIDI.sendRealTime(midi::Clock); MIDI_USB.sendRealTime(midi::Clock); + // blink tempo handle_bpm_led(tick); - - Serial.printf("ticked with %u\n", tick); } void onClockStart() { + MIDI.sendRealTime(midi::Start); MIDI_USB.sendRealTime(midi::Start); } void onClockStop() { + MIDI.sendRealTime(midi::Stop); MIDI_USB.sendRealTime(midi::Stop); } -#ifdef ENABLE_MULTICORE - void setup1() { - } - - void loop1() { - if (count%1000==0) - ATOMIC(Serial.println("loop1()!"); Serial.flush()); - } -#endif - void setup() { #if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040) // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040 TinyUSB_Device_Init(0); #endif + // Initialize USB midi stack MIDI_USB.begin(MIDI_CHANNEL_OMNI); + // Initialize UART midi stack + MIDI.begin(MIDI_CHANNEL_OMNI); - // A led to count bpms - pinMode(LED_BUILTIN, OUTPUT); - - Serial.begin(115200); - #ifdef WAIT_FOR_SERIAL - while (!Serial) - delay(1); - #endif - - // wait until device mounted - while( !TinyUSBDevice.mounted() ) { - Serial.println("waiting for usb.."); - Serial.flush(); - delay(1); - } + // Initialize builtin led for clock timer blinking + initBlinkLed(); // Setup our clock system // Inits the clock - Serial.println("about to uClock.init()..."); Serial.flush(); uClock.init(); // Set the callback function for the clock output to send MIDI Sync message. uClock.setOnSync24(onSync24Callback); @@ -97,17 +76,14 @@ void setup() { uClock.setOnClockStart(onClockStart); uClock.setOnClockStop(onClockStop); // Set the clock BPM to 126 BPM - uClock.setTempo(60); + uClock.setTempo(126); // Starts the clock, tick-tac-tick-tac.. - Serial.println("about to uClock.start()..."); Serial.flush(); uClock.start(); - ATOMIC(Serial.println("uClock.start()ed!"); Serial.flush();) } // Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... void loop() { + // handle midi input? + MIDI.read(); MIDI_USB.read(); - count++; - if (millis()%1000==0) - ATOMIC(Serial.println("loop()!"); Serial.flush();); } diff --git a/examples/RP2040UsbUartMasterClock/builtin_led.ino b/examples/RP2040UsbUartMasterClock/builtin_led.ino new file mode 100644 index 0000000..7a1eedf --- /dev/null +++ b/examples/RP2040UsbUartMasterClock/builtin_led.ino @@ -0,0 +1,37 @@ +#if defined(WS2812_BUILTIN_LED) +#include +#define NUMPIXELS 1 +Adafruit_NeoPixel pixels(NUMPIXELS, WS2812_BUILTIN_LED, NEO_GRB + NEO_KHZ800); +#endif + +// check the pinage for BUILTIN LED of your model in case LED_BUILTIN wont ligth up +// this is valid only if you're not using rgb version ws2812 (WS2812_BUILTIN_LED) +//#define LED_BUILTIN PIN_LED_B + +void initBlinkLed() { +#if defined(WS2812_BUILTIN_LED) + // use adafruit neo pixel + pixels.begin(); +#else + // normal led pin + pinMode(LED_BUILTIN, OUTPUT); +#endif +} + +void ledOn() { +#if defined(WS2812_BUILTIN_LED) + pixels.setPixelColor(0, pixels.Color(0, 0, 20)); + pixels.show(); // turn the LED on (HIGH is the voltage level) +#else + digitalWrite(LED_BUILTIN, LOW); +#endif +} + +void ledOff() { +#if defined(WS2812_BUILTIN_LED) + pixels.setPixelColor(0, pixels.Color(0, 0, 0)); + pixels.show(); +#else + digitalWrite(LED_BUILTIN, HIGH); +#endif +} \ No newline at end of file diff --git a/src/platforms/rp2040.h b/src/platforms/rp2040.h index 7cf1069..eb918c2 100644 --- a/src/platforms/rp2040.h +++ b/src/platforms/rp2040.h @@ -1,98 +1,30 @@ #include #include "pico/sync.h" -// todo: make this a build flag, so user can choose which method to use? -#define MULTICORE +// RPi-specific timer +struct repeating_timer timer; -#ifdef MULTICORE - // use interrupt version -- works for 2 cores ie can run loop1() and loop() simultaneously as well as the clock callback? +#define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } - // RPi-specific timer - struct repeating_timer timer; +// forward declaration of uClockHandler +void uClockHandler(); - #define ATOMIC(X) { uint32_t __interrupt_mask = save_and_disable_interrupts(); X; restore_interrupts(__interrupt_mask); } +// ISR handler -- called when tick happens +bool handlerISR(repeating_timer *timer) +{ + uClockHandler(); - // forward declaration of uClockHandler - void uClockHandler(); + return true; +} - // ISR handler -- called when tick happens - bool handlerISR(repeating_timer *timer) - { - uClockHandler(); +void initTimer(uint32_t init_clock) { + // set up RPi interrupt timer + // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); +} - return true; - } - - void initTimer(uint32_t init_clock) { - // set up RPi interrupt timer - // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! - add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); - } - - void setTimer(uint32_t us_interval) { - cancel_repeating_timer(&timer); - // todo: actually should be -us_interval so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! - add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); - } -#else - // use FreeRTOS scheduling/mutex version -- doesn't work (task starts but does not run) if using loop1() ie core 2 - - #include "FreeRTOS.h" - #include - #include - - // RPi-specific timer - struct repeating_timer timer; - - // FreeRTOS main clock task size in bytes - #define CLOCK_STACK_SIZE 5*1024 // adjust for your needs, a sequencer with heavy serial handling should be large in size - TaskHandle_t taskHandle; - // mutex to protect the shared resource - SemaphoreHandle_t _mutex; - // mutex control for task - #define ATOMIC(X) xSemaphoreTake(_mutex, portMAX_DELAY); X; xSemaphoreGive(_mutex); - - // forward declaration of uClockHandler - void uClockHandler(); - - // ISR handler -- called when tick happens - bool handlerISR(repeating_timer *timer) - { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - // Send a notification to task1 - vTaskNotifyGiveFromISR(taskHandle, &xHigherPriorityTaskWoken); - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); - - return true; - } - - // task for user clock process - void clockTask(void *pvParameters) - { - while (1) { - // wait for a notification from ISR - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - uClockHandler(); - } - } - - void initTimer(uint32_t init_clock) - { - // initialize the mutex for shared resource access - _mutex = xSemaphoreCreateMutex(); - - // create the clockTask - xTaskCreate(clockTask, "clockTask", CLOCK_STACK_SIZE, NULL, 1, &taskHandle); - - // set up RPi interrupt timer - // todo: actually should be -init_clock so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! - add_repeating_timer_us(init_clock, &handlerISR, NULL, &timer); - } - - void setTimer(uint32_t us_interval) { - cancel_repeating_timer(&timer); - // todo: actually should be -us_interval so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! - add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); - } - -#endif \ No newline at end of file +void setTimer(uint32_t us_interval) { + cancel_repeating_timer(&timer); + // todo: actually should be -us_interval so that timer is set to start init_clock us after last tick, instead of init_clock us after finished processing last tick! + add_repeating_timer_us(us_interval, &handlerISR, NULL, &timer); +} \ No newline at end of file From ceddb190f0f6c5279d8cce59224c4bff3b937a2e Mon Sep 17 00:00:00 2001 From: midilab Date: Thu, 11 Apr 2024 11:18:57 -0300 Subject: [PATCH 10/13] remove know problems related to freertos. lets asume it also works on non earlephilhower core --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0f1de32..2782dd5 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interruption. It is designed to be multi-architecture, portable, and easy to use within the open source community universe. -We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, and Seedstudio XIAO M0)**. It has experimental support for **RP2040 boards (Raspberry Pico, Seeed XIAO RP2040)** (see notes). +We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, and Seedstudio XIAO M0, Raspberry Pico, Seeed XIAO RP2040)**. The absence of real-time features necessary for creating professional-level embedded devices for music and video on open source community-based platforms like Arduino led to the development of uClock. By leveraging the use of timer hardware interruptions, the library can schedule and manage real-time-like processing with safe shared resource access through its API. @@ -370,13 +370,4 @@ void loop() //processYourLeds(); //processYourPots(); } -``` - -## Known problems - -### RP2040 support - -- Uses the [earlephilhower core](https://github.com/earlephilhower/arduino-pico) -- Doing a 'soft reboot' (eg from reflashing) seems to crash on startup, but starting from cold and powering on works fine. -- Using FreeRTOS multithreading fails if the second core is used (via setup1() and loop1()) - using the 'interrupts-based' version of the RP2040 uClock support seems to solve this. -- Tick ticking may be off due to repeating_timer following from the end of previous tick, rather than following the start of the previous tick. +``` \ No newline at end of file From ea3375c2667ff4103725a5da50f97cf3168fe7f3 Mon Sep 17 00:00:00 2001 From: midilab Date: Thu, 11 Apr 2024 17:01:41 -0300 Subject: [PATCH 11/13] add searchable RP2040 support reference --- README.md | 2 +- library.json | 2 +- library.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2782dd5..d189fa2 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The **uClock BPM Generator library** is designed to implement precise and reliable BPM clock tick calls using the microcontroller's timer hardware interruption. It is designed to be multi-architecture, portable, and easy to use within the open source community universe. -We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, and Seedstudio XIAO M0, Raspberry Pico, Seeed XIAO RP2040)**. +We have chosen PlatformIO and Arduino as our official deployment platforms. The library has been supported and tested on general **AVR boards (ATmega168/328, ATmega16u4/32u4, and ATmega2560)** as well as **ARM boards (Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040)**. The absence of real-time features necessary for creating professional-level embedded devices for music and video on open source community-based platforms like Arduino led to the development of uClock. By leveraging the use of timer hardware interruptions, the library can schedule and manage real-time-like processing with safe shared resource access through its API. diff --git a/library.json b/library.json index 1952300..20a3781 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "uClock", "version": "2.1.0", - "description": "A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32)", + "description": "A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040)", "keywords": "bpm, clock, timing, tick, music, generator", "repository": { diff --git a/library.properties b/library.properties index c03f701..8911b8b 100755 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=2.1.0 author=Romulo Silva maintainer=Romulo Silva sentence=BPM clock generator for Arduino platform. -paragraph=A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) +paragraph=A Library to implement BPM clock tick calls using hardware interruption. Supported and tested on AVR boards(ATmega168/328, ATmega16u4/32u4 and ATmega2560) and ARM boards(Teensy, STM32XX, ESP32, Raspberry Pico, Seedstudio XIAO M0 and RP2040) category=Timing url=https://github.com/midilab/uClock architectures=avr,arm,samd,stm32,esp32,rp2040 From 778868e2f4d508aa33511f471793c760d81afca8 Mon Sep 17 00:00:00 2001 From: midilab Date: Fri, 12 Apr 2024 08:30:56 -0300 Subject: [PATCH 12/13] added generic master and external sync example --- README.md | 94 ++++++++++++++----- .../AcidStepSequencer/AcidStepSequencer.ino | 2 +- .../GenericMasterOrExternalSync.ino | 62 ++++++++++++ 3 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino diff --git a/README.md b/README.md index d189fa2..3b603b6 100755 --- a/README.md +++ b/README.md @@ -17,6 +17,21 @@ The uClock library API operates through attached callback functions mechanism: 4. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** on uClock Start event 5. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** on uClock Stop event +## Set your own resolution for your clock needs + +1. **PPQN_24** 24 Pulses Per Quarter Note +2. **PPQN_48** 48 Pulses Per Quarter Note +3. **PPQN_96** 96 Pulses Per Quarter Note +1. **PPQN_384** 384 Pulses Per Quarter Note +2. **PPQN_480** 480 Pulses Per Quarter Note +3. **PPQN_960** 960 Pulses Per Quarter Note + +To generate a MIDI sync signal and synchronize external MIDI devices, you can start working with the resolution of 24PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available in the market. By sending 24 pulses per quarter note interval, you can ensure effective synchronization among your MIDI devices. + +If you are working on the development of a vintage-style step sequencer, utilizing a resolution of 96PPQN is a fitting option to initiate the coding process. Then you can use onStepCallback call which corresponds to a step played, note or event. + +Furthermore, it is possible to utilize all three resolutions simultaneously, allowing for flexibility based on your specific requirements and preferences. + ## uClock v2.0 Breaking Changes If you are coming from uClock version < 2.0 versions pay attention to the breaking changes so you can update your code to reflect the new API interface: @@ -32,27 +47,39 @@ If you are coming from uClock version < 2.0 versions pay attention to the breaki If created a device using setClock16PPQNOutput only you just change the API call to setOnStep. If you were dependent on setClock96PPQNOutput you might need to review your tick counting system, depending on which PPQN clock resolution you choose to use. -## Set your own resolution for your clock needs +# Examples -1. **PPQN_24** 24 Pulses Per Quarter Note -2. **PPQN_48** 48 Pulses Per Quarter Note -3. **PPQN_96** 96 Pulses Per Quarter Note -1. **PPQN_384** 384 Pulses Per Quarter Note -2. **PPQN_480** 480 Pulses Per Quarter Note -3. **PPQN_960** 960 Pulses Per Quarter Note +You will find more complete examples on examples/ folder: -To generate a MIDI sync signal and synchronize external MIDI devices, you can start working with the resolution of 24PPQN, which aligns with the clocking standards of modern MIDI-syncable devices commonly available in the market. By sending 24 pulses per quarter note interval, you can ensure effective synchronization among your MIDI devices. +```c++ +#include -If you are working on the development of a vintage-style step sequencer, utilizing a resolution of 96PPQN is a fitting option to initiate the coding process. Then you can use onStepCallback call which corresponds to a step played, note or event. +// external or internal sync? +bool _external_sync_on = false; -Furthermore, it is possible to utilize all three resolutions simultaneously, allowing for flexibility based on your specific requirements and preferences. +// the main uClock PPQN resolution ticking +void onPPQNCallback(uint32_t tick) { + // tick your sequencers or tickable devices... +} -### Example +void onStepCallback(uint32_t step) { + // triger step data for sequencer device... +} -You will find more complete examples on examples/ folder: +// The callback function called by uClock each Pulse of 24PPQN clock resolution. +void onSync24Callback(uint32_t tick) { + // send sync signal to... +} -```c++ -#include +// The callback function called when clock starts by using uClock.start() method. +void onClockStartCallback() { + // send start signal to... +} + +// The callback function called when clock stops by using uClock.stop() method. +void onClockStopCallback() { + // send stop signal to... +} void setup() { // avaliable resolutions @@ -68,15 +95,32 @@ void setup() { uClock.setOnClockStart(onClockStartCallback); uClock.setOnClockStop(onClockStopCallback); + // set external sync mode? + if (_external_sync_on) { + uClock.setMode(uClock.EXTERNAL_CLOCK); + } + uClock.init(); } + +void loop() { + // do we need to external sync? + if (_external_sync_on) { + // watch for external sync signal income + bool signal_income = true; // your external input signal check will be this condition result + if (signal_income) { + // at each clockMe call uClock will process and handle external/internal syncronization + uClock.clockMe(); + } + } +} ``` ## MIDI Examples Here a few examples on the usage of Clock library for MIDI devices, keep in mind the need to make your own MIDI interface, more details will be avaliable soon but until that, you can find good material over the net about the subject. -If you dont want to build a MIDI interface and you are going to use your arduino only with your PC, you can use a Serial-to-Midi bridge and connects your arduino via USB cable to your conputer to use it as a MIDI tool [like this one](http://projectgus.github.io/hairless-midiserial/). +If you don't have native USB/MIDI support on your microcontroller and don't want to build a MIDI interface and you are going to use your arduino only with your PC, you can use a Serial-to-Midi bridge and connects your arduino via USB cable to your conputer to use it as a MIDI tool [like this one](http://projectgus.github.io/hairless-midiserial/). ### A Simple MIDI Sync Box sketch example @@ -90,18 +134,18 @@ Here is an example on how to create a simple MIDI Sync Box on Arduino boards #define MIDI_START 0xFA #define MIDI_STOP 0xFC -// The callback function wich will be called by Clock each Pulse of 24PPQN clock resolution. +// The callback function called by Clock each Pulse of 24PPQN clock resolution. void onSync24Callback(uint32_t tick) { // Send MIDI_CLOCK to external gears Serial.write(MIDI_CLOCK); } -// The callback function wich will be called when clock starts by using Clock.start() method. +// The callback function called when clock starts by using Clock.start() method. void onClockStart() { Serial.write(MIDI_START); } -// The callback function wich will be called when clock stops by using Clock.stop() method. +// The callback function called when clock stops by using Clock.stop() method. void onClockStop() { Serial.write(MIDI_STOP); } @@ -137,18 +181,18 @@ An example on how to create a simple MIDI Sync Box on Teensy boards and USB Midi ```c++ #include -// The callback function wich will be called by Clock each Pulse of 96PPQN clock resolution. +// The callback function called by Clock each Pulse of 96PPQN clock resolution. void onSync24Callback(uint32_t tick) { // Send MIDI_CLOCK to external gears usbMIDI.sendRealTime(usbMIDI.Clock); } -// The callback function wich will be called when clock starts by using Clock.start() method. +// The callback function called when clock starts by using Clock.start() method. void onClockStart() { usbMIDI.sendRealTime(usbMIDI.Start); } -// The callback function wich will be called when clock stops by using Clock.stop() method. +// The callback function called when clock stops by using Clock.stop() method. void onClockStop() { usbMIDI.sendRealTime(usbMIDI.Stop); } @@ -241,7 +285,7 @@ void sendMidiMessage(uint8_t command, uint8_t byte1, uint8_t byte2) Serial.write(byte2); } -// The callback function wich will be called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step. +// The callback function called by uClock each Pulse of 16PPQN clock resolution. Each call represents exactly one step. void onStepCallback(uint32_t tick) { uint16_t step; @@ -279,7 +323,7 @@ void onStepCallback(uint32_t tick) } } -// The callback function wich will be called by uClock each Pulse of 96PPQN clock resolution. +// The callback function called by uClock each Pulse of 96PPQN clock resolution. void onPPQNCallback(uint32_t tick) { // Send MIDI_CLOCK to external hardware @@ -297,14 +341,14 @@ void onPPQNCallback(uint32_t tick) } } -// The callback function wich will be called when clock starts by using Clock.start() method. +// The callback function called when clock starts by using Clock.start() method. void onClockStart() { Serial.write(MIDI_START); _playing = true; } -// The callback function wich will be called when clock stops by using Clock.stop() method. +// The callback function called when clock stops by using Clock.stop() method. void onClockStop() { Serial.write(MIDI_STOP); diff --git a/examples/AcidStepSequencer/AcidStepSequencer.ino b/examples/AcidStepSequencer/AcidStepSequencer.ino index 5531fa9..41f3928 100644 --- a/examples/AcidStepSequencer/AcidStepSequencer.ino +++ b/examples/AcidStepSequencer/AcidStepSequencer.ino @@ -80,7 +80,7 @@ void onStepCallback(uint32_t tick) ++step; step = step % _step_length; if ( _sequencer[step].glide == true && _sequencer[step].rest == false ) { - length = NOTE_LENGTH + (i * 6); + length = NOTE_LENGTH + (i * 24); break; } else if ( _sequencer[step].rest == false ) { break; diff --git a/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino new file mode 100644 index 0000000..5f1c677 --- /dev/null +++ b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino @@ -0,0 +1,62 @@ +#include + +// external or internal sync? +bool _external_sync_on = false; + +// the main uClock PPQN resolution ticking +void onPPQNCallback(uint32_t tick) { + // tick your sequencers or tickable devices... +} + +void onStepCallback(uint32_t step) { + // triger step data for sequencer device... +} + +// The callback function called by uClock each Pulse of 24PPQN clock resolution. +void onSync24Callback(uint32_t tick) { + // send sync signal to... +} + +// The callback function called when clock starts by using uClock.start() method. +void onClockStartCallback() { + // send start signal to... +} + +// The callback function called when clock stops by using uClock.stop() method. +void onClockStopCallback() { + // send stop signal to... +} + +void setup() { + // avaliable resolutions + // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] + // not mandatory to call, the default is 96PPQN if not set + uClock.setPPQN(uClock.PPQN_96); + + // you need to use at least one! + uClock.setOnPPQN(onPPQNCallback); + uClock.setOnStep(onStepCallback); + uClock.setOnSync24(onSync24Callback); + + uClock.setOnClockStart(onClockStartCallback); + uClock.setOnClockStop(onClockStopCallback); + + // set external sync mode? + if (_external_sync_on) { + uClock.setMode(uClock.EXTERNAL_CLOCK); + } + + uClock.init(); +} + +void loop() { + // do we need to external sync? + if (_external_sync_on) { + // watch for external sync signal income + bool signal_income = true; // your external input signal check will be this condition result + if (signal_income) { + // at each clockMe call uClock will process and handle external/internal syncronization + uClock.clockMe(); + } + } +} \ No newline at end of file From c0196f31a288c48d0a6e5fde641981881fc0b0ba Mon Sep 17 00:00:00 2001 From: midilab Date: Mon, 15 Apr 2024 15:33:56 -0300 Subject: [PATCH 13/13] added uClock.start() to generic example and README --- README.md | 7 ++++++- .../GenericMasterOrExternalSync.ino | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3b603b6..d00f917 100755 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ void onClockStopCallback() { } void setup() { + + // inits the clock library + uClock.init(); + // avaliable resolutions // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] // not mandatory to call, the default is 96PPQN if not set @@ -100,7 +104,8 @@ void setup() { uClock.setMode(uClock.EXTERNAL_CLOCK); } - uClock.init(); + // starts clock + uClock.start(); } void loop() { diff --git a/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino index 5f1c677..366e938 100644 --- a/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino +++ b/examples/GenericMasterOrExternalSync/GenericMasterOrExternalSync.ino @@ -28,6 +28,10 @@ void onClockStopCallback() { } void setup() { + + // inits the clock library + uClock.init(); + // avaliable resolutions // [ uClock.PPQN_24, uClock.PPQN_48, uClock.PPQN_96, uClock.PPQN_384, uClock.PPQN_480, uClock.PPQN_960 ] // not mandatory to call, the default is 96PPQN if not set @@ -46,7 +50,8 @@ void setup() { uClock.setMode(uClock.EXTERNAL_CLOCK); } - uClock.init(); + // starts clock + uClock.start(); } void loop() {