From 91ed686ea8f0ddf33d462e8c744d8b69e76c028f Mon Sep 17 00:00:00 2001 From: doctea Date: Tue, 26 Mar 2024 00:03:18 +0000 Subject: [PATCH 1/7] proof-of-concept of a board-agnostic, simple micros()-check-based mode --- src/platforms/generic.h | 30 +++++++++++++++++ src/uClock.cpp | 71 +++++++++++++++++++++++++---------------- 2 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 src/platforms/generic.h diff --git a/src/platforms/generic.h b/src/platforms/generic.h new file mode 100644 index 0000000..9b543a4 --- /dev/null +++ b/src/platforms/generic.h @@ -0,0 +1,30 @@ +#include + +//#define ATOMIC(X) noInterrupts(); X; interrupts(); +#define ATOMIC(X) X; + +// forward declaration of ISR +void uClockHandler(); + +uint32_t uclock_last_time_ticked; +uint32_t uclock_us_interval; + +// call this as often as possible to tick the uClock +void uClockCheckTime(uint32_t micros_time) { + if (micros_time - uclock_last_time_ticked >= uclock_us_interval) { + uclock_last_time_ticked = micros(); + uClockHandler(); + } +} + +void initTimer(uint32_t init_clock) +{ + // basically nothing to do for software-implemented version..? + uclock_last_time_ticked = micros(); +} + +void setTimer(uint32_t us_interval) +{ + uclock_us_interval = us_interval; + Serial.printf("setTimer(%d)\n", us_interval); Serial.flush(); +} \ No newline at end of file diff --git a/src/uClock.cpp b/src/uClock.cpp index 3ad2473..1525fae 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -28,36 +28,52 @@ #include "uClock.h" // -// General Arduino AVRs port +// Generic, board-agnostic, not-accurate, no-interrupt, software-only port // -#if defined(ARDUINO_ARCH_AVR) - #include "platforms/avr.h" -#endif -// -// Teensyduino ARMs port -// -#if defined(TEENSYDUINO) - #include "platforms/teensy.h" -#endif -// -// Seedstudio XIAO M0 port -// -#if defined(SEEED_XIAO_M0) - #include "platforms/samd.h" +#if !defined(USE_UCLOCK_GENERIC) + // + // General Arduino AVRs port + // + #if defined(ARDUINO_ARCH_AVR) + #include "platforms/avr.h" + #define UCLOCK_PLATFORM_FOUND + #endif + // + // Teensyduino ARMs port + // + #if defined(TEENSYDUINO) + #include "platforms/teensy.h" + #define UCLOCK_PLATFORM_FOUND + #endif + // + // Seedstudio XIAO M0 port + // + #if defined(SEEED_XIAO_M0) + #include "platforms/samd.h" + #define UCLOCK_PLATFORM_FOUND + #endif + // + // ESP32 family + // + #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) + #include "platforms/esp32.h" + #define UCLOCK_PLATFORM_FOUND + #endif + // + // STM32XX family + // + #if defined(ARDUINO_ARCH_STM32) + #include "platforms/stm32.h" + #define UCLOCK_PLATFORM_FOUND + #endif #endif -// -// ESP32 family -// -#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) - #include "platforms/esp32.h" -#endif -// -// STM32XX family -// -#if defined(ARDUINO_ARCH_STM32) - #include "platforms/stm32.h" + +#if !defined(UCLOCK_PLATFORM_FOUND) + #pragma message ("NOTE: uClock is using the 'generic' approach instead of specific board support, because board is not supported or because of USE_UCLOCK_GENERIC build flag.") + #include "platforms/generic.h" #endif + // // Platform specific timer setup/control // @@ -72,6 +88,7 @@ void uclockInitTimer() void setTimerTempo(float bpm) { + Serial.printf("setTimerTempo(%3.3f)..", bpm); setTimer(uClock.bpmToMicroSeconds(bpm)); } @@ -119,7 +136,7 @@ void uClockClass::init() uint32_t uClockClass::bpmToMicroSeconds(float bpm) { - return (60000000 / ppqn / bpm); + return (60000000.0f / (float)ppqn / bpm); } void uClockClass::setPPQN(PPQNResolution resolution) From 3b2d17f5f85a7f72d2f9f57e789c1e08881c7ad1 Mon Sep 17 00:00:00 2001 From: doctea Date: Tue, 26 Mar 2024 21:58:08 +0000 Subject: [PATCH 2/7] update readme, remove debug, add some comments --- README.md | 26 +++++++++++++++++++++++++- src/platforms/generic.h | 11 ++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 68115fd..4630911 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ 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)**. + +(See also the generic fallback mode) 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. @@ -18,6 +20,28 @@ The uClock library API operates through attached callback functions mechanism: 5. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** on uClock Start event 6. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** on uClock Stop event +### Generic mode - for unsupported boards (or avoiding usage of interrupts) +If a supported board isn't detected during compilation then a generic fallback approach will be used that does not utilise any interrupts to ensure accurate timekeeping. This can be useful to port projets to boards that do not have support in uClock yet, or to test if suspected bugs are related to interactions with interrupts or task handling. + +You can force this non-interrupt "generic mode" even on supported boards by defining the build flag `USE_UCLOCK_GENERIC`. + +In order for generic mode to work, you need to add a call to your `loop()` function to process ticks. For example, + +```c++ + +// prototype of +void uClockCheckTime(uint32_t micros_time); + +void loop() { + #ifdef USE_UCLOCK_GENERIC + uClockCheckTime(micros()); + #endif + + // do anything else you need to do inside loop()... +} +``` + + ## uClock v2.0 Breakchanges If you are comming from uClock version < 2.0 versions keep attention to the breakchanges so you can update your code to the new API interface changes: diff --git a/src/platforms/generic.h b/src/platforms/generic.h index 9b543a4..4e381a4 100644 --- a/src/platforms/generic.h +++ b/src/platforms/generic.h @@ -1,6 +1,12 @@ #include -//#define ATOMIC(X) noInterrupts(); X; interrupts(); +/* + Generic fallback approach that doesn't rely on any particular MCU's interrupts or RTOS threads etc. + Simply checks micros() and compares last time tick happened and interval size to determine when a tick is due. + requires calling uClockCheckTime(micros()); inside loop() in order to trigger tick processing. + function signature: void uClockCheckTime(uint32_t micros_time); +*/ + #define ATOMIC(X) X; // forward declaration of ISR @@ -12,7 +18,7 @@ uint32_t uclock_us_interval; // call this as often as possible to tick the uClock void uClockCheckTime(uint32_t micros_time) { if (micros_time - uclock_last_time_ticked >= uclock_us_interval) { - uclock_last_time_ticked = micros(); + uclock_last_time_ticked = micros_time; uClockHandler(); } } @@ -26,5 +32,4 @@ void initTimer(uint32_t init_clock) void setTimer(uint32_t us_interval) { uclock_us_interval = us_interval; - Serial.printf("setTimer(%d)\n", us_interval); Serial.flush(); } \ No newline at end of file From f27ad802310c3ddd305e410f7a09d0cebd5c35c9 Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Tue, 26 Mar 2024 22:02:45 +0000 Subject: [PATCH 3/7] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4630911..3f93e62 100755 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ The uClock library API operates through attached callback functions mechanism: 5. **setOnClockStart(onClockStartCallback) > onClockStartCallback()** on uClock Start event 6. **setOnClockStop(onClockStopCallback) > onClockStopCallback()** on uClock Stop event -### Generic mode - for unsupported boards (or avoiding usage of interrupts) -If a supported board isn't detected during compilation then a generic fallback approach will be used that does not utilise any interrupts to ensure accurate timekeeping. This can be useful to port projets to boards that do not have support in uClock yet, or to test if suspected bugs are related to interactions with interrupts or task handling. +### (optional) Generic mode - for unsupported boards (or avoiding usage of interrupts) +If a supported board isn't detected during compilation then a generic fallback approach will be used. This does not utilise any interrupts and so does not ensure accurate timekeeping. This can be useful to port your projects to boards that do not have support in uClock yet, or to test if suspected bugs in your code are related to interactions with interrupts or task handling. You can force this non-interrupt "generic mode" even on supported boards by defining the build flag `USE_UCLOCK_GENERIC`. @@ -29,7 +29,7 @@ In order for generic mode to work, you need to add a call to your `loop()` funct ```c++ -// prototype of +// pre-declare this function somewhere, so that compiler knows about it. void uClockCheckTime(uint32_t micros_time); void loop() { @@ -55,12 +55,12 @@ If you are comming from uClock version < 2.0 versions keep attention to the brea #### Tick resolution and sequencers -If you have write a sequencer using setClock16PPQNOutput only its ok to just change the API call to setOnStep, but if you were dependent on setClock96PPQNOutput you migth need to review you tick counting system depending on wich PPQN clock resolution you choose. +If you have write a sequencer using setClock16PPQNOutput only its ok to just change the API call to setOnStep, but if you were dependent on setClock96PPQNOutput you might need to review your tick-counting system, depending on which PPQN clock resolution you choose. ## Examples -You will find more complete examples on examples/ folder: +You will find more complete examples in `examples/` folder: ```c++ @@ -84,7 +84,7 @@ void setup() { } ``` -Resolutions: set youw own resolution for your clock needs +Resolutions: set your own resolution for your clock needs: 1. **PPQN_24** 24 Pulses Per Quarter Note 2. **PPQN_48** 48 Pulses Per Quarter Note From a3310e363458c1e0d06160165776032ef62b4dc7 Mon Sep 17 00:00:00 2001 From: doctea Date: Tue, 26 Mar 2024 22:05:10 +0000 Subject: [PATCH 4/7] remove debug --- src/uClock.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uClock.cpp b/src/uClock.cpp index 1525fae..fd18740 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -88,7 +88,6 @@ void uclockInitTimer() void setTimerTempo(float bpm) { - Serial.printf("setTimerTempo(%3.3f)..", bpm); setTimer(uClock.bpmToMicroSeconds(bpm)); } From ec19c8d6340d27ff692e9d2e9c3ce3fd686772bc Mon Sep 17 00:00:00 2001 From: Tristan Rowley Date: Wed, 27 Mar 2024 11:22:21 +0000 Subject: [PATCH 5/7] Update README.md --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2fb6478..cfadb9e 100755 --- a/README.md +++ b/README.md @@ -32,9 +32,7 @@ In order for generic mode to work, you need to add a call to your `loop()` funct void uClockCheckTime(uint32_t micros_time); void loop() { - #ifdef USE_UCLOCK_GENERIC - uClockCheckTime(micros()); - #endif + uClockCheckTime(micros()); // do anything else you need to do inside loop()... } @@ -43,7 +41,7 @@ void loop() { ## 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: +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: ### setCallback function name changes From c2cc95e30e1f027bed38d57246e1fcfea4a6fbd5 Mon Sep 17 00:00:00 2001 From: midilab Date: Sun, 3 Nov 2024 11:19:02 -0300 Subject: [PATCH 6/7] - library interface suggestions for generic fallback support. --- README.md | 17 +++++---- library.json | 2 +- library.properties | 2 +- src/platforms/{generic.h => software.h} | 8 ++-- src/uClock.cpp | 49 +++++++++++++++---------- src/uClock.h | 5 ++- 6 files changed, 50 insertions(+), 33 deletions(-) rename src/platforms/{generic.h => software.h} (76%) diff --git a/README.md b/README.md index e3c496a..4c9568d 100755 --- a/README.md +++ b/README.md @@ -17,22 +17,23 @@ 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 -### (optional) Generic mode - for unsupported boards (or avoiding usage of interrupts) +### (optional) Software Timer mode - for unsupported boards (or avoiding usage of interrupts) If a supported board isn't detected during compilation then a generic fallback approach will be used. This does not utilise any interrupts and so does not ensure accurate timekeeping. This can be useful to port your projects to boards that do not have support in uClock yet, or to test if suspected bugs in your code are related to interactions with interrupts or task handling. -You can force this non-interrupt "generic mode" even on supported boards by defining the build flag `USE_UCLOCK_GENERIC`. +You can force this non-interrupt "software timer mode" even on supported boards by defining the build flag `USE_UCLOCK_SOFTWARE_TIMER`. -In order for generic mode to work, you need to add a call to your `loop()` function to process ticks. For example, +In order for software timer mode to work, you need to add a call to your `loop()` function to process ticks. For example, ```c++ - -// pre-declare this function somewhere, so that compiler knows about it. -void uClockCheckTime(uint32_t micros_time); - void loop() { - uClockCheckTime(micros()); + uClock.run(); // do anything else you need to do inside loop()... + // you can intercalate your main processing with other uClock.run() calls to avoid timming accuracy loss. + //uClock.run(); + // do anything other inside loop()... + //uClock.run(); + // the faster you can call uClock.run() without blocking the better and accurate timming you can achieve. } ``` diff --git a/library.json b/library.json index 20a3781..c66d060 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "uClock", - "version": "2.1.0", + "version": "2.2.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, 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 8911b8b..0f4cce0 100755 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=uClock -version=2.1.0 +version=2.2.0 author=Romulo Silva maintainer=Romulo Silva sentence=BPM clock generator for Arduino platform. diff --git a/src/platforms/generic.h b/src/platforms/software.h similarity index 76% rename from src/platforms/generic.h rename to src/platforms/software.h index 4e381a4..1363aeb 100644 --- a/src/platforms/generic.h +++ b/src/platforms/software.h @@ -3,8 +3,10 @@ /* Generic fallback approach that doesn't rely on any particular MCU's interrupts or RTOS threads etc. Simply checks micros() and compares last time tick happened and interval size to determine when a tick is due. - requires calling uClockCheckTime(micros()); inside loop() in order to trigger tick processing. - function signature: void uClockCheckTime(uint32_t micros_time); + requires calling softwareTimerHandler(micros()); inside loop() in order to trigger tick processing. + function signature: void softwareTimerHandler(uint32_t micros_time); + + @author Doctea */ #define ATOMIC(X) X; @@ -16,7 +18,7 @@ uint32_t uclock_last_time_ticked; uint32_t uclock_us_interval; // call this as often as possible to tick the uClock -void uClockCheckTime(uint32_t micros_time) { +void softwareTimerHandler(uint32_t micros_time) { if (micros_time - uclock_last_time_ticked >= uclock_us_interval) { uclock_last_time_ticked = micros_time; uClockHandler(); diff --git a/src/uClock.cpp b/src/uClock.cpp index ffa97a5..9f27935 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -2,7 +2,7 @@ * @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(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.1.0 + * @version 2.2.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co @@ -27,10 +27,8 @@ */ #include "uClock.h" -// -// Generic, board-agnostic, not-accurate, no-interrupt, software-only port -// -#if !defined(USE_UCLOCK_GENERIC) +// no hardware timer clock? use USE_UCLOCK_SOFTWARE_TIMER +#if !defined(USE_UCLOCK_SOFTWARE_TIMER) // // General Arduino AVRs port // @@ -66,17 +64,21 @@ #include "platforms/stm32.h" #define UCLOCK_PLATFORM_FOUND #endif + // + // RP2040 (Raspberry Pico) family + // + #if defined(ARDUINO_ARCH_RP2040) + #include "platforms/rp2040.h" + #define UCLOCK_PLATFORM_FOUND + #endif #endif -#if !defined(UCLOCK_PLATFORM_FOUND) - #pragma message ("NOTE: uClock is using the 'generic' approach instead of specific board support, because board is not supported or because of USE_UCLOCK_GENERIC build flag.") - #include "platforms/generic.h" -#endif // -// RP2040 (Raspberry Pico) family +// Software Timer for generic, board-agnostic, not-accurate, no-interrupt, software-only port // -#if defined(ARDUINO_ARCH_RP2040) - #include "platforms/rp2040.h" +#if !defined(UCLOCK_PLATFORM_FOUND) + #pragma message ("NOTE: uClock is using the 'software timer' approach instead of specific board interrupted support, because board is not supported or because of USE_UCLOCK_SOFTWARE_TIMER build flag. Remember to call uClock.run() inside your loop().") + #include "platforms/software.h" #endif @@ -209,13 +211,6 @@ void uClockClass::setTempo(float bpm) setTimerTempo(bpm); } -// this function is based on sync24PPQN -float inline uClockClass::freqToBpm(uint32_t freq) -{ - float usecs = 1/((float)freq/1000000.0); - return (float)((float)(usecs/(float)24) * 60.0); -} - float uClockClass::getTempo() { if (mode == EXTERNAL_CLOCK) { @@ -234,6 +229,22 @@ float uClockClass::getTempo() return tempo; } +// for software timer implementation(fallback for no board support) +void uClockClass::run() +{ +#if !defined(UCLOCK_PLATFORM_FOUND) + // call software timer implementation of software + softwareTimerHandler(micros()); +#endif +} + +// this function is based on sync24PPQN +float inline uClockClass::freqToBpm(uint32_t freq) +{ + float usecs = 1/((float)freq/1000000.0); + return (float)((float)(usecs/(float)24) * 60.0); +} + void uClockClass::setMode(SyncMode tempo_mode) { mode = tempo_mode; diff --git a/src/uClock.h b/src/uClock.h index 9b438c2..df1a08d 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -2,7 +2,7 @@ * @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(RPI2040, Teensy, Seedstudio XIAO M0 and ESP32) - * @version 2.1.0 + * @version 2.2.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2024 - Romulo Silva - contact@midilab.co @@ -130,6 +130,9 @@ class uClockClass { void setTempo(float bpm); float getTempo(); + // for software timer implementation(fallback for no board support) + void run(); + // external timming control void setMode(SyncMode tempo_mode); SyncMode getMode(); From 3f5b20a92450f8bfded62202f5991eefe260164d Mon Sep 17 00:00:00 2001 From: midilab Date: Sat, 16 Nov 2024 05:13:33 -0300 Subject: [PATCH 7/7] Update README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 4c9568d..7c52d2c 100755 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 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 -### (optional) Software Timer mode - for unsupported boards (or avoiding usage of interrupts) +### Software Timer mode - for unsupported boards (or avoiding usage of interrupts) If a supported board isn't detected during compilation then a generic fallback approach will be used. This does not utilise any interrupts and so does not ensure accurate timekeeping. This can be useful to port your projects to boards that do not have support in uClock yet, or to test if suspected bugs in your code are related to interactions with interrupts or task handling. You can force this non-interrupt "software timer mode" even on supported boards by defining the build flag `USE_UCLOCK_SOFTWARE_TIMER`. @@ -86,14 +86,10 @@ void onStepCallback(uint32_t step) { // triger step data for sequencer device... } -<<<<<<< HEAD -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... } ->>>>>>> main // The callback function called when clock starts by using uClock.start() method. void onClockStartCallback() {