Главная » Программирование » PIC - микроконтроллеры » interrupt в PIC18
interrupt в PIC18

interrupt в PIC18

Цикл статей – PIC начинающим или основы основ

В прошлой статье, про порты I/O, прерывания были ни к чему. Однако чем дальше в лес… Тем сильнее нужда в прерываниях.

Прерывания в PIC18 или зачем пишут interrupt

Хотите верьте, хотите нет, но прерывания делают именно то, что как нельзя больше соответствует их названию: прерывают основной код программы и передают управление другому куску кода.

Этот»другой» кусок кода должен завершить свое выполнение возвратом в основной код, а если точнее — в то место, где было прервано выполнение кода. Одна из основных характеристик прерывания — количество тактов, необходимо для перехода из выполняющейся программы в прерывание.

Источники прерываний

Когда требуется прервать программу? Обычно, когда происходит какое-нибудь важное событие. Что является важным событием — необходимо определить самим, настроив разрешения прерываний от используемых модулей. Например, пришел байт на UART. Если его не обработать вовремя, то следующий пришедший байт затрет уже имеющийся, и, информация будет потеряна. Поэтому по приходу байта на UART необходимо «все бросить» и считать байт из приемного буфера.

Хотя я и написал «все бросить», на самом деле, при возникновении прерывания блоком прерываний выполняется много работы, т.к. необходимо запомнить (сохранить) текущее состояние микроконтроллера, передать управление обработчику прерывания, затем восстановить состояние микроконтроллера и продолжить работу.

Итак, если в проекте используется UART, его необходимо настроить так, чтобы он генерировал прерывание по заполнению приемного буфера. Если это сделано — UART является источником прерывания. Кроме UART источниками прерываний обычно служат модули:

Можно сказать, что практически вся периферия может генерировать прерывания. Если периферия какого-либо микроконтроллера не может генерировать прерывания — это сильно ограничивает возможности микроконтроллера.

Кроме прочего, можно ошибочно подумать, что прерывания настраиваются только при запуске микроконтроллера. На самом деле это не так. Прерывания можно включать и отключать, или менять их приоритет в любом месте программы.

Уровни прерываний

Но и сама реализация прерываний может быть попроще — как в микроконтроллерах начального уровня, так и посложнее — как в «продвинутых» микроконтроллерах.

В простых микроконтроллерах реализуется так называемая одноуровневая система прерываний. Это означает, что:

а) Все источники прерываний равны друг перед другом. Если друг за другом наступили два прерывания, т.е. второе наступило когда первое еще не закончили обрабатывать, второе прерывание в любом случае будет ждать окончания первого.

б) Все источники прерываний указывают на один и тот же адрес в памяти. Это означает, что все прерывания обрабатываются в одной Си-функции. Если наступило какое-либо прерывание, запускается специальная функция, которая имеет в атрибут interrupt, в которой необходимо с помощью операторов if или switch выяснить, кто является источником прерываний. Конечно, не требуется проверять все возможные источники — достаточно проверить только те источники, прерывания от которых были разрешены. Разумеется, такая проверка занимает драгоценные машинные такты… Но ведь за дешевый микроконтроллер и заплатили немного 🙂

В многоуровневой системе прерываний источники прерываний имеют индивидуальные адреса для перехода. Более того, например UART может иметь несколько отдельных прерываний — по приему байта, по передаче, по обнаружению ошибки. Наименования функций для прерываний можно посмотреть в документации на компилятор. Кроме прочего, источники прерываний могут иметь приоритеты. Прерывание с высоким приоритетом прервет уже выполняющееся прерывание с низким приоритетом. Всего приоритетов, обычно от 16 и больше.

Несмотря на то, что блок прерываний у высокопроизводительных микроконтроллеров значительно сложнее, чем в дешевых, пользоваться им даже проще, т.к. войдя в прерывание не нужно выяснять, что это за прерывание. Да и логику работы всей программы легче разработать за счет применения различных приоритетов прерываний.

Прерывания в PIC18F

PIC18 находится в среднем сегменте — все еще достаточно дешевый, но уже вроде как высокопроизводительный. На многоуровневый блок прерываний не тянет, но и обычный блок прерываний от PIC16 оставлять тоже как-то… слишком бюджетно. Именно поэтому в PIC18 применена двухуровневая система прерываний, т.е. прерывания могут иметь два приоритета: низкий и высокий; прерывания располагаются по двум адресам: адрес для прерываний с низким приоритетом и адрес для прерываний с высоким приоритетом. При настройке источника прерывания (АЦП, таймера, UART…) нужно указать, будет являться прерывание низкоприоритетным или высокоприоритетным.

Настройка системы прерываний:

/* Настройки прерываний */
INTCONbits.PEIE = 1; // Периферийные прерывания разрешены
INTCONbits.GIE  = 1; // Глобальные прерывания разрешены
RCONbits.IPEN   = 1; // Разрешить двуприоритетные прерывания

Применение прерываний:

/*###################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с НИЗКИМ ПРИОРИТЕТОМ
 */
void interrupt low_priority low_isr (void)
{
  // тут пишем обработчики прерываний
}
/*###################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с ВЫСОКИМ ПРИОРИТЕТОМ
 */
void interrupt high_isr (void)
{
  // тут пишем обработчики прерываний
}

Практический пример. Раньше, эффект «Двоичный счетчик»  делали при помощи задержки Delay. Теперь, будем делать все тоже самое, но только при помощи таймера.

Создайте новый проект с именем int45k20. Создайте новый си-файл с именем main_int. Скопируйте (Ctrl+C; Ctrl+V) h-файл config_bits из имеющегося проекта io45k20. Да-да, так тоже можно 🙂

Замените содержимое файла main_int на следующее:

/*
 * File:   main_ioled.c
 * Author: PRO-DIOD.RU
 *
 */

//#include <stdio.h>
//#include <stdlib.h>

#include <xc.h> // В этом проекте можно заменить строкой #include <pic18f45k20.h>
#include "config_bits.h"
//#include "delays.h"
/*######################################################################################################################
 * Г Л А В Н А Я   Ф У Н К Ц И Я
 */
int main(void)
{
/* Переменные. Действуют внутри функции main() */

/* Включаем кварц */
OSCCON = 0b01110001; // Internal; 16MHz;
OSCTUNEbits.PLLEN = 0; // PLL отключен

/* Настройки прерываний */
INTCONbits.PEIE = 1;            // Периферийные прерывания разрешены
INTCONbits.GIE  = 1;            // Глобальные прерывания разрешены
RCONbits.IPEN   = 1;            // Разрешить двуприоритетные прерывания

/* Настройка портов I/O */
TRISD = 0x00;// Порт D как выход
LATD = 0x00; // Гасим все светодиоды на PORTD

TRISB = 0x00000001; // RB0 как вход (для кнопки)

/* Настройка таймера TMR1 */
PIE1bits.TMR1IE   = 1;  // Включить прерывание (interrupt) TMR1
IPR1bits.TMR1IP   = 1;  // Установить высокий приоритет для прерывания TMR1
T1CONbits.TMR1CS  = 0;  // Internal clock - (FOSC/4)
//T1CONbits.T1CKPS0 = 1;  // Предделитель 1:8
//T1CONbits.T1CKPS1 = 1;  // ....... ticks per second
T1CONbits.T1CKPS = 0b11;  // Предделитель 1:8
//TMR1L             = 0;
//TMR1H             = 0;
TMR1              = 0x0000; // Заменит TMR1L и TMR1H. Счет идет от числа записанного в этот регистр до 0xFFFF.
PIR1bits.TMR1IF   = 0; // Очищаем флаг прерывания (чтобы сразу не свалиться в прерывание)
T1CONbits.TMR1ON  = 1;  // Запуск таймера

while(1)
  {
	// ничего нет =)
  } // while(1) end
} // main end

/*################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с НИЗКИМ ПРИОРИТЕТОМ
 */
void interrupt low_priority low_isr (void)
{

} // low_isr END

/*################################################
 * ОБРАБОТЧИК ПРЕРЫВАНИЙ с ВЫСОКИМ ПРИОРИТЕТОМ
 */
void interrupt high_isr (void)
{
if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) // Проверка на нужный нам флаг.
	{
	PIR1bits.TMR1IF = 0; // Сбросили флаг прерывания по TMR1

	/* Скорость таймера:
	 * PORTBbits.RB0 = 0 (кнопка нажата) -> медленно.
	 * PORTBbits.RB0 = 1 (кнопка отжата) -> быстро.
	 * TMR1 сбрасывается на 0x0000 при переполнении,
	 * поэтому необходимо TMR1 устанавливать заново при каждом переполнении счетчика
	 *
	 * Скорость таймера:
	 * 16 000 000 / 4 / 8 / (65535 - 0) = 7,6 Hz (медленно)
	 * 16 000 000 / 4 / 8 / (65535 - 61440) = 122 Hz (быстро)
	 */
	if (PORTBbits.RB0) TMR1 = 0xF000;

	LATD = PORTD + 1;
	} //if (PIR1bits.TMR1IF && PIE1bits.TMR1IE)

} // high_isr END

Пока кнопка не нажата — счетчик работает быстро, когда нажата — значительно медленнее. Хочу заметить, что в данном примере код в главном цикле вообще отсутствует.

Ах, да!!! Чуть не забыл — готовый проект для тех, у кого ничего не получилось или просто лень одолела:

Прерывания в PIC18F
Прерывания в PIC18F
project_int45k20.zip
89.2 KiB
208 Downloads
Детали

Мои рекомендации по применению прерываний в PIC18:

  • При использовании одного источника прерываний подойдет любой режим, можно обойтись без проверки источника прерывания, т.к. он известен.
  • При использовании двух источников прерывания рекомендую использовать двухуровневые прерывания. В одном уровне одно прерывание, в другом — другое, в этом случае можно обойтись без проверки источника прерывания. И еще продумать, какое из прерываний лучше сделать более приоритетным.
  • При использовании более двух прерываний посмотреть есть ли одно и явно более приоритетное. Если есть — использовать одно высокоприоритетное без проверки вектора прерывания, остальные как низкоприоритетные с проверкой источника.

Развлекайтесь! 🙂

 

Метки::

6 Отзывы Ваш отзыв

  1. Alex #

    Спасибо за статью. С нетерпением жду продолжения.

  2. Andrey #

    Спасибо, очень интересно! А продолжение будет?

  3. iglemaro #

    Спасибо. Нравится ваш стиль: просто и информативно.(Осваиваю Си).

  4. Олег #

    Супердоходчиво. Спасибо автору статьи.

  5. Old_seemann #

    Ох, Си… А нас на ассемблере писать заставляют. Один вопрос тогда. На ассемблере настройка системы прерываний имеет ту же структуру?

Ваш отзыв