000000widow-hackspacecon/fw/HackSpaceCon_AS7/Arduino/megatinycore/timers.h

344 lines
14 KiB
C

#ifndef __TIMERS_H__
#define __TIMERS_H__
#include <core_parameters.h>
/*****************************************
| # # ### # # ### ### |
| ## ## # # # # # |
| # # # # # # # ### |
| # # # # # # # |
| # # ### #### #### ### #### |
|_______________________________________*/
#ifdef MILLIS_USE_TIMERNONE
#define DISABLE_MILLIS
#endif
#if (defined(MILLIS_USE_TIMERRTC_XTAL) || defined(MILLIS_USE_TIMERRTC_XOSC))
#define MILLIS_USE_TIMERRTC
#endif
#if !defined(TCA_PORTMUX)
#define TCA_PORTMUX (0x00)
#endif
#define _TCA_USE_WO0
#define _TCA_USE_WO1
#define _TCA_USE_WO2
#if TCA_PORTMUX & 0x01
#define _TCA_ALT_WO0
#endif
#if TCA_PORTMUX & 0x02
#define _TCA_ALT_WO1
#endif
#if TCA_PORTMUX & 0x04
#define _TCA_ALT_WO2
#endif
#if !defined(TCA_BUFFERED_3PIN)
#if TCA_PORTMUX & 0x08
#define _TCA_ALT_WO3
#else
#define _TCA_USE_WO3
#endif
#if TCA_PORTMUX & 0x10
#define _TCA_ALT_WO4
#else
#define _TCA_USE_WO4
#endif
#if TCA_PORTMUX & 0x20
#define _TCA_ALT_WO5
#else
#define _TCA_USE_WO5
#endif
#endif
#if defined(USE_TIMERD0_PWM)
#if !defined(TCD0)
#if defined(ARDUINO_MAIN)
#pragma message("Note: This part does not have a TCD, hence there is no PWM from TCD available.")
#endif
#undef USE_TIMERD0_PWM
#endif
#endif
#if (defined(MILLIS_USE_TIMERB0) || defined(MILLIS_USE_TIMERB1) || defined(MILLIS_USE_TIMERB2) || defined(MILLIS_USE_TIMERB3) || defined(MILLIS_USE_TIMERB4) || defined(MILLIS_USE_TIMERB5) || defined(MILLIS_USE_TIMERB6) || defined(MILLIS_USE_TIMERB7))
#if (F_CPU == 1000000UL)
#define TIME_TRACKING_TIMER_DIVIDER (1)
#define TIME_TRACKING_TIMER_PERIOD ((F_CPU/500)-1)
#elif (F_CPU == 2000000UL)
#define TIME_TRACKING_TIMER_DIVIDER (2)
#define TIME_TRACKING_TIMER_PERIOD ((F_CPU/1000)-1)
#else
#define TIME_TRACKING_TIMER_DIVIDER (2)
#define TIME_TRACKING_TIMER_PERIOD ((F_CPU/2000)-1)
#endif
#elif defined(MILLIS_USE_TIMERD0)
#define TIME_TRACKING_TIMER_PERIOD (0x1FD)
#if (F_CPU == 1000000UL)
#define TIME_TRACKING_TIMER_DIVIDER (64) /* Clock divider for TCD0 */
#else
#define TIME_TRACKING_TIMER_DIVIDER (32) /* Clock divider for TCD0 */
#endif
#else // Otherwise it must be a TCA
#define TIME_TRACKING_TIMER_PERIOD (0xFE)
#if (F_CPU > 30000000UL)
#define TIME_TRACKING_TIMER_DIVIDER (256)
#elif (F_CPU <= 1000000UL)
#define TIME_TRACKING_TIMER_DIVIDER (8)
#elif (F_CPU <= 5000000UL)
#define TIME_TRACKING_TIMER_DIVIDER (16)
#else // 30000000UL >= F_CPU > 5000000
#define TIME_TRACKING_TIMER_DIVIDER (64)
#endif
#endif
#define TIME_TRACKING_TICKS_PER_OVF (TIME_TRACKING_TIMER_PERIOD + 1UL)
#define TIME_TRACKING_CYCLES_PER_OVF (TIME_TRACKING_TICKS_PER_OVF * TIME_TRACKING_TIMER_DIVIDER)
// For a type B timer as millis, these #defines aren't needed, but they're defined accurately anyway,
#if defined(MILLIS_USE_TIMERA0)
#ifndef TCA0
#error "TCA0, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERA0
#define MILLIS_VECTOR TCA0_HUNF_vect
#elif defined(MILLIS_USE_TIMERA1)
#ifndef TCA1
#error "TCA1, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERA1
#define MILLIS_VECTOR TCA1_HUNF_vect
#elif defined(MILLIS_USE_TIMERB0)
#ifndef TCB0
#error "TCB0, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB0
#elif defined(MILLIS_USE_TIMERB1)
#ifndef TCB1
#error "TCB1, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB1
#elif defined(MILLIS_USE_TIMERB2)
#ifndef TCB2
#error "TCB2, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB2
#elif defined(MILLIS_USE_TIMERB3)
#ifndef TCB3
#error "TCB3, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB3
#elif defined(MILLIS_USE_TIMERB4)
#ifndef TCB4
#error "TCB4, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB4
#elif defined(MILLIS_USE_TIMERB5)
#ifndef TCB5
#error "TCB5, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB5
#elif defined(MILLIS_USE_TIMERB6)
#ifndef TCB6
#error "TCB6, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB6
#elif defined(MILLIS_USE_TIMERB7)
#ifndef TCB7
#error "TCB7, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERB7
#elif defined(MILLIS_USE_TIMERD0)
#ifndef TCD0
#error "TCD0, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERD0
#elif defined(MILLIS_USE_TIMERRTC)
#ifndef RTC
#error "RTC, selected for millis, does not exist on this part"
#endif
#define MILLIS_TIMER TIMERRTC
#elif defined(MILLIS_USE_TIMERNONE)
#define MILLIS_TIMER NOT_ON_TIMER
#else
#error "Millis timer not specified, nor is millis disabled - can't happen!"
#endif
#endif
/* These allow other libraries to detect whether timing functions are available with #ifdef */
#define delay delay
#define delayMicroseconds delayMicroseconds
#if !defined(MILLIS_USE_TIMERNONE)
#define millis millis
#if !defined(MILLIS_USE_TIMERRTC)
#define micros micros
#endif
#endif
/****************************************
| #### # # # # |
| # # # # ## ## |
| #### # # # # # # |
| # # # # # # |
| # # # # # |
|______________________________________*/
/* TYPE-A TIMERS */
#define PWM_TIMER_PERIOD (0xFE) // For frequency
#define PWM_TIMER_COMPARE (0x00) // For duty cycle - this is never used.
/* The original implementation set the compare registers (all 6 of them, with an STS instruction),
* and also set a number of other TCA registers to their POR values. That was dumb, and is no longer done.
* TCA0 is present on all parts and always used for PWM.
* TCA1 is used for PWM on Dx-series parts that have it.
*/
/* TYPE-B TIMERS */
#if defined(TCB2) && !defined(MEGATINYCORE)
// On tinyAVR, and the low pincount DD's TCB PWM isn't helpful and the timer is too valuable anyway.
// These use the same values as above for TCA, only they're only 1 channel each.
#define USE_TCB_PWM
#if defined(SUPPORT_TCB_MUX)
#undef SUPPORT_TCB_MUX
#pragma message("TCB autoMUXing is not yet supported")
#endif
/* If this feature is ever implemented, calling analogWrite() on a pin that has PWM controlled by a TCB
* it will automatically also set or clear the PORTMUX bit to point the TCB at the right place.
* These pins will be included in the pin timer table.
*/
#endif
/* TYPE-D TIMERS */
#if defined(MEGATINYCORE)
/* TCD0 on megaTinyCore is run from the unprescaled clock, at either 20 or 16 MHz depending on the fuse setting.
* It will always use a count prescale of 32. If the system clock is cranked all the way back to 1 MHz, though
* we knock it down by another factor of 2 because otherwise the interrupts were taking a large fraction of
* available CPU time. */
#if (defined(MILLIS_USE_TIMERD0) && F_CPU == 1000000)
#define TIMERD0_PRESCALER (TCD_CLKSEL_20MHZ_gc | TCD_CNTPRES_DIV32_gc | TCD_SYNCPRES_DIV2_gc)
// TODO: change to TIMERD0_CLOCK_SETTING here and elsewhere
// along with TIMERD0_TOP_SETTING and TIMERD0_WGMODE_SETTING
#else
#define TIMERD0_PRESCALER (TCD_CLKSEL_20MHZ_gc | TCD_CNTPRES_DIV32_gc)
#endif
// Pretty simple here!
#elif defined(DXCORE)
#if defined(TIMERD0_TOP_SETTING)
#if (TIMERD0_TOP_SETTING == 254 || TIMERD0_TOP_SETTING == 509 ||TIMERD0_TOP_SETTING == 1019 ||TIMERD0_TOP_SETTING == 2039 ||TIMERD0_TOP_SETTING == 4079 )
#if defined(TCD0)
#define USE_TIMERD0_PWM
#endif
#endif
#else
#endif
/* On the DX-series parts, we can have a lot more fun with the Type D timer!
* This flexibility comes at the price of losing the use of TCD0 as a millis timer, but that was being done
* as a desperation move on the highly timer-constrained non-golden tinyAVR 1-series parts. The TCD0 on the
* tinyAVRs is also far less interesting - it's virtually identical as far as the timer itself, but here
* we have a much richer selection timing clock sources.
*/
#if defined(USE_TIMERD0_PWM)
#if !defined(TIMERD0_WGMODE_SETTING)
#define TIMERD0_WGMODE_SETTING (TCD_WGMODE_ONERAMP_gc) // only WGMODE_ONERAMP and WGMODE_DUALSLOPE are supported by the core, as the other options
// cannot reproduce the behavior of analogWrite as they enforce PWM being non-overlapping. If you want those modes (I think 4 ramp mode has some
// relevant applications in combination with the TCD.CTRLD options, particularly in custom single wire protocols, particularly if you need to generate events
// at certain points in the cycle.
#endif
#if !defined(TIMERD0_CLOCK_SETTING)
#if (CLOCK_SOURCE != 0)
/*
Hey, we're not using the internal oscillator for the clock! We can totally run it at an ideal frequency from internal and use external for system clock!
There are at least five ways to slice this that look nearly identical. These are virtually indistinguishable from each other giving PWM at 980 Hz.
One runs the clock at 8 MHz with /32 division on the count prescaler and /1 on the synchronizer.
Another method drops the count prescaler back to /4, and compensates by increasing the sync prescaler to /8
A third way is to drop the count prescaler back to /4, sync prescale to /1, and set the FREQSEL to 1 MHz.
Sync prescale options of /2 and /4 can be paired with setting the osc to 2 or 4 MHz as well, with the same result
Lowering count prescale dodges some bugs, and there isn't exactly a wealth of options. (it goes /4 to /32, nothing in the middle,
and no undocumented fourth option (I checked :-P).
But /32 will get you right on target (that being ~1 kHz) if CLK_OSCHF / Sync prescale = 8 MHz
And /4 will get you right on target if CLK_OSCHF / Sync prescale = 1 MHz
There are still more equivalent settings if you consider that there are more than one way to get to 1 MHz from the internal HF oscillator.
Those settings are strictly worse if you aren't using the internal osc for anything else.
On the one hand, running with clock set to 1 MHz is probably more power efficient, though nothing using a crystal is going to set any records
for power consumption - and using an external clock, well, you have the power consumption of the clock to worry about, which is often larger
than the chip itself...
On the other hand, using the /32 count prescaling exposes us to some silicon errata on the DA/DB - but in weirdo features nobody uses.
On the third hand, if the crystal or clock is defective, and you were relying on overriding clock failure callbacks with your own functions
that enable the internal oscillator at a known speed to communicate that to you (because you got a killer deal on crystals that have
astonishingly high DOA rate or something?) If clock failure is a normal occurrence, you're doing something wrong...
Anyway, regarding how we get the same frequency, well... very few Arduino folks care!
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV4_gc | TCD_SYNCPRES_DIV8_gc | TCD_CLKSEL_OSCHF_gc)
#define TIMERD0_SET_CLOCK (CLKCTRL_FREQSEL_8M_gc)
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV4_gc | TCD_SYNCPRES_DIV4_gc | TCD_CLKSEL_OSCHF_gc)
#define TIMERD0_SET_CLOCK (CLKCTRL_FREQSEL_4M_gc)
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV4_gc | TCD_SYNCPRES_DIV2_gc | TCD_CLKSEL_OSCHF_gc)
#define TIMERD0_SET_CLOCK (CLKCTRL_FREQSEL_2M_gc)
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV4_gc | TCD_SYNCPRES_DIV1_gc | TCD_CLKSEL_OSCHF_gc)
#define TIMERD0_SET_CLOCK (CLKCTRL_FREQSEL_1M_gc)
Hell... we could even set TOP to 2039 and do
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV4_gc | TCD_SYNCPRES_DIV1_gc | TCD_CLKSEL_OSCHF_gc)
#define TIMERD0_SET_CLOCK (CLKCTRL_FREQSEL_1M_gc)
#define TIMERD0_TOP_SETTING (2039)
*/
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV32_gc | TCD_SYNCPRES_DIV1_gc | TCD_CLKSEL_OSCHF_gc)
#define TIMERD0_SET_CLOCK (CLKCTRL_FREQSEL_8M_gc)
// And we can set TOP to the default 254.
#if !defined(TIMERD0_TOP_SETTING)
#define TIMERD0_TOP_SETTING (0xFE)
// that gives the target 980 kHz PWM freqwuency....
#endif
// if it's internal HF osc as system clock, it's more complicated.....
#elif (F_CPU == 5000000UL || F_CPU == 10000000UL || F_CPU == 6000000UL || F_CPU == 7000000UL || F_CPU == 14000000UL)
// These speeds are prescaled so we can run from unprescaled clock, and keep the same settings we use at higher clock.
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV32_gc | TCD_SYNCPRES_DIV1_gc | TCD_CLKSEL_OSCHF_gc)
#if !defined(TIMERD0_TOP_SETTING)
#if (F_CPU == 7000000 || F_CPU == 14000000)
#define TIMERD0_TOP_SETTING (1019)
#else
#define TIMERD0_TOP_SETTING (509)
#endif
#endif
#elif F_CPU < 4000000UL
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV4_gc | TCD_SYNCPRES_DIV1_gc | TCD_CLKSEL_CLKPER_gc)
#if !defined(TIMERD0_TOP_SETTING)
#if (F_CPU == 1000000)
#define TIMERD0_TOP_SETTING (254)
#else
#define TIMERD0_TOP_SETTING (509)
#endif
#endif
#else //catchall F_CPU speeds
#define TIMERD0_CLOCK_SETTING (TCD_CNTPRES_DIV32_gc | TCD_SYNCPRES_DIV1_gc | TCD_CLKSEL_CLKPER_gc)
#if !defined(TIMERD0_TOP_SETTING)
#if (F_CPU <= 8000000)
#define TIMERD0_TOP_SETTING (254)
#elif (F_CPU <= 20000000)
#define TIMERD0_TOP_SETTING (509)
#else
#define TIMERD0_TOP_SETTING (1019)
#endif
#endif
#endif // end of F_CPU tests
#else //CLOCK setting IS defined!! That is unusual indeed!
#if !defined(TIMERD0_TOP_SETTING)
#define TIMERD0_TOP_SETTING (254)
#endif
#endif
#endif // End of USE_TIMERD0_PWM
#endif // end of DxCore TCD stuff - see why we don't support it for millis here?