uclock 0.10.0. added high resolution clock for teensy power. lower examples midi read interrupt to 250 microseconds

pull/7/head 0.10.0
midilab 4 years ago
parent f7870cad2e
commit 3473865002
  1. 11
      examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino
  2. 2
      examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino
  3. 2
      examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino
  4. 10
      examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino
  5. 2
      library.properties
  6. 217
      src/uClock.cpp
  7. 38
      src/uClock.h

@ -9,12 +9,6 @@
* - USB-MIDI and MIDIUSB
* - u8g2
* - uClock
*
* This example make use of drift values (10, 14)
* respectively for internal and external drift reference.
* This example was tested on a macbook
* runing ableton live 9 as master clock
*
* This example code is in the public domain.
*/
@ -102,19 +96,22 @@ void setup() {
u8x8->begin();
u8x8->setFont(u8x8_font_pressstart2p_r);
u8x8->clear();
u8x8->setFlipMode(true);
u8x8->drawUTF8(0, 0, "uClock");
//
// uClock Setup
//
// fine tunning adjstments for you clock slaves/host setDrift(internal, external)
uClock.setDrift(10, 14);
uClock.setDrift(10, 2);
uClock.init();
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// For MIDI Sync Start and Stop
uClock.setOnClockStartOutput(onClockStart);
uClock.setOnClockStopOutput(onClockStop);
uClock.setMode(uClock.EXTERNAL_CLOCK);
//uClock.setTempo(126.5);
//uClock.start();
}
void printBpm(float _bpm, uint8_t col, uint8_t line) {

@ -56,7 +56,7 @@ void setup() {
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(1);
uClock.setDrift(6, 1);
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.

@ -77,7 +77,7 @@ void setup() {
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(1);
uClock.setDrift(6, 1);
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.

@ -4,14 +4,14 @@
* MIDI hid compilant slave clock box with
* monitor support using oled displays
*
* Making use of a 16 usceconds timer to
* Making use of a 250 usceconds timer to
* handle MIDI input to avoid jitter on clock
*
* You need the following libraries to make it work
* - u8g2
* - uClock
*
* This example make use of drift values (1, 4)
* This example make use of drift values (6, 1)
* respectively for internal and external drift reference.
* This example was tested on a macbook
* running ableton live 9 as master clock
@ -112,15 +112,15 @@ void setup() {
//
// Setup our clock system
// drift for USB Teensy
uClock.setDrift(0, 4);
uClock.setDrift(6, 1);
uClock.init();
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// For MIDI Sync Start and Stop
uClock.setOnClockStartOutput(onClockStart);
uClock.setOnClockStopOutput(onClockStop);
uClock.setMode(uClock.EXTERNAL_CLOCK);
// make use of 16us timer to handle midi input sync
teensyTimer.begin(handleMidiInput, 16);
// make use of 250us timer to handle midi input sync
teensyTimer.begin(handleMidiInput, 250);
teensyTimer.priority(80);
}

@ -1,5 +1,5 @@
name=uClock
version=0.9.4
version=0.10.0
author=Romulo Silva <contact@midilab.co>, Manuel Odendahl <wesen@ruinwesen.com>
maintainer=Romulo Silva <contact@midilab.co>
sentence=BPM clock generator for Arduino and Teensy boards

@ -3,7 +3,7 @@
* Project BPM clock generator for Arduino
* @brief A Library to implement BPM clock tick calls using hardware timer1 interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560.
* Derived work from mididuino MidiClock class. (c) 2008 - 2011 - Manuel Odendahl - wesen@ruinwesen.com
* @version 0.9.4
* @version 0.10.0
* @author Romulo Silva
* @date 08/21/2020
* @license MIT - (c) 2020 - Romulo Silva - contact@midilab.co
@ -31,56 +31,101 @@
#define ATOMIC(X) noInterrupts(); X; interrupts();
//
// Timer setup
// Work clock at: 62.5kHz/16usec
// Timer setup for work clock
//
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
IntervalTimer _teensyTimer;
void teensyInterrupt();
void initTeensyTimer()
void workClock(uint32_t freq_resolution)
{
// 62500Hz
_teensyTimer.begin(teensyInterrupt, 16);
// 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).
_teensyTimer.priority(0);
// fallback default frequency (CLOCK_250000HZ) if no requested freq available
uint8_t microseconds = 4;
const bool running = false;
switch(freq_resolution) {
case CLOCK_62500HZ:
microseconds = 16;
break;
case CLOCK_125000HZ:
microseconds = 8;
break;
case CLOCK_250000HZ:
microseconds = 4;
break;
default:
return;
}
if (running) {
_teensyTimer.update(microseconds);
} else {
_teensyTimer.begin(teensyInterrupt, microseconds);
// 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).
_teensyTimer.priority(0);
}
}
#else
void initArduinoTimer()
{
//
// Configure timers and prescale
// Timmer1: ATMega128, ATMega328, AtMega16U4 and AtMega32U4
// Clock Speed Selection
// CS10: Clock (No prescaling)
// Waveform Generation Mode (WGM) 16-bit timer settings
// (WGM10, WGM12) Mode 5
// Fast Pulse Width Modulation (PWM), 8-bit:
// TOP: 0x00FF (255)
// OCR1x Update: BOTTOM
// TOV1 Flag: TOP
// Overflow Interrupt Enable
ATOMIC(
void workClock(uint32_t freq_resolution)
{
// fallback default frequency (CLOCK_62500HZ) if no requested freq available
uint8_t comparator = 255;
const bool running = false;
switch(freq_resolution) {
case CLOCK_62500HZ:
comparator = 255;
break;
//case CLOCK_125000HZ:
// comparator = 127;
// break;
//case CLOCK_250000HZ:
// comparator = 63;
// break;
default:
return;
}
if (running) {
// update comparator speed of our internal clock system
OCR1A = comparator;
//OCR2A = comparator;
} else {
// Timer1
TCCR1A = 0;
TCCR1A = _BV(WGM10);
TCCR1B = 0;
TCCR1B = _BV(CS10) | _BV(WGM12);
TIMSK1 |= _BV(TOIE1);
)
TCNT1 = 0;
// set the speed of our internal clock system
OCR1A = comparator;
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 1 prescaler
TCCR1B |= (0 << CS12) | (0 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
/*
// Timer2
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
// set the speed of our internal clock system
OCR2A = comparator;
// turn on CTC mode
TCCR2B |= (1 << WGM21);
// Set CS22, CS21 and CS20 bits for 1 prescaler
TCCR2B |= (0 << CS22) | (0 << CS21) | (1 << CS20);
// enable timer compare interrupt
TIMSK2 |= (1 << OCIE2A);
*/
}
}
#endif
void initWorkTimer() {
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
initTeensyTimer();
#else
initArduinoTimer();
#endif
}
namespace umodular { namespace clock {
static inline uint32_t phase_mult(uint32_t val)
@ -99,10 +144,13 @@ static inline uint16_t clock_diff(uint16_t old_clock, uint16_t new_clock)
uClockClass::uClockClass()
{
// some tested values
// 11 is good for native 31250bps midi interface
// 4 is good for usb-to-midi hid on leonardo
// 1 is good on teensy lc usb midi
// (6, 1) is good on teensy lc usb midi
// internal drift is used to calibrate master clock
internal_drift = 11;
// internal drift is used to calibrate slave clock
external_drift = 11;
tempo = 120;
pll_x = 220;
@ -111,6 +159,7 @@ uClockClass::uClockClass()
sync_interval = 0;
state = PAUSED;
mode = INTERNAL_CLOCK;
ext_interval_acc = 0;
resetCounters();
onClock96PPQNCallback = NULL;
@ -119,13 +168,52 @@ uClockClass::uClockClass()
onClockStartCallback = NULL;
onClockStopCallback = NULL;
// set initial default clock operate frequency
// to higher one. If you experience problems
// with your sequencer app process try to go lower
// avr at 16mhz suffers from bellow 16us clock
// but lets get teensy running at higher clock!
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
//freq_resolution = CLOCK_62500HZ; // 62500Hz/16us
//freq_resolution = CLOCK_125000HZ; // 125000Hz/8us
freq_resolution = CLOCK_250000HZ; // 250000Hz/4us
#else
freq_resolution = CLOCK_62500HZ; // 62500Hz/16us
//freq_resolution = CLOCK_125000HZ; // 125000Hz/8us
//freq_resolution = CLOCK_250000HZ; // 250000Hz/4us
#endif
// first interval calculus
setTempo(tempo);
}
void uClockClass::init()
{
initWorkTimer();
// init work clock timer interrupt
workClock(freq_resolution);
}
void uClockClass::setResolution(uint32_t hertz)
{
// only registred frequencies!
switch(hertz) {
case CLOCK_62500HZ:
case CLOCK_125000HZ:
case CLOCK_250000HZ:
break;
default:
return;
}
ATOMIC(
freq_resolution = hertz;
setTempo(tempo);
workClock(freq_resolution);
)
}
uint32_t uClockClass::getResolution()
{
return freq_resolution;
}
void uClockClass::start()
@ -171,18 +259,15 @@ void uClockClass::setTempo(float bpm)
return;
}
if (tempo == bpm) {
return;
}
if (bpm > 300 || bpm < 10) {
if (bpm < MIN_BPM || bpm > MAX_BPM) {
return;
}
tempo = bpm;
ATOMIC(
interval = (uint16_t)((156250.0 / tempo) - internal_drift);
interval = (freq_resolution / (tempo * 24 / 60)) - internal_drift;
//interval = (uint16_t)((156250.0 / tempo) - internal_drift);
//interval = 62500 / (tempo * 24 / 60) - internal_drift;
)
}
@ -200,7 +285,9 @@ float uClockClass::getTempo()
}
if (acc != 0) {
// get average interval, because MIDI sync world is a wild place...
tempo = (float)(156250.0 / ((acc / acc_counter) + external_drift));
tempo = (((float)freq_resolution/24) * 60) / (acc / acc_counter);
// derivated one time calc value = ( freq_resolution / 24 ) * 60
//tempo = (float)(156250.0 / ((acc / acc_counter)));
}
}
return tempo;
@ -212,11 +299,27 @@ void uClockClass::setDrift(uint8_t internal, uint8_t external)
internal_drift = internal;
external_drift = external == 255 ? internal : external;
)
// force set tempo to update runtime interval
setTempo(tempo);
}
uint8_t uClockClass::getMode()
uint8_t uClockClass::getInternalDrift()
{
return mode;
return internal_drift;
}
uint8_t uClockClass::getExternalDrift()
{
return external_drift;
}
uint16_t uClockClass::getInterval()
{
// since this is a debug method
// we are not going to stop interrupt here
// avoiding jitter
// so interval returned here are not always trust data!
return interval;
}
void uClockClass::setMode(uint8_t tempo_mode)
@ -224,6 +327,11 @@ void uClockClass::setMode(uint8_t tempo_mode)
mode = tempo_mode;
}
uint8_t uClockClass::getMode()
{
return mode;
}
void uClockClass::clockMe()
{
if (mode == EXTERNAL_CLOCK) {
@ -243,7 +351,6 @@ void uClockClass::resetCounters()
mod6_counter = 0;
indiv96th_counter = 0;
inmod6_counter = 0;
ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE] = {0};
ext_interval_idx = 0;
}
@ -282,9 +389,9 @@ void uClockClass::handleExternalClock()
case STARTED:
if (indiv96th_counter == 2) {
interval = last_interval;
interval = last_interval + external_drift;
} else {
interval = (((uint32_t)interval * (uint32_t)pll_x) + (uint32_t)(256 - pll_x) * (uint32_t)last_interval) >> 8;
interval = ((((uint32_t)interval * (uint32_t)pll_x) + (uint32_t)(256 - pll_x) * (uint32_t)last_interval) >> 8) + external_drift;
}
// accumulate interval incomming ticks data(for a better getTempo stability over bad clocks)
ext_interval_buffer[ext_interval_idx] = interval;
@ -406,13 +513,13 @@ volatile uint32_t _timer = 0;
#if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__)
void teensyInterrupt()
#else
ISR(TIMER1_OVF_vect)
ISR(TIMER1_COMPA_vect)
#endif
{
// global timer counter
_timer = millis();
if (uClock.state == umodular::clock::STARTED) {
if (uClock.state == uClock.STARTED) {
_clock++;
uClock.handleTimerInt();
}

@ -3,7 +3,7 @@
* Project BPM clock generator for Arduino
* @brief A Library to implement BPM clock tick calls using hardware timer1 interruption. Tested on ATmega168/328, ATmega16u4/32u4 and ATmega2560.
* Derived work from mididuino MidiClock class. (c) 2008 - 2011 - Manuel Odendahl - wesen@ruinwesen.com
* @version 0.9.4
* @version 0.10.0
* @author Romulo Silva
* @date 08/21/2020
* @license MIT - (c) 2020 - Romulo Silva - contact@midilab.co
@ -35,19 +35,20 @@
#define PHASE_FACTOR 16
#define EXT_INTERVAL_BUFFER_SIZE 40
#define EXT_INTERVAL_BUFFER_SIZE 24
#define SECS_PER_MIN (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY (SECS_PER_HOUR * 24L)
namespace umodular { namespace clock {
#define CLOCK_62500HZ 62500 // 16 microseconds
#define CLOCK_125000HZ 125000 // 8 microseconds
#define CLOCK_250000HZ 250000 // 4 microseconds
#define MIN_BPM 1
#define MAX_BPM 300
enum {
PAUSED = 0,
STARTING,
STARTED
} state;
namespace umodular { namespace clock {
class uClockClass {
@ -59,6 +60,9 @@ class uClockClass {
void (*onClockStartCallback)();
void (*onClockStopCallback)();
// expressed in Hertz
uint32_t freq_resolution;
volatile uint8_t inmod6_counter;
volatile uint32_t indiv96th_counter;
volatile uint16_t interval;
@ -81,12 +85,21 @@ class uClockClass {
uint16_t sync_interval;
uint16_t ext_interval_buffer[EXT_INTERVAL_BUFFER_SIZE];
uint32_t ext_interval_acc;
uint16_t ext_interval_idx;
public:
uint8_t INTERNAL_CLOCK = 0;
uint8_t EXTERNAL_CLOCK = 1;
enum {
INTERNAL_CLOCK = 0,
EXTERNAL_CLOCK
};
enum {
PAUSED = 0,
STARTING,
STARTED
};
uint8_t state;
@ -124,6 +137,11 @@ class uClockClass {
void setTempo(float bpm);
float getTempo();
void setDrift(uint8_t internal, uint8_t external = 255);
uint8_t getInternalDrift();
uint8_t getExternalDrift();
void setResolution(uint32_t hertz);
uint32_t getResolution();
uint16_t getInterval();
// external timming control
void setMode(uint8_t tempo_mode);

Loading…
Cancel
Save