System tick timer для ATmega8

01.04.2011 20:59 / Артём Волк / 1301 просмотр / ...

Для одного проекта на 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;
}