cbr Flag, $80 ;по выходу из сна сбрасываем флаг sleep
clr count_min ;отсчет времени
;сначала установка портов вход-выход обратно
cli ;на всякий случай запрещаем прерывания
ldi temp,0b00111111 ; разряды out DDRB,temp
ldi temp,0b01111111 ; сегменты out DDRC,temp
ldi temp,0b11000000 ; знак минус и питание
out DDRD,temp
clr temp
out PortD,temp ;включить аналоговое питание
out MCUCR,temp ;запрещаем режим Sleep
out TCNT1H,temp ;очищаем счетные регистры таймера
out TCNT1L,temp
ldi temp,0b00001101 ; запускаем таймер
out TCCR1B,temp ;1/1024 очистить после совпадения
sei ;разрешаем прерывания
rjmp Gcykle ;бесконечный цикл
Теперь самое сложное: разобраться с прерываниями и установкой флага sleep. Не забудьте заменить reti на rjmp TIM1_COMPA в таблице прерываний, в строке для прерывания Timer1 Compare А (шестое сверху, не считая RESET). Прерывание таймера иллюстрирует листинг 17.3.
Листинг 17.3
ТIМ1_СОМРА:
inc count_min
cpi count_min,1 ;через 7,5 с разрешаем INTO
brne schet_time
ldi temp,(1<
out GICR,temp ;GIMSK и GIGR — синонимы
schet_time: ;здесь отсчет времени
sbrs count_min,3 ;если бит 3=1, то прошло 8 интервалов =1 мин
reti ;иначе выходим
clr count_min ;в след, раз — сначала
sbr Flag,$80 ;устанавливаем бит sleep
clr temp ;останавливаем таймер
out TCCR1B,temp
reti ;выходим
Если бы мы не связывались с кнопкой, то на этом можно было бы закончить — через 1 минуту после включения у нас измеритель уходит в «сон», из которого его можно вывести только выключением-включением питания или подачей сигнала Reset. Но мы хотим все делать грамотно, потому используем кнопку. Прерывание INTO тогда будет выглядеть так, как показано в листинге 17.4 (также не забудьте заменить в таблице прерываний во второй строке reti на rjmp EXT_INT0).
Листинг 17.4
EXT_INT0:
clr temp
out GICR,temp ;запрещаем внешние прерывания
sbrs Flag,7 ;если были во сне, то больше ничего не делаем
reti
clr temp ;иначе готовимся ко сну
out TCCR1B,temp ;останавливаем таймер
clr temp ;чистим счетные регистры таймера
out TCNT1H,temp
out TCNT1L,temp
ldi temp,0b00001101 ;заново запускаем
out TCCR1B,temp
ldi count_min,7 ;на интервал 7,5 с
reti
Теперь у нас измеритель будет работать как задумывали: после включения через 1 мин. он уходит в режим энергосбережения, когда индикаторы гаснут, и потребление минимизируется. Если нажать на кнопку, то МК «проснется» и выполнит все процедуры после команды Sleep. Если ничего не делать, то через минуту измеритель опять «заснет». Если нажать до истечения этого срока на кнопку (но не ранее, чем через 7,5 с), то он «заснет» через 7,5 с после нажатия. В принципе таймер можно было бы и не останавливать перед засыпанием, но так мы более уверены, что он начнет отсчет с самого начала.
Время задержки вы легко можете регулировать, просто меняя число, которое загружается в регистры OCR1AH/L. Оно рассчитывается исходя из формулы: время задержки в секундах равно частоте кварца в герцах, деленному на коэффициент предварительного деления (1024) и на это число. Например, если вместо 29 297 загрузить 11 719, то пауза до засыпания по нажатию кнопки станет равной 3 с, а время работы сократится менее чем до полуминуты. Чтобы увеличить время «бодрствования» вдвое, команду sbrs count_min,3 в прерывании таймера нужно заменить на sbrs count_min,4.
Заметки на полях
В прерывании кнопки для исключения случайного попадания в конец отсчитываемого интервала времени используется очистка регистров таймера. Тогда таймер начнет считать сначала, пока не достигнет заданного числа. Если вам потребуется задать отсчитываемый интервал очень точно, то следует почистить также счетчик предделителя (в семействе Classic такой возможности не было). Для этого в регистре SFIOR нужно записать единицу (не ноль!) в бит, соответствующий таймеру: для Timer 1 (а также Timer 0, так как у них предделитель общий) это будет бит 0 под названием PSR10. Этого не нужно делать при установленном счете напрямую (с коэффициентом деления тактовой частоты 1/1), в остальных случаях при запуске таймера в произвольный момент времени счетчик предделителя начнет со случайного числа (за исключением момента начального запуска при включении питания). Чем меньше коэффициент деления и чем больше число, отсчитываемое таймером, тем меньше относительная погрешность, но абсолютная ошибка всегда может достигнуть величины интервала между отчетами таймера (при коэффициенте 1/1 она попросту неустранима, только и всего).
Вторая особенность таймеров семейства Меда — наличие асинхронного режима работы для одного из таймеров (в большинстве моделей это Timer 2). Тогда его можно завести от независимого источника импульсов (от внешней частоты или низкочастотного кварца, в том числе часового 32 768 Гц), и он может считать независимо от работы всей остальной схемы. Теоретически эту функцию можно использовать как RTC, но на практике это неудобно (нет ни счета времени, ни календаря), зато ее очень удобно применять для вывода МК из «сна» по времени. В режиме энергосбережения под названием Power Save МК будет «просыпаться» каждое прерывание от Timer 2, и подсчетом этих прерываний его можно окончательно «разбудить» автоматически через нужный промежуток времени.
Сторожевой (watchdog) таймер — одно из самых полезных устройств в составе микроконтроллеров AVR. Причем это неочевидно: в нормальном режиме работы, когда все настроено идеально, он вовсе не нужен. Но представьте себе такую ситуацию: МК настроен на прием данных от компьютера, причем по простейшей схеме из главы 16, с непрерывным опросом бита UDRE. К примеру, он ожидает шесть байтов, как в нашей программе, но на пятом байте ПК внезапно ломается (кто-то прошел и ногой выдернул провод из COM-порта). Что будет с контроллером? Естественно, он повиснет в ожидании байта, и из этого состояния его не удастся вывести никаким способом, кроме полного сброса. Еще более опасны в этом отношении наши процедуры опроса линии по интерфейсу I2С — при программировании для ПК нам бы «голову оторвали» за такую организацию процесса.
И тем не менее здесь это нормальный способ программирования — нецелесообразно усложнять программу на порядки только для того, чтобы исключить все возможные ситуации, которые, может быть, за время «жизни» прибора вообще ни разу не произойдут. В ПК все иначе: во-первых, там есть удобные инструменты для таких случаев, во-вторых, там от одной программы могут зависеть и другие. Вот если бы мы на МК сооружали устройство управления космическим кораблем — тогда другое дело…
Но все же, как быть в таких случаях — ставить специальную кнопку Reset (как в компьютере) или писать в инструкции «если прибор не реагирует, то выключите и включите питание»? Вот тут-то на помощь и приходит сторожевой таймер, который, будучи включен, выполняет одну-единственную операцию: считает импульсы от собственного генератора (абсолютно независимо от всей остальной схемы МК), и когда досчитает до заданного их числа, не обращая внимания ни на что, попросту сбрасывает процессор, как будто был подан сигнал Reset. Самая длительная выдержка, которую можно получить от сторожевого таймера, составляет примерно 2 с (с большим разбросом, т. к. задающий генератор простейшего RC-типа).
Ну и что с этим делать, спросите вы? Нельзя же сбрасывать МК каждые две секунды «на всякий случай» и начинать работу заново, правда? Но этого и не требуется: достаточно завести сторожевой таймер, а потом сбрасывать его в исходное состояние, не дожидаясь, пока он сбросит контроллер. Это можно делать специально по таймеру или в любой другой периодически протекающей процедуре, которая должна выполняться раньше, чем таймер успеет сбросить процессор. Тогда, если МК завис по любой причине (даже просто из-за ошибки в программе), таймер сработает и приведет все в начальное состояние. В том числе, кстати, «разбудит» МК, даже если тот находится в самом глубоком «сне» (в режиме Power Down).
Для примера разберем такой случай возможного использования сторожевого таймера. В измерителе с часами, который был описан в главе 16, опасная ситуация может возникнуть, если по каким-то причинам не «придет» прерывание от часов. Это может произойти, например, если сами часы «повисли» (у них ведь тоже достаточно сложный алгоритм) или просто «потерялся» фронт очередного импульса. А так как мы настраиваемся на круглогодичную работу этого прибора, то подобную ситуацию следует рассматривать как вероятную. В конце концов, космическая частица может прилететь и все нарушить — согласно некоторым исследованиям, сбои такого рода неизбежны с вероятностью примерно 1 сбой на 1000 часов работы (правда, автор этих строк лично ничего такого не наблюдал, но и специально вопрос не исследовал). В этом случае неплохо выполнить процедуру включения прибора заново: в начале программы мы в том числе инициализируем и часы, а уж если они совсем сломались, тут ничего не поделаешь.
Для этого перед началом основного цикла инициализируем сторожевой таймер:
;запускаем WDT на 2 сек: