Ранее на нашем сайте мы рассмотрели основы работы с микроконтроллерами PIC с помощью MPLABX IDE, отладку программы мигания светодиодом для них в симуляторе и на «реальном» железе, а также мигание с их помощью последовательностью светодиодов. В этой же статье на основе предыдущего проекта мигания последовательностью светодиодов мы рассмотрим основы работы с таймерами в микроконтроллерах PIC. По сравнению с предыдущим проектом мигания последовательностью светодиодов в данном проекте мы добавим одну дополнительную кнопку.
Ранее на нашем сайте мы достаточно подробно рассматривали работу с таймерами в платах Arduino.
Зачем нужны таймеры
Таймеры играют важную роль в большинстве проектов встраиваемой электроники. Они позволяют отмерять заданные интервалы времени. Но вы можете спросить зачем нам нужны таймеры если мы можем делать то же самое с помощью функции задержки (__delay_ms())?
Здесь дело в том, что во время задержки, организованной с помощью функции __delay_ms()), микроконтроллер не может больше делать ничего: ни считывать значения со входов АЦП, ни проверять состояние контактов, ни записывать какие либо значения в свои регистры и т.д.
Также у задержек, организуемых с помощью функции __delay_ms()), есть и другие недостатки:
- Значение задержки задается константой и его нельзя изменить во время исполнения программы.
- Значение задержки не такое точное как в случае использования для этой цели таймеров.
- Невозможно организовать очень длительные задержки, к примеру, полчаса и более. Максимальная величина задержки зависит от используемой частоты кварцевого генератора.
Таймеры в микроконтроллерах PIC
На физическом уровне таймер представляет собой регистр, значение в котором непрерывно увеличивается до 255, и затем счет в нем начинается сначала: 0, 1, 2, 3, 4…255….0, 1, 2, 3……и т. д.
Микроконтроллер PIC16F877A содержит три таймера, с именами Timer0, Timer1 и Timer2. Timer0 и Timer2 являются 8-битными, а Timer1 – 16-битным. В нашем проекте мы рассмотрим работу с Timer0. Если вы поймете принцип его работы, то по аналогии с ним вы сможете работать и с таймерами Timer1 и Timer2
Таймер/счетчик Timer0 имеет следующие особенности:
- 8-битный;
- можно записывать и считывать информацию;
- 8-битный программируемый предварительный делитель частоты (предделитель, prescaler);
- выбор внешней или внутренней частоты синхронизации;
- прерывание при переполнении от FFh до 00h;
- выбор спада или нарастания импульса для сигнала внешней синхронизации.
Для начала разберемся с рядом терминов, оказывающих влияние на функционирование таймера.
Предварительный делитель частоты (предделитель, prescaler) – это компонент микроконтроллера, который делит частоту задающего генератора перед тем как она достигает логического уровня, увеличивающего состояние таймера. Коэффициент деления предделителя для микроконтроллеров PIC лежит в диапазоне от 1 до 256, он устанавливается с помощью регистра OPTION (этот же регистр используется и для управления подтягивающими резисторами). К примеру, если значение (его коэффициент деления) предделителя равно 64, то с каждым 64-м импульсом частоты генератора значение таймера будет увеличиваться на 1.
Когда при инкрементировании таймера его значение достигает 255 (максимальное его значение) он инициализирует прерывание и сбрасывает свое значение снова до 0. Этот прерывание называется прерыванием от таймера. Оно информирует микроконтроллер что истек определенный промежуток времени.
Обозначим за Fosc частоту тактового генератора (Frequency of the Oscillator), то есть частоту используемого кристалла. Интервал времени, отсчитываемый таймером, зависит от значения предделителя (Prescaler) и значения частоты Fosc.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
В нашем проекте мы будем использовать 2 кнопки и 8 светодиодов, которые подключены к 8 контактам микроконтроллера PIC. С помощью 1-й кнопки мы будем устанавливать временную задержку (500ms на каждое нажатие), а 2-й кнопкой будем запускать процесс мигания последовательности светодиодов. К примеру, если первую кнопку мы нажмем трижды (500*3 = 1500ms), то задержка составит 1,5 секунды и при нажатии второй кнопки светодиоды будут включаться и выключаться с этим значением задержки. Более подробно работу проекта вы можете посмотреть на видео, приведенном в конце статьи.
Первым делом в программе мы зададим значение битов конфигурации, но этот фрагмент программы мы здесь рассматривать не будем поскольку мы его подробно рассмотрели в соответствующей статье. Поэтому перейдем сразу к функции main, в которой мы будем задавать параметры работы таймера Timer0.
1 2 3 4 5 6 7 8 9 |
void main() { /*****Port Configuration for Timer ******/ OPTION_REG = 0b00000101; // Timer0 with external freq and 64 as prescalar // Also Enables PULL UPs TMR0=100; // Load the time value for 0.0019968s; delayValue can be between 0-256 only TMR0IE=1; //Enable timer interrupt bit in PIE1 register GIE=1; //Enable Global Interrupt PEIE=1; //Enable the Peripheral Interrupt /***********______***********/ |
Вначале посмотрим на структуру регистра OPTION в даташите нашего микроконтроллера PIC.
Как мы уже рассмотрели в предыдущей статье, бит 7 используется для управления внутренними подтягивающими резисторами порта PORTB. Как видно из представленного рисунка, если бит 3 установить в 0, то предделитель будет устанавливаться для нашего таймера, а не для сторожевого таймера (WatchDogTimer, WDT). Режим работы таймера устанавливается при помощи установки бита 5 (T0CS) в 0 (OPTION_REG<5>).
Биты 2-0 используются для установки значения предделителя. Как видно из представленной таблицы, чтобы установить значение предделителя равным 64, в эти биты нужно записать 101.
Теперь посмотрим на регистры, ассоциированные с Timer0.
Таймер при своей установке начинает счет и переполняется при достижении значения 256, чтобы разрешить срабатывание прерывания от таймера в этой точке регистр TMR0IE необходимо установить в high. Поскольку Timer 0 по своей сути является периферийным устройством, мы также должны разрешить прерывания от периферийных устройств установив PEIE=1. И, наконец, мы должны установить глобальное разрешение прерываний для микроконтроллера, это делается при помощи установки GIE=1.
Задержку, отсчитываемую таймером, можно определить по формуле:
Delay = ((256-REG_val)*(Prescal*4))/Fosc
Если
REG_val = 100,
Prescal = 64,
Fosc = 20000000,
То по данной формуле получим значение задержки равное Delay = 0.0019968s
Далее в программе сконфигурируем работу портов ввода/вывода.
1 2 3 4 5 6 |
/*****Port Configuration for I/O ******/ TRISB0=1; //Instruct the MCU that the PORTB pin 0 is used as input for button 1. TRISB1=1; //Instruct the MCU that the PORTB pin 1 is used as input for button 1. TRISD = 0x00; //Instruct the MCU that all pins on PORT D are output PORTD=0x00; //Initialize all pins to 0 /***********______***********/ |
Конфигурация работы портов такая же, как и в нашей предыдущей статье поскольку мы используем одно и то же «железо», за исключением того, что мы добавили в схему проекта еще одну дополнительную кнопку – она конфигурируется с помощью строки TRISB1=1.
Далее, внутри нашего бесконечного цикла мы имеем два блока кода. Один из них используется для установки значения таймера пользователем, а второй управляет включением/выключением последовательности светодиодов.
1 2 3 4 5 6 7 8 9 10 11 12 |
while(1) { count =0; //Do not run timer while in main loop //*******Get the number delay from user****////// if (RB0==0 && flag==0) //When input given { get_scnds+=1; //get_scnds=get_scnds+1//Increment variable flag=1; } if (RB0==1) //To prevent continuous incrementation flag=0; /***********______***********/ |
Переменная get_scnds инкрементируется с каждым нажатием первой кнопки. Переменная flag (определяемая программно) используется для удержания процесса инкрементирования пока пользователь не удалит свой палец с кнопки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//*******Execute sequence with delay****////// while (RB1==0) { PORTD = 0b00000001<<i; //Left shit LED by i if(hscnd==get_scnds) //If the required time is reached { i+=1; //Move to next LED after the defined Delay hscnd=0; } flag=2; } if (flag==2 && RB1==1) //Reset timer if button is high again { get_scnds=0;hscnd=0;i=0; PORTD=0; //Turn off all LEDs } /***********______***********/ |
Следующий блок программы начинает работать при нажатии второй кнопки. Значение заданной пользователем задержки в результате работы первого блока сохранилось у нас в переменной get_scnds. В этом же блоке программы мы будем использовать переменную hscnd, которая будет управлять процедурой обработки прерывания (Interrupt service routine, ISR).
Данная процедура будет вызываться каждый раз при переполнении Timer0. Как мы уже указывали ранее, значение задержки должно увеличиваться на полсекунды при каждом нажатии первой кнопки, поэтому мы должны инкрементировать переменную hscnd для каждой полсекунды. Поскольку ранее мы запрограммировали задержку для нашего таймера равную 0.0019968s (~ 2ms), то чтобы отсчитать полсекунды значение переменной должно быть равно 250 поскольку 250*2ms = 0.5 секунды. Поэтому когда значение переменной count достигает 250 (250*2ms = 0.5 секунды) это будет означать что прошло полсекунды, поэтому увеличиваем значение переменной hscnd на 1 и сбрасываем значение переменной count в 0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void interrupt timer_isr() { if(TMR0IF==1) // Timer flag has been triggered due to timer overflow { TMR0 = 100; //Load the timer Value TMR0IF=0; // Clear timer interrupt flag count++; } if (count == 250) { hscnd+=1; // hscnd will get incremented for every half second count=0; } } |
В дальнейшем мы переключаем состояние светодиодов, основываясь на заданной задержке.
Схема проекта
Схема проекта для демонстрации использования таймеров в микроконтроллерах PIC представлена на следующем рисунке.
Исходный код программы
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
// CONFIG #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) // #pragma config statements should precede project file includes. // Use project enums instead of #define for ON and OFF. #include <xc.h> #define _XTAL_FREQ 20000000 //TIMER0 8-bit $$RegValue = 256-((Delay * Fosc)/(Prescalar*4)) delay in sec and Fosc in hz //формула для расчета значения задержки //Delay = ((256-REG_val)*(Prescal*4))/Fosc char hscnd = 0; int count = 0; char get_scnds =0; char flag =0; char i=0; void interrupt timer_isr() { if(TMR0IF==1) // Timer flag has been triggered due to timer overflow { TMR0 = 100; //загружаем значение в таймер TMR0IF=0; // очищаем флаг обработки прерываний от таймера count++; } if (count == 250) { hscnd+=1; // hscnd будет инкрементироваться каждые полсекунды count=0; } } void main() { /*****Port Configuration for Timer ******/ OPTION_REG = 0b00000101; // Timer0 с внешней частотой и значение 64 для предделителя // Also Enables PULL UPs TMR0=100; // загружаем значение в таймер для задержки 0.0019968s; delayValue can be between 0-256 only TMR0IE=1; //устанавливаем бит разрешения прерываний от таймера в регистре PIE1 GIE=1; //глобальное разрешение прерываний PEIE=1; //разрешение прерываний от периферийных устройств /***********______***********/ /*****Port Configuration for I/O ******/ TRISB0=1; //Instruct the MCU that the PORTB pin 0 is used as input for button 1 (к данному контакту подключена 1-я кнопка) TRISB1=1; //Instruct the MCU that the PORTB pin 1 is used as input for button 2 (к данному контакту подключена 2-я кнопка) TRISD = 0x00; //режим работы контактов порта PORT D – на вывод данных PORTD=0x00; //устанавливаем значение всех контактов порта в 0 /***********______***********/ while(1) { count =0; //Do not run timer while in main loop //*******получаем значение задержки от пользователя****////// if (RB0==0 && flag==0) //When input given { get_scnds+=1; //get_scnds=get_scnds+1//Increment variable flag=1; } if (RB0==1) //для предотвращения непрерывного инкрементирования flag=0; /***********______***********/ //*******управляем последовательностью включения светодиодов с заданной задержкой****////// while (RB1==0) { PORTD = 0b00000001<<i; //Left shit LED by i if(hscnd==get_scnds) //если достигнуто заданное время { i+=1; //переходим к следующему светодиоду после заданной задержки hscnd=0; } flag=2; } if (flag==2 && RB1==1) //Reset timer if button is high again { get_scnds=0;hscnd=0;i=0; PORTD=0; //выключаем все светодиоды } /***********______***********/ } } |
3 ответа к “Руководство по таймерам в микроконтроллерах PIC для начинающих”
Не компилируется файл, пишет ошибку…
из за вот это строчки void interrupt timer_isr()
слово interrupt убираешь, комплируется, в протеусе не работает, пишет оверфлоу интеррупт.
всю голову сломал, так и не понял в чем проблема…
2 errors generated.
(908) exit status = 1
nbproject/Makefile-default.mk:107: recipe for target ‘build/default/production/main.p1’ failed
make[2]: Leaving directory ‘C:/Users/Dream/MPLABXProjects/Timer Crank.X’
nbproject/Makefile-default.mk:91: recipe for target ‘.build-conf’ failed
make[1]: Leaving directory ‘C:/Users/Dream/MPLABXProjects/Timer Crank.X’
nbproject/Makefile-impl.mk:39: recipe for target ‘.build-impl’ failed
BUILD FAILED (exit value 2, total time: 205ms)
К сожалению, с протеусом уже давно не работал, не могу подсказать здесь что то конкретное. А оверфлоу это означает переполнение в общем случае. У вас другие проекты (без использования прерываний) нормально компилируются?
Я не пробовал. Я только учусь.
Может подскажете как решить такую задучу.
Мне нужно что бы с выхода контроллера выходили импульсы, обычные прямоугольные импульсы.
Надо зайдествовать 2 порта. На одном генерируется 24 импульса, на другом 1. И так до бесконечности.
Не могу сообразить как написать код. сижу изучаю таймеры уже весь день.