diff --git a/ATMEGA4809/EveryTimerB.h b/ATMEGA4809/EveryTimerB.h new file mode 100644 index 0000000..6d9b481 --- /dev/null +++ b/ATMEGA4809/EveryTimerB.h @@ -0,0 +1,474 @@ +// EveryTimerB library. +// by Kees van der Oord Kees.van.der.Oord@inter.nl.net + +// Timer library for the TCB timer of the AtMega4809 processor. +// tested on the Arduino Nano Every (AtMega4809) and the Arduino 1.8.12 IDE +// support for the Every is the 'Arduino MegaAVR' boards module (Tools | Board | Boards Manager) + +// usage: +/* +#ifdef ARDUINO_ARCH_MEGAAVR +#include "EveryTimerB.h" +#define Timer1 TimerB2 // use TimerB2 as a drop in replacement for Timer1 +#else // assume architecture supported by TimerOne library .... +#include "TimerOne.h" +#endif + +// code below will now work both on the MegaAVR and AVR processors + +void setup() { + Timer1.initialize(); + Timer1.attachInterrupt(myisr); + Timer1.setPeriod(1000000UL); // like the TimerOne library this will start the timer as well +} + +void myisr() { + // do something useful every second +} +*/ +// clock source options: +// The TCB clock source is specified in the initialize() function with default value EveryTimerB_CLOCMODE. +// define this macro before including this file to use a different default clock mode +// e.g.: +// #define EveryTimerB_CLOCMODE TCB_CLKSEL_CLKTCA_gc // 250 kHz ~ 4 us +// #define EveryTimerB_CLOCMODE TCB_CLKSEL_CLKDIV2_gc // 8 MHz ~ 0.125 us +// #define EveryTimerB_CLOCMODE TCB_CLKSEL_CLKDIV_gc // 16 MHz ~ 0.0625 us + +// timer options +// The 4809 has one A timer (TCA) and four B timers (TCB). +// TCA and TCB3 are used by the arduino core to generate the clock used by millis() and micros(). +// TCB0 generates the PWM timing for pin D6, TCB1 for pin D3. +// By default Timer Control B2 is defined as TimerB2 in the EveryTimerB library. +// If you would like to use the TCB0 and TCB1 as well you have to copy the code +// from the EveryTimerB.cpp into your product file and adapt for B0 and B1 timers. +// +// for information on the 4809 TCA and TCB timers: +// http://ww1.microchip.com/downloads/en/AppNotes/TB3217-Getting-Started-with-TCA-90003217A.pdf +// http://ww1.microchip.com/downloads/en/Appnotes/TB3214-Getting-Started-with-TCB-90003214A.pdf +// %LOCALAPPDATA%\Arduino15\packages\arduino\hardware\megaavr\1.8.5\cores\arduino\wiring.c +// %LOCALAPPDATA%\Arduino15\packages\arduino\hardware\megaavr\1.8.5\variants\nona4809\variant.c +// %LOCALAPPDATA%\Arduino15\packages\arduino\tools\avr-gcc\7.3.0-atmel3.6.1-arduino5\avr\include\avr\iom4809.h + +// 20 MHz system clock +// to run the Every at 20 MHz, add the lines below to the nona4809 section of the boards.txt file +// in %LOCALAPPDATA%\Arduino15\packages\arduino\hardware\megaavr\1.8.5. +// they add the sub menu 'Tools | Clock' to choose between 16MHz and 20MHz. +/* +menu.clock=Clock +nona4809.menu.clock.16internal=16MHz +nona4809.menu.clock.16internal.build.f_cpu=16000000L +nona4809.menu.clock.16internal.bootloader.OSCCFG=0x01 +nona4809.menu.clock.20internal=20MHz +nona4809.menu.clock.20internal.build.f_cpu=20000000L +nona4809.menu.clock.20internal.bootloader.OSCCFG=0x02 +*/ +// On 20Mhz, the 1.8.12 IDE MegaAvr core library implementation +// of the millis() and micros() functions is not accurate. +// the file "MegaAvr20MHz.h" implements a quick hack to correct for this +// +// to do: +// there is no range check on the 'period' arguments of setPeriod ... +// check if it is necessary to set the CNT register to 0 in start() + +#ifndef EveryTimerB_h_ +#define EveryTimerB_h_ +#ifdef ARDUINO_ARCH_MEGAAVR + +#ifndef EveryTimerB_CLOCMODE +#define EveryTimerB_CLOCMODE TCB_CLKSEL_CLKTCA_gc +#endif + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif +#include "MegaAvr20MHz.h" +#include "pins_arduino.h" +#include "../VirtualTimer.h" + +#define TCB_RESOLUTION 65536UL // TCB is 16 bit +// CLOCK F_CPU DIV TICK OVERFLOW OVERFLOW/s +// CLKTCA 16MHz 64 4000 ns 262144us 3.8 Hz +// CLKDIV2 16MHz 2 125 ns 8192us 122 Hz +// CLKDIV1 16MHz 1 62.5ns 4096us 244 Hz +// CLKTCA 20MHz 64 3200 ns 209716us 4.8 Hz +// CLKDIV2 20MHz 2 100 ns 6554us 153 Hz +// CLKDIV1 20MHz 1 50 ns 3277us 305 Hz + +class EveryTimerB : public VirtualTimer +{ +public: + // The AtMega Timer Control B clock sources selection: + // TCB_CLKSEL_CLKTCA_gc, // Timer Controller A, Arduino framework sets TCA to F_CPU/64 = 250kHz (4us) @ 16MHz or 312.5kHz (3.2us) @ 20MHz + // TCB_CLKSEL_CLKDIV2_gc, // CLK_PER/2 Peripheral Clock / 2: 8MHz @ 16Mhz or 10MHz @ 20MHz + // TCB_CLKSEL_CLKDIV1_gc // CLK_PER Peripheral Clock: 16MHz @ 16Mhz or 20MHz @ 20MHz + + // intialize: sets the timer compare mode and the clock source + void initialize(TCB_t *timer_ = &TCB2, TCB_CLKSEL_t clockSource = EveryTimerB_CLOCMODE, unsigned long period = 1000000UL) /*__attribute__((always_inline))*/ + { + timer = timer_; +#if defined(MegaAvr20MHzCorrected) + corrected20MHzInit(); // see commment in MegaAvr20MHz_h +#endif + stop(); + timer->CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled + if (clockSource) + setClockSource(clockSource); + if (period) + setPeriod(period); + } + + void initialize() + { + unsigned long period = 1000000UL; + timer = &TCB2; +#if defined(MegaAvr20MHzCorrected) + corrected20MHzInit(); // see commment in MegaAvr20MHz_h +#endif + stop(); + timer->CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled + if (EveryTimerB_CLOCMODE) + setClockSource(EveryTimerB_CLOCMODE); + if (period) + setPeriod(period); + } + + void setClockSource(TCB_CLKSEL_t clockSource) __attribute__((always_inline)) + { + timer->CTRLA = clockSource; // this stops the clock as well ... + switch (clockSource) + { +#if F_CPU == 20000000UL + case TCB_CLKSEL_CLKTCA_gc: + maxTimeWithoutOverflow = 209715; + break; // (TCB_RESOLUTION * 64) / 20 + case TCB_CLKSEL_CLKDIV2_gc: + maxTimeWithoutOverflow = 6553; + break; // (TCB_RESOLUTION * 2) / 20 + case TCB_CLKSEL_CLKDIV1_gc: + maxTimeWithoutOverflow = 3276; + break; // (TCB_RESOLUTION * 1) / 20 +#else + case TCB_CLKSEL_CLKTCA_gc: + maxTimeWithoutOverflow = 262144; + break; + case TCB_CLKSEL_CLKDIV2_gc: + maxTimeWithoutOverflow = 8192; + break; + case TCB_CLKSEL_CLKDIV1_gc: + maxTimeWithoutOverflow = 4096; + break; +#endif + } + } + + TCB_CLKSEL_t getClockSource() + { + return (TCB_CLKSEL_t)(timer->CTRLA & (TCB_CLKSEL_CLKTCA_gc | TCB_CLKSEL_CLKDIV2_gc | TCB_CLKSEL_CLKDIV1_gc)); + } + + double getFrequencyOfClock(TCB_CLKSEL_t clock) + { + switch (clock) + { + // suppose nobody touched the default TCA configuration ... + case TCB_CLKSEL_CLKTCA_gc: + return double(F_CPU / 64); + break; + case TCB_CLKSEL_CLKDIV2_gc: + return double(F_CPU / 2); + break; + case TCB_CLKSEL_CLKDIV1_gc: + return double(F_CPU); + break; + } + return 0.0; + } + + double getClockFrequency() + { + return getFrequencyOfClock(getClockSource()); + } + + // setPeriod: sets the period + // note: max and min values are different for each clock + // CLKTCA: conversion from us to ticks multiplies 'period' first with 10, so max value is MAX_ULONG/10 ~ 1 hr 11 minutes 34 seconds + // CLKDIV2: conversion from us to ticks is a *10 multiplication, so max value is 420M us (~ 7 minutes) + // CLKDIV1: conversion from us to ticks is a *20 multiplication, so max value is 210M us (~ 3.5 minutes) + void setPeriod(unsigned long period /* us */) /*__attribute__((always_inline))*/ + { + timer->CTRLA &= ~TCB_ENABLE_bm; + // conversion from us to ticks depends on the clock + switch (timer->CTRLA & TCB_CLKSEL_gm) + { + case TCB_CLKSEL_CLKTCA_gc: +#if F_CPU == 20000000UL + period = (period * 10) / 32; // 20Mhz / 64x clock divider of TCA => 3.2 us / tick +#else // 16000000UL + period /= 4; // 16MHz / 64x clock divider of TCA => 4 us / tock +#endif + break; + case TCB_CLKSEL_CLKDIV2_gc: +#if F_CPU == 20000000UL + period *= 10; // 20MHz / 2x clock divider => 10 ticks / us +#else // 16000000UL + period *= 8; // 16MHz / 2x clock divider => 8 ticks / us +#endif + break; + case TCB_CLKSEL_CLKDIV1_gc: +#if F_CPU == 20000000UL + period *= 20; // 20MHz: 20 ticks / us +#else // 16000000UL + period *= 16; // 16MHz: 16 ticks / u3 +#endif + break; + } + + // to support longer than TCB_RESOLUTION ticks, + // this class supports first waiting for N 'overflowCounts' + // and next program the timer the remaining 'remainder' ticks: + countsPerOverflow = TCB_RESOLUTION; + overflowCounts = period / TCB_RESOLUTION; + remainder = period % TCB_RESOLUTION; + + // the timer period is always one tick longer than programmed, + // so a remainder of 1 is not possible. reduce the length of + // the 'overflow' cycles to get a remainder that is not 1 + if (overflowCounts) + { + while (remainder == 1) + { + --countsPerOverflow; + overflowCounts = period / countsPerOverflow; + remainder = period % countsPerOverflow; + } + } + + // the timer period is always one tick longer than programmed + --countsPerOverflow; + if (remainder) + --remainder; + + // let's go + start(); + } + + void start() /*__attribute__((always_inline))*/ + { + stop(); + overflowCounter = overflowCounts; + timer->CCMP = overflowCounts ? countsPerOverflow : remainder; + timer->CNT = 0; + timer->CTRLA |= TCB_ENABLE_bm; + } + + void stop() /*__attribute__((always_inline))*/ + { + timer->CTRLA &= ~TCB_ENABLE_bm; + timer->INTFLAGS = TCB_CAPT_bm; // writing to the INTFLAGS register will clear the interrupt request flag + } + + bool isEnabled(void) __attribute__((always_inline)) + { + return timer->CTRLA & TCB_ENABLE_bm ? true : false; + } + + void enable(void) __attribute__((always_inline)) + { + timer->CTRLA |= TCB_ENABLE_bm; + } + + bool disable(void) __attribute__((always_inline)) + { + timer->CTRLA &= ~TCB_ENABLE_bm; + } + + void attachInterrupt(void (*isr)()) /*__attribute__((always_inline))*/ + { + isrCallback = isr; + timer->INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag + timer->INTCTRL = TCB_CAPT_bm; // Enable the interrupt + } + + void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) + { + if (microseconds > 0) + stop(); + attachInterrupt(isr); + if (microseconds > 0) + setPeriod(microseconds); + } + + void detachInterrupt() /*__attribute__((always_inline))*/ + { + timer->INTCTRL &= ~TCB_CAPT_bm; // Disable the interrupt + isrCallback = isrDefaultUnused; + } + + void enableInterrupt() __attribute__((always_inline)) + { + timer->INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag + timer->INTCTRL = TCB_CAPT_bm; // Enable the interrupt + } + + void disableInterrupt() __attribute__((always_inline)) + { + timer->INTCTRL &= ~TCB_CAPT_bm; // Enable the interrupt + } + + TCB_CNTMODE_enum getMode() __attribute__((always_inline)) + { + return (TCB_CNTMODE_enum)(timer->CTRLB & 0x7); + } + + void setMode(TCB_CNTMODE_enum mode) __attribute__((always_inline)) + { + timer->CTRLB = (timer->CTRLB & ~0x7) | mode; + } + + uint8_t isOutputEnabled() __attribute__((always_inline)) + { + return timer->CTRLB & TCB_CCMPEN_bm; + } + + uint8_t enableOutput() __attribute__((always_inline)) + { + timer->CTRLB |= TCB_CCMPEN_bm; + } + + uint8_t disableOutput() __attribute__((always_inline)) + { + timer->CTRLB &= ~TCB_CCMPEN_bm; + } + + // this will start PWM on pin 6 (TCB0) or pin 3 (TCB1) + // set the pins to output with setMode(x,OUTPUT) before calling this function + // period determines the clock ticks in one cycle: + // 16MHz clock: slowest frequency at 255 = 62 kHz. + // 8MHz clock: slowest frequency at 255 = 31 kHz. + // 256kHz clock: slowest frequency at 255 = 1 kHz. + // compare determines the duty cycle. + // with a period of 255, set the compare to 128 to get 50% duty cycle. + void setPwmMode(byte period, byte compare) + { + disableInterrupt(); + setMode(TCB_CNTMODE_PWM8_gc); + timer->CCMPL = period; + timer->CCMPH = compare; + enableOutput(); + enable(); + } + + void getPwmMode(byte &period, byte &compare) + { + period = timer->CCMPL; + compare = timer->CCMPH; + } + + void setPwm(double frequency, double dutyCycle) + { + TCB_CLKSEL_t clockSource = TCB_CLKSEL_CLKDIV1_gc; + double clockFrequency = getFrequencyOfClock(clockSource); + if (frequency < (clockFrequency / 256.)) + { + clockSource = TCB_CLKSEL_CLKDIV2_gc; + clockFrequency = getFrequencyOfClock(clockSource); + } + if (frequency < (clockFrequency / 256.)) + { + clockSource = TCB_CLKSEL_CLKTCA_gc; + clockFrequency = getFrequencyOfClock(clockSource); + } + double period = (clockFrequency / frequency) - 1.0 + 0.5; + if (period > 255.) + period = 255.; + if (period < 0.) + period = 0.0; + double compare = period * dutyCycle + 0.5; + if (compare < 0.0) + compare = 0.0; + if (compare > period) + compare = period; + setPwmMode((byte)(period), (byte)(compare)); + } + + void getPwm(double &frequency, double &dutyCycle) + { + byte period, compare; + getPwmMode(period, compare); + frequency = getClockFrequency() / (((double)period) + 1); + dutyCycle = (double)compare / (((double)period) + 1); + } + + void setTimerMode() + { + disable(); + disableOutput(); + setMode(TCB_CNTMODE_INT_gc); + if (isrCallback != isrDefaultUnused) + { + enableInterrupt(); + } + } + + TCB_t *getTimer() { return timer; } + long getOverflowCounts() { return overflowCounts; } + long getRemainder() { return remainder; } + long getOverflowCounter() { return overflowCounter; } + long getOverflowTime() { return maxTimeWithoutOverflow; } + + //protected: + // the next_tick function is called by the interrupt service routine TCB0_INT_vect + //friend extern "C" void TCB0_INT_vect(void); + void next_tick() __attribute__((always_inline)) + { + --overflowCounter; + if (overflowCounter > 0) + { + return; + } + if (overflowCounter < 0) + { + // finished waiting for remainder + if (overflowCounts) + { + // restart with a max counter + overflowCounter = overflowCounts; + timer->CCMP = countsPerOverflow; + } + } + else + { + // overflowCounter == 0 + // the overflow series has finished: to the remainder if any + if (remainder) + { + timer->CCMP = remainder; + if (timer->CNT < remainder) + return; + // remainder is so short: already passed ! + timer->CCMP = countsPerOverflow; + } + // no remainder series: reset the overflow counter and do the callback + overflowCounter = overflowCounts; + } + (*isrCallback)(); + } + +private: + TCB_t *timer = &TCB0; + long overflowCounts = 0; + long remainder = 10; + long overflowCounter = 0; + unsigned long countsPerOverflow = TCB_RESOLUTION - 1; + void (*isrCallback)(); + static void isrDefaultUnused(); + unsigned long maxTimeWithoutOverflow; + +}; // EveryTimerB + +extern EveryTimerB TimerA; + +#endif // ARDUINO_ARCH_MEGAAVR +#endif // EveryTimerB_h_ diff --git a/ATMEGA4809/MegaAvr20Mhz.h b/ATMEGA4809/MegaAvr20Mhz.h new file mode 100644 index 0000000..ed9f807 --- /dev/null +++ b/ATMEGA4809/MegaAvr20Mhz.h @@ -0,0 +1,126 @@ +#if !defined(MegaAvr20MHz_h_) +#define MegaAvr20MHz_h_ +#if defined(ARDUINO_ARCH_MEGAAVR) && (F_CPU == 20000000UL) && defined(MILLIS_USE_TIMERB3) +#define MegaAvr20MHzCorrected +// Quick hack to correct the millis() and micros() functions for 20MHz MegaAVR boards. +// by Kees van der Oord +// Remember to call the function corrected20MHzInit() from setup() or an class constructor ! + +// in the IDE 1.8.5 the implementation of millis() and micros() is not accurate +// for the MegaAvr achitecture board clocked at 20 MHz: +// 1) +// in ~\Arduino15\packages\arduino\hardware\megaavr\1.8.5\cores\arduino\wiring.c(386) +// microseconds_per_timer_overflow is initialized as: +// microseconds_per_timer_overflow = clockCyclesToMicroseconds(TIME_TRACKING_CYCLES_PER_OVF); +// this evaluates to (256 * 64) / (20000000/1000000)) = 819.2 which is rounded 819. +// the rounding causes millis() and micros() to report times that are 0.2/819.2 = 0.024 % too short +// 2) +// in ~\Arduino15\packages\arduino\hardware\megaavr\1.8.5\cores\arduino\wiring.c(387) +// microseconds_per_timer_tick is defined as: +// microseconds_per_timer_tick = microseconds_per_timer_overflow/TIME_TRACKING_TIMER_PERIOD; +// which evaluates to 819.2 / 255 = 3.21254901960784 which is rounded to 3 +// this is wrong in two ways: +// - the TIME_TRACKING_TIMER_PERIOD constant is wrong: this should be TIME_TRACKING_TICKS_PER_OVF +// so the correct value is 3.2 ns/tick +// - the rounding causes micros() to return times that are 0.2/3 = 6.25 % too short +// as a quick hack, initialize these variables with settings a factor 5 larger +// and redefine the millis() and micros() functions to return the corrected values + +// The code in this header file corrects for these problems by incrementing the counters +// with increments that are 5 times larger (the lowest factor that gives integer values). +// The millis() and micros() functions are redefined to return the counters / 5. +// The costs you pay is that the number of clock cycles of the new millis() and micros() +// functions is higher. This should be covered by the fact that the chip runs 25% faster +// at 20 MHz than at 16 MHz. + +// This header file redefines the millis() and micros() functions. The redefinition +// is only active for source files in which this header file is included. If you link +// to libraries with a .cpp file, you have to manually change the library .cpp file to +// include this header as well. In addition the corrected20MHzInit() method must be called +// from your sketch to re-initialize the variables used by the timer isr function. + +// for micros() +// from wiring.c: +extern volatile uint32_t timer_overflow_count; + +inline unsigned long corrected_micros() { + + static volatile unsigned long microseconds_offset = 0; + + unsigned long overflows, microseconds; + uint8_t ticks; + unsigned long offset; + + // Save current state and disable interrupts + uint8_t status = SREG; + cli(); + + // we need to prevent that the double calculation below exceeds MAX_ULONG + // this assumes that micros() is called at least once every 35mins) + while(timer_overflow_count > 500000UL) { + microseconds_offset += 409600000UL; // 500000 * 819.2 ~ almost 7 minutes + timer_overflow_count -= 500000UL; + } + + // Get current number of overflows and timer count + overflows = timer_overflow_count; + ticks = TCB3.CNTL; + offset = microseconds_offset; + + // If the timer overflow flag is raised, we just missed it, + // increment to account for it, & read new ticks + if(TCB3.INTFLAGS & TCB_CAPT_bm){ + overflows++; + ticks = TCB3.CNTL; + } + + // Restore state + SREG = status; + + // Return microseconds of up time (resets every ~70mins) + // float aritmic is faster than integer multiplication ? + return offset + (unsigned long)((overflows * 819.2) + (ticks * 3.2)); +} +#define micros corrected_micros + +// for millis() +// from wiring.c: +extern volatile uint32_t timer_millis; +extern uint16_t millis_inc; +extern uint16_t fract_inc; + +// call this method from your sketch setup() if you include this file ! +inline void corrected20MHzInit(void) { + fract_inc = 96; // (5 * 819.2) % 1000 + millis_inc = 4; // (5 * 819.2) / 1000 +} + +inline unsigned long corrected_millis() { + static volatile unsigned long last = 0; + static volatile unsigned long integer = 0; + static volatile unsigned long fraction = 0; + + unsigned long m; + + // disable interrupts while we read timer_millis or we might get an + // inconsistent value (e.g. in the middle of a write to timer_millis) + uint8_t status = SREG; + cli(); + + unsigned long elapsed = timer_millis - last; + last = timer_millis; + integer += elapsed / 5; + fraction += elapsed % 5; + if(fraction >= 5) { ++integer; fraction -= 5; } + + m = integer; + + SREG = status; + + return m; +} +#define millis corrected_millis + +#endif // defined(ARDUINO_ARCH_MEGAAVR) && (F_CPU == 20000000UL) && defined(MILLIS_USE_TIMERB3) + +#endif // !defined(MegaAvr20MHz_h_)