diff --git a/examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino b/examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino new file mode 100644 index 0000000..969e403 --- /dev/null +++ b/examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino @@ -0,0 +1,83 @@ +/* Uart MIDI out + * + * This example demonstrates how to send MIDI data via Uart + * interface on STM32 family. + * + * This example code is in the public domain. + * + * Requires STM32Duino board manager to be installed. + * + * Define HardwareSerial using any available UART/USART. + * Nucleo boards have UART/USART pins that are used by the ST-LINK interface (unless using solder bridging). + * + * Tested on Nucleo-F401RE and Nucleo-F072RB (PA9=D8 PA10=D2 on the Arduino pins) + * + * Code by midilab contact@midilab.co + * Example modified by Jackson Devices contact@jacksondevices.com + */ +#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 + +HardwareSerial Serial1(PA10, PA9); + +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 gear + Serial1.write(MIDI_CLOCK); + handle_bpm_led(tick); +} + +void onClockStart() { + // Send MIDI_START to external gear + Serial1.write(MIDI_START); +} + +void onClockStop() { + // Send MIDI_STOP to external gear + Serial1.write(MIDI_STOP); +} + +void setup() { + // Initialize Serial1 communication at 31250 bits per second, the default MIDI Serial1 speed communication: + Serial1.begin(31250); + + // An led to display BPM + 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(120); + // 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/examples/TeensyUsbMasterMidiClock/name.c b/examples/TeensyUsbMasterMidiClock/name.c new file mode 100644 index 0000000..0e26fb3 --- /dev/null +++ b/examples/TeensyUsbMasterMidiClock/name.c @@ -0,0 +1,19 @@ +// To give your project a unique name, this code must be +// placed into a .c file (its own tab). It can not be in +// a .cpp file or your main sketch (the .ino file). + +#include "usb_names.h" + +// Edit these lines to create your own name. The length must +// match the number of characters in your custom name. + +#define MIDI_NAME {'u','c','l','o','c','k','_','1'} +#define MIDI_NAME_LEN 8 + +// Do not change this part. This exact format is required by USB. + +struct usb_string_descriptor_struct usb_string_product_name = { + 2 + MIDI_NAME_LEN * 2, + 3, + MIDI_NAME +}; diff --git a/examples/TeensyUsbSlaveMidiClock/name.c b/examples/TeensyUsbSlaveMidiClock/name.c new file mode 100644 index 0000000..0e26fb3 --- /dev/null +++ b/examples/TeensyUsbSlaveMidiClock/name.c @@ -0,0 +1,19 @@ +// To give your project a unique name, this code must be +// placed into a .c file (its own tab). It can not be in +// a .cpp file or your main sketch (the .ino file). + +#include "usb_names.h" + +// Edit these lines to create your own name. The length must +// match the number of characters in your custom name. + +#define MIDI_NAME {'u','c','l','o','c','k','_','1'} +#define MIDI_NAME_LEN 8 + +// Do not change this part. This exact format is required by USB. + +struct usb_string_descriptor_struct usb_string_product_name = { + 2 + MIDI_NAME_LEN * 2, + 3, + MIDI_NAME +}; diff --git a/examples/TeensyUsbSlaveMidiClockMonitor/name.c b/examples/TeensyUsbSlaveMidiClockMonitor/name.c new file mode 100644 index 0000000..0e26fb3 --- /dev/null +++ b/examples/TeensyUsbSlaveMidiClockMonitor/name.c @@ -0,0 +1,19 @@ +// To give your project a unique name, this code must be +// placed into a .c file (its own tab). It can not be in +// a .cpp file or your main sketch (the .ino file). + +#include "usb_names.h" + +// Edit these lines to create your own name. The length must +// match the number of characters in your custom name. + +#define MIDI_NAME {'u','c','l','o','c','k','_','1'} +#define MIDI_NAME_LEN 8 + +// Do not change this part. This exact format is required by USB. + +struct usb_string_descriptor_struct usb_string_product_name = { + 2 + MIDI_NAME_LEN * 2, + 3, + MIDI_NAME +}; 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..0a8dac9 --- /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 freqeuncy setup at compile time +#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..5d4b906 --- /dev/null +++ b/src/platforms/stm32.h @@ -0,0 +1,33 @@ +#include + +#if !defined(STM32_CORE_VERSION) || (STM32_CORE_VERSION < 0x01090000) + #error "Due to API change, this library is compatible with STM32_CORE_VERSION >= 0x01090000. Please update/install stm32duino core." +#endif + +#if defined(TIM1) + TIM_TypeDef * TimerInstance = TIM1; +#else + TIM_TypeDef * TimerInstance = TIM2; +#endif + +// instantiate HardwareTimer object +HardwareTimer * _uclockTimer = new HardwareTimer(TimerInstance); + +#define ATOMIC(X) noInterrupts(); X; interrupts(); + +// forward declaration of ISR +void uclockISR(); + +void initTimer(uint32_t us_interval) +{ + _uclockTimer->setOverflow(us_interval, MICROSEC_FORMAT); + _uclockTimer->attachInterrupt(uclockISR); + _uclockTimer->resume(); +} + +void setTimer(uint32_t us_interval) +{ + _uclockTimer->setOverflow(us_interval, MICROSEC_FORMAT); + _uclockTimer->refresh(); +} + 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..f204a4f 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,55 @@ */ #include "uClock.h" -// Timer setup for work clock // -// Teensyduino port +// General Arduino AVRs port +// +#if defined(ARDUINO_ARCH_AVR) + #include "platforms/avr.h" +#endif +// +// Teensyduino ARMs 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 +// STM32XX family // -#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); -// -// singlecore archs -// -#else - #define ATOMIC(X) noInterrupts(); X; interrupts(); +#if defined(ARDUINO_ARCH_STM32) + #include "platforms/stm32.h" #endif -#if defined(ARDUINO_ARCH_AVR) +// +// Platform specific timer setup/control +// +// initTimer(uint32_t us_interval) and setTimer(uint32_t us_interval) +// are called from architecture specific module included at the +// header of this file 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) + initTimer(20833); } -#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 us_interval = (60000000 / 24 / bpm); + setTimer(us_interval); } -#endif namespace umodular { namespace clock { @@ -208,59 +154,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);