Merge pull request #24 from midilab/stm32xx-support

* STM32 generic support by using STM32Duino and Hal library.
* Independent platform implementation support for easy arch ports, check platforms/ dir.
* Fixed missing name.c file for usb-midi definitions on teensy arm platforms
pull/26/head v1.3.0
midilab 2 years ago committed by GitHub
commit ea8ef07e5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 83
      examples/STM32UartMasterMidiClock/STM32UartMasterMidiClock.ino
  2. 19
      examples/TeensyUsbMasterMidiClock/name.c
  3. 19
      examples/TeensyUsbSlaveMidiClock/name.c
  4. 19
      examples/TeensyUsbSlaveMidiClockMonitor/name.c
  5. 6
      library.properties
  6. 62
      src/platforms/avr.h
  7. 29
      src/platforms/esp32.h
  8. 27
      src/platforms/samd.h
  9. 33
      src/platforms/stm32.h
  10. 25
      src/platforms/teensy.h
  11. 159
      src/uClock.cpp
  12. 5
      src/uClock.h

@ -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 <uClock.h>
// 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() {
}

@ -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
};

@ -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
};

@ -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
};

@ -1,10 +1,10 @@
name=uClock name=uClock
version=1.2.0 version=1.3.0
author=Romulo Silva <contact@midilab.co> author=Romulo Silva <contact@midilab.co>
maintainer=Romulo Silva <contact@midilab.co> maintainer=Romulo Silva <contact@midilab.co>
sentence=BPM clock generator for Arduino platform. 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 category=Timing
url=https://github.com/midilab/uClock url=https://github.com/midilab/uClock
architectures=avr,arm,samd,esp32 architectures=avr,arm,samd,stm32,esp32
includes=uClock.h includes=uClock.h

@ -0,0 +1,62 @@
#include <Arduino.h>
#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;
)
}

@ -0,0 +1,29 @@
#include <Arduino.h>
#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);
}

@ -0,0 +1,27 @@
#include <Arduino.h>
// 24 bits timer
#include <TimerTCC0.h>
// uses TimerTcc0
// 16 bits timer
//#include <TimerTC3.h>
// 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);
}

@ -0,0 +1,33 @@
#include <Arduino.h>
#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();
}

@ -0,0 +1,25 @@
#include <Arduino.h>
#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);
}

@ -2,7 +2,7 @@
* @file uClock.cpp * @file uClock.cpp
* Project BPM clock generator for Arduino * 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) * @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 * @author Romulo Silva
* @date 10/06/2017 * @date 10/06/2017
* @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co * @license MIT - (c) 2022 - Romulo Silva - contact@midilab.co
@ -27,109 +27,55 @@
*/ */
#include "uClock.h" #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) #if defined(TEENSYDUINO)
IntervalTimer _uclockTimer; #include "platforms/teensy.h"
#endif #endif
// //
// Seedstudio XIAO M0 port // Seedstudio XIAO M0 port
// //
#if defined(SEEED_XIAO_M0) #if defined(SEEED_XIAO_M0)
// 24 bits timer #include "platforms/samd.h"
#include <TimerTCC0.h>
// uses TimerTcc0
// 16 bits timer
//#include <TimerTC3.h>
// uses TimerTc3
#endif #endif
// //
// ESP32 family // ESP32 family
// //
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) #if defined(ARDUINO_ARCH_ESP32) || defined(ESP32)
hw_timer_t * _uclockTimer = NULL; #include "platforms/esp32.h"
#define TIMER_ID 0
#endif #endif
// //
// multicore archs // STM32XX family
// //
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32) #if defined(ARDUINO_ARCH_STM32)
portMUX_TYPE _uclockTimerMux = portMUX_INITIALIZER_UNLOCKED; #include "platforms/stm32.h"
#define ATOMIC(X) portENTER_CRITICAL_ISR(&_uclockTimerMux); X; portEXIT_CRITICAL_ISR(&_uclockTimerMux);
//
// singlecore archs
//
#else
#define ATOMIC(X) noInterrupts(); X; interrupts();
#endif #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() void uclockInitTimer()
{ {
ATOMIC( // begin at 120bpm (20833us)
// 16bits Timer1 init initTimer(20833);
// 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);
)
} }
#else
// forward declaration of ISR void setTimerTempo(float bpm)
#if defined(ARDUINO_ARCH_ESP32) || defined(ESP32)
void ARDUINO_ISR_ATTR uclockISR();
#else
void uclockISR();
#endif
void uclockInitTimer()
{ {
// begin at 120bpm (20833us) // convert bpm float into 96 ppqn resolution microseconds interval
const uint16_t init_clock = 20833; uint32_t us_interval = (60000000 / 24 / bpm);
#if defined(TEENSYDUINO) setTimer(us_interval);
_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
} }
#endif
namespace umodular { namespace clock { 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) void uClockClass::setTempo(float bpm)
{ {
if (mode == EXTERNAL_CLOCK) { if (mode == EXTERNAL_CLOCK) {

@ -43,10 +43,6 @@ namespace umodular { namespace clock {
#define MIN_BPM 1 #define MIN_BPM 1
#define MAX_BPM 300 #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 PHASE_FACTOR 16
#define PLL_X 220 #define PLL_X 220
@ -58,7 +54,6 @@ class uClockClass {
private: private:
void setTimerTempo(float bpm);
float inline freqToBpm(uint32_t freq); float inline freqToBpm(uint32_t freq);
void (*onClock96PPQNCallback)(uint32_t tick); void (*onClock96PPQNCallback)(uint32_t tick);

Loading…
Cancel
Save