Table of Contents

Аппаратная задержка

Необходимые знания: [HW] Kontrollermoodul, [AVR] Loendurid/Taimerid, [LIB] Sisend-väljundviigud, [LIB] Viide, [LIB] Taimerid, [PRT] Tarkvaraline viide

Teooria

Программная задержка не единственный метод создания пауз. То же самое можно сделать и при помощи таймера. Таймер – это аппаратный счетчик, увеличивающийся или уменьшающийся с определенной частотой. Тактовый сигнал счетчика, как правило, можно создать из рабочего такта микроконтроллера или из другого внешнего такта. В целом, частоту тактового сигнала можно поделить каким-либо умножителем частоты для получения меньшей тактовой частоты – это делается тактовым делителем, который на английском языке называется prescaler. Важным фактом является то, что значение счётчика с фиксированной тактовой частотой находится в линеарной зависимости от времени. Время можно рассчитать, умножив тактовый сигнал счетчика на значение периода счетчика.

События, сопровождающие изменения значения счетчика AVR

AVR счетчик можно заставить сообщать о переполнении счетчика (на английском overflow) или при достижении определенного значения (на английском compare match).Переполнение создается в тот момент, когда счетчик приобрел максимально возможное значение и начинает заново считать от нуля. При достижении конкретного значения, в момент увеличения счётчика, происходит сравнение нового значения со значением, заданным пользователем. В момент возникновения события, соответствующие биты в регистре состояния AVR автоматически настраиваются верхним.

Для того, чтобы создать задержку таймером, достаточно лишь настроить счетчик и ожидать достижения верхнего значения разряда состояния. В отличии от программных задержек, работа таймера не зависит от компилятора, что делает их использование надежней. Наряду с этим, по причине разнообразия (или сложности) счётчиков AVR их налаживание может показаться затруднительным. В зависимости от сигнала такта микроконтроллера может так же случиться то, что он не делится на желаемый период задержки и задержка не точна.

Практика

Находящийся ниже программный код является функцией задержки основанной на таймере, которая немного упрощена. Принцип считывания такой же, как и при программной функции задержки – создается желаемое число задержек длинной в 1 мс. Для создания задержки используется 8-битный ATmega128 счетчик 0. Ранее уже было вычислено, что при тактовой частоте 14,7456 Mhz тактовый сигнал счетчика должен быть разделен по крайней мере на 64, чтобы в течении 1 мс 8-битный счетчик не переполнился. То, каким значением должен обладать счетчик, чтобы переполнение происходило через 1 мс, представлено в виде выражения и присвоено переменной timer_start. F_CPU на макро языке является константой, которая показывает тактовую частоту в герцах. В случае упомянутой тактовой частоты значение счетчика должно быть 25,6, но т.к. число с дробью использовать нельзя, то начальное значение счетчика будет 26. К сожалению, здесь возникает ошибка во времени задержки, но она довольно маленькая (-1,7 μs).

В цикле происходит инициализация счетчика и обнуление флага переполнения (вписывая туда 1). В этом случае ожидается пока счетчик считает начальное значение до 256, т.е. до переполнения. В момент переполнения, флаг переполнения идет вверх и происходит 1 мс задержка. В конце функции таймер останавливается.

//
// Аппаратная задержка в миллисекундах
//
void hw_delay_ms(unsigned short count)
{	
	// Вычисление начального значения таймера
	register unsigned char timer_start = 256 - F_CPU / 1000 / 64;
 
	// Запуск таймера
	timer0_init_normal(TIMER0_PRESCALE_64);
 
	// Счет задержки переменной до нуля
	while (count-- > 0)
	{
		// Инициализация счетчика
		timer0_set_value(timer_start);
 
		// Обнуление флага переполнения
		timer0_overflow_flag_clear();			
 
		// Ожидание переполнения
		while (!timer0_overflow_flag_is_set())
		{
			asm volatile ("nop");
		}			
	}
 
	// Обнуление флага переполнения
	timer0_overflow_flag_clear();	
 
	// Остановка таймера
	timer0_stop();	
}

 

Представленная функция задержки использует библиотеку таймеров, исходный код которого выглядит следующим образом:

//
// Выбор типа делителя такта таймера 0
//
typedef enum
{
	TIMER0_NO_PRESCALE         = 0x01,
	TIMER0_PRESCALE_8          = 0x02,
	TIMER0_PRESCALE_32         = 0x03,
	TIMER0_PRESCALE_64         = 0x04,
	TIMER0_PRESCALE_128        = 0x05,
	TIMER0_PRESCALE_256        = 0x06,
	TIMER0_PRESCALE_1024       = 0x07
}
timer0_prescale;
 
//
// Настройка таймера 0 в нормальный режим
//
inline void timer0_init_normal(timer0_prescale prescale)
{
	TCCR0 = prescale & 0x07;
}
 
//
// Остановка таймера 0
//
inline void timer0_stop()
{
	TCCR0 = 0x00;
}
 
//
// Обозначение значения счетчика таймера 0
//
inline void timer0_set_value(unsigned char value)
{
	TCNT0 = value;
}
 
//
// Обнуление флага переполнения таймера 0
//
inline void timer0_overflow_flag_clear(void)
{
	bit_set(TIFR, TOV0);
}
 
//
// Считывание состояния флага переполнения таймера 0
//
inline bool timer0_overflow_flag_is_set(void)
{
	return (bit_is_set(TIFR, TOV0) ? true : false);
}

 

Далее приведена такая же программа как и в примере программной задержки. В коротком 100 мс полупериоде загорается LED, в длинном 900 мс полупериоде он потухает. В результате мигает LED через каждую секунду. К сожалению, и в этом примере период не равен 1 секунде, потому что заполнение других функций программы в каждом цикле занимает время. Для точного расчёта времени придется использовать 16-битовый таймер вместе с перерывами.

//
// Демонстрационная программа аппаратной задержки Домашней Лаборатории.
// Программма мигает каждую секунду LED-ом.
//
#include <homelab/pin.h>
#include <homelab/delay.h>
 
//
// Тест обозначение вывода LED-а
//
pin debug_led = PIN(B, 7);
 
//
// Основная программа
//
int main(void)
{
	// Настройка вывода LED-а выходом
	pin_setup_output(debug_led);
 
	// Бесконечный цикл	
	while (true)
	{
		// Зажигание LED-а
		pin_clear(debug_led);
 
		// Аппаратная задержка в 100 миллисекунд
		hw_delay_ms(100);
 
		// Выключение LED-а
		pin_set(debug_led);
 
		// Аппаратная задержка в 900 миллисекунд
		hw_delay_ms(900);
	}
}