Прерывание таймера по переполнению
С учетом всего сказанного напишем программу, переключающую светодиод. В данном случае она будет это делать по событию переполнения таймера‑счетчика Timer 1 (вектор у нас обозначен: TIM1_OVF). Так как счетчик 16‑разрядный, то событие переполнения будет возникать при каждом 65 536‑м импульсе входной частоты. Если мы зададим коэффициент деления тактовой частоты на входе Timer 1 равным 64, то при 4 МГц частоты генератора мы получим примерно 1 Гц: 4 000 000/64/65 536 = 0,953674 Гц.
Это не совсем то, что нам требуется, и к тому же частота неточно равна одному герцу. Для того чтобы светодиод переключался точно раз в полсекунды (т. е. период его был равен секунде), программу придется немного усложнить, загружая каждый раз в счетные регистры определенное значение, которое рассчитывается просто: если период одного тактового импульса таймера равен 16 мкс (частота 4 000 000/64), то для получения 500 000 микросекунд надо отсчитать таких импульсов 31 250. Так как счетчик суммирующий, а прерывание возникает при достижении числа 65 536, то нужно предварительно загружать в него необходимое число 65 536 – 31250 = 34 286.
Это не единственный способ, но наиболее универсальный, годящийся для всех таймеров. Кстати, именно таким способом реализован отсчет времени в Arduino (см. главу 21 ). Иной способ – использовать прерывание по достижению определенного числа, загруженного в регистр сравнения А или В . Как это делается, мы увидим далее в этой главе. Для того чтобы осуществить само переключение из красного в зеленый, нам придется поступить как раньше, т. е. по каждому событию переполнения перебрасывать два бита в регистре PortD .
Полностью программа тогда будет выглядеть так:
Я не буду комментировать подробно каждый оператор, т. к. это заняло бы слишком много места. После выполнения всех команд начальной установки МК зацикливается, но бесконечный цикл будет прерываться возникновением прерывания – здесь все аналогично операционной системе Windows, которая также представляет собой бесконечный цикл ожидания событий. Как вы узнаете из последующих глав, в Arduino такой цикл – одна из главных составляющих любой программы, как раз потому что прерывания там почти не используются. Внутрь бесконечного цикла здесь можно поставить знакомую команду sleep , без дополнительных настроек режима энергопотребления она будет экономить около 30 % питания. А вот сэкономить еще больше просто так не получится, поскольку придется останавливать процессорное ядро, и таймер перестанет работать.
* * *
Заметки на полях
Кстати, а как остановить запущенный таймер, если это потребуется? Очень просто: если обнулить регистр TCCR1B (тот, в котором задается коэффициент деления тактовой частоты), то таймер остановится. Чтобы запустить его опять с коэффициентом 1/64, нужно снова записать в этот регистр значение 0b00000011.
* * *
Обратите внимание, что оператор reti (окончание обработки прерывания) при обработке прерывания таймера встречается дважды – это вполне нормальный прием, когда подпрограмма разветвляется. Можно, конечно, и пометить последний оператор reti меткой, и тогда текст процедуры стал бы неотличим от первого варианта, но так будет корректнее.
Обратите также внимание на форму записи ldi temp, (1 << TOIE1) . Поскольку бит, обозначаемый как TOIE1, в регистре TIMSK имеет номер 7, то эта запись эквивалентна записи ldi temp,0b10000000 – можно писать и так, и так, и еще кучей разных способов. Например, для запуска таймера с коэффициентом 1/64 требуется, как видно из текста программы, установить младшие два бита регистра TCCR1B. Здесь мы устанавливаем их в temp напрямую, но поскольку эти биты называются CS11 и CS10, то можно записать так:
ldi temp, (1 << CS11) I (1 << CS10)
или даже так:
ldi temp, (3 << CS10)
Подробно этот способ записи приведен в описании AVR‑ассемблера на сайте Atmel [35].
* * *
Подробности
В этой программе есть один тонкий момент, связанный с загрузкой счетных регистров таймера. При чтении и записи 16‑разрядных регистров Timer 1 их содержимое может измениться в промежутке между чтением или записью отдельных 8‑разрядных «половинок» (ведь, например, в данном случае таймер продолжает считать, пока идет обработка прерывания). Потому в 16‑разрядных таймерах AVR предусмотрен специальный механизм чтения и записи таких регистров. При записи первым загружается значение старшего байта, которое автоматически помещается в некий (недоступный для программиста) буферный регистр. Затем, когда поступает команда на запись младшего байта, оба значения объединяются, и запись производится одновременно в обе «половинки» 16‑разрядного регистра. Наоборот, при чтении первым должен быть прочитан младший байт, при этом значение старшего автоматически фиксируется помещением в тот же буферный регистр, и при следующей операции чтения старшего байта его значение извлекается оттуда. Таким образом и при чтении значения оба байта соответствуют одному и тому же моменту времени.
Повторим: для того, чтобы манипуляции со счетными регистрами были успешными, при чтении необходимо сначала прочесть младший байт TCNTxL, потом старший TCNTxH, при записи сначала записать старший байт TCNTxH, потом младший TCNTxL. Аналогичное правило действует для всех 16‑разрядных регистров Timer 1, которые мы будем рассматривать далее, за исключением регистров управления TCCR1A и TCCR1B, которые по сути есть два раздельных регистра, а не один.
* * *
Напомним, что если вам попадется старый «классический» AT90S2313, то приведенную здесь программу можно использовать для него без изменений.
Дата добавления: 2016-05-11; просмотров: 2674;