Прерывание по совпадению таймеру avr. Учебный курс AVR. Таймер - счетчик Т0. Режим Normal. Ч2. Векторы прерываний в Atmega8

Одним из преимуществ микроконтроллера ATmega8 является широкий диапазон различных прерываний.

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

Прерывания делятся на внутренние и внешние. К источникам внутренних прерываний относятся встроенные модули микроконтроллера (таймеры, приёмопередатчик USART и т.д). Внешние прерывания возникают при поступлении внешних сигналов на выводы микроконтроллера (например сигналы на выводы RESET и INT). Характер сигналов, приводящих к возникновению прерывания задаётся в регистре управления MCUCR , в частности в разрядах - ISC00 (бит 0) и ISC01 (бит 1) для входа INT 0; ISC10 (бит2) и ISC11 (бит3) для входа INT1.

В микроконтроллере ATmega8 каждому прерыванию соответствует свой вектор прерывания (адрес в начале области памяти программ, в которой хранится команда для перехода к заданной подпрограмме обработки прерывания). В mega8 все прерывания имеют одинаковый приоритет. В случае одновременного возникновения нескольких прерываний первым будет обрабатываться прерывание с меньшим номером вектора.

Векторы прерываний в Atmega8

Адрес Источник прерывания Описание
0x0000 RESET Сигнал сброса
0x0001 INT0 Внешний запрос на прерывание по входу INT0
0x0002 INT1 Внешний запрос на прерывание по входу INT1
0x0003 T/C1 Захват по таймеру T/C1
0x0004 T/C1 Совпадение с регистром сравнения A таймера T/C1
0x0005 T/C1 Совпадение с регистром сравнения B таймера T/C1
0x0006 T/C1 Переполнение счётчика T/C1
0x0007 T/C0 Переполнение счётчика T/C0
0x0008 SPI Передача данных по интерфейсу SPI завершена
0x0009 UART Приём данных приёмопередптчиком UART завершен
0x000A UART Регистр данных UART пуст
0x000B UART Передача данных приёмопередптчиком UART завершена
0x000C ANA_COMP Прерывание от аналогового компаратора

Управления прерываниями

За управление прерываниями в ATmega8 отвечают 4 регистра:

GIMSK (он же GICR) - запрет/разрешение прерываний по сигналам на входах INT0, INT1

GIFR - управление всеми внешними прерываниями

TIMSK , TIFR - управление прерываниями от таймеров/счётчиков

Регистр GIMSK(GICR)

INTFx=1: произошло прерывание на входе INTx. При входе в подпрограмму обработки прерывания INTFx автоматически сбрасывается в сотояние лог. 0

Регистр TIMSK

7 6 5 4 3 2 1 0
TOIE1
OCIE1A
OCIE1B
-
TICIE
-
TOIE0
-

TOIE1=1 : прерывание по переполнению T/C1 разрешено

OCIE1A=1 : прерывание при совпадении регистра сравнения A с содержимым счётчика T/C1 разрешено

OCIE1B=1 : прерывание при совпадении регистра сравнения B с содержимым счётчика T/C1 разрешено

TICIE=1 : разрешено прерывание при выполнении условия захвата

TOIE0=1 : прерывание по переполнению T/C0 разрешено

Регистр TIFR

7 6 5 4 3 2 1 0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-

TOV1=1 : произошло переполнение T/C1

OCF1A=1 : произошло совпадение регистра сравнения A с содержимым счётчика T/C1 разрешено

OCF1B=1 : произошло совпадение регистра сравнения B с содержимым счётчика T/C1 разрешено

ICF=1 : выполнилось условия захвата

TOV0=1 : произошло переполнение T/C0

При входе в подпрограмму обработки прерывания соответствующий прерыванию флаг регистра TIFR автоматически сбрасывается в сотояние лог. 0

Прерывания работают только тогда, когда в регистре состояния SREG разрешены общие прерывания (бит 7 = 1). В случае наступления прерывания этот бит автоматически сбрасывается в 0, блокируя выполнение последующих прерываний.

В данном примере вывод INT0 включён в режиме входа с подтяжкой. При замыкании вывода на землю при помощи кнопки на нём устанавливается лог.0 (фронт сигнала ниспадает с напряжения питания до 0) и срабатывает обработчик прерывания, включающий лампочку, подключённую к нулевому выводу порта B

void lampON()
{
PORTB.0=1;
DDRB.0=1;
}

interrupt void ext_int0_isr(void)
{
lampON();
}

DDRD.2=0;
PORTD.2=1;

SREG|= (1 while(1) {

На приведённом примере также видно, как задаются векторы прерываний в Code Vision AVR (interrupt void ext_int0_isr(void)). Аналогично задаются вектора прерываний и для других случаев:

EXT_INT0 2
EXT_INT1 3
TIM2_COMP 4
TIM2_OVF 5
TIM1_CAPT 6
TIM1_COMPA 7
TIM1_COMPB 8
TIM1_OVF 9
TIM0_OVF 10
SPI_STC 11
USART_RXC 12
USART_DRE 13
USART_TXC 14
ADC_INT 15
EE_RDY 16
ANA_COMP 17
TWI 18
SPM_READY 19

$37 ($57)
OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1 OCIE0 TOIE0
TIMSK Чтение/Запись
  • Bit 7 - OCIE2: Timer/Counter2 Output Compare Interrupt Enable - Разрешение прерывания по совпадению таймера/счетчика2
    При установленном бите OCIE2 и установленном бите I регистра статуса разрешается прерывание по совпадению содержимого регистра сравнения и состояния таймера/ счетчика2. Соответствующее прерывание (с вектором $0012) выполняется если произойдет совпадение при сравнении содержимого регистра сравнения и состояния таймера/счетчика2. В регистре флагов прерывания TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения таймера/счетчика2.
  • Bit 6 - TOIE2: Timer/Counter2 Overflow Interrupt Enable - Разрешение прерывания по переполнению таймера/счетчика2
    При установленном бите TOIE2 и установленном бите I регистра статуса разрешается прерывание по переполнению таймера/счетчика2. Соответствующее прерывание (с вектором $0014) выполняется если произойдет переполнение таймера/счетчика2. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг переполнения таймера/счетчика2.
  • Bit 5 - TICIE1: Timer/Counter1 Input Capture Interrupt Enable - Разрешение прерывания по захвату таймера/счетчика1
    При установленном бите TICIE1 и установленном бите I регистра статуса разрешается прерывание по захвату таймера/счетчика1. Соответствующее прерывание (с вектором $0016) выполняется если произойдет запуск захвата по выводу 29, PD4(IC1). В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг захвата таймера/счетчика1.
  • Bit 4 - OCE1A: Timer/Counter1 Output CompareA Match Interrupt Enable - Разрешение прерывания по совпадению регистра A с таймером/счетчиком1
    При установленном бите OCIE1A и установленном бите I регистра статуса разрешается прерывание по совпадению регистра A с состоянием таймера/счетчика1. Соответствующее прерывание (с вектором $0018) выполняется если произойдет совпадение содержимого регистра A сравнения выхода с состоянием таймера/ счетчика1. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения регистра A с таймером/счетчиком1.
  • Bit 3 - OCIE1B: Timer/Counter1 Output CompareB Match Interrupt Enable - Разрешение прерывания по совпадению регистра B с таймером/счетчиком1
    При установленном бите OCIE1B и установленном бите I регистра статуса разрешается прерывание по совпадению регистра B с состоянием таймера/счетчика1. Соответствующее прерывание (с вектором $001A) выполняется если произойдет совпадение содержимого регистра B сравнения выхода с состоянием таймера/счетчика1. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения регистра B с таймером/счетчиком1.
  • Bit 2 - TOIE1: Timer/Counter1 Overflow Interrupt Enable - Разрешение прерывания по переполнению таймера/счетчика1
    При установленном бите OCIE1B и установленном бите I регистра статуса разрешается прерывание по переполнению таймера/счетчика1. Соответствующее прерывание (с вектором $001C) выполняется если произойдет переполнение таймера/счетчика1. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг переполнения таймера/счетчика1.При нахождении таймера/счетчика1 в PWM режиме флаг переполнения счетчика устанавливается когда счетчик изменит направление счета при $0000.
  • Bit 1 - OCIE0: Timer/Counter0 Output Compare Interrupt Enable - Разрешение прерывания по совпадению таймера/счетчика0
    При установленном бите OCIE0 и установленном бите I регистра статуса разрешается прерывание по совпадению содержимого регистра сравнения и состояния таймера/ счетчика0. Соответствующее прерывание (с вектором $001E) выполняется если произойдет совпадение при сравнении содержимого регистра сравнения и состояния таймера/счетчика0. В регистре флагов прерывания TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг совпадения таймера/счетчика0.
  • Bit 0 - TOIE0: Timer/Counter0 Overflow Interrupt Enable - Разрешение прерывания по переполнению таймера/счетчика0
    При установленном бите TOIE0 и установленном бите I регистра статуса разрешается прерывание по переполнению таймера/счетчика0. Соответствующее прерывание (с вектором $0020) выполняется если произойдет переполнение таймера/счетчика0. В регистре флагов TIFR (Timer/Counter Interrupt Flag Register) устанавливается флаг переполнения таймера/счетчика0.

Урок 10

Таймеры-счетчики. Прерывания

Сегодня мы узнаем, что такое таймеры-счётчики в микроконтроллерах и для чего они нужны, а также что такое прерывания и для чего они тоже нужны.

Таймеры-счётчики — это такие устройства или модули в микроконтроллере, которые, как видно из названия, постоянно что-то считают. Считают они либо до определённой величины, либо до такой величины, сколько они битности. Считают они постоянно с одной скоростью, со скоростью тактовой частоты микроконтроллера, поправленной на делители частоты, которые мы будем конфигурировать в определённых регистрах.

И вот эти таймеры-счётчики постоянно считают, если мы их инициализируем.

Таймеров в МК Atmega8 три.

Два из них — это восьмибитные таймеры, то есть такие, которые могут максимально досчитать только до 255. Данной величины нам будет маловато. Даже если мы применим максимальный делитель частоты, то мы не то что секунду не отсчитаем, мы даже полсекунды не сможем посчитать. А у нас задача именно такая, чтобы досчитывать до 1 секунды, чтобы управлять наращиванием счёта светодиодного индикатора. Можно конечно применить ещё наращивание переменной до определенной величины, но хотелось бы полностью аппаратного счёта.

Но есть ещё один таймер — это полноправный 16-битный таймер. Он не только 16-битный , но есть в нём ещё определённые прелести, которых нет у других таймеров. С данными опциями мы познакомимся позже.

Вот этот 16-битный таймер мы и будем сегодня изучать и использовать. Также, познакомившись с данным таймером, вам ничего не будет стоить самостоятельно изучить работу двух других, так как они значительно проще. Но тем не менее 8-битные таймеры в дальнейшем мы также будем рассматривать, так как для достижения более сложных задач нам одного таймера будет недостаточно.

Теперь коротко о прерываниях.

Прерывания (Interrupts ) — это такие механизмы, которые прерывают код в зависимости от определённых условий или определённой обстановки, которые будут диктовать некоторые устройства, модули и шины, находящиеся в микроконтроллере.

В нашем контроллере Atmega8 существует 19 видов прерываний. Вот они все находятся в таблице в технической документации на контроллер

Какого типа могут быть условия? В нашем случае, например, досчитал таймер до определённой величины, либо например в какую-нибудь шину пришёл байт и другие условия.

На данный момент мы будем обрабатывать прерывание, которое находится в таблице, размещённой выше на 7 позиции — TIMER1 COMPA , вызываемое по адресу 0x006.

Теперь давайте рассмотрим наш 16-битный таймер или TIMER1 .

Вот его структурная схема

Мы видим там регистр TCNTn , в котором постоянно меняется число, то есть оно постоянно наращивается. Практически это и есть счётчик. То есть данный регистр и хранит число, до которого и досчитал таймер.

А в регистры OCRnA и OCRnB (буквы n — это номер таймера, в нашем случае будет 1) — это регистры, в которые мы заносим число, с которым будет сравниваться чило в регистре TCNTn.

Например, занесли мы какое-нибудь число в регистр OCRnA и как только данное число совпало со значением в регистре счёта, то возникнет прерывание и мы его сможем обработать. Таймеры с прерываниями очень похожи на обычную задержку в коде, только когда мы находимся в задержке, то мы в это время не можем выполнять никакой код (ну опять же образно "мы", на самом деле АЛУ). А когда считает таймер, то весь код нашей программы в это время спокойно выполняется. Так что мы выигрываем колоссально, не давая простаивать огромным ресурсам контроллера по секунде или даже по полсекунды. В это время мы можем обрабатывать нажатия кнопок, которые мы также можем обрабатывать в таймере и многое другое.

Есть также регистр TCCR. Данный регистр — это регистр управления. Там настраиваются определенные биты, отвечающие за конфигурацию таймера.

Также у таймера существует несколько режимов, с которыми мы также познакомимся немного позденее.

Он состоит из двух половинок, так как у нас конотроллер 8-битный и в нем не может быть 16-битных регистров. Поэтому в одной половинке регистра (а физически в одном регистре) хранится старшая часть регистра, а в другом — младшая. Можно также назвать это регистровой парой, состоящей из двух отдельных регистров TCCR1A и TCCR1B. Цифра 1 означает то, что регистр принадлежит именно таймеру 1.

Даный регист TCCR отвечает за установку делителя, чтобы таймер не так быстро считал, также он отвечает (вернее его определённые биты) за установку определённого режима.

За установку режима отвечают биты WGM

Мы видим здесь очень много разновидностей режимов.

Normal — это обычный режим, таймер считает до конца.

PWM — это ШИМ только разные разновидности, то есть таймер может играть роль широтно-импульсного модулятора . С данной технологией мы будем знакомиться в более поздних занятиях.

CTC — это сброс по совпадению, как раз то что нам будет нужно. Здесь то и сравнивются регистры TCNT и OCR. Таких режима два, нам нужен первый, второй работает с другим регистром.

Все разновидности режимов мы в данном занятии изучать не будем. Когда нам эти режимы потребуются, тогда и разберёмся.

Ну давайте не будем томить себя документацией и наконец-то попробуем что-то в какие-нибудь регистры занести.

Код, как всегда, был создан из прошлого проекта. Для протеуса также код был скопирован и переименован с прошлого занятия, также в свойствах контроллера был указан путь к новой прошивке. Проекты мы назовем Test07 .

Попробуем как всегда скомпилировать код и запустить его в протеусе. Если всё нормально работает, то начинаем добавлять новый код.

Добавим ещё одну функцию, благо добавлять функции мы на прошлом занятии научились. Код функции разместим после функции segchar и до функции main. После из-за того, что мы будем внутри нашей новой функции вызывать функцию segchar.

Мало того, мы создадим не одну функцию, а целых две. В одну функцию мы разместим весь код инициализации нашего таймеру, а другая функция будет являться обработчиком прерывания от таймера, а такие функции они специфичны и вызывать их не требуется. Когда возникнет необходимость, они вызовутся сами в зависимости от определённых условий, которые были оговорены выше.

Поэтому первую функцию мы назвовём timer_ini

//———————————————

void timer_ini ( void )

{

}

//———————————————

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

Данная функция, как мы видим не имеет ни каких аргументов — ни входных, не возвращаемых. Давайте сразу данную функцию вызовем в функции main()

unsigned char butcount=0, butstate=0;

timer_ini ();

Теперь мы данную функцию начнём потихонечку наполнять кодом.

Начнем с регистра управления таймером, например с TCCR1B. Используя нашу любимую операцию "ИЛИ", мы в определённый бит регистра занесём единичку

void timer_ini ( void )

TCCR1B |= (1<< WGM12 );

Из комментария мы видим, что мы работает с битами режима, и установим мы из них только бит WGM12, остальные оставим нули. Исходя из этого мы сконфигурировали вот такой режим:

Также у таймера существует ещё вот такой регистр — TIMSK . Данный регистр отвечает за маски прерываний — Interrupt Mask . Доступен данный регистр для всех таймеров, не только для первого, он общий. В данном регистре мы установим бит OCIE1A , который включит нужный нам тип прерывания TIMER1 COMPA

TCCR1B |= (1<< WGM12 ); // устанавливаем режим СТС (сброс по совпадению)

TIMSK |= (1<< OCIE1A );

Теперь давайте поиграемся с самими регистрами сравнения OCR1A(H и L) . Для этого придётся немного посчитать. Регистр OCR1AH хранит старшую часть числа для сравнения, а регистр OCR1AL — младшую.

Но прежде чем посчитать, давайте пока напишем код с любыми значениями данного регистра и потом поправим, так как дальше мы будем инициализировать делитель и он тоже будет учавствовать в расчёте требуемого времени счёта. Без делителя таймер будет слишком быстро считать.

TIMSK |= (1<< OCIE1A ); //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A(H и L)

OCR1AH = 0b10000000;

OCR1AL = 0b00000000;

TCCR1B |= ( ); //установим делитель.

Пока никакой делитель не устанавливаем, так как мы его ещё не посчитали. Давайте мы этим и займёмся.

Пока у нас в регистре OCR1A находится число 0b1000000000000000, что соответствует десятичному числу 32768.

Микроконтроллер у нас работает, как мы договорились, на частоте 8000000 Гц.

Разделим 8000000 на 32768, получим приблизительно 244,14. Вот с такой частотой в герцах и будет работать наш таймер, если мы не применим делитель. То есть цифры наши будут меняться 244 раза в секунду, поэтому мы их даже не увидим. Поэтому нужно будет применить делитель частоты таймера. Выберем делитель на 256. Он нам как раз подойдёт, а ровно до 1 Гц мы скорректируем затем числом сравнения.

Вот какие существуют делители для 1 таймера

Я выделил в таблице требуемый нам делитель. Мы видим, что нам требуется установить только бит CS12 .

Так как делитель частоты у нас 256, то на этот делитель мы поделим 8000000, получится 31250, вот такое вот мы и должны занести число в TCNT. До такого числа и будет считать наш таймер, чтобы досчитать до 1 секунды. Число 31250 — это в двоичном представлении 0b0111101000010010. Занесём данное число в регистровую пару, и также применим делитель

OCR1AH = 0b01111010 ; //записываем в регистр число для сравнения

OCR1AL = 0b00010010 ;

TCCR1B |= (1<< CS12 ); //установим делитель.

С данной функцией всё.

Теперь следующая функция — обработчик прерывания от таймера по совпадению. Пишется она вот так

ISR ( TIMER1_COMPA_vect )

{

}

И тело этой функции будет выполняться само по факту наступления совпадения чисел.

Нам нужна будет переменная. Объявим её глобально, в начале файла

#include

//———————————————

unsigned char i ;

//———————————————

Соответственно, из кода в функции main() мы такую же переменную уберём

int main ( void )

unsigned char i ;

Также закомментируем весь код в бесконечном цикле. Его роль теперь у нас будет выполнять таймер, и, я думаю, он с этим справится не хуже, а даже лучше, "никому" при этом не мешая.

while (1)

{

// for(i=0;i<10;i++)

// {

// while (butstate==0)

// {

// if (!(PINB&0b00000001))

// {

// if(butcount < 5)

// {

// butcount++;

// }

// else

// {

// i=0;

// butstate=1;

// }

// }

// else

// {

// if(butcount > 0)

// {

// butcount—;

// }

// else

// {

// butstate=1;

// }

// }

// }

// segchar(i);

// _delay_ms(500);

// butstate=0;

// }

Теперь, собственно, тело функции-обработчика. Здесь мы будем вызывать функцию segchar. Затем будем наращивать на 1 переменную i . И чтобы она не ушла за пределы однозначного числа, будем её обнулять при данном условии

ISR ( TIMER1_COMPA_vect )

if ( i >9) i =0;

segchar ( i );

i ++;

Теперь немного исправим код вначале функции main(). Порт D , отвечающий за состояние сегментов, забьём единичками, чтобы при включении у нас не светился индикатор, так как он с общим анодом. Затем мы здесь занесём число 0 в глобавльную переменную i, просто для порядка. Вообще, как правило, при старте в неициализированных переменных и так всегда нули. Но мы всё же проинициализируем её. И, самое главное, чтобы прерывание от таймера работало, её недостаточно включить в инициализации таймера. Также вообще для работы всех прерываний необходимо разрешить глобальные прерывания. Для этого существует специальная функция sei() — Set Interrupt .

Теперь код будет вот таким

DDRB = 0x00;

PORTD = 0b11111111 ;

PORTB = 0b00000001;

i =0;

sei ();

while (1)

Также ещё мы обязаны подключить файл библиотеки прерываний вначале файла

#include

#include

#include

Также переменные для кнопки нам пока не потребуются, так как с кнопкой мы сегодня работать не будем. Закомментируем их

int main ( void )

//unsigned char butcount=0, butstate=0;

timer_ini ();

Соберём наш код и проверим его работоспособность сначала в протеусе. Если всё нормально работает, то проверим также в живой схеме

Всё у нас работает. Отлично!

Вот такой вот получился секундомер. Но так как у нас даже нет кварцевого резонатора, то данный секундомер нельзя назвать точным.

Тем не менее сегодня мы с вами много чему научились. Мы узнали о прерываниях, также научились их обрабатывать, Научились работать с таймерами, конфигурировать несколько новых регистров микроконтроллера, до этого мы работали только с регистрами портов. Также за счёт всего этого мы значительно разгрузили арифметическо-логическое устройство нашего микроконтроллера.

Смотреть ВИДЕОУРОК

Post Views: 17 413


Таймеры счетчики микроконтроллеров AVR (часы реального времени). Урок AVR 7

Когда я еще начинал изучать микроконтроллеры, мне захотелось сделать , чтобы . Честно признаюсь, я хотел попробовать включать телевизор только с 7 до 8 часов, а все остальное время он должен был быть отключен. Устройство я сделал, но так его и не применил...

Во всех микроконтроллерах AVR есть несколько встроенных таймеров. Их еще можно разделить на таймеры общего назначения и сторожевой таймер, который предназначен для перезагрузки МК при зависании.

Таймеры общего назначения умеют:

  • Тактировать от внешнего часового кварца на 32768 герц
  • Считать разные временные интервалы
  • Считать внешние импульсы в режиме счетчика
  • Генерировать ШИМ-сигнал на определённых выводах МК
  • Генерировать прерывания по какому-то событию, например, при переполнению

Таймеры счетчики могут тактировать от внутреннего генератора тактовой частоты и от счетного входа. Давайте рассмотрим функционал таймера-счетчика 1 в микроконтроллере atmega8 . Запускаем CodeVision AVR, создаем новый проект и соглашаемся на предложение запустить Code WizardAVR

Давайте на примере timer2 реализуем часы реального времени с выводом на lcd дисплей, для этого выставляем таймер как показано на скриншоте

здесь выставляется внешний источник тактирования таймера, в качестве внешнего источника мы будем использовать часовой кварц на 32768 герц, далее установим предделитель на 128, то есть таймер будет работать на частоте 32768/128=256, а счетный регистр то в нас 8-битный (максимальное число 255), получается, что он будет переполнятся раз в секунду, далее мы выставляем галочку возле Overflow interrupt и кликаем на file->Generate, save and exit.

Code Wizard cгенерировал вот такой код

#include // Timer2 overflow interrupt service routine interrupt void timer2_ovf_isr(void) { } void main(void) { // Input/Output Ports initialization // Port B initialization PORTB=0x00; DDRB=0x00; // Port C initialization PORTC=0x00; DDRC=0x00; // Port D initialization PORTD=0x00; DDRD=0x00; // Timer/Counter 0 initialization // Clock source: System Clock // Clock value: Timer 0 Stopped TCCR0=0x00; TCNT0=0x00; // Timer/Counter 1 initialization // Clock source: System Clock // Clock value: 125,000 kHz // Mode: Fast PWM top=00FFh // OC1A output: Discon. // OC1B output: Discon. // Noise Canceler: Off // Input Capture on Falling Edge // Timer1 Overflow Interrupt: Off // Input Capture Interrupt: Off // Compare A Match Interrupt: Off // Compare B Match Interrupt: Off TCCR1A=0x01; TCCR1B=0x0A; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; // Timer/Counter 2 initialization // Clock source: TOSC1 pin // Clock value: PCK2/128 // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x08; TCCR2=0x05; TCNT2=0x00; OCR2=0x00; // External Interrupt(s) initialization // INT0: Off // INT1: Off MCUCR=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x40; // Analog Comparator initialization // Analog Comparator: Off // Analog Comparator Input Capture by Timer/Counter 1: Off ACSR=0x80; SFIOR=0x00; // Global enable interrupts #asm("sei") while (1) { }; }

#include #include // Alphanumeric LCD Module functions #asm .equ __lcd_port=0x12 ;PORTD #endasm #include unsigned char second=0; //переменная для хранения секунд unsigned char minute=0; //переменная для хранения минут unsigned char hour=0; //переменная для хранения часов char lcd_buffer; //переменная буфер для вывода на дисплей // Timer2 overflow interrupt service routine interrupt void timer2_ovf_isr(void) { if (++second==59) //увеличиваем количество секунд на 1 и проверяем равенство 59 {second = 0; if (++minute==59) {minute = 0; if (++hour==59) { hour = 0; } } } lcd_clear(); //чистим дисплей перед выводом lcd_gotoxy(0,0); // переводим курсор в точку x=0 y=0 sprintf(lcd_buffer,"%i:%i:%i",hour,minute,second); // формируем строку для вывода lcd_puts(lcd_buffer); // выводим строку на дисплей } void main(void) { // Timer/Counter 2 initialization // Clock source: TOSC1 pin // Clock value: PCK2/128 // Mode: Normal top=FFh // OC2 output: Disconnected ASSR=0x08; TCCR2=0x05; TCNT2=0x00; OCR2=0x00; // Timer(s)/Counter(s) Interrupt(s) initialization TIMSK=0x40; // LCD module initialization lcd_init(16); // Global enable interrupts #asm("sei") while (1) { }; }

Программа готова, теперь составим схему в Proteus

По сути, таймер микроконтроллера - это цифровой счетчик, только "навороченый". На вход счетчика подается тактовый сигнал, по перепадам которого счетчик увеличивает свое значение. При возникновении событий - переполнение счетчика или совпадение его значения с заданным - генерируется запрос на прерывание.

Давайте разберем, как пользоваться таймером Т0 в режиме Normal. В этом режиме таймер считает от какого-то начального значения счетного регистра до максимально возможного (до 255 или 0xFF). Когда таймер Т0 досчитывает до максимума, то в следующий такт таймера возникает переполнение счетного регистра TCNT0 - он обнуляется и устанавливается флаг TOV0. Если в программе разрешены прерывания глобально (флаг I регистра SREG) и прерывание таймера Т0 по переполнению (флаг TOIE0 регистра TIMSK), то микроконтроллер вызовет соответствующий обработчик. Если значение счетного регистра совпадет с регистром сравнения OCR0, то установится флаг OCF0 и при разрешенном прерывании по событию совпадение, запустится его обработчик.

Таймер Т0 в режиме Normal

Рассмотрим практическую задачу - нам нужно каждые 20 мс опрашивать кнопку. Частота микроконтроллера 8 МГц, микроконтроллер ATmega16.

Первое, что нужно сделать - это определиться с выбором коэффициента предделителя таймера и рассчитать начальное значение для счетного регистра TCNT0.

Таймер Т0 может тактироваться от внутреннего тактового сигнала микроконтроллера или от внешнего, который подается на вывод Т0. При работе от внутреннего тактового сигнала пользователь может выбирать коэффициенты деления частоты этого сигнала. У таймера Т0 есть пять возможных вариантов коэффициента предделителя - 1, 8, 64, 256, 1024.

Для решения поставленной задачи, я рассуждаю следующим образом. Если бы один такт таймера Т0 имел период 1 мс, то мне бы это подошло. 20 тактов дают 20 мс. Какой коэффициент предделителя таймера позволит получить близкий к 1 мс период тактовой частоты? Можно посчитать.

Тактовая частота микроконтроллера Fcpu = 8000000 Гц
Период тактового сигнала микроконтроллера Tcpu = 1/Fcpu
Период тактового сигнала таймера Т0 равен Tt0 = (1/Fcpu)/k = k/Fcpu

При k = 1024 период тактовой частоты таймера Т0 будет равен Tt0 = 1024/8000000 = 0.128 мс

Это максимальный период тактового сигнала таймера, который мы можем получить при наших условиях (Fcpu = 8 МГц). При меньших коэффициентах - период получится еще меньше.

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

n = t/Tto = 20 мс/ 0.128 мс = 156.25

Округлив до целого, получаем 156 тактов. Это меньше 255 (максимального значения счетного регистра), значит разрядности счетного регистра TCNT0 хватит.

Начальное значение для счетного регистра TCNT0 вычисляем как разницу между максимальным числом тактов таймера Т0 и требуемым, то есть 256 - 156 = 100. (256 - это максимальное количество временных интервалов, которые может отсчитать любой 8-и разрядный таймер.)

Думаю, теперь понятно, как рассчитывать начальное значение TCNT0 для режима Normal :

Вычисляем период одного такта таймера Tt0 = k/Fcpu,
- вычисляем требуемое количество тактов для заданного интервала n = t/Tto,
- вычисляем начальное значение для счетного регистра TCNT0 = 256 - n.

Можно автоматизировать эту процедуру с помощью макросов. Например, так:

#define F_CPU 8000000UL
#define TIME_MS(time, k) (256L - ((time)*(F_CPU))/(1000L*(k)))

Но с таким макросом нужно быть начеку, при определенных значениях time и k могут возникать ошибки.

Теперь переходим к коду. Чтобы использовать таймер Т0 (да и любой другой тоже), его нужно настроить (инициализировать) и описать обработчик прерывания (если они используются).

Инициализация таймера состоит из следующих шагов:

Остановка таймера,
- задание режима Normal в TCCR0 без старта,
- установка начального значения TCNT0,
- сброс флагов в регистре TIFR,
- разрешение прерывания по переполнению в TIMSK,
- установка предделителя в TCCR0, то есть старт таймера

В данной последовательности возможны вариации.

Для нашей задачи код инициализации будет выглядеть так:


/*значение для счетного регистра*/
#define T_POLL 100

TCCR0 = 0;
TCCR0 = (0< TCNT0 = T_POLL;
TIFR = (1< TIMSK |= (1< TCCR0 |= (1<

Вторая строчка инициализации, по сути, бесполезна, она добавлена для наглядности. Чтобы четко видеть, какой режим таймера устанавливается.

Сброс флагов прерываний в регистре TIFR выполняется записью 1 в соответствующий разряд. Эту операцию нужно выполнять именно перезаписью регистра, а не с помощью побитового ИЛИ. И вот почему.

Допустим, в регистре TIFR устанавлены два флага прерывания - TOV1 и TOV0. TOV0 нам нужно сбросить. При установке требуемого разряда с помощью ИЛИ происходит примерно следующая вещь.


//TIFR имеет значение 0b00000101
//установлены флаги TOV1 и TOV0
//выполняется код TIFR |= (1<
//TIFR копируется в R16
IN R16, 0x38

//в R16 устанавливается разряд TOV0
//хотя он и так уже установлен
ORI R16, 0x02

//R16, равный 0b00000101, записывается в регистр TIFR
OUT 0x38, R16

В результате сброшены оба флага, а мы хотели сбросить один.

Продолжаем.

Синтаксис описания обработчиков прерывания у разных компиляторов немного отличается. Для IAR`a обработчик прерывания таймера Т0 по событию переполнение будет выглядеть так:



{
TCNT0 = T_POLL;

/*здесь должен быть опрос кнопки*/

TIMER0_OVF_vect - это адрес вектора прерывания по событию переполнение. Он берется из заголовочных файлов на микроконтроллер. В данном случае я взял его из файла iom16.h.

Первая строка обработчика (TCNT0 = T_POLL;) выполняет перезапись счетного регистра, то устанавливает его начальное значение. Если этого не сделать, таймер продолжит счет с 0. Перезапись счетного регистра нужно выполнять в начале обработчика прерывания.

Весь код для нашей задачи будет выглядеть примерно так. (Код приведен для IAR`a. Для других компиляторов нужно изменить заголовочные файлы и обработчик прерывания.)

#include
#include
#include

#define T_POLL 100

int main(void)
{
/*инициализация таймера*/

TCCR0 = 0;
TCCR0 = (0< TCNT0 = T_POLL;
TIFR |= (1< TIMSK |= (1< TCCR0 |= (1<

/*инициализация остальной периферии*/
DDRB |= (1<

Enable_interrupt();
while(1);

/*обработчик прерывания T0
по событию переполнение*/
#pragma vector = TIMER0_OVF_vect
__interrupt void TimerT0Ovf(void)
{
/*перезапись счетного регистра*/
TCNT0 = T_POLL;

/*опрос кнопки*/

/*инверсия PB0 для отладки*/
PORTB ^= (1<

Управление выводом OC0

В режиме Normal таймер Т0 может изменять состояние вывода OC0 при совпадении счетного регистра и регистра сравнения. Причем даже без прерываний. Варианты управления определяются разрядами COM01 и COM00 регистра TCCR0.

Вот пример программы, генерирующей прямоугольный сигнала на выводе ОС0.

#include
#include

int main(void)
{
/*инициализация таймера Т0*/

TCCR0 = 0;
TCCR0 = (0< TCNT0 = 0;
OCR0 = 0;
TIMSK = 0;
TCCR0 |= (1<

/*инициализация OC0*/
DDRB |= (1<

While(1);
return 0;
}

Вывод ОС0 будет менять свое состояние на противоположное при нулевом значении счетного регистра.

Несколько моментов относительно использования таймера

Обработчик прерывания таймера (да и любой другой периферии) нужно делать как можно короче.

Если расчетное значение для счетного регистра (или регистра сравнения) округляется, то временной интервал будет отсчитываться таймером с погрешностью.

И последнее. Может случится ситуация, что обработка прерывания таймера задержится (например, по вине другого обработчика) и регистр TCNT0 уже посчитает несколько тактов. Если просто перезаписать значение TCNT0, то следующее прерывание вызовется позже, чем нужно. Получится, что предыдущее (задержанное) и новое прерывания не выдержат требуемый интервал.

Эту ситуацию можно сгладить, если выполнять перезапись счетного регистра вот так:

TCNT0 = TCNT0 + startValue;

Сложение текущего значения счетного регистра с инициализируемым, учтет эти лишние такты. Правда есть одно НО! При больших значения startValue операция сложения может вызвать переполнение счетного регистра.

Например, startValue = 250, а таймер успел досчитать до 10. Тогда операция сложения приведет к такому результату:

10 + 250 = 260

Берем 8 разрядов от 260 получаем 4. В TCNT0 запишется 4.

error: Content is protected !!