Skip to content

Commit 298601b

Browse files
committed
Substantial rewrite of timer for timing to use a dedicated timer
that timeouts exactly every millisecond(). This really improves everything, it simpifies the code a lot, makes the timer much more accurate (micros() will be super accurate) and also makes the timing timer fully independent of the PWM stuff.
1 parent f01ed32 commit 298601b

File tree

3 files changed

+36
-95
lines changed

3 files changed

+36
-95
lines changed

megaavr/cores/coreX-corefiles/Tone.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ static volatile TCB_t* _timer =
6666
#if defined(USE_TIMERB2)
6767
&TCB2;
6868
#endif
69+
#if defined(USE_TIMERB3)
70+
&TCB3;
71+
#endif
6972

7073
static int _pin = NOT_A_PIN;
7174

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
#ifndef __TIMERS_H__
22
#define __TIMERS_H__
33

4-
#define TIME_TRACKING_TIMER_PERIOD 0xFF
5-
#define TIME_TRACKING_TICKS_PER_OVF (TIME_TRACKING_TIMER_PERIOD + 1) // Timer ticks per overflow of TCB3
6-
#define TIME_TRACKING_TIMER_DIVIDER 64 // Clock divider for TCB0
7-
#define TIME_TRACKING_CYCLES_PER_OVF (TIME_TRACKING_TICKS_PER_OVF * TIME_TRACKING_TIMER_DIVIDER)
4+
// The assumption is that we have a 16 bit timer fully available for timing purposes.
5+
#define TIME_TRACKING_TIMER_DIVIDER 2 // Timer F_CPU Clock divider (can be 1 or 2)
6+
#define TIME_TRACKING_TIMER_COUNT (F_CPU/(1000*TIME_TRACKING_TIMER_DIVIDER)) // Should correspond to exactly 1 ms, i.e. millis()
87

98
#define PWM_TIMER_PERIOD 0xFF // For frequency
109
#define PWM_TIMER_COMPARE 0x80 // For duty cycle
1110

12-
#endif
11+
#endif

megaavr/cores/coreX-corefiles/wiring.c

Lines changed: 29 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
wiring.c - Partial implementation of the Wiring API for the ATmega8.
2+
wiring.c
33
Part of Arduino - http://www.arduino.cc/
44
55
Copyright (c) 2005-2006 David A. Mellis
@@ -18,45 +18,20 @@
1818
Public License along with this library; if not, write to the
1919
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
2020
Boston, MA 02111-1307 USA
21+
22+
Substantially rewritten by Egil Kvaleberg, 23 Sep 2019, to
23+
use a dedicated 16 bit TSB timer for all timing purposes. This
24+
cleans up a lot of things, and makes the code much simpler and
25+
faster. The TCB timers have limitations making it impossible to
26+
use the same timer for both PWM and timing, so this is no loss.
27+
2128
*/
2229

2330
#include "wiring_private.h"
2431

25-
// the prescaler is set so that timer ticks every 64 clock cycles, and the
26-
// the overflow handler is called every 256 ticks.
27-
volatile uint16_t microseconds_per_timer_overflow;
28-
volatile uint16_t microseconds_per_timer_tick;
29-
3032
uint32_t F_CPU_CORRECTED = F_CPU;
3133

32-
// the whole number of milliseconds per timer overflow
33-
uint16_t millis_inc;
34-
35-
// the fractional number of milliseconds per timer overflow
36-
uint16_t fract_inc;
37-
#define FRACT_MAX (1000)
38-
39-
// whole number of microseconds per timer tick
40-
41-
volatile uint32_t timer_overflow_count = 0;
4234
volatile uint32_t timer_millis = 0;
43-
static uint16_t timer_fract = 0;
44-
45-
inline uint16_t clockCyclesPerMicrosecondComp(uint32_t clk){
46-
return ( (clk) / 1000000L );
47-
}
48-
49-
inline uint16_t clockCyclesPerMicrosecond(){
50-
return clockCyclesPerMicrosecondComp(F_CPU_CORRECTED);
51-
}
52-
53-
inline unsigned long clockCyclesToMicroseconds(unsigned long cycles){
54-
return ( cycles / clockCyclesPerMicrosecond() );
55-
}
56-
57-
inline unsigned long microsecondsToClockCycles(unsigned long microseconds){
58-
return ( microseconds * clockCyclesPerMicrosecond() );
59-
}
6035

6136
static volatile TCB_t* _timer =
6237
#if defined(MILLIS_USE_TIMERB0)
@@ -65,10 +40,8 @@ static volatile TCB_t* _timer =
6540
&TCB1;
6641
#elif defined(MILLIS_USE_TIMERB2)
6742
&TCB2;
68-
#elif defined(MILLIS_USE_TIMERB3)
69-
&TCB3;
70-
#else
71-
&TCB0; //TCB0 fallback
43+
#else // fallback or defined(MILLIS_USE_TIMERB3)
44+
&TCB3; //TCB3 fallback
7245
#endif
7346

7447
#if defined(MILLIS_USE_TIMERB0)
@@ -77,27 +50,11 @@ static volatile TCB_t* _timer =
7750
ISR(TCB1_INT_vect)
7851
#elif defined(MILLIS_USE_TIMERB2)
7952
ISR(TCB2_INT_vect)
80-
#elif defined(MILLIS_USE_TIMERB3)
53+
#else // fallback or defined(MILLIS_USE_TIMERB3)
8154
ISR(TCB3_INT_vect)
82-
#else //TCB0 fallback
83-
ISR(TCB0_INT_vect)
8455
#endif
8556
{
86-
// copy these to local variables so they can be stored in registers
87-
// (volatile variables must be read from memory on every access)
88-
uint32_t m = timer_millis;
89-
uint16_t f = timer_fract;
90-
91-
m += millis_inc;
92-
f += fract_inc;
93-
if (f >= FRACT_MAX) {
94-
f -= FRACT_MAX;
95-
m++;
96-
}
97-
98-
timer_fract = f;
99-
timer_millis = m;
100-
timer_overflow_count++;
57+
timer_millis++;
10158

10259
/* Clear flag */
10360
_timer->INTFLAGS = TCB_CAPT_bm;
@@ -108,9 +65,10 @@ unsigned long millis()
10865
unsigned long m;
10966

11067
// disable interrupts while we read timer0_millis or we might get an
111-
// inconsistent value (e.g. in the middle of a write to timer0_millis)
68+
// inconsistent value (e.g. in the middle of a write to timer_millis)
11269
uint8_t status = SREG;
11370
cli();
71+
11472
m = timer_millis;
11573

11674
SREG = status;
@@ -120,44 +78,27 @@ unsigned long millis()
12078

12179
unsigned long micros() {
12280
uint32_t m;
123-
uint8_t t;
81+
uint16_t t;
12482

12583
/* Save current state and disable interrupts */
12684
uint8_t status = SREG;
12785
cli();
12886

129-
/* Get current number of overflows and timer count */
130-
m = timer_overflow_count;
131-
t = _timer->CNTL;
87+
/* Get current number of millis (i.e. overflows) and timer count */
88+
m = timer_millis;
89+
t = _timer->CNT;
13290

13391
/* If the timer overflow flag is raised, we just missed it,
13492
increment to account for it, & read new ticks */
13593
if(_timer->INTFLAGS & TCB_CAPT_bm){
13694
m++;
137-
t = _timer->CNTL;
95+
t = _timer->CNT;
13896
}
13997

14098
// Restore SREG
14199
SREG = status;
142100

143-
#if F_CPU >= 24000000L && F_CPU < 32000000L
144-
// m needs to be multiplied by 682.67
145-
// and t by 2.67
146-
m = (m << 8) + t;
147-
return (m << 1) + (m >> 1) + (m >> 3) + (m >> 4); // Multiply by 2.6875
148-
#elif F_CPU == 20000000L
149-
// m needs to be multiplied by 819.2
150-
// t needs to be multiplied by 3.2
151-
m = (m << 8) + t;
152-
return m + (m << 1) + (m >> 2) - (m >> 4); // Multiply by 3.1875
153-
#elif F_CPU == 12000000L
154-
// m needs to be multiplied by 1365.33
155-
// and t by 5.33
156-
m = (m << 8) + t;
157-
return m + (m << 2) + (m >> 2) + (m >> 3) - (m >> 4) + (m >> 5); // Multiply by 5.3437
158-
#else // 16 MHz, 8 MHz, 4 MHz, 2 MHz, 1 MHz
159-
return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
160-
#endif
101+
return (m * 1000L) + (t / (TIME_TRACKING_TIMER_COUNT / 1000));
161102
}
162103

163104
void delay(unsigned long ms)
@@ -178,6 +119,7 @@ void delay(unsigned long ms)
178119
}
179120

180121
/* Delay for the given number of microseconds. Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */
122+
// BUG: should really be implemented using _timer instead!!!!!!!!!!
181123
void delayMicroseconds(unsigned int us)
182124
{
183125
// call = 4 cycles + 2 to 4 cycles to init us(2 for constant delay, 4 for variable)
@@ -387,22 +329,19 @@ void init()
387329

388330
/********************* TCB for system time tracking **************************/
389331

390-
/* Calculate relevant time tracking values */
391-
microseconds_per_timer_overflow = clockCyclesToMicroseconds(TIME_TRACKING_CYCLES_PER_OVF);
392-
microseconds_per_timer_tick = microseconds_per_timer_overflow/TIME_TRACKING_TIMER_PERIOD;
332+
// BUG: we can compensate for F_CPU_CORRECTED by fine tuning value of TIME_TRACKING_TIMER_COUNT
393333

394-
millis_inc = microseconds_per_timer_overflow / 1000;
395-
fract_inc = ((microseconds_per_timer_overflow % 1000));
334+
/* Select vanilla 16 bit periodic interrupt mode */
335+
_timer->CTRLB = TCB_CNTMODE_INT_gc;
396336

397-
/* Default Periodic Interrupt Mode */
398-
/* TOP value for overflow every 1024 clock cycles */
399-
_timer->CCMP = TIME_TRACKING_TIMER_PERIOD;
337+
/* TOP value for overflow every N clock cycles */
338+
_timer->CCMP = TIME_TRACKING_TIMER_COUNT - 1;
400339

401340
/* Enable TCB interrupt */
402341
_timer->INTCTRL |= TCB_CAPT_bm;
403342

404-
/* Clock selection -> same as TCA (F_CPU/64 -- 250kHz) */
405-
_timer->CTRLA = TCB_CLKSEL_CLKTCA_gc;
343+
/* Clock selection is F_CPU/2 -- ~8 MHz -- which is independent of TCA */
344+
_timer->CTRLA = TCB_CLKSEL_CLKDIV2_gc;
406345

407346
/* Enable & start */
408347
_timer->CTRLA |= TCB_ENABLE_bm; /* Keep this last before enabling interrupts to ensure tracking as accurate as possible */
@@ -412,4 +351,4 @@ void init()
412351
sei();
413352
}
414353

415-
void setup_timers(void) __attribute__((weak));
354+
void setup_timers(void) __attribute__((weak));

0 commit comments

Comments
 (0)