Подробности
Как мы уже отмечали в главе 18
, стек — область памяти, куда будут записываться адреса точек возврата при вызове подпрограмм по команде rcall или перехода к обработчикам прерываний. По окончании процедуры обработки прерывания (по команде reti) или обычной подпрограммы (по команде ret) этот адрес считывается в программный счетчик, и выполнение основной программы продолжается. Если во время выполнения обработчика данного прерывания происходит другое прерывание с большим приоритетом (это нужно специально разрешать, по умолчанию в AVR любое прерывание будет ожидать окончания обработки предыдущего), то в стек записывается также и этот текущий адрес команды, таким образом ошибиться при последовательном возврате невозможно. Стек устроен по принципу «последним вошел — первым вышел», т. е. извлекается всегда последнее записанное туда значение. Программист может использовать стек и для своих целей (командами push и pop — например, для сохранения текущего значения рабочих регистров), но неопытным программистам лучше активно этим способом не пользоваться — вероятность допустить трудно обнаруживаемую ошибку значительно возрастает. Тем более что в AVR и без того достаточно РОН (в отличие, например, от х51, где без стека обойтись практически невозможно), а при необходимости можно еще и хранить текущие значения в SRAM.
Прерывание таймера по переполнению
С учетом всего сказанного напишем программу, переключающую светодиод. В данном случае она будет это делать по событию переполнения таймера-счетчика 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.Полностью программа тогда будет выглядеть так: