diff --git a/cores/esp8266/Arduino.h b/cores/esp8266/Arduino.h index 5424d3baf3..b274f17121 100644 --- a/cores/esp8266/Arduino.h +++ b/cores/esp8266/Arduino.h @@ -279,6 +279,7 @@ unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 100000 unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L); void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0); +void tone(uint8_t _pin, double frequency, unsigned long duration = 0); void noTone(uint8_t _pin); // WMath prototypes diff --git a/cores/esp8266/Tone.cpp b/cores/esp8266/Tone.cpp index fb7837ee46..a3345bb612 100644 --- a/cores/esp8266/Tone.cpp +++ b/cores/esp8266/Tone.cpp @@ -3,7 +3,7 @@ A Tone Generator Library for the ESP8266 - Copyright (c) 2016 Ben Pirt. All rights reserved. + Original Copyright (c) 2016 Ben Pirt. All rights reserved. This file is part of the esp8266 core for Arduino environment. This library is free software; you can redistribute it and/or @@ -22,115 +22,59 @@ */ #include "Arduino.h" -#include "pins_arduino.h" +#include "core_esp8266_waveform.h" -#define AVAILABLE_TONE_PINS 1 -const uint8_t tone_timers[] = { 1 }; -static uint8_t tone_pins[AVAILABLE_TONE_PINS] = { 255, }; -static long toggle_counts[AVAILABLE_TONE_PINS] = { 0, }; -#define T1INDEX 0 +// Which pins have a tone running on them? +static uint32_t _toneMap = 0; -void t1IntHandler(); -static int8_t toneBegin(uint8_t _pin) { - int8_t _index = -1; - - // if we're already using the pin, reuse it. - for (int i = 0; i < AVAILABLE_TONE_PINS; i++) { - if (tone_pins[i] == _pin) { - return i; - } +static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) { + if (_pin > 16) { + return; } - // search for an unused timer. - for (int i = 0; i < AVAILABLE_TONE_PINS; i++) { - if (tone_pins[i] == 255) { - tone_pins[i] = _pin; - _index = i; - break; - } - } + pinMode(_pin, OUTPUT); - return _index; -} + high = std::max(high, (uint32_t)100); + low = std::max(low, (uint32_t)100); -// frequency (in hertz) and duration (in milliseconds). -void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) { - int8_t _index; - - _index = toneBegin(_pin); - - if (_index >= 0) { - // Set the pinMode as OUTPUT - pinMode(_pin, OUTPUT); - - // Alternate handling of zero freqency to avoid divide by zero errors - if (frequency == 0) - { - noTone(_pin); - return; - } - - // Calculate the toggle count - if (duration > 0) { - toggle_counts[_index] = 2 * frequency * duration / 1000; - } else { - toggle_counts[_index] = -1; - } - - // set up the interrupt frequency - switch (tone_timers[_index]) { - case 0: - // Not currently supported - break; - - case 1: - timer1_disable(); - timer1_isr_init(); - timer1_attachInterrupt(t1IntHandler); - timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP); - timer1_write((clockCyclesPerMicrosecond() * 500000) / frequency); - break; - } + if (startWaveform(_pin, high, low, (uint32_t) duration * 1000)) { + _toneMap |= 1 << _pin; } } -void disableTimer(uint8_t _index) { - tone_pins[_index] = 255; - - switch (tone_timers[_index]) { - case 0: - // Not currently supported - break; - case 1: - timer1_disable(); - break; +void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) { + if (frequency == 0) { + noTone(_pin); + } else { + uint32_t period = 1000000L / frequency; + uint32_t high = period / 2; + uint32_t low = period - high; + _startTone(_pin, high, low, duration); } } -void noTone(uint8_t _pin) { - for (int i = 0; i < AVAILABLE_TONE_PINS; i++) { - if (tone_pins[i] == _pin) { - tone_pins[i] = 255; - disableTimer(i); - break; - } + +// Separate tone(float) to hopefully not pull in floating point libs unless +// it's called with a float. +void tone(uint8_t _pin, double frequency, unsigned long duration) { + if (frequency < 1.0) { // FP means no exact comparisons + noTone(_pin); + } else { + double period = 1000000.0 / frequency; + uint32_t high = (uint32_t)((period / 2.0) + 0.5); + uint32_t low = (uint32_t)(period + 0.5) - high; + _startTone(_pin, high, low, duration); } - digitalWrite(_pin, LOW); } -ICACHE_RAM_ATTR void t1IntHandler() { - if (toggle_counts[T1INDEX] != 0){ - // toggle the pin - digitalWrite(tone_pins[T1INDEX], toggle_counts[T1INDEX] % 2); - toggle_counts[T1INDEX]--; - // handle the case of indefinite duration - if (toggle_counts[T1INDEX] < -2){ - toggle_counts[T1INDEX] = -1; - } - }else{ - disableTimer(T1INDEX); - digitalWrite(tone_pins[T1INDEX], LOW); + +void noTone(uint8_t _pin) { + if (_pin > 16) { + return; } + stopWaveform(_pin); + _toneMap &= ~(1 << _pin); + digitalWrite(_pin, 0); } diff --git a/cores/esp8266/base64.cpp b/cores/esp8266/base64.cpp old mode 100755 new mode 100644 diff --git a/cores/esp8266/base64.h b/cores/esp8266/base64.h old mode 100755 new mode 100644 diff --git a/cores/esp8266/core_esp8266_waveform.c b/cores/esp8266/core_esp8266_waveform.c new file mode 100644 index 0000000000..0454c768b2 --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform.c @@ -0,0 +1,304 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + + The core idea is to have a programmable waveform generator with a unique + high and low period (defined in microseconds). TIMER1 is set to 1-shot + mode and is always loaded with the time until the next edge of any live + waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "cycles" is used, it means ESP.getCycleTime() + cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "core_esp8266_waveform.h" + +// Need speed, not size, here +#pragma GCC optimize ("O3") + +// Map the IRQ stuff to standard terminology +#define cli() ets_intr_lock() +#define sei() ets_intr_unlock() + +// Maximum delay between IRQs +#define MAXIRQUS (10000) + +// If the cycles from now to an event are below this value, perform it anyway since IRQs take longer than this +#define CYCLES_FLUFF (100) + +// Macro to get count of predefined array elements +#define countof(a) ((size_t)(sizeof(a)/sizeof(a[0]))) + +// Set/clear *any* GPIO +#define SetGPIOPin(a) do { if (a < 16) { GPOS |= (1<high to keep smooth waveform + unsigned enabled : 1; // Is this GPIO generating a waveform? + unsigned nextTimeLowCycles : 31; // Copy over high->low to keep smooth waveform +} Waveform; + +// These can be accessed in interrupts, so ensure to bracket access with SEI/CLI +static Waveform waveform[] = { + {0, 0, 1<<0, 0, 0, 0, 0, 0}, // GPIO0 + {0, 0, 1<<1, 0, 0, 0, 0, 0}, // GPIO1 + {0, 0, 1<<2, 0, 0, 0, 0, 0}, + {0, 0, 1<<3, 0, 0, 0, 0, 0}, + {0, 0, 1<<4, 0, 0, 0, 0, 0}, + {0, 0, 1<<5, 0, 0, 0, 0, 0}, + // GPIOS 6-11 not allowed, used for flash + {0, 0, 1<<12, 0, 0, 0, 0, 0}, + {0, 0, 1<<13, 0, 0, 0, 0, 0}, + {0, 0, 1<<14, 0, 0, 0, 0, 0}, + {0, 0, 1<<15, 0, 0, 0, 0, 0}, + {0, 0, 0, 1, 0, 0, 0, 0} // GPIO16 +}; + +static uint32_t (*timer1CB)() = NULL;; + + +// Helper functions +static inline ICACHE_RAM_ATTR uint32_t MicrosecondsToCycles(uint32_t microseconds) { + return clockCyclesPerMicrosecond() * microseconds; +} + +static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) { + if (a < b) { + return a; + } + return b; +} + +static inline ICACHE_RAM_ATTR uint32_t min_s32(int32_t a, int32_t b) { + if (a < b) { + return a; + } + return b; +} + +static inline ICACHE_RAM_ATTR void ReloadTimer(uint32_t a) { + // Below a threshold you actually miss the edge IRQ, so ensure enough time + if (a > 32) { + timer1_write(a); + } else { + timer1_write(32); + } +} + +static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() { + uint32_t ccount; + __asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount)); + return ccount; +} + +// Interrupt on/off control +static ICACHE_RAM_ATTR void timer1Interrupt(); +static uint8_t timerRunning = false; +static uint32_t lastCycleCount = 0; // Last ESP cycle counter on running the interrupt routine + +static void initTimer() { + timer1_disable(); + timer1_isr_init(); + timer1_attachInterrupt(timer1Interrupt); + lastCycleCount = GetCycleCount(); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); + timerRunning = true; +} + +static void deinitTimer() { + timer1_attachInterrupt(NULL); + timer1_disable(); + timer1_isr_init(); + timerRunning = false; +} + +// Set a callback. Pass in NULL to stop it +void setTimer1Callback(uint32_t (*fn)()) { + timer1CB = fn; + if (!timerRunning && fn) { + initTimer(); + } else if (timerRunning && !fn) { + int cnt = 0; + for (size_t i = 0; i < countof(waveform); i++) { + cnt += waveform[i].enabled ? 1 : 0; + } + if (!cnt) { + deinitTimer(); + } + } + ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste +} + +// Start up a waveform on a pin, or change the current one. Will change to the new +// waveform smoothly on next low->high transition. For immediate change, stopWaveform() +// first, then it will immediately begin. +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) { + Waveform *wave = NULL; + for (size_t i = 0; i < countof(waveform); i++) { + if (((pin == 16) && waveform[i].gpio16Mask==1) || ((pin != 16) && (waveform[i].gpioMask == 1<nextTimeHighCycles = MicrosecondsToCycles(timeHighUS) - 70; // Take out some time for IRQ codepath + wave->nextTimeLowCycles = MicrosecondsToCycles(timeLowUS) - 70; // Take out some time for IRQ codepath + wave->timeLeftCycles = MicrosecondsToCycles(runTimeUS); + if (!wave->enabled) { + wave->state = 0; + // Actually set the pin high or low in the IRQ service to guarantee times + wave->nextServiceCycle = GetCycleCount() + MicrosecondsToCycles(1); + wave->enabled = 1; + if (!timerRunning) { + initTimer(); + } + ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste + } + return true; +} + +// Stops a waveform on a pin +int stopWaveform(uint8_t pin) { + for (size_t i = 0; i < countof(waveform); i++) { + if (((pin == 16) && waveform[i].gpio16Mask) || ((pin != 16) && (waveform[i].gpioMask == 1<enabled) { + continue; + } + + // Check for toggles + now = GetCycleCount(); + if (now >= wave->nextServiceCycle) { + wave->state = !wave->state; + if (wave->state) { + SetGPIO(wave->gpioMask); + if (wave->gpio16Mask) { + GP16O |= wave->gpio16Mask; // GPIO16 write slow as it's RMW + } + wave->nextServiceCycle = now + wave->nextTimeHighCycles; + nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles); + } else { + ClearGPIO(wave->gpioMask); + if (wave->gpio16Mask) { + GP16O &= ~wave->gpio16Mask; + } + wave->nextServiceCycle = now + wave->nextTimeLowCycles; + nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles); + } + } else { + uint32_t deltaCycles = wave->nextServiceCycle - now; + nextEventCycles = min_u32(nextEventCycles, deltaCycles); + } + } + } while (--cnt && (nextEventCycles < MicrosecondsToCycles(4))); + + uint32_t curCycleCount = GetCycleCount(); + uint32_t deltaCycles = curCycleCount - lastCycleCount; + lastCycleCount = curCycleCount; + + // Check for timed-out waveforms out of the high-frequency toggle loop + for (size_t i = 0; i < countof(waveform); i++) { + Waveform *wave = &waveform[i]; + if (wave->timeLeftCycles) { + // Check for unsigned underflow with new > old + if (deltaCycles >= wave->timeLeftCycles) { + // Done, remove! + wave->enabled = false; + ClearGPIO(wave->gpioMask); + GP16O &= ~wave->gpio16Mask; + } else { + uint32_t newTimeLeftCycles = wave->timeLeftCycles - deltaCycles; + wave->timeLeftCycles = newTimeLeftCycles; + } + } + } + + if (timer1CB) { + nextEventCycles = min_u32(nextEventCycles, timer1CB()); + } + + #if F_CPU == 160000000 + if (nextEventCycles <= 5 * MicrosecondsToCycles(1)) { + nextEventCycles = MicrosecondsToCycles(1) / 2; + } else { + nextEventCycles -= 5 * MicrosecondsToCycles(1); + } + nextEventCycles = nextEventCycles >> 1; + #else + if (nextEventCycles <= 6 * MicrosecondsToCycles(1)) { + nextEventCycles = MicrosecondsToCycles(1) / 2; + } else { + nextEventCycles -= 6 * MicrosecondsToCycles(1); + } + #endif + + ReloadTimer(nextEventCycles); +} diff --git a/cores/esp8266/core_esp8266_waveform.h b/cores/esp8266/core_esp8266_waveform.h new file mode 100644 index 0000000000..24ce91fb36 --- /dev/null +++ b/cores/esp8266/core_esp8266_waveform.h @@ -0,0 +1,71 @@ +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + + The core idea is to have a programmable waveform generator with a unique + high and low period (defined in microseconds). TIMER1 is set to 1-shot + mode and is always loaded with the time until the next edge of any live + waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "cycles" is used, it means ESP.getCycleTime() + cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include + +#ifndef __ESP8266_WAVEFORM_H +#define __ESP8266_WAVEFORM_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Start or change a waveform of the specified high and low times on specific pin. +// If runtimeUS > 0 then automatically stop it after that many usecs. +// Returns true or false on success or failure. +int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS); +// Stop a waveform, if any, on the specified pin. +// Returns true or false on success or failure. +int stopWaveform(uint8_t pin); + +// Add a callback function to be called on *EVERY* timer1 trigger. The +// callback returns the number of microseconds until the next desired call. +// However, since it is called every timer1 interrupt, it may be called +// again before this period. It should therefore use the ESP Cycle Counter +// to determine whether or not to perform an operation. +// Pass in NULL to disable the callback and, if no other waveforms being +// generated, stop the timer as well. +// Make sure the CB function has the ICACHE_RAM_ATTR decorator. +void setTimer1Callback(uint32_t (*fn)()); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/cores/esp8266/core_esp8266_wiring_digital.c b/cores/esp8266/core_esp8266_wiring_digital.c index 6b9b129cac..4ee398d615 100644 --- a/cores/esp8266/core_esp8266_wiring_digital.c +++ b/cores/esp8266/core_esp8266_wiring_digital.c @@ -24,13 +24,12 @@ #include "c_types.h" #include "eagle_soc.h" #include "ets_sys.h" - -extern void pwm_stop_pin(uint8_t pin); +#include "core_esp8266_waveform.h" uint8_t esp8266_gpioToFn[16] = {0x34, 0x18, 0x38, 0x14, 0x3C, 0x40, 0x1C, 0x20, 0x24, 0x28, 0x2C, 0x30, 0x04, 0x08, 0x0C, 0x10}; extern void __pinMode(uint8_t pin, uint8_t mode) { - pwm_stop_pin(pin); + stopWaveform(pin); if(pin < 16){ if(mode == SPECIAL){ GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED) @@ -80,7 +79,7 @@ extern void __pinMode(uint8_t pin, uint8_t mode) { } extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) { - pwm_stop_pin(pin); + stopWaveform(pin); if(pin < 16){ if(val) GPOS = (1 << pin); else GPOC = (1 << pin); @@ -91,7 +90,7 @@ extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) { } extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) { - pwm_stop_pin(pin); + stopWaveform(pin); if(pin < 16){ return GPIP(pin); } else if(pin == 16){ diff --git a/cores/esp8266/core_esp8266_wiring_pwm.c b/cores/esp8266/core_esp8266_wiring_pwm.c index 671eaa4145..153f30ee37 100644 --- a/cores/esp8266/core_esp8266_wiring_pwm.c +++ b/cores/esp8266/core_esp8266_wiring_pwm.c @@ -1,7 +1,9 @@ /* pwm.c - analogWrite implementation for esp8266 - Copyright (c) 2015 Hristo Gochkov. All rights reserved. + Use the shared TIMER1 utilities to generate PWM signals + + Original Copyright (c) 2015 Hristo Gochkov. All rights reserved. This file is part of the esp8266 core for Arduino environment. This library is free software; you can redistribute it and/or @@ -18,204 +20,58 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "wiring_private.h" -#include "pins_arduino.h" -#include "c_types.h" -#include "eagle_soc.h" -#include "ets_sys.h" - -#ifndef F_CPU -#define F_CPU 800000000L -#endif - -struct pwm_isr_table { - uint8_t len; - uint16_t steps[17]; - uint32_t masks[17]; -}; -struct pwm_isr_data { - struct pwm_isr_table tables[2]; - uint8_t active;//0 or 1, which table is active in ISR -}; +#include +#include "core_esp8266_waveform.h" -static struct pwm_isr_data _pwm_isr_data; -uint32_t pwm_mask = 0; -uint16_t pwm_values[17] = {0,}; -uint32_t pwm_freq = 1000; -uint32_t pwm_range = PWMRANGE; +static uint32_t analogMap = 0; +static int32_t analogScale = 255; +static uint16_t analogFreq = 1000; -uint8_t pwm_steps_changed = 0; -uint32_t pwm_multiplier = 0; - -int pwm_sort_array(uint16_t a[], uint16_t al) -{ - uint16_t i, j; - for (i = 1; i < al; i++) { - uint16_t tmp = a[i]; - for (j = i; j >= 1 && tmp < a[j-1]; j--) { - a[j] = a[j-1]; - } - a[j] = tmp; - } - int bl = 1; - for(i = 1; i < al; i++) { - if(a[i] != a[i-1]) { - a[bl++] = a[i]; - } - } - return bl; +extern void __analogWriteRange(uint32_t range) { + if (range > 0) { + analogScale = range; + } } -uint32_t pwm_get_mask(uint16_t value) -{ - uint32_t mask = 0; - int i; - for(i=0; i<17; i++) { - if((pwm_mask & (1 << i)) != 0 && pwm_values[i] == value) { - mask |= (1 << i); - } - } - return mask; +extern void __analogWriteFreq(uint32_t freq) { + if (freq < 100) { + analogFreq = 100; + } else if (freq > 40000) { + analogFreq = 40000; + } else { + analogFreq = freq; + } } -void prep_pwm_steps() -{ - if(pwm_mask == 0) { - return; +extern void __analogWrite(uint8_t pin, int val) { + if (pin >= 16) { + return; + } + uint32_t analogPeriod = 1000000L / analogFreq; + if (val < 0) { + val = 0; + } else if (val > analogScale) { + val = analogScale; + } + + analogMap &= ~(1 << pin); + uint32_t high = (analogPeriod * val) / analogScale; + uint32_t low = analogPeriod - high; + if (low == 0) { + stopWaveform(pin); + digitalWrite(pin, HIGH); + } else if (high == 0) { + stopWaveform(pin); + digitalWrite(pin, LOW); + } else { + if (startWaveform(pin, high, low, 0)) { + analogMap |= (1 << pin); } - - int pwm_temp_steps_len = 0; - uint16_t pwm_temp_steps[17]; - uint32_t pwm_temp_masks[17]; - uint32_t range = pwm_range; - - if((F_CPU / ESP8266_CLOCK) == 1) { - range /= 2; - } - - int i; - for(i=0; i<17; i++) { - if((pwm_mask & (1 << i)) != 0 && pwm_values[i] != 0) { - pwm_temp_steps[pwm_temp_steps_len++] = pwm_values[i]; - } - } - pwm_temp_steps[pwm_temp_steps_len++] = range; - pwm_temp_steps_len = pwm_sort_array(pwm_temp_steps, pwm_temp_steps_len) - 1; - for(i=0; i0; i--) { - pwm_temp_steps[i] = pwm_temp_steps[i] - pwm_temp_steps[i-1]; - } - - pwm_steps_changed = 0; - struct pwm_isr_table *table = &(_pwm_isr_data.tables[!_pwm_isr_data.active]); - table->len = pwm_temp_steps_len; - ets_memcpy(table->steps, pwm_temp_steps, (pwm_temp_steps_len + 1) * 2); - ets_memcpy(table->masks, pwm_temp_masks, pwm_temp_steps_len * 4); - pwm_multiplier = ESP8266_CLOCK/(range * pwm_freq); - pwm_steps_changed = 1; -} - -void ICACHE_RAM_ATTR pwm_timer_isr() //103-138 -{ - struct pwm_isr_table *table = &(_pwm_isr_data.tables[_pwm_isr_data.active]); - static uint8_t current_step = 0; - TEIE &= ~TEIE1;//14 - T1I = 0;//9 - if(current_step < table->len) { //20/21 - uint32_t mask = table->masks[current_step] & pwm_mask; - if(mask & 0xFFFF) { - GPOC = mask & 0xFFFF; //15/21 - } - if(mask & 0x10000) { - GP16O = 0; //6/13 - } - current_step++;//1 - } else { - current_step = 0;//1 - if(pwm_mask == 0) { //12 - table->len = 0; - return; - } - if(pwm_mask & 0xFFFF) { - GPOS = pwm_mask & 0xFFFF; //11 - } - if(pwm_mask & 0x10000) { - GP16O = 1; //5/13 - } - if(pwm_steps_changed) { //12/21 - _pwm_isr_data.active = !_pwm_isr_data.active; - table = &(_pwm_isr_data.tables[_pwm_isr_data.active]); - pwm_steps_changed = 0; - } - } - T1L = (table->steps[current_step] * pwm_multiplier);//23 - TEIE |= TEIE1;//13 -} - -void pwm_start_timer() -{ - timer1_disable(); - ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); - ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_timer_isr); - timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); - timer1_write(1); -} - -void ICACHE_RAM_ATTR pwm_stop_pin(uint8_t pin) -{ - if(pwm_mask){ - pwm_mask &= ~(1 << pin); - if(pwm_mask == 0) { - ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); - timer1_disable(); - timer1_isr_init(); - } - } -} - -extern void __analogWrite(uint8_t pin, int value) -{ - bool start_timer = false; - if(value == 0) { - digitalWrite(pin, LOW); - prep_pwm_steps(); - return; - } - if((pwm_mask & (1 << pin)) == 0) { - if(pwm_mask == 0) { - memset(&_pwm_isr_data, 0, sizeof(_pwm_isr_data)); - start_timer = true; - } - pinMode(pin, OUTPUT); - digitalWrite(pin, LOW); - pwm_mask |= (1 << pin); - } - if((F_CPU / ESP8266_CLOCK) == 1) { - value = (value+1) / 2; - } - pwm_values[pin] = value % (pwm_range + 1); - prep_pwm_steps(); - if(start_timer) { - pwm_start_timer(); - } -} - -extern void __analogWriteFreq(uint32_t freq) -{ - pwm_freq = freq; - prep_pwm_steps(); -} - -extern void __analogWriteRange(uint32_t range) -{ - pwm_range = range; - prep_pwm_steps(); + } } -extern void analogWrite(uint8_t pin, int val) __attribute__ ((weak, alias("__analogWrite"))); -extern void analogWriteFreq(uint32_t freq) __attribute__ ((weak, alias("__analogWriteFreq"))); -extern void analogWriteRange(uint32_t range) __attribute__ ((weak, alias("__analogWriteRange"))); +extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite"))); +extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq"))); +extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange"))); diff --git a/libraries/Servo/src/Servo.cpp b/libraries/Servo/src/Servo.cpp new file mode 100644 index 0000000000..305d2c82a5 --- /dev/null +++ b/libraries/Servo/src/Servo.cpp @@ -0,0 +1,129 @@ +/* +Servo library using shared TIMER1 infrastructure + +Original Copyright (c) 2015 Michael C. Miller. All right reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined(ESP8266) + +#include +#include +#include "core_esp8266_waveform.h" + +// similiar to map but will have increased accuracy that provides a more +// symetric api (call it and use result to reverse will provide the original value) +int improved_map(int value, int minIn, int maxIn, int minOut, int maxOut) +{ + const int rangeIn = maxIn - minIn; + const int rangeOut = maxOut - minOut; + const int deltaIn = value - minIn; + // fixed point math constants to improve accurancy of divide and rounding + const int fixedHalfDecimal = 1; + const int fixedDecimal = fixedHalfDecimal * 2; + + return ((deltaIn * rangeOut * fixedDecimal) / (rangeIn) + fixedHalfDecimal) / fixedDecimal + minOut; +} + +//------------------------------------------------------------------- +// Servo class methods + +Servo::Servo() +{ + _attached = false; + _valueUs = DEFAULT_PULSE_WIDTH; + _minUs = MIN_PULSE_WIDTH; + _maxUs = MAX_PULSE_WIDTH; +} + +Servo::~Servo() { + detach(); +} + + +uint8_t Servo::attach(int pin) +{ + return attach(pin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); +} + +uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) +{ + if (!_attached) { + digitalWrite(pin, LOW); + pinMode(pin, OUTPUT); + _pin = pin; + _attached = true; + } + + // keep the min and max within 200-3000 us, these are extreme + // ranges and should support extreme servos while maintaining + // reasonable ranges + _maxUs = max((uint16_t)250, min((uint16_t)3000, maxUs)); + _minUs = max((uint16_t)200, min(_maxUs, minUs)); + + write(_valueUs); + + return pin; +} + +void Servo::detach() +{ + if (_attached) { + stopWaveform(_pin); + _attached = false; + digitalWrite(_pin, LOW); + } +} + +void Servo::write(int value) +{ + // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds) + if (value < MIN_PULSE_WIDTH) { + // assumed to be 0-180 degrees servo + value = constrain(value, 0, 180); + // writeMicroseconds will contrain the calculated value for us + // for any user defined min and max, but we must use default min max + value = improved_map(value, 0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); + } + writeMicroseconds(value); +} + +void Servo::writeMicroseconds(int value) +{ + _valueUs = value; + if (_attached) { + startWaveform(_pin, _valueUs, REFRESH_INTERVAL - _valueUs, 0); + } +} + +int Servo::read() // return the value as degrees +{ + // read returns the angle for an assumed 0-180, so we calculate using + // the normal min/max constants and not user defined ones + return improved_map(readMicroseconds(), MIN_PULSE_WIDTH, MAX_PULSE_WIDTH, 0, 180); +} + +int Servo::readMicroseconds() +{ + return _valueUs; +} + +bool Servo::attached() +{ + return _attached; +} + +#endif diff --git a/libraries/Servo/src/Servo.h b/libraries/Servo/src/Servo.h index d4bf861baf..6b19a477bd 100644 --- a/libraries/Servo/src/Servo.h +++ b/libraries/Servo/src/Servo.h @@ -1,6 +1,6 @@ /* Servo.h - Interrupt driven Servo library for Esp8266 using timers - Copyright (c) 2015 Michael C. Miller. All right reserved. + Original Copyright (c) 2015 Michael C. Miller. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -23,13 +23,6 @@ // The servos are pulsed in the background using the value most recently // written using the write() method. // -// This library uses timer0 and timer1. -// Note that timer0 may be repurposed when the first servo is attached. -// -// Timers are seized as needed in groups of 12 servos - 24 servos use two -// timers, there are only two timers for the esp8266 so the support stops here -// The sequence used to sieze timers is defined in timers.h -// // The methods are: // // Servo - Class for manipulating servo motors connected to Arduino pins. @@ -58,15 +51,7 @@ #define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached #define REFRESH_INTERVAL 20000 // minumim time to refresh servos in microseconds -// NOTE: to maintain a strict refresh interval the user needs to not exceede 8 servos -#define SERVOS_PER_TIMER 12 // the maximum number of servos controlled by one timer -#define MAX_SERVOS (ServoTimerSequence_COUNT * SERVOS_PER_TIMER) - -#if defined(ESP8266) - -#include "esp8266/ServoTimers.h" - -#else +#if !defined(ESP8266) #error "This library only supports esp8266 boards." @@ -76,6 +61,7 @@ class Servo { public: Servo(); + ~Servo(); uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure uint8_t attach(int pin, uint16_t min, uint16_t max); // as above but also sets min and max values for writes. void detach(); @@ -85,9 +71,11 @@ class Servo int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release) bool attached(); // return true if this servo is attached, otherwise false private: - uint8_t _servoIndex; // index into the channel data for this servo + bool _attached; + uint8_t _pin; uint16_t _minUs; uint16_t _maxUs; + uint16_t _valueUs; }; #endif diff --git a/libraries/Servo/src/esp8266/Servo.cpp b/libraries/Servo/src/esp8266/Servo.cpp deleted file mode 100644 index 676aab2cc6..0000000000 --- a/libraries/Servo/src/esp8266/Servo.cpp +++ /dev/null @@ -1,308 +0,0 @@ -/* -Copyright (c) 2015 Michael C. Miller. All right reserved. - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#if defined(ESP8266) - -#include -#include - - -#define INVALID_SERVO 255 // flag indicating an invalid servo index - -const uint32_t c_CycleCompensation = 4; // compensation us to trim adjust for digitalWrite delays - -#define INVALID_PIN 63 // flag indicating never attached servo - -struct ServoInfo { - uint8_t pin : 6; // a pin number from 0 to 62, 63 reserved - uint8_t isActive : 1; // true if this channel is enabled, pin not pulsed if false - uint8_t isDetaching : 1; // true if this channel is being detached, maintains pulse integrity -}; - -struct ServoState { - ServoInfo info; - volatile uint16_t usPulse; -}; - -#if !defined (SERVO_EXCLUDE_TIMER0) -ServoTimer0 s_servoTimer0; -#endif - -#if !defined (SERVO_EXCLUDE_TIMER1) -ServoTimer1 s_servoTimer1; -#endif - -static ServoState s_servos[MAX_SERVOS]; // static array of servo structures - -static uint8_t s_servoCount = 0; // the total number of attached s_servos - - -// inconvenience macros -#define SERVO_INDEX_TO_TIMER(servoIndex) ((ServoTimerSequence)(servoIndex / SERVOS_PER_TIMER)) // returns the timer controlling this servo -#define SERVO_INDEX(timerId, channel) ((timerId * SERVOS_PER_TIMER) + channel) // macro to access servo index by timer and channel - -// similiar to map but will have increased accuracy that provides a more -// symetric api (call it and use result to reverse will provide the original value) -// -int improved_map(int value, int minIn, int maxIn, int minOut, int maxOut) -{ - const int rangeIn = maxIn - minIn; - const int rangeOut = maxOut - minOut; - const int deltaIn = value - minIn; - // fixed point math constants to improve accurancy of divide and rounding - const int fixedHalfDecimal = 1; - const int fixedDecimal = fixedHalfDecimal * 2; - - return ((deltaIn * rangeOut * fixedDecimal) / (rangeIn) + fixedHalfDecimal) / fixedDecimal + minOut; -} - -//------------------------------------------------------------------------------ -// Interrupt handler template method that takes a class that implements -// a standard set of methods for the timer abstraction -//------------------------------------------------------------------------------ -template -static void Servo_Handler(T* timer) ICACHE_RAM_ATTR; - -template -static void Servo_Handler(T* timer) -{ - uint8_t servoIndex; - - // clear interrupt - timer->ResetInterrupt(); - - if (timer->isEndOfCycle()) { - timer->StartCycle(); - } - else { - servoIndex = SERVO_INDEX(timer->timerId(), timer->getCurrentChannel()); - if (servoIndex < s_servoCount && s_servos[servoIndex].info.isActive) { - // pulse this channel low if activated - digitalWrite(s_servos[servoIndex].info.pin, LOW); - - if (s_servos[servoIndex].info.isDetaching) { - s_servos[servoIndex].info.isActive = false; - s_servos[servoIndex].info.isDetaching = false; - } - } - timer->nextChannel(); - } - - servoIndex = SERVO_INDEX(timer->timerId(), timer->getCurrentChannel()); - - if (servoIndex < s_servoCount && - timer->getCurrentChannel() < SERVOS_PER_TIMER) { - timer->SetPulseCompare(timer->usToTicks(s_servos[servoIndex].usPulse) - c_CycleCompensation); - - if (s_servos[servoIndex].info.isActive) { - if (s_servos[servoIndex].info.isDetaching) { - // it was active, reset state and leave low - s_servos[servoIndex].info.isActive = false; - s_servos[servoIndex].info.isDetaching = false; - } - else { - // its an active channel so pulse it high - digitalWrite(s_servos[servoIndex].info.pin, HIGH); - } - } - } - else { - if (!isTimerActive(timer->timerId())) { - // no active running channels on this timer, stop the ISR - finISR(timer->timerId()); - } - else { - // finished all channels so wait for the refresh period to expire before starting over - // allow a few ticks to ensure the next match is not missed - uint32_t refreshCompare = timer->usToTicks(REFRESH_INTERVAL); - if ((timer->GetCycleCount() + c_CycleCompensation * 2) < refreshCompare) { - timer->SetCycleCompare(refreshCompare - c_CycleCompensation); - } - else { - // at least REFRESH_INTERVAL has elapsed - timer->SetCycleCompare(timer->GetCycleCount() + c_CycleCompensation * 2); - } - } - - timer->setEndOfCycle(); - } -} - -static void handler0() ICACHE_RAM_ATTR; -static void handler0() -{ - Servo_Handler(&s_servoTimer0); -} - -static void handler1() ICACHE_RAM_ATTR; -static void handler1() -{ - Servo_Handler(&s_servoTimer1); -} - -static void initISR(ServoTimerSequence timerId) -{ -#if !defined (SERVO_EXCLUDE_TIMER0) - if (timerId == ServoTimerSequence_Timer0) - s_servoTimer0.InitInterrupt(&handler0); -#endif -#if !defined (SERVO_EXCLUDE_TIMER1) - if (timerId == ServoTimerSequence_Timer1) - s_servoTimer1.InitInterrupt(&handler1); -#endif -} - -static void finISR(ServoTimerSequence timerId) ICACHE_RAM_ATTR; -static void finISR(ServoTimerSequence timerId) -{ -#if !defined (SERVO_EXCLUDE_TIMER0) - if (timerId == ServoTimerSequence_Timer0) - s_servoTimer0.StopInterrupt(); -#endif -#if !defined (SERVO_EXCLUDE_TIMER1) - if (timerId == ServoTimerSequence_Timer1) - s_servoTimer1.StopInterrupt(); -#endif -} - -// returns true if any servo is active on this timer -static boolean isTimerActive(ServoTimerSequence timerId) ICACHE_RAM_ATTR; -static boolean isTimerActive(ServoTimerSequence timerId) -{ - for (uint8_t channel = 0; channel < SERVOS_PER_TIMER; channel++) { - if (s_servos[SERVO_INDEX(timerId, channel)].info.isActive) { - return true; - } - } - return false; -} - -//------------------------------------------------------------------- -// Servo class methods - -Servo::Servo() -{ - if (s_servoCount < MAX_SERVOS) { - // assign a servo index to this instance - _servoIndex = s_servoCount++; - // store default values - s_servos[_servoIndex].usPulse = DEFAULT_PULSE_WIDTH; - - // set default _minUs and _maxUs incase write() is called before attach() - _minUs = MIN_PULSE_WIDTH; - _maxUs = MAX_PULSE_WIDTH; - - s_servos[_servoIndex].info.isActive = false; - s_servos[_servoIndex].info.isDetaching = false; - s_servos[_servoIndex].info.pin = INVALID_PIN; - } - else { - _servoIndex = INVALID_SERVO; // too many servos - } -} - -uint8_t Servo::attach(int pin) -{ - return attach(pin, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); -} - -uint8_t Servo::attach(int pin, uint16_t minUs, uint16_t maxUs) -{ - ServoTimerSequence timerId; - - if (_servoIndex < MAX_SERVOS) { - if (s_servos[_servoIndex].info.pin == INVALID_PIN) { - pinMode(pin, OUTPUT); // set servo pin to output - digitalWrite(pin, LOW); - s_servos[_servoIndex].info.pin = pin; - } - - // keep the min and max within 200-3000 us, these are extreme - // ranges and should support extreme servos while maintaining - // reasonable ranges - _maxUs = max((uint16_t)250, min((uint16_t)3000, maxUs)); - _minUs = max((uint16_t)200, min(_maxUs, minUs)); - - // initialize the timerId if it has not already been initialized - timerId = SERVO_INDEX_TO_TIMER(_servoIndex); - if (!isTimerActive(timerId)) { - initISR(timerId); - } - s_servos[_servoIndex].info.isDetaching = false; - s_servos[_servoIndex].info.isActive = true; // this must be set after the check for isTimerActive - } - return _servoIndex; -} - -void Servo::detach() -{ - if (s_servos[_servoIndex].info.isActive) { - s_servos[_servoIndex].info.isDetaching = true; - } -} - -void Servo::write(int value) -{ - // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds) - if (value < MIN_PULSE_WIDTH) { - // assumed to be 0-180 degrees servo - value = constrain(value, 0, 180); - // writeMicroseconds will contrain the calculated value for us - // for any user defined min and max, but we must use default min max - value = improved_map(value, 0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH); - } - writeMicroseconds(value); -} - -void Servo::writeMicroseconds(int value) -{ - // ensure channel is valid - if ((_servoIndex < MAX_SERVOS)) { - // ensure pulse width is valid - value = constrain(value, _minUs, _maxUs); - - s_servos[_servoIndex].usPulse = value; - } -} - -int Servo::read() // return the value as degrees -{ - // read returns the angle for an assumed 0-180, so we calculate using - // the normal min/max constants and not user defined ones - return improved_map(readMicroseconds(), MIN_PULSE_WIDTH, MAX_PULSE_WIDTH, 0, 180); -} - -int Servo::readMicroseconds() -{ - unsigned int pulsewidth; - if (_servoIndex != INVALID_SERVO) { - pulsewidth = s_servos[_servoIndex].usPulse; - } - else { - pulsewidth = 0; - } - - return pulsewidth; -} - -bool Servo::attached() -{ - return s_servos[_servoIndex].info.isActive; -} - -#endif diff --git a/libraries/Servo/src/esp8266/ServoTimers.h b/libraries/Servo/src/esp8266/ServoTimers.h deleted file mode 100644 index 90a19e1651..0000000000 --- a/libraries/Servo/src/esp8266/ServoTimers.h +++ /dev/null @@ -1,225 +0,0 @@ -/* - Copyright (c) 2015 Michael C. Miller. All right reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -// -// Defines for timer abstractions used with Servo library -// -// ServoTimerSequence enumerates the sequence that the timers should be allocated -// ServoTimerSequence_COUNT indicates how many timers are available. -// -enum ServoTimerSequence { - -#if !defined (SERVO_EXCLUDE_TIMER0) - ServoTimerSequence_Timer0, -#endif - -#if !defined (SERVO_EXCLUDE_TIMER1) - ServoTimerSequence_Timer1, -#endif - - ServoTimerSequence_COUNT -}; - - -#if !defined (SERVO_EXCLUDE_TIMER0) - -struct ServoTimer0 -{ -public: - ServoTimer0() - { - setEndOfCycle(); - } - - - uint32_t usToTicks(uint32_t us) const - { - return (clockCyclesPerMicrosecond() * us); // converts microseconds to tick - } - uint32_t ticksToUs(uint32_t ticks) const - { - return (ticks / clockCyclesPerMicrosecond()); // converts from ticks back to microseconds - } - - void InitInterrupt(timercallback handler) - { - timer0_isr_init(); - timer0_attachInterrupt(handler); - } - - void ResetInterrupt() {}; // timer0 doesn't have a clear interrupt - - void StopInterrupt() - { - timer0_detachInterrupt(); - } - - void SetPulseCompare(uint32_t value) - { - timer0_write(ESP.getCycleCount() + value); - } - - void SetCycleCompare(uint32_t value) - { - timer0_write(_cycleStart + value); - } - - uint32_t GetCycleCount() const - { - return ESP.getCycleCount() - _cycleStart; - } - - - void StartCycle() - { - _cycleStart = ESP.getCycleCount(); - _currentChannel = 0; - } - - int8_t getCurrentChannel() const - { - return _currentChannel; - } - - void nextChannel() - { - _currentChannel++; - } - - void setEndOfCycle() - { - _currentChannel = -1; - } - - bool isEndOfCycle() const - { - return (_currentChannel == -1); - } - - ServoTimerSequence timerId() const - { - return ServoTimerSequence_Timer0; - } - -private: - volatile uint32_t _cycleStart; - volatile int8_t _currentChannel; -}; - -#endif - - -#if !defined (SERVO_EXCLUDE_TIMER1) - -#define TIMER1_TICKS_PER_US (APB_CLK_FREQ / 1000000L) - -struct ServoTimer1 -{ -public: - ServoTimer1() - { - setEndOfCycle(); - } - - - uint32_t usToTicks(uint32_t us) const - { - return (TIMER1_TICKS_PER_US / 16 * us); // converts microseconds to tick - } - uint32_t ticksToUs(uint32_t ticks) const - { - return (ticks / TIMER1_TICKS_PER_US * 16); // converts from ticks back to microseconds - } - - void InitInterrupt(timercallback handler) - { - timer1_isr_init(); - timer1_attachInterrupt(handler); - timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); - timer1_write(usToTicks(REFRESH_INTERVAL)); - } - - void ResetInterrupt() {}; // timer1 doesn't have a clear interrupt - - void StopInterrupt() - { - timer1_detachInterrupt(); - } - - void SetPulseCompare(uint32_t value) - { - _cycleTicks += value; - timer1_write(value); - } - - void SetCycleCompare(uint32_t value) - { - if (value <= _cycleTicks) - { - value = 1; - } - else - { - value -= _cycleTicks; - } - timer1_write(value); - } - - uint32_t GetCycleCount() const - { - return _cycleTicks; - } - - - void StartCycle() - { - _cycleTicks = 0; - _currentChannel = 0; - } - - int8_t getCurrentChannel() const - { - return _currentChannel; - } - - void nextChannel() - { - _currentChannel++; - } - - void setEndOfCycle() - { - _currentChannel = -1; - } - - bool isEndOfCycle() const - { - return (_currentChannel == -1); - } - - ServoTimerSequence timerId() const - { - return ServoTimerSequence_Timer1; - } - -private: - volatile uint32_t _cycleTicks; - volatile int8_t _currentChannel; -}; - -#endif