diff --git a/examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino b/examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino new file mode 100644 index 0000000..18ad367 --- /dev/null +++ b/examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino @@ -0,0 +1,75 @@ +/* Uart MIDI Sync Box + * + * This example demonstrates how to send MIDI data via Uart + * interface on STM32 family. + * + * This example code is in the public domain. + * + * ... + * + */ +#include + +// MIDI clock, start and stop byte definitions - based on MIDI 1.0 Standards. +#define MIDI_CLOCK 0xF8 +#define MIDI_START 0xFA +#define MIDI_STOP 0xFC + +// the blue led +#define LED_BUILTIN 2 + +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, HIGH); + } else if ( !(tick % (24)) ) { // each quarter led on + bpm_blink_timer = 1; + digitalWrite(LED_BUILTIN, HIGH); + } else if ( !(tick % bpm_blink_timer) ) { // get led off + digitalWrite(LED_BUILTIN, LOW); + } +} + +// Internal clock handlers +void ClockOut96PPQN(uint32_t tick) { + // Send MIDI_CLOCK to external gears + Serial.write(MIDI_CLOCK); + handle_bpm_led(tick); +} + +void onClockStart() { + Serial.write(MIDI_START); +} + +void onClockStop() { + Serial.write(MIDI_STOP); +} + +void setup() { + // Initialize serial communication at 31250 bits per second, the default MIDI serial speed communication: + Serial.begin(31250); + + // A led to count bpms + pinMode(LED_BUILTIN, OUTPUT); + + // Setup our clock system + // Inits the clock + uClock.init(); + // Set the callback function for the clock output to send MIDI Sync message. + uClock.setClock96PPQNOutput(ClockOut96PPQN); + // Set the callback function for MIDI Start and Stop messages. + uClock.setOnClockStartOutput(onClockStart); + uClock.setOnClockStopOutput(onClockStop); + // Set the clock BPM to 126 BPM + uClock.setTempo(126); + // Starts the clock, tick-tac-tick-tac... + uClock.start(); +} + +// Do it whatever to interface with Clock.stop(), Clock.start(), Clock.setTempo() and integrate your environment... +void loop() { + +} diff --git a/library.properties b/library.properties index d70c307..2a4655d 100755 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=uClock -version=1.2.0 +version=1.3.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 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, Seedstudio XIAO M0. ESP32 and STM32) category=Timing url=https://github.com/midilab/uClock -architectures=avr,arm,samd,esp32 +architectures=avr,arm,samd,stm32,esp32 includes=uClock.h diff --git a/src/platforms/avr.h b/src/platforms/avr.h new file mode 100644 index 0000000..c96db5f --- /dev/null +++ b/src/platforms/avr.h @@ -0,0 +1,62 @@ +#include + +#define ATOMIC(X) noInterrupts(); X; interrupts(); + +// want a different avr clock support? +// TODO: we should do this using macro guards for avrs different clocks +#define AVR_CLOCK_FREQ 16000000 + +void initTimer(uint32_t init_clock) +{ + ATOMIC( + // 16bits Timer1 init + // begin at 120bpm (48.0007680122882 Hz) + TCCR1A = 0; // set entire TCCR1A register to 0 + TCCR1B = 0; // same for TCCR1B + TCNT1 = 0; // initialize counter value to 0 + // set compare match register for 48.0007680122882 Hz increments + OCR1A = 41665; // = 16000000 / (8 * 48.0007680122882) - 1 (must be <65536) + // turn on CTC mode + TCCR1B |= (1 << WGM12); + // Set CS12, CS11 and CS10 bits for 8 prescaler + TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10); + // enable timer compare interrupt + TIMSK1 |= (1 << OCIE1A); + ) +} + +void setTimer(uint32_t us_interval) +{ + float tick_hertz_interval = 1/((float)us_interval/1000000); + + uint32_t ocr; + uint8_t tccr = 0; + + // 16bits avr timer setup + if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1 )) < 65535) { + // Set CS12, CS11 and CS10 bits for 1 prescaler + tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10); + } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 8 )) < 65535) { + // Set CS12, CS11 and CS10 bits for 8 prescaler + tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10); + } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 64 )) < 65535) { + // Set CS12, CS11 and CS10 bits for 64 prescaler + tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10); + } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 256 )) < 65535) { + // Set CS12, CS11 and CS10 bits for 256 prescaler + tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10); + } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1024 )) < 65535) { + // Set CS12, CS11 and CS10 bits for 1024 prescaler + tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10); + } else { + // tempo not achiavable + return; + } + + ATOMIC( + TCCR1B = 0; + OCR1A = ocr-1; + TCCR1B |= (1 << WGM12); + TCCR1B |= tccr; + ) +} \ No newline at end of file diff --git a/src/platforms/esp32.h b/src/platforms/esp32.h new file mode 100644 index 0000000..5da8080 --- /dev/null +++ b/src/platforms/esp32.h @@ -0,0 +1,29 @@ +#include + +#define TIMER_ID 0 + +hw_timer_t * _uclockTimer = NULL; +portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED; +#define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux); + +// forward declaration of ISR +void ARDUINO_ISR_ATTR uclockISR(); + +void initTimer(uint32_t init_clock) +{ + _uclockTimer = timerBegin(TIMER_ID, 80, true); + + // attach to generic uclock ISR + timerAttachInterrupt(_uclockTimer, &uclockISR, true); + + // init clock tick time + timerAlarmWrite(_uclockTimer, init_clock, true); + + // activate it! + timerAlarmEnable(_uclockTimer); +} + +void setTimer(uint32_t us_interval) +{ + timerAlarmWrite(_uclockTimer, us_interval, true); +} \ No newline at end of file diff --git a/src/platforms/samd.h b/src/platforms/samd.h new file mode 100644 index 0000000..87f9fca --- /dev/null +++ b/src/platforms/samd.h @@ -0,0 +1,27 @@ +#include + +// 24 bits timer +#include +// uses TimerTcc0 +// 16 bits timer +//#include +// uses TimerTc3 +#define ATOMIC(X) noInterrupts(); X; interrupts(); + +IntervalTimer _uclockTimer; + +// forward declaration of ISR +void uclockISR(); + +void initTimer(uint32_t init_clock) +{ + TimerTcc0.initialize(init_clock); + + // attach to generic uclock ISR + TimerTcc0.attachInterrupt(uclockISR); +} + +void setTimer(uint32_t us_interval) +{ + TimerTcc0.setPeriod(us_interval); +} \ No newline at end of file diff --git a/src/platforms/stm32.h b/src/platforms/stm32.h new file mode 100644 index 0000000..45436fe --- /dev/null +++ b/src/platforms/stm32.h @@ -0,0 +1,65 @@ +#include + +static TIM_HandleTypeDef s_TimerInstance = {0}; + +typedef void (*timer_callback_t)(void); +timer_callback_t timer_callback = NULL; + +#define ATOMIC(X) noInterrupts(); X; interrupts(); + +// forward declaration of ISR +void uclockISR(); + +void timer_attachInterrupt(TIM_TypeDef *tim, uint32_t microseconds, timer_callback_t callback) +{ + // Enable timer clock + if (tim == TIM2) __HAL_RCC_TIM2_CLK_ENABLE(); + else if (tim == TIM3) __HAL_RCC_TIM3_CLK_ENABLE(); + else if (tim == TIM4) __HAL_RCC_TIM4_CLK_ENABLE(); + + // Calculate the prescaler value + uint32_t prescaler = (SystemCoreClock / 1000000UL) - 1; + + // Calculate the period value + uint32_t period = (microseconds * 2UL) - 1UL; + + // Set up the timer instance + s_TimerInstance.Instance = tim; + s_TimerInstance.Init.Prescaler = prescaler; + s_TimerInstance.Init.CounterMode = TIM_COUNTERMODE_UP; + s_TimerInstance.Init.Period = period; + s_TimerInstance.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + + // Configure the timer instance + HAL_TIM_Base_Init(&s_TimerInstance); + HAL_TIM_Base_Start_IT(&s_TimerInstance); + + // Save the callback function + timer_callback = callback; +} + +void TIM2_IRQHandler() +{ + // Call the callback function + timer_callback(); + + // Clear the interrupt flag + __HAL_TIM_CLEAR_FLAG(&s_TimerInstance, TIM_FLAG_UPDATE); +} + +void initTimer(uint32_t us_interval) +{ + // Set up the timer to call the callback function every us_interval microseconds + timer_attachInterrupt(TIM2, us_interval, uclockISR); +} + +void setTimer(uint32_t us_interval) +{ + // Calculate the period value + uint32_t period = (us_interval * 2UL) - 1UL; + + // Update the timer instance with the new period value + s_TimerInstance.Init.Period = period; + HAL_TIM_Base_Init(&s_TimerInstance); +} + diff --git a/src/platforms/teensy.h b/src/platforms/teensy.h new file mode 100644 index 0000000..764b7e8 --- /dev/null +++ b/src/platforms/teensy.h @@ -0,0 +1,25 @@ +#include + +#define ATOMIC(X) noInterrupts(); X; interrupts(); + +IntervalTimer _uclockTimer; + +// forward declaration of ISR +void uclockISR(); + +void initTimer(uint32_t init_clock) +{ + _uclockTimer.begin(uclockISR, init_clock); + + // Set the interrupt priority level, controlling which other interrupts + // this timer is allowed to interrupt. Lower numbers are higher priority, + // with 0 the highest and 255 the lowest. Most other interrupts default to 128. + // As a general guideline, interrupt routines that run longer should be given + // lower priority (higher numerical values). + _uclockTimer.priority(80); +} + +void setTimer(uint32_t us_interval) +{ + _uclockTimer.update(us_interval); +} \ No newline at end of file diff --git a/src/uClock.cpp b/src/uClock.cpp index 3eddb64..b03a50d 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(Teensy, Seedstudio XIAO M0 and ESP32) - * @version 1.2.0 + * @version 1.3.0 * @author Romulo Silva * @date 10/06/2017 * @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co @@ -27,109 +27,46 @@ */ #include "uClock.h" -// Timer setup for work clock // // Teensyduino port // #if defined(TEENSYDUINO) - IntervalTimer _uclockTimer; + #include "platforms/teensy.h" #endif // // Seedstudio XIAO M0 port // #if defined(SEEED_XIAO_M0) - // 24 bits timer - #include - // uses TimerTcc0 - // 16 bits timer - //#include - // uses TimerTc3 + #include "platforms/samd.h" #endif // // ESP32 family // #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) - hw_timer_t * _uclockTimer = NULL; - #define TIMER_ID 0 + #include "platforms/esp32.h" #endif - -// -// multicore archs // -#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) - portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED; - #define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux); +// STM32XX family? // -// singlecore archs -// -#else - #define ATOMIC(X) noInterrupts(); X; interrupts(); +#if defined(ARDUINO_ARCH_STM32) + #include "platforms/stm32.h" #endif -#if defined(ARDUINO_ARCH_AVR) void uclockInitTimer() { - ATOMIC( - // 16bits Timer1 init - // begin at 120bpm (48.0007680122882 Hz) - TCCR1A = 0; // set entire TCCR1A register to 0 - TCCR1B = 0; // same for TCCR1B - TCNT1 = 0; // initialize counter value to 0 - // set compare match register for 48.0007680122882 Hz increments - OCR1A = 41665; // = 16000000 / (8 * 48.0007680122882) - 1 (must be <65536) - // turn on CTC mode - TCCR1B |= (1 << WGM12); - // Set CS12, CS11 and CS10 bits for 8 prescaler - TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10); - // enable timer compare interrupt - TIMSK1 |= (1 << OCIE1A); - ) + // begin at 120bpm (20833us) + const uint32_t init_clock = 20833; + // called from architecture specific module + initTimer(init_clock); } -#else - // forward declaration of ISR - #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) - void ARDUINO_ISR_ATTR uclockISR(); - #else - void uclockISR(); - #endif - -void uclockInitTimer() +void setTimerTempo(float bpm) { - // begin at 120bpm (20833us) - const uint16_t init_clock = 20833; - #if defined(TEENSYDUINO) - _uclockTimer.begin(uclockISR, init_clock); - - // Set the interrupt priority level, controlling which other interrupts - // this timer is allowed to interrupt. Lower numbers are higher priority, - // with 0 the highest and 255 the lowest. Most other interrupts default to 128. - // As a general guideline, interrupt routines that run longer should be given - // lower priority (higher numerical values). - _uclockTimer.priority(0); - #endif - - #if defined(SEEED_XIAO_M0) - TimerTcc0.initialize(init_clock); - - // attach to generic uclock ISR - TimerTcc0.attachInterrupt(uclockISR); - #endif - - #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) - _uclockTimer = timerBegin(TIMER_ID, 80, true); - - // attach to generic uclock ISR - timerAttachInterrupt(_uclockTimer, &uclockISR, true); - - // init clock tick time - timerAlarmWrite(_uclockTimer, init_clock, true); - - // activate it! - timerAlarmEnable(_uclockTimer); - #endif + // convert bpm float into 96 ppqn resolution microseconds interval + uint32_t tick_us_interval = (60000000 / 24 / bpm); + // called from architecture specific module + setTimer(tick_us_interval); } -#endif namespace umodular { namespace clock { @@ -208,59 +145,6 @@ void uClockClass::pause() } } -void uClockClass::setTimerTempo(float bpm) -{ - // 96 ppqn resolution - uint32_t tick_us_interval = (60000000 / 24 / bpm); - -#if defined(ARDUINO_ARCH_AVR) - float tick_hertz_interval = 1/((float)tick_us_interval/1000000); - - uint32_t ocr; - uint8_t tccr = 0; - - // 16bits avr timer setup - if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1 )) < 65535) { - // Set CS12, CS11 and CS10 bits for 1 prescaler - tccr |= (0 << CS12) | (0 << CS11) | (1 << CS10); - } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 8 )) < 65535) { - // Set CS12, CS11 and CS10 bits for 8 prescaler - tccr |= (0 << CS12) | (1 << CS11) | (0 << CS10); - } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 64 )) < 65535) { - // Set CS12, CS11 and CS10 bits for 64 prescaler - tccr |= (0 << CS12) | (1 << CS11) | (1 << CS10); - } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 256 )) < 65535) { - // Set CS12, CS11 and CS10 bits for 256 prescaler - tccr |= (1 << CS12) | (0 << CS11) | (0 << CS10); - } else if ((ocr = AVR_CLOCK_FREQ / ( tick_hertz_interval * 1024 )) < 65535) { - // Set CS12, CS11 and CS10 bits for 1024 prescaler - tccr |= (1 << CS12) | (0 << CS11) | (1 << CS10); - } else { - // tempo not achiavable - return; - } - - ATOMIC( - TCCR1B = 0; - OCR1A = ocr-1; - TCCR1B |= (1 << WGM12); - TCCR1B |= tccr; - ) -#else - #if defined(TEENSYDUINO) - _uclockTimer.update(tick_us_interval); - #endif - - #if defined(SEEED_XIAO_M0) - TimerTcc0.setPeriod(tick_us_interval); - #endif - - #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) - timerAlarmWrite(_uclockTimer, tick_us_interval, true); - #endif -#endif -} - void uClockClass::setTempo(float bpm) { if (mode == EXTERNAL_CLOCK) { diff --git a/src/uClock.h b/src/uClock.h index 09b7a74..ad717fe 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -43,10 +43,6 @@ namespace umodular { namespace clock { #define MIN_BPM 1 #define MAX_BPM 300 -// want a different avr clock support? -// TODO: we should do this using macro guards for avrs different clocks -#define AVR_CLOCK_FREQ 16000000 - #define PHASE_FACTOR 16 #define PLL_X 220 @@ -58,7 +54,6 @@ class uClockClass { private: - void setTimerTempo(float bpm); float inline freqToBpm(uint32_t freq); void (*onClock96PPQNCallback)(uint32_t tick);