В данной статье мы рассмотрим работу с внешними прерываниями в микроконтроллере PIC и почему в этом возникает необходимость. Перед началом изучения данной статьи рекомендуем ознакомиться с общими принципами программирования микроконтроллеров PIC с помощью программы MPLABX и подключения к микроконтроллеру PIC ЖК дисплея.
Также на нашем сайте вы можете посмотреть статьи про использование прерываний в плате Arduino и плате STM32 Blue Pill.
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Держатель микросхем на 40 контактов (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсаторы 22 пФ (2 шт.) и 0,1 мкФ (купить на AliExpress).
- Конденсатор 10 мкФ (купить на AliExpress).
- Регулятор напряжения 7805 (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Макетная плата.
- Соединительные провода.
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Что такое прерывания и зачем они нужны
В микроконтроллере PIC16F877A можно использовать 15 типов прерываний. Так зачем они нужны?
Как мы знаем, все микроконтроллеры программируются на выполнение определенной последовательности действий, задаваемой программой. Но достаточно часто в реальных проектах возникают ситуации, в которых требуется немедленное (первоочередное) выполнение каких либо действий. В этом случае на помощь и приходят прерывания.
Прерывания постоянно анализируют ситуацию на предмет появления определенных событий и когда эти события наступают, они исполняют небольшой фрагмент кода, после чего основная программа продолжает свое нормальное функционирование. Этот небольшой фрагмент кода называется процедурой обработки (обслуживания) прерывания (Interrupt Service Routine, ISR). На нашем сайте мы уже рассматривали один пример использования прерываний в микроконтроллере PIC – в проекте цифрового спидометра и одометра.
В микроконтроллерах существует два основных типа прерываний: внешние и внутренние. Внутренние прерывания происходят непосредственно внутри микроконтроллера, к из примерам можно отнести прерывания от таймеров, модулей АЦП и т.д. Эти прерывания срабатывают программным способом, то есть непосредственно в программе.
Срабатывание внешних прерываний могут вызывать какие-либо внешние устройства или действия пользователя. В данном проекте для срабатывания внешнего прерывания мы будем использовать обычную кнопку. Мы будем производить на экране ЖК дисплея счет от 0 до 1000 и когда будет происходить внешнее прерывание, мы будем выводить сообщение об этом на ЖК дисплей и затем продолжать счет.
Схема проекта
Схема проекта для демонстрации возможностей использования прерываний в микроконтроллере PIC представлена на следующем рисунке.
Как видите, в этом проекте нам необходимо всего лишь подключить ЖК дисплей к микроконтроллеру PIC.
Для определения контакта, на котором микроконтроллером PIC16F877A возможна обработка внешнего прерывания, необходимо обратиться к даташиту на данный микроконтроллер. Из данного даташита можно узнать, что 33-й контакт микроконтроллера с названием RBO/INT может использоваться для обработки сигнала внешнего прерывания. Другой контакт нельзя будет использовать для этой цели.
Схема соединений микроконтроллера PIC приведена в следующей таблице.
№ п/п | № контакта микроконтроллера | Наименование контакта микроконтроллера | Куда подключен |
1 | 21 | RD2 | RS of LCD |
2 | 22 | RD3 | E of LCD |
3 | 27 | RD4 | D4 of LCD |
4 | 28 | RD5 | D5 of LCD |
5 | 29 | RD6 | D6 of LCD |
6 | 30 | RD7 | D7 of LCD |
7 | 33 | RBO/INT | кнопка |
На порту PORT B мы будем использовать внутренние подтягивающие резисторы, поэтому мы непосредственным образом можем замкнуть контакт RB0 на землю (общий провод) через кнопку. Таким образом, всегда, когда на этом контакте будет появляться уровень LOW, будет срабатывать прерывание.
Внешний вид собранной на макетной плате конструкции проекта показан на следующем рисунке.
Моделирование работы проекта
Перед проверкой работы схемы на "реальном железе" мы протестировали ее работу в симуляторе Proteus. Схема проекта в данном симуляторе показана на следующем рисунке.
Во время тестирования работы схемы в симуляторе Proteus вы должны увидеть как на экране ЖК дисплея будет последовательно инкрементироваться счетчик. А когда пользователь будет нажимать кнопку, будет происходить выполнение процедуры обработки прерывания (ISR).
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Первым делом в программе настроим биты конфигурации. После этого необходимо указать, что на контакте RB0/INT мы будем производить обработку сигнала внешнего прерывания, то есть он не будет обычным цифровым входом или выходом. Следующая строка кода подключает внутренние подтягивающие резисторы к контактам порта portB при помощи установки 7-го бита регистра OPTION_REG в 0.
1 |
OPTION_REG = 0b00000000; |
Затем мы разрешим прерывания от периферийных устройств, установим глобальное разрешение прерываний и укажем что контакт RB0 будет использоваться в качестве контакта для обработки внешнего прерывания.
1 2 3 4 5 |
GIE=1; //Enable Global Interrupt PEIE=1; //Enable the Peripheral Interrupt INTE = 1; //Enable RB0 as external Interrupt pin |
Когда контакт RB0 сконфигурирован для обработки внешнего прерывания то каждый раз, когда на нем будет уровень low, будет устанавливаться в 1 флаг внешнего прерывания INTF и будет вызываться функция обработки прерывания.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void interrupt ISR_example() { if (INTF==1) //External Interrupt detected { Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String(" Entered ISR"); INTF = 0; // clear the interrupt flag after done with it __delay_ms(2000); Lcd_Clear(); } } |
Как вы можете видеть, мы назвали функцию обработки прерывания ISR_example. Вы можете дать ей любое другое имя. Внутри данной функции мы будем проверять установлен ли флаг INTF и выполнять необходимые действия. Также внутри этой функции очень важно очистить флаг INTF, только после этого произойдет переход (возврат) в основную функцию программы (main). Очистить флаг INTF можно с помощью команды:
1 |
INTF = 0; // clear the interrupt flag after done with it |
Внутри основной функции программы (main) мы будем инкрементировать значение счетчика каждые 500 ms и отображать его значение на экране ЖК дисплея. Прерывание всегда будет оставаться активным, всегда при нажатии кнопки оно будет срабатывать и при этом будет происходить переход к выполнению функции обработки прерывания (ISR).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Lcd_Set_Cursor(2,1); Lcd_Print_String("Inside Main Loop"); Lcd_Set_Cursor(1,1); Lcd_Print_String("Number: "); Lcd_Print_Char(ch1+'0'); Lcd_Print_Char(ch2+'0'); Lcd_Print_Char(ch3+'0'); Lcd_Print_Char(ch4+'0'); __delay_ms(500); number++; |
Тестирование работы проекта
После сборки схемы проекта на макетной плате и загрузки кода программы в микроконтроллер 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 |
#define _XTAL_FREQ 20000000 #define RS RD2 #define EN RD3 #define D4 RD4 #define D5 RD5 #define D6 RD6 #define D7 RD7 #include <xc.h> #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) //LCD Functions Developed by Circuit Digest. void Lcd_SetBit(char data_bit) //Based on the Hex value Set the Bits of the Data Lines { if(data_bit& 1) D4 = 1; else D4 = 0; if(data_bit& 2) D5 = 1; else D5 = 0; if(data_bit& 4) D6 = 1; else D6 = 0; if(data_bit& 8) D7 = 1; else D7 = 0; } void Lcd_Cmd(char a) { RS = 0; Lcd_SetBit(a); //Incoming Hex value EN = 1; __delay_ms(4); EN = 0; } void Lcd_Clear() { Lcd_Cmd(0); //очищаем экран ЖК дисплея Lcd_Cmd(1); //перемещаем курсор на 1-ю позицию } void Lcd_Set_Cursor(char a, char b) { char temp,z,y; if(a== 1) { temp = 0x80 + b - 1; //80H is used to move the curser z = temp>>4; //младшие 8 бит y = temp & 0x0F; //старшие 8 бит Lcd_Cmd(z); //устанавливаем строку Lcd_Cmd(y); //устанавливаем столбец } else if(a== 2) { temp = 0xC0 + b - 1; z = temp>>4; // младшие 8 бит y = temp & 0x0F; // старшие 8 бит Lcd_Cmd(z); // устанавливаем строку Lcd_Cmd(y); // устанавливаем столбец } } void Lcd_Start() { Lcd_SetBit(0x00); for(int i=1065244; i<=0; i--) NOP(); Lcd_Cmd(0x03); __delay_ms(5); Lcd_Cmd(0x03); __delay_ms(11); Lcd_Cmd(0x03); Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD Lcd_Cmd(0x08); //Select Row 1 Lcd_Cmd(0x00); //Clear Row 1 Display Lcd_Cmd(0x0C); //Select Row 2 Lcd_Cmd(0x00); //Clear Row 2 Display Lcd_Cmd(0x06); } void Lcd_Print_Char(char data) //Send 8-bits through 4-bit mode { char Lower_Nibble,Upper_Nibble; Lower_Nibble = data&0x0F; Upper_Nibble = data&0xF0; RS = 1; // => RS = 1 Lcd_SetBit(Upper_Nibble>>4); //Send upper half by shifting by 4 EN = 1; for(int i=2130483; i<=0; i--) NOP(); EN = 0; Lcd_SetBit(Lower_Nibble); //Send Lower half EN = 1; for(int i=2130483; i<=0; i--) NOP(); EN = 0; } void Lcd_Print_String(char *a) { int i; for(i=0;a[i]!='\0';i++) Lcd_Print_Char(a[i]); //разделяем строку на отдельные символы с помощью указателей и по очереди передаем каждый символ } /*****End of LCD Functions*****/ /****Interrupt function (функция для обработки прерывания) ****/ void interrupt ISR_example() { if (INTF==1) //External Interrupt detected (обнаружено внешнее прерывание) { Lcd_Clear(); Lcd_Set_Cursor(1,1); Lcd_Print_String(" Entered ISR"); INTF = 0; // очищаем флаг внешнего прерывания (иначе не произойдет возврат в основную программу) __delay_ms(2000); Lcd_Clear(); } } /****End of Interrupt Function****/ int number =0; char ch1,ch2,ch3,ch4; int main() { TRISD = 0x00; //контакты PORTD будут работать на вывод данных – к ним подключен ЖК дисплей TRISB0 = 1; //контакт RB0 будет работать на ввод данных – на нем бы будем производить обнаружение внешнего прерывания OPTION_REG = 0b00000000; // подключаем внутренние подтягивающие резисторы GIE=1; //глобальное разрешение прерываний PEIE=1; //разрешение прерываний от периферийных устройств INTE = 1; //конфигурируем RB0 в качестве контакта для обработки внешнего прерывания Lcd_Start(); while(1) { ch1 = (number/1000)%10; ch2 = (number/100)%10; ch3 = (number/10)%10; ch4 = (number/1)%10; Lcd_Set_Cursor(2,1); Lcd_Print_String("Inside Main Loop"); Lcd_Set_Cursor(1,1); Lcd_Print_String("Number: "); Lcd_Print_Char(ch1+'0'); Lcd_Print_Char(ch2+'0'); Lcd_Print_Char(ch3+'0'); Lcd_Print_Char(ch4+'0'); __delay_ms(500); number++; } return 0; } |
Получилось, слава Богу, добиться работы прерывания. Как я и предполагал, проблема в схеме была. И синатксис обработчика прерываний void __interrupt() ISR_example()
{
if(GPIF==1)
{
Code....
GPIF=0;
}
}
Ну вот, мы рады что у вас все таки получилось. И вам спасибо за ценные комментарии по данному проекту
Сколько смотрю уроки по pic контроллерам, возникают вопросы касательно:
1. Прерываний. У pic один вектор прерываний, в отличие от avr и при этом прерывание по флагу можно определить. Вопрос, откуда в коде берут название обработчика прерываний "void interrupt ISR_example()"? Если для avr я могу взять название из даташита, то тут неясно.
2. Установка отдельного бита. Именно в статьях где pic, там делают так, "GP4=1;", а avr, например, бит PD4 в 1 будет "PORTD|=(1<<4);" Как я знаю в си, в отличие от ассемблера, нельзя выставить отдельный бит, поэтому применяется битовая маска. А в pic получается можно? Самое интересное работает такая конструкция.
1. Ну с таким названием функции обработчика прерывания у вас заработал данный проект или нет?
2. Да, в микроконтроллерах AVR при работе в языке С необходимо использовать битовую маску, ну а в PIC, наверное, установка отдельного бита работает из-за того что для них написан более продвинутый компилятор, который понимает что делать с такой конструкцией
1. Нет, не работает, компилятор ругается, при этом, в мануале к компилятору правильный синтаксис показан. Я переписал, но теперь почему-то код в обработчике не выполняется.
А в мануале к компилятору какой синтаксис этой команды приведен? У меня такое ощущение что в программе пропущена одна строчка, в которой компилятору указывается имя функции обработчика прерывания, по крайней мере, так в платах Ардуино делается. Если нужно я могу вам ссылку на статью-оригинал привести, но вряд ли там будет что то отличающееся от моей статьи. Статья от индийского энтузиаста, как видно из видео у него все заработало. Но вот опечатки в своих программах в статьях они иногда делают, это я уже неоднократно замечал. Но что поделать, программисты часто народ рассеянный
Спасибо, за ответ. Синтаксис на который компилятор не ругается, такой: "void __interrupt() ISR_example()", причем, после "ISR_" ,по-моему, неважно какое слово стоит. Но вот дела, несколько форумов пересмотрел, а в моем случае контроллер pic12f683, все регистры вроде нормально выставлены, но переывание по выводам GPIO не работает. А в этом контроллере даже есть регистр который позволяет разрешить прерывание с конкретного вывода, регистр "IOC".
Оригинал статьи, возможно, видел.
В моем случае, идёт отслеживание перехода через ноль, для управления семистором и где-то схематическая ошибка, как вариант, могла попасть.
И вам спасибо за конструктивный комментарий. Если все таки у вас получится обработку этого прерывания запустить буду признателен если отпишитесь потом о своих успехах
1. Тоже сегодня весь день искал это.
Нашелся в моем даташите вот такой пример:Example 11-4. Setting up Vectored Interrupts using XC8.
Из него стал ясен синтаксис функции для прерывания:
void __interrupt(irq(IRQ_SW), base(0x3008)) SW_ISR(void)
Где __interrupt(irq(IRQ_SW), base(0x3008)) - макрос для компилятора, который разворачивается в __attribute__((picinterrupt(("irq(31), base(0x3008)")))) (что делает picinterrupt найти уже не смог.)
А SW_ISR - уже произвольное название функции.
2. "PORTD|=(1<<4);" - такая конструкция и даст вам выставление определенного бита в единицу.
Спасибо за полезный комментарий