From ed2dcef683f90db279252e42d7232ed7a35581af Mon Sep 17 00:00:00 2001 From: midilab Date: Wed, 4 Nov 2020 07:54:44 -0500 Subject: [PATCH] remove last feature - different resolutions. Added setSlaveDrift for slave clock. update and test examples. code revised --- .../LeonardoUsbSlaveMidiClockMonitor.ino | 17 +- .../TeensyUsbMasterMidiClock.ino | 2 +- .../TeensyUsbSlaveMidiClock.ino | 2 +- .../TeensyUsbSlaveMidiClockMonitor.ino | 2 +- library.properties | 2 +- src/uClock.cpp | 256 +++++------------- src/uClock.h | 48 +--- 7 files changed, 95 insertions(+), 234 deletions(-) diff --git a/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino b/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino index 8cb7992..18a1013 100644 --- a/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino +++ b/examples/LeonardoUsbSlaveMidiClockMonitor/LeonardoUsbSlaveMidiClockMonitor.ino @@ -32,6 +32,7 @@ char bpm_str[4]; float bpm = 126.0; uint8_t bpm_blink_timer = 1; uint8_t clock_state = 1; +uint8_t clock_mode = 0; void handle_bpm_led(uint32_t * tick) { @@ -102,15 +103,15 @@ void setup() { // // uClock Setup // - // fine tunning adjstments for you clock slaves/host setDrift(internal, external) - uClock.setDrift(10, 2); + // Drift for arudino leonardo over USB as MIDI HID + uClock.setSlaveDrift(10); 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.setTempo(136.5); //uClock.start(); } @@ -133,7 +134,7 @@ void printBpm(float _bpm, uint8_t col, uint8_t line) { } void loop() { - MIDI.read(); + while(MIDI.read()) {} // DO NOT ADD MORE PROCESS HERE AT THE COST OF LOSING CLOCK SYNC // Since arduino make use of Serial RX interruption we need to // read Serial as fast as we can on the loop @@ -149,4 +150,12 @@ void loop() { u8x8->drawUTF8(0, 7, "stoped "); } } + if (clock_mode != uClock.getMode()) { + clock_mode = uClock.getMode(); + if (clock_mode == uClock.EXTERNAL_CLOCK) { + u8x8->drawUTF8(10, 0, "slave "); + } else { + u8x8->drawUTF8(10, 0, "master"); + } + } } diff --git a/examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino b/examples/TeensyUsbMasterMidiClock/TeensyUsbMasterMidiClock.ino index dc7eb05..436a8dd 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(6, 1); + uClock.setDrift(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 fe079b9..1fde0b8 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(6, 1); + uClock.setDrift(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 108b462..533cb26 100644 --- a/examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino +++ b/examples/TeensyUsbSlaveMidiClockMonitor/TeensyUsbSlaveMidiClockMonitor.ino @@ -112,7 +112,7 @@ void setup() { // // Setup our clock system // drift for USB Teensy - uClock.setDrift(6, 1); + uClock.setDrift(1); uClock.init(); uClock.setClock96PPQNOutput(ClockOut96PPQN); // For MIDI Sync Start and Stop diff --git a/library.properties b/library.properties index 34a24d9..5fdb528 100755 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=uClock -version=0.10.0 +version=0.10.2 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 02a6764..ea8cd78 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.10.0 + * @version 0.10.2 * @author Romulo Silva * @date 08/21/2020 * @license MIT - (c) 2020 - Romulo Silva - contact@midilab.co @@ -36,93 +36,50 @@ #if defined(TEENSYDUINO) && !defined(__AVR_ATmega32U4__) IntervalTimer _teensyTimer; void teensyInterrupt(); -void workClock(uint32_t freq_resolution) +void workClock() { - // 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); - } + _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); } #else -void workClock(uint32_t freq_resolution) +void workClock() { - // 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 { + ATOMIC( // Timer1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; // set the speed of our internal clock system - OCR1A = comparator; + OCR1A = 255; // 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); - - /* + ) + /* + ATOMIC( // Timer2 TCCR2A = 0; TCCR2B = 0; TCNT2 = 0; // set the speed of our internal clock system - OCR2A = comparator; + OCR2A = 255; // 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 @@ -144,16 +101,10 @@ static inline uint16_t clock_diff(uint16_t old_clock, uint16_t new_clock) uClockClass::uClockClass() { - // some tested values - // 1 is good for native 31250bps midi interface - // 4 is good for usb-to-midi hid on leonardo - // (6, 1) is good on teensy lc usb midi - // internal drift is used to calibrate master clock - internal_drift = 1; - // internal drift is used to calibrate slave clock - external_drift = 1; + // drift is used to sligth calibrate with your slave clock + drift = 1; + slave_drift = 0; tempo = 120; - pll_x = 220; start_timer = 0; last_interval = 0; sync_interval = 0; @@ -162,58 +113,14 @@ uClockClass::uClockClass() ext_interval_acc = 0; resetCounters(); - onClock96PPQNCallback = NULL; - onClock32PPQNCallback = NULL; - onClock16PPQNCallback = NULL; - 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() { - // 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; + // init work clock timer interrupt in Hz + workClock(); } void uClockClass::start() @@ -266,9 +173,9 @@ void uClockClass::setTempo(float bpm) tempo = bpm; ATOMIC( - interval = (freq_resolution / (tempo * 24 / 60)) - internal_drift; - //interval = (uint16_t)((156250.0 / tempo) - internal_drift); - //interval = 62500 / (tempo * 24 / 60) - internal_drift; + //interval = (freq_resolution / (tempo * 24 / 60)) - drift; + //interval = 62500 / (tempo * 24 / 60) - drift; + interval = (uint16_t)((156250.0 / tempo) - drift); ) } @@ -285,43 +192,50 @@ float uClockClass::getTempo() } if (acc != 0) { // get average interval, because MIDI sync world is a wild place... - tempo = (((float)freq_resolution/24) * 60) / (acc / acc_counter); + //tempo = (((float)freq_resolution/24) * 60) / (float)(acc / acc_counter); // derivated one time calc value = ( freq_resolution / 24 ) * 60 - //tempo = (float)(156250.0 / ((acc / acc_counter))); + tempo = (float)(156250.0 / (acc / acc_counter)); } } return tempo; } -void uClockClass::setDrift(uint8_t internal, uint8_t external) +void uClockClass::setDrift(uint8_t value) { - ATOMIC( - internal_drift = internal; - external_drift = external == 255 ? internal : external; - ) + ATOMIC(drift = value) // force set tempo to update runtime interval setTempo(tempo); } -uint8_t uClockClass::getInternalDrift() +void uClockClass::setSlaveDrift(uint8_t value) { - return internal_drift; + ATOMIC(slave_drift = value) } -uint8_t uClockClass::getExternalDrift() + +uint8_t uClockClass::getDrift() { - return external_drift; + return drift; } +// each interval is 16us +// this method is usefull for debug 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; } +// Main poolling tick call +uint8_t uClockClass::getTick(uint32_t *_tick) +{ + ATOMIC(uint32_t last_tick = tick) + if (*_tick != last_tick) { + *_tick = last_tick; + return 1; + } + return 0; +} + void uClockClass::setMode(uint8_t tempo_mode) { mode = tempo_mode; @@ -345,12 +259,8 @@ void uClockClass::resetCounters() { counter = 0; last_clock = 0; - div96th_counter = 0; - div32th_counter = 0; - div16th_counter = 0; - mod6_counter = 0; - indiv96th_counter = 0; - inmod6_counter = 0; + tick = 0; + intick = 0; ext_interval_idx = 0; } @@ -370,16 +280,10 @@ void uClockClass::handleExternalClock() { last_interval = clock_diff(last_clock, _clock); last_clock = _clock; - - indiv96th_counter++; - inmod6_counter++; - - if (inmod6_counter == 6) { - inmod6_counter = 0; - } + // slave tick me! + intick++; switch (state) { - case PAUSED: break; @@ -388,14 +292,9 @@ void uClockClass::handleExternalClock() break; case STARTED: - if (indiv96th_counter == 2) { - interval = last_interval + external_drift; - } else { - 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; - ext_interval_idx = ++ext_interval_idx % EXT_INTERVAL_BUFFER_SIZE; + interval = last_interval + slave_drift; + // accumulate interval incomming ticks data for getTempo() smooth reads on slave mode + ext_interval_buffer[ext_interval_idx++ % EXT_INTERVAL_BUFFER_SIZE] = interval; break; } } @@ -403,41 +302,21 @@ void uClockClass::handleExternalClock() void uClockClass::handleTimerInt() { if (counter == 0) { - - counter = interval; - + // need a callback? + // please, use the polling method with getTick() instead... if (onClock96PPQNCallback) { - onClock96PPQNCallback(&div96th_counter); - } - - if (mod6_counter == 0) { - if (onClock32PPQNCallback) { - onClock32PPQNCallback(&div32th_counter); - } - if (onClock16PPQNCallback) { - onClock16PPQNCallback(&div16th_counter); - } - div16th_counter++; - div32th_counter++; + onClock96PPQNCallback(&tick); } - if (mod6_counter == 3) { - if (onClock32PPQNCallback) { - onClock32PPQNCallback(&div32th_counter); - } - div32th_counter++; - } - - div96th_counter++; - mod6_counter++; - + // tick me! + tick++; + counter = interval; if (mode == EXTERNAL_CLOCK) { sync_interval = clock_diff(last_clock, _clock); - if ((div96th_counter < indiv96th_counter) || (div96th_counter > (indiv96th_counter + 1))) { - div96th_counter = indiv96th_counter; - mod6_counter = inmod6_counter; + if ((tick < intick) || (tick > (intick + 1))) { + tick = intick; } - if (div96th_counter <= indiv96th_counter) { + if (tick <= intick) { counter -= phase_mult(sync_interval); } else { if (counter > sync_interval) { @@ -445,15 +324,9 @@ void uClockClass::handleTimerInt() } } } - - if (mod6_counter == 6) { - mod6_counter = 0; - } - } else { counter--; } - } // elapsed time support @@ -514,6 +387,7 @@ volatile uint32_t _timer = 0; void teensyInterrupt() #else ISR(TIMER1_COMPA_vect) +//ISR(TIMER2_COMPA_vect) #endif { // global timer counter diff --git a/src/uClock.h b/src/uClock.h index 04e42d5..a6f3bc4 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.10.0 + * @version 0.10.2 * @author Romulo Silva * @date 08/21/2020 * @license MIT - (c) 2020 - Romulo Silva - contact@midilab.co @@ -41,10 +41,6 @@ #define SECS_PER_HOUR (3600UL) #define SECS_PER_DAY (SECS_PER_HOUR * 24L) -#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 @@ -53,30 +49,21 @@ namespace umodular { namespace clock { class uClockClass { private: - + void (*onClock96PPQNCallback)(uint32_t * tick); - void (*onClock32PPQNCallback)(uint32_t * tick); - void (*onClock16PPQNCallback)(uint32_t * tick); void (*onClockStartCallback)(); void (*onClockStopCallback)(); - // expressed in Hertz - uint32_t freq_resolution; - - volatile uint8_t inmod6_counter; - volatile uint32_t indiv96th_counter; + volatile uint32_t tick; + volatile uint32_t intick; volatile uint16_t interval; volatile uint16_t last_clock; - - uint32_t div96th_counter; - uint32_t div32th_counter; - uint32_t div16th_counter; - uint8_t mod6_counter; + uint16_t counter; + uint32_t last_tick; - uint16_t pll_x; - uint8_t internal_drift; - uint8_t external_drift; + uint8_t drift; + uint8_t slave_drift; float tempo; uint32_t start_timer; uint8_t mode; @@ -108,15 +95,7 @@ class uClockClass { void setClock96PPQNOutput(void (*callback)(uint32_t * tick)) { onClock96PPQNCallback = callback; } - - void setClock32PPQNOutput(void (*callback)(uint32_t * tick)) { - onClock32PPQNCallback = callback; - } - - void setClock16PPQNOutput(void (*callback)(uint32_t * tick)) { - onClock16PPQNCallback = callback; - } - + void setOnClockStartOutput(void (*callback)()) { onClockStartCallback = callback; } @@ -136,12 +115,11 @@ class uClockClass { void pause(); 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(); + void setDrift(uint8_t value); + uint8_t getDrift(); + void setSlaveDrift(uint8_t value); uint16_t getInterval(); + uint8_t getTick(uint32_t *_tick); // external timming control void setMode(uint8_t tempo_mode);