From 34738650021a198a36bad6915ffa42a73fe8fe5b Mon Sep 17 00:00:00 2001 From: midilab Date: Tue, 3 Nov 2020 11:59:56 -0500 Subject: [PATCH] uclock 0.10.0. added high resolution clock for teensy power. lower examples midi read interrupt to 250 microseconds --- .../LeonardoUsbSlaveMidiClockMonitor.ino | 11 +- .../TeensyUsbMasterMidiClock.ino | 2 +- .../TeensyUsbSlaveMidiClock.ino | 2 +- .../TeensyUsbSlaveMidiClockMonitor.ino | 10 +- library.properties | 2 +- src/uClock.cpp | 217 +++++++++++++----- src/uClock.h | 38 ++- 7 files changed, 202 insertions(+), 80 deletions(-) diff --git a/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino b/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino index aa55dae..8cb7992 100644 --- a/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino +++ b/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino @@ -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) { diff --git a/examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino b/examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino index 436a8dd..dc7eb05 100644 --- a/examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino +++ b/examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino @@ -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. diff --git a/examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino b/examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino index 1fde0b8..fe079b9 100644 --- a/examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino +++ b/examples/TeensyUsbSlaveMidiClock/TeensyUsbSlaveMidiClock.ino @@ -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. diff --git a/examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino b/examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino index cf291e3..108b462 100644 --- a/examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino +++ b/examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino @@ -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); } diff --git a/library.properties b/library.properties index 3628a4f..34a24d9 100755 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=uClock -version=0.9.4 +version=0.10.0 author=Romulo Silva , Manuel Odendahl maintainer=Romulo Silva sentence=BPM clock generator for Arduino and Teensy boards diff --git a/src/uClock.cpp b/src/uClock.cpp index 6877fb8..9143ed0 100755 --- a/src/uClock.cpp +++ b/src/uClock.cpp @@ -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(); } diff --git a/src/uClock.h b/src/uClock.h index 3043309..04e42d5 100755 --- a/src/uClock.h +++ b/src/uClock.h @@ -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);