Таймеры и каунтеры. Бегущий огонек v2.0

Сегодня мы поговорим о таких вещах как таймеры. А их, между прочим, в 90s2313 - аж целых два!

Что такое таймер? Это - счетчик импульсов!
Мы можем в любой момент из программы причитать текущее состояние таймера (сколько импульсов он сосчитал), или записать в него новое значение. Также, таймер может генерировать прерывания по каким-то важным событиям в своей жизни. О прерываниях мы только что говорили…

В нашем контроллере 2 таймера, которые называются Timer-Counter 0 и Timer-Counter 1

.

Как мы помним, у Timer-Counter 1 есть прерывание компаратора.
Что такое компаратор? Аглицкое слово compare слышали? Что означает? Правильно: to compare - сравнивать. Короче говоря, компаратор - это "сравниватель". В данном случае, он сравнивает значение таймера с некоторым числом, и как только они сравняются - тут же дает запрос на прерывание.

Это-то нам и нужно!
Мы запишем в компаратор какое-то число и будем ждать, пока таймер дойдет до этого числа.
Как дойдет - переключим светодиодик, обнулим таймер - и все начнется по новой.

Архитектура таймера

Ну, пошли вникать в архитектуру таймер-каунтера 1.

Для этого я бы с удовольствием отправил вас на страничку 27 даташита по AT90s2313.
Ну а тем лентяям, которые остались здесь, придется снова растолковывать все на пальцах…

Все начинается с предделителя. По-ихнему, он зовется prescaler (предмасштабатор).
Для чего оно надо? Для того, чтобы выбирать частоту, подаваемую на тактовый вход таймеров. Смотрим схему:

НЕ нашли? Не то? Что вы ищете?

Предделитель (prescaler) контроллера AT90s2313

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

CK/8
CK/64
CK/256
CK/1024

Две трапеции внизу - енто мультиплексоры (переключатели). В зависимости от подаваемого адреса (CS10..CS12 и CS00..CS02), на выход мультиплексора подается сигнал с одного их восьми входов:

0. (000) Нет тактовой частоты (по умолчанию)
1. (001) CK (не деленная тактовая частота контроллера)
2. (010) CK/8
3. (011) CK/64
4. (100) CK/256
5. (101) CK/1024
6. (110) Инвертированный сигнал с внешней ноги (T0 или T1)
7. (111) Неинвертированный сигнал с внешней ноги.

Чтобы выбрать для таймера необходимый источник тактового сигнала, необходимо записать его адрес в соответствующие биты регистров TCCR0 - для 0-го таймера, и TCCR1B - для 1-го таймера. Какие биты - смотрим на рисунки :)

Регистры

Далее перенесемся на страничку 30 и взглянем ясными очами на схему 1-го таймера:

Структурная схема Timer/Counter 1

Страшно? Конечно страшно! Стока всего!!!

Коротенько перечислим все, что мы видим:

TIMSK - Timer Interrupt MaSK register - регистр маски прерываний
TIFR - Timer Interrupt Flag Register - регистр флагов прерываний
TCCR1A - Timer/Counter1 Control Register A - контрольный регистр 1-го таймера А
TCCR1B - Timer/Counter1 Control Register B - контрольный регистр 1-го таймера B
Control logic - управляющая логика
ICR1 - timer/counter1 Input Capture Register1 - входной регистр защелки 1-го таймера
TCNT1 - Timer/CouNTer1 - собственно, регистр состояния таймера
16-bit Comparator - компаратор
OCR1A - timer/counter Output1 Compare Register A - выходной регистр компаратора A

Из всей этой веселой компании нас интересуют следующие товарищи:

TIMSK - он определяет, какие прерывания таймера мы будем использовать
TCCR1B - регистр управления 1-м таймером
TCNT0 - собственно, регистр состояния таймера. Его мы будем обнулять по прерыванию компаратора
OCR1A - в него загружается число, с которым сравнивает компаратор

Вот так выглядит регистр TIMSK:

Регистр

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

7 - TOIE1 - Timer/Counter1 Overflow Interrupt Enable - разрешение прерывания по переполнению 1-го таймера

6 - OCIE1A - Timer/Counter1 Output Compare Match Interrupt Enable - разрешение прерывания компаратора 1-го таймера

5,4 - ни за что не отвечают (зарезервированы)

3 - TICIE1 - Timer/Counter1 Input Capture Interrupt Enable - разрешение прерывания защелки 1-го таймера

2 - зарезервирован

1 - TOIE0 - Timer/Counter0 Overflow Interrupt Enable - разрешение прерывания по переполнению 0-го таймера

0 - зарезервирован

Нам нужно только прерывание компаратора, а остальные идут лесом. Поэтому, мы смело устанавливаем в TIMSK 6-й бит, а остальные оставляем равными 0.
Итак:

TIMSK = 0b

Теперь определимся с регистром TCCR1B. Вот он:

Регистр

Не хочу подробно распекаться о значении каждого бита, это и так разжевано в даташите. Нас сейчас интересуют только 3 младших бита (CS10…CS12). С ними мы уже знакомы - они определяют источник тактового сигнала…

Так. Надо определиться с тактовым сигналом!
Я так понимаю, что огонек должен пробегать примерно один раз в секунду, то есть, между переключениями светодиодов должно быть где-то 1/8с. То есть - 0,125с или 125 мс
Тактовая частота нашего контроллера (частота кварца): 10 МГц. Его период: 1/10е6 = 100 нс.
Максимальное значение таймера: 2^16 = 65535.

Наша задача - подобрать такую тактовую частоту таймера, чтобы он считал до 65535 немного дольше, чем 1/8 секунды (125 мс).

Пойдем методом околонаучного тыка:

Сначала вычислим, за сколько досчитает до конца таймер при тактовой частоте равной частоте кварца:
100 нС * 65536 = 6,6 мс - маловато будет!

Хорошо, а что если делить тактовую частоту на 8 (то есть, увеличить ее период в 8 раз):
6,6 * 8 = 52 мс - тоже маловато. А если на 64?
6,6 * 64 = 419 мс - а вот это уже больше чем 125 - стало быть подойдет :)

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

Соответствие кодов TCCR1B тактовым сигналам

Увидели: сигналу CK/64 соответствует код 011.
Пишем его в соответствующие биты TCCR1B. Так как остальные биты этого регистра нам не интересны - они тоже идут лесом, то есть, остаются в нулях.
Итак:

TCCR1B = 0b

Осталось только рассчитать число, которое мы загрузим в OCR1A, то есть - с которым будет сравнивать компаратор текущее состояние таймера. Оно считается очень просто.

Мы уже знаем, что тактовая частота таймера в 64 раза меньше частоты кварца. Значит ее период - в 64 раза больше:
100 нс * 64 = 6,4 мкс.
Нам нужно, чтобы сравнение происходило в момент времени, отстоящий от запуска таймера на 125 мс.
Считаем количество тактовых импульсов, которое пройдет за это время:
125мс / 6,4 мкс = 19531 имп.

То есть, задержка в 125мс равна 19531 такту. Именно это число мы и загрузим в OCR1A. Единственное, что надо помнить: этот регистр - составной. То есть, он состоит из двух 8-битных регистров. Поэтому, сначала нужно преобразовать это число в шестнадцатеричную систему и загрузить старшие и младшие разряды в соответствующие регистры: OCR1AH и OCR1AL.

19531(10) = 4C4B(16). Итого имеем:

OCR1AH = 0x4c
OCR1AL = 0x4B

Ну все! Теперь пишем прогу...
Ах, да! Чуть не забыл! Еще две малюсенькие командочки:

sei - global interrupt enable - глобальное разрешение прерываний
cli - global interrupt disable - глобальный запрет прерываний (по умолчанию).

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

.cseg

.org 0

rjmp Reset ;вектора прерываний

rjmp INT_0

rjmp INT_1

rjmp Timer1_capt1

rjmp Timer1_comp1

rjmp Timer1_OVF1

rjmp Timer0_OVF0

rjmp UART_RX

rjmp UART_UDRE

rjmp UART_TX

rjmp ANA_COMP

;Reset:

INT_0:

INT_1:

Timer1_capt1:

;Timer1_comp1:

Timer1_OVF1:

Timer0_OVF0:

UART_RX:

UART_UDRE:

UART_TX:

ANA_COMP:

reti

;****

; ИНИЦИАЛИЗАЦИЯ

;****

Reset: ldi Temp,0b ;настройка портов

out DDRB, Temp

ldi Temp,0b ;разрешить прерывание компаратора

out TIMSK, Temp

ldi Temp,0b ;тактовый сигнал = CK/64

out TCCR1B, Temp

ldi Temp,0x4C ;инициализация компаратора

out OCR1AH, Temp

ldi Temp,0x4B

out OCR1AL, Temp

ldi Temp, RamEnd ;установка указателя стека

out SPL, Temp

ldi Temp1,0b ;инициализация индикатора

ldi Temp,0 ;обнуление таймера

out TCNT1H, Temp

out TCNT1L, Temp

sei ;разрешить прерывания

;****

; ОСНОВНОЙ ЦИКЛ

;****

Inf: rjmp Inf ;бесконечный цикл

;****

; ОБРАБОТЧИК ПРЕРЫВАНИЯ КОМПАРАТОРА

;****

Timer1_comp1:

ldi Temp,0 ;обнуление таймера

out TCNT1H, Temp

out TCNT1L, Temp

Shift: cpi Temp1,0b ;сравнить с крайним знач.

breq Init ;если равно - загрузка нач. знач.

lsl Temp1 ;иначе - сдвиг влево

rjmp Output ;перейти на вывод в порт

Init: ldi Temp1,0b ;загрузить нач. значение

Output: out PortB, Temp1 ;вывод в порт

reti ;выход из обработчика

Как видите, начальные настройки заняли в программе даже больше места, чем функциональная ее часть.:)

Что мы сделали?
Сначала идет настройка - мы настраиваем порты, таймеры и все такое - это понятно. Разрешаем прерывания командой sei и выходим в основной цикл.

Основной цикл - это одна команда rjmp со ссылкой на метку, стоящую на этой же команде. Получается "бесконечный цикл". Войдя в этот цикл, процессор будет выполнять его до тех пор, пока не возникнет прерывание.

По прерыванию, он перейдет на начало обработчика прерываний. Текст обработчика с небольшими изменениями взят из предыдущей версии программы. Думаю, всем ясно, что в нем происходит. По команде reti процессор выходит из обработчика и возвращается в бесконечный цикл…

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

Ну все, компилируем, шьем, любуемся.

Далее мы заставим огонек не только бегать слева-направо, но и выполнять другие "трюки"…