System tick timer для ATmega8
Для одного проекта на ATmega8A понадобилась реализация системного таймера, отмеряющего время в миллисекундах. Один из вариантов был найден в исходниках библиотек Arduino, второй у DI HALT'a. В обоих случаях используется самый простой 8-битный TIMER0, поэтому два других, более продвинутых таймера остаются свободными, например, для ШИМа. Значения посчитаны для тактовой частоты 8МГц.
Пример на псевдокоде с использованием супер-макросов — мигалка без использования _delay_ms().
#include "st.h"
...
int main(void)
{
st_init();
uint32_t last_timer = st_millis();
while(1)
{
if ((st_millis() - last_timer) >= 1000)
{
TOGGLE(LED);
last_timer = st_millis();
}
}
}
Arduino-вариант
st.h:
/* System tick timer Code from Arduino's \hardware\arduino\cores\arduino\wiring.c by Arduino team (http://arduino.cc) WARNING: - Timer configuration for ATmega8A only! - millis() value will rollover each 4294967295 milliseconds (about 49 days), one can add rollover count as here: http://www.faludi.com/2007/12/18/arduino-millis-rollover-handling/ */ #ifndef ST_H #define ST_H #define ST_CLOCK_CYCLES_TO_MICROSECONDS(a) ( ((a) * 1000L) / (F_CPU / 1000L) ) // the prescaler is set so that timer0 ticks every 64 clock cycles, and the // the overflow handler is called every 256 ticks. #define ST_MICROSECONDS_PER_TIMER0_OVERFLOW (ST_CLOCK_CYCLES_TO_MICROSECONDS(64 * 256)) // the whole number of milliseconds per timer0 overflow #define ST_MILLIS_INC (ST_MICROSECONDS_PER_TIMER0_OVERFLOW / 1000) // the fractional number of milliseconds per timer0 overflow. we shift right // by three to fit these numbers into a byte. (for the clock speeds we care // about - 8 and 16 MHz - this doesn't lose precision.) #define ST_FRACT_INC ((ST_MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3) #define ST_FRACT_MAX (1000 >> 3) uint32_t st_millis(void); void st_init(void); #endif // ST_H
st.c:
/*
Code from Arduino's \hardware\arduino\cores\arduino\wiring.c
by Arduino team (http://arduino.cc)
*/
#include <avr/interrupt.h>
#include "st.h"
volatile static uint32_t st_timer0_millis;
static uint8_t st_timer0_fract;
ISR(TIMER0_OVF_vect)
{
// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
uint32_t m = st_timer0_millis;
uint8_t f = st_timer0_fract;
m += ST_MILLIS_INC;
f += ST_FRACT_INC;
if (f >= ST_FRACT_MAX) {
f -= ST_FRACT_MAX;
m += 1;
}
st_timer0_fract = f;
st_timer0_millis = m;
}
uint32_t st_millis(void)
{
uint32_t m;
uint8_t oldSREG = SREG;
// disable interrupts while we read st_timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to st_timer0_millis)
cli();
m = st_timer0_millis;
SREG = oldSREG; // restore sei() if it was enabled
return m;
}
void st_init(void)
{
// Set prescaler to 64
TCCR0 |= (_BV(CS01) | _BV(CS00));
// Enable timer 0 overflow interrupt
TIMSK |= _BV(TOIE0);
}
«Ручной способ :)»
Идея этого решения была найдена в статье DI HALT'а про ШИМ, не уверен в точности отсчёта времени этим методом, но для моих нужд сгодилось.
st.h
/* System tick timer for ATmega8 @ 8Mhz on 8bit TIMER0 */ #ifndef ST_H #define ST_H uint32_t st_millis(void); void st_init(void); #endif // ST_H
st.c
/*
Выдержка из http://easyelectronics.ru/avr-uchebnyj-kurs-ispolzovanie-shim.html
Например, надо нам прерывание каждую миллисекунду. И чтобы вот точно. Как
это реализовать проще? Через Режим СТС! Пусть у нас частота 8Мгц.
Прескалер будет равен 64, таким образом, частота тиков таймера составит 125000 Гц.
А нам надо прерывание с частотой 1000Гц. Поэтому настраиваем прерывание по совпадению с числом 125.
Дотикал до 125 — дал прерывание, обнулился. Дотикал до 125 — дал прерывание, обнулился. И так бесконечно, пока не выключим.
Вот вам и точная тикалка.
Нет, конечно, можно и вручную. Через переполнение, т.е. дотикал до переполнения,
загрузил в обработчике прерывания заново нужные значение TCNTх=255-125,
сделал нужные полезные дела и снова тикать до переполнения. Но ведь через СТС красивей! :)
У TIMER0 CTC нет, поэтому делаем вручную:
8000000 / 64 = 125000 / 125 = 1000 Hz = 1 ms
*/
#include <avr/interrupt.h>
#include <util/atomic.h>
#include "st.h"
#define ST_CTC_HANDMADE 255-125
volatile static uint32_t st_timer0_millis;
ISR(TIMER0_OVF_vect)
{
st_timer0_millis++;
TCNT0 = ST_CTC_HANDMADE;
}
uint32_t st_millis(void)
{
uint32_t m;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
m = st_timer0_millis;
}
return m;
}
void inline st_init(void)
{
// Set prescaler to 64
TCCR0 |= (_BV(CS01) | _BV(CS00));
// Enable interrupt
TIMSK |= _BV(TOIE0);
// Set default value
TCNT0 = ST_CTC_HANDMADE;
}
