В предыдущей части урока мы познакомились со сторожевым таймером (Watchdog Timer или WDT), узнали, как он устроен в микроконтроллере AVR, создали и настроили проект урока.
Включим ножки PD3:PD7 на выход, а ножку PD2 на вход. Ножки PD0 и PD1 не трогаем. Ножка PD2 нужна для отслеживания внешних прерываний
1 2 3 4 |
int main(void) { DDRD |= 0b11111000; DDRD &= ~(0b00000100);//INT0 |
На ножках PD3:PD7 сначала установим низкий уровень, а с ножки PD2 подтянем резистор к питанию
1 2 3 |
DDRD &= ~(0b00000100);//INT0 PORTD &= ~(0b11111000); PORTD |= 0b00000100;//PD2 PULL UP |
Также включим на выход ножки PB0:PB4, выставив на них также низкий уровень
1 2 3 |
PORTD |= 0b00000100;//PD2 PULL UP DDRB |= 0b00011111; PORTB &= ~(0b00011111); |
Настроим таймер 1 приблизительно с периодом в 0,5 секунды
1 2 3 4 5 6 |
PORTB &= ~(0b00011111); TCCR1B |= (1<<WGM12);//CTC (Clear Timer on Compare match) TIMSK1 |= (1<<OCIE1A);//Output Compare A Match Interrupt Enable OCR1AH = 0b01111010; //compare value OCR1AL = 0b00010010; TCCR1B |= (1<<CS12);//div 256 |
Настроим внешние прерывания от ножки INT0 на срабатывания по спаду, так как при нажатии на кнопку у нас будет уровень ножки падать в ноль ибо она притянута вторым контактом к земле. Можно было сделать и по фронту, чтобы событие наступало при отжатии кнопки, только в таком случае событие наступит и в момент включения контроллера
1 2 3 |
TCCR1B |= (1<<CS12);//div 256 EICRA |= (1<<ISC01);//falling eddge EIMSK |= (1<<INT0);//EXT INT |
Разрешим глобальные прерывания
1 2 |
EIMSK |= (1<<INT0);//EXT INT sei(); |
Включим сторожевой таймер с интервалом 2 секунды
1 2 |
sei(); wdt_enable(WDTO_2S); |
Вот так вот с помощью библиотечной функции очень быстро инициализируется Watchdog Timer.
Добавим глобальную переменную для сохранения счёта таймера
1 2 3 |
#include "main.h" //----------------------------------------------------- unsigned char tim1_count=0; |
Также напишем макроподстановки для удобства управления светодиодами с ножек портов
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include "main.h" //----------------------------------------------------- #define LED1_ON() PORTD|=0b00001000 #define LED1_OFF() PORTD&=~(0b00001000) #define LED2_ON() PORTD|=0b00010000 #define LED2_OFF() PORTD&=~(0b00010000) #define LED3_ON() PORTD|=0b00100000 #define LED3_OFF() PORTD&=~(0b00100000) #define LED4_ON() PORTD|=0b01000000 #define LED4_OFF() PORTD&=~(0b01000000) #define LED5_ON() PORTD|=0b10000000 #define LED5_OFF() PORTD&=~(0b10000000) #define LED6_ON() PORTB|=0b00000001 #define LED6_OFF() PORTB&=~(0b00000001); #define LED7_ON() PORTB|=0b00000010 #define LED7_OFF() PORTB&=~(0b00000010); #define LED8_ON() PORTB|=0b00000100 #define LED8_OFF() PORTB&=~(0b00000100); #define LED9_ON() PORTB|=0b00001000 #define LED9_OFF() PORTB&=~(0b00001000); #define LED10_ON() PORTB|=0b00010000 #define LED10_OFF() PORTB&=~(0b00010000); //----------------------------------------------------- |
Добавим обработчик прерываний от таймера, в котором будем попеременно при каждом вхождении зажигать очередной светодиод, а предыдущий тушить
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
unsigned char tim1_count=0; //----------------------------------------------------- ISR(TIMER1_COMPA_vect) { switch (tim1_count) { case 0: LED10_OFF(); LED1_ON(); break; case 1: LED1_OFF(); LED2_ON(); break; case 2: LED2_OFF(); LED3_ON(); break; case 3: LED3_OFF(); LED4_ON(); break; case 4: LED4_OFF(); LED5_ON(); break; case 5: LED5_OFF(); LED6_ON(); break; case 6: LED6_OFF(); LED7_ON(); break; case 7: LED7_OFF(); LED8_ON(); break; case 8: LED8_OFF(); LED9_ON(); break; case 9: LED9_OFF(); LED10_ON(); break; } tim1_count++; if(tim1_count>9) tim1_count=0; } //---------------------------------------- |
Обработчик для внешнего прерывания пока не добавляем. Пока проверим, что таймер у нас работает правильно.
Соберём код с помощью вот этой кнопки либо с помощью функциональной клавиши F11
Если всё правильно соберётся, то в окне Output запомним местонахождения файла с прошивкой
Запустим программу avrdude33 (я пока не нашел способов её подключения к среде) и сначала убедимся в видимости нашего контроллера, нажав Чтение
Перейдём во вкладку настройки фьюзов и прочитаем их, также нажав кнопку Чтение. Значения должны быть именно такие. Если не такие, то добиваемся таких и прошиваем фьюзы
Возвращаемся на первую вкладку, выбираем файл с прошивкой и прошиваем её в контроллер
Светодиоды должны зажигаться по очереди, только как только дело дойдёт до того времени, как после включения сторожевого таймера программа проработает 2 секунды, контроллер перезагрузится и весь процесс начнётся сначала. Но, как ни странно, не совсем сначала, просто светодиод будет на время затухать, затем зажигаться тот же светодиод и программа будет продолжаться именно с этого же светодиода
Почему так происходит? Потому что мы явно не проинициализировали переменную счёта таймера, а проинициализировали её при объявлении глобально, скорей всего процедура перезагрузки затирает не всё. Поэтому произведём инициализацию данной переменной в функции main() явно
1 2 |
sei(); tim1_count = 0; |
Соберём код, прошьём контроллер и убедимся, что теперь после перезагрузки по сторожевому таймеру процесс будет начинаться сначала
Отлично! Сторожевой таймер срабатывает правильно.
Следующая задача — расставить в процедуре обработки прерываний от таймера 1 сбросы сторожевого таймера раньше того момента, когда истечёт интервал времени настройки. Таймер 1 у нас работает с интервалом в полсекунды, значит достаточно будет воткнуть функции сброса сторожевого таймера в каждое третье вхождение в процедуру обработки прерывания. Нулевое не считаем, так как контроллер только что включился, считаем с первого. Для начала воткнём функцию только в третий кейс
1 2 |
case 3: wdt_reset(); |
Соберём код, прошьём контроллер. Теперь процедура перезагрузки контроллера начнётся только в момент начала зажигания 8-го светодиода
Поэтому вставим функцию перезагрузки сторожевого таймера ещё в седьмой кейс
1 2 |
case 7: wdt_reset(); |
Соберём код, прошьём контроллер.
Теперь светодиод до конца нормально добегает
Только вот после этого до третьего кейса дело не доходит, так как от седьмого до третьего интервал более 2 секунд.
Поэтому уберём все функции перезагрузки сторожевого таймера и расставим их немного по-другому в кейсы 2, 5 и 8
1 2 |
case 2: wdt_reset(); |
1 2 |
case 5: wdt_reset(); |
1 2 |
case 8: wdt_reset(); |
Соберём код, прошьём контроллер. Теперь светодиоды бегут и не затыкаются.
Если таймер будет работать нормально, то сторожевой таймер уже не сработает, а вот если «сломается» или зависнет, то контроллер перезагрузится, произойдёт повторная инициализация всей периферии и всё опять будет работать правильно.
Давайте наш таймер «сломаем». Это конечно всё сказано образно, мы не будем таймер высверливать из контроллера.
С помощью обработчика внешнего прерывания мы отключим наш таймер.
Добавим такой обработчик и отключим в нём прерывания от таймера 1
1 2 3 4 5 6 |
unsigned char tim1_count=0; //----------------------------------------------------- ISR(INT0_vect) { TIMSK1 &= ~(1<<OCIE1A); } |
Соберём код, прошьём контроллер.
Теперь, если мы нажмём кратковременно на кнопку, то следующий светодиод у нас уже не загорится. Тот светодиод, который светится, будет светиться до тех пор, пока не истечёт интервал времени сторожевого таймера. Как только интервал истечёт, то контроллер перезагрузится и весь процесс возобновится
Таким образом, в данном уроке мы разобрались со сторожевым таймером (Watchdog timer), поняли, для чего он нужен и закрепили свои знания на практике.
Всем спасибо за внимание!
Предыдущая часть Программирование МК AVR Следующий урок
Приобрести плату Arduino Nano V3.0 оригинальный FT232RL можно здесь.
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Класс! Сперва не понял, потом дошло. Спасибо!