Прерывания

Прерывания в микроконтроллерах очень часто используются при разработке радиоэлектронных устройств, они позволяют реализовать многозадачность, то есть параллельное выполнение нескольких задач. Под прерыванием подразумевается условный переход из какой-либо области памяти программ (во время выполнения основной программы) на четко определенный адрес в этой памяти, данный адрес еще называют вектором прерываний. Векторов может быть несколько, но в микроконтроллерах PIC16 только один вектор прерываний, и это адрес в памяти программ равный 0004h. По этому адресу размещают подпрограмму обработки прерываний (обработчик прерываний).

Например, во время выполнения основной программы происходит прерывание от какого-либо источника, при этом в стек закладывается текущее значение счетчика команд PC (текущий адрес памяти программ на момент возникновения прерывания), после чего в счетчик команд загружается вектор прерываний и происходит переход на адрес 0004h в памяти программ. Здесь, как было сказано, находится подпрограмма обработки прерываний, в которой обычно выполняются другие задачи в отличии от основной программы, выход из обработчика прерывания выполняется командой retfie, при этом из стека в счетчик команд загружается ранее сохраненный адрес памяти программ, тем самым происходит возврат и дальнейшее выполнение основной программы. Таким образом, уходя на прерывания во время выполнения основной программы можно решать сразу несколько задач.

В микроконтроллерах PIC16 имеется несколько источников прерываний:
• внешнее прерывание по входу RB0/INT
• прерывание по переполнению таймера TMR0
• прерывание по изменению уровня сигнала на входах порта

Прерывания от периферийных модулей:
• прерывание по переполнению таймера TMR1
• прерывание по переполнению таймера TMR2
• прерывание от модуля компараторов
• прерывание от приемо-передатчика USART
• прерывание от модуля сравнения-захвата CCP1
• прерывание от модуля сравнения-захвата CCP2
• прерывание по окончанию записи в EEPROM
• прерывание по окончанию преобразования АЦП
• прерывание от модуля последовательного порта MSSP (прерывание в режиме SPI, I2C)

В различных моделях микроконтроллеров реализовано разное количество источников прерываний, в зависимости от наличия тех или иных модулей. Наиболее часто используются (на моем опыте) следующие: внешнее прерывание по входу RB0/INT; прерывание по переполнению TMR0, TMR1; прерывание по изменению уровня сигнала на входах порта; реже прерывание от компараторов и по переполнению TMR2.

Каждое прерывание имеет собственный флаг (бит) состояния, при возникновении прерывания флаг устанавливается в 1 и происходит переход в обработчик прерываний. Флаги необходимо сбрасывать программно в подпрограмме обработки прерываний, если этого не сделать, то при выходе из подпрограммы произойдет повторный переход в обработчик. Прерывание для каждого источника можно разрешить или запретить. Регистры PIR1, PIR2 содержат флаги прерываний периферийных модулей, а биты для их разрешения находятся в регистрах PIE1, PIE2 (эти два регистра расположены в 1-ом банке памяти данных). В регистре INTCON содержатся биты разрешения и соответствующие флаги следующих прерываний: внешнее по входу RB0/INT, по переполнению TMR0, по изменению уровня сигнала на входах порта.

Также регистр INTCON содержит бит GIE глобального разрешения прерываний, и бит PEIE для разрешения прерываний от периферийных модулей. Если бит GIE сброшен, то запрещены все прерывания, если бит равен 1 разрешены все немаскированные прерывания, то есть те, для которых стоит разрешение в индивидуальном порядке. Кстати при переходе в обработчик, бит GIE сбрасывается аппаратно, а при возврате снова устанавливается. Перед разрешением какого-либо прерывания необходимо сбросить соответствующий флаг, иначе сразу же произойдет переход в обработчик, вообще флаги прерываний устанавливаются независимо от того разрешено прерывание или нет, флаги устанавливаются аппаратно по соответствующим событиям. После сброса микроконтроллера все индивидуальные прерывания запрещены, в том числе и глобальное.

Любое прерывание вызывает переход по одному вектору (адрес 0004h), поэтому, когда разрешено сразу несколько прерываний, в обработчике первым делом необходимо опросить флаги этих прерываний, чтобы определить источник прерываний. Таким образом, можно выстроить приоритеты для прерываний, флаг самого ответственного прерывания опрашиваем в первую очередь, а далее остальные флаги по мере важности.

В общем, алгоритм задействования прерываний выглядит следующим образом: сбрасываем флаг, разрешаем индивидуальное прерывание, если необходимо разрешаем прерывание периферийных модулей, разрешаем глобальное прерывание. В подпрограмме обработки прерываний обязательно сбрасываем флаг. Можно делать иначе, разрешить глобальное прерывание, затем сбросить флаг, и под конец разрешить индивидуальное прерывание, тут в принципе есть различные варианты, все зависит от требуемых условий и количества задействованных прерываний. Запрещать индивидуальные прерывания (а также периферию) можно в любое время, в том числе и внутри обработчика. Глобальное прерывание можно запретить только в основной программе, так как при выходе из обработчика бит GIE аппаратно устанавливается в 1.

Теперь расскажу про очень важный момент, это сохранение контекста при обработке прерываний. При переходе на подпрограмму обработки прерываний необходимо сохранять содержимое ключевых регистров, а при возврате восстанавливать. К таким регистрам относятся STATUS, FSR, PCLATH, а также аккумулятор W. Например, в основной программе мы записали число в аккумулятор W и собрались сложить его с каким-либо регистром, и тут возникает прерывание. В обработчике также для выполнения каких-либо операций может использоваться аккумулятор (почти всегда). После возврата из обработчика в аккумуляторе может лежать уже другое число, что приведет к неправильному результату в основной программе.

Биты регистра STATUS также могут использоваться в основной программе и в обработчике, для выполнения арифметических операций, сравнений и т.д. Соответственно при возврате из обработчика биты, которые опрашивались в основной программе, могут измениться, что также приведет к неправильному результату в основной программе. Кроме того регистр STATUS содержит биты переключения банков памяти данных, если их поменять в обработчике, то в основной программе это может привести к неправильной адресации регистров. Все это также касается регистров FSR и PCLATH.

Ниже представлен пример кода для сохранения содержимого важных регистров:

Первым делом сохраняем содержимое аккумулятора в регистр W_TEMP, далее меняем местами полубайты регистра STATUS, с сохранением в аккумуляторе, очищаем регистр STATUS (при этом выбирается 0-й банк памяти данных), записываем содержимое аккумулятора в регистр STATUS_TEMP. Регистр STATUS очищать не обязательно, если работаем только в одном 0-ом банке памяти данных.

Если код превышает 2048 слов в памяти программ (это размер одной страницы, об организации памяти программ я писал в первой статье), необходимо также сохранить содержимое регистра PCLATH, после чего очистить его, в противном случае при использовании команд перехода CALL, GOTO внутри обработчика, мы перескочим на другие страницы памяти программ.

При использовании косвенной адресации в основной программе, а также внутри обработчика, следует также сохранить содержимое регистра FSR. Сначала сохраняем текущее значение FSR в регистр FSR_osn, далее записываем в FSR ранее сохраненное значение из регистра FSR_prer (это значение было сохранено при предыдущем переходе в обработчик). При выходе из подпрограммы сохраняем текущее значение FSR в регистр FSR_prer, и восстанавливаем значение из регистра FSR_osn, таким образом, получаем независимое использование косвенной адресации в обработчике и основной программе. Регистр FSR_osn предназначен для сохранения текущего значения (на момент прерывания) регистра FSR для работы в основной программе, а регистр FSR_prer для хранения текущего значения FSR при работе в подпрограмме обработки прерываний.

Далее восстанавливаем значения всех остальных регистров. Здесь может возникнуть вопрос, почему при сохранении и восстановлении аккумулятора W и регистра STATUS используется команда swapf? Можно же было воспользоваться командой movf, дело в том что, данная команда воздействует на биты регистра STATUS, что не желательно в данном случае. Команды swapf, movwf, bsf, bcf не воздействуют на биты регистра STATUS.

И снова напомню про банки памяти, при переходе в обработчик прерываний, содержимое аккумулятора W записывается во временный регистр W_TEMP, на этот момент неизвестно какой банк памяти выбран в регистре STATUS, например, в основной программе мы могли работать с регистрами 3-го банка на момент возникновения прерывания. Поэтому регистр W_TEMP должен быть определен во всех банках. После сохранения аккумулятора по вышеприведенному коду происходит очистка регистра STATUS, при этом выбирается 0-й банк памяти данных, соответственно остальные временные регистры STATUS_TEMP, PCLATH_TEMP, FSR_osn, FSR_prer, должны располагаться в 0-ом банке. Если мы работаем только в одном 0-ом банке, как было сказано выше, можно не очищать регистр STATUS, при этом все временные регистры должны располагаться в 0-ом банке. Разумеется, в самом обработчике прерываний после сохранения всех важных регистров, возможно, придется манипулировать банками памяти, смотря, как задумана программа и с какими регистрами придется работать. Вообще можно поступить проще, что я и советую, в большинстве микроконтроллеров последние 16 байт 0-го банка (по адресам 70h-7Fh) доступны из всех банков, поэтому все временные регистры можно смело расположить по этим адресам.

У этой записи один комментарий

  1. Добрый день, Руслан.
    Спасибо за статью, как все у вас очень толково.
    Так как я больше интересуюсь программированием контроллеров на СИ (xc8), у меня возник вопрос:
    как в СИ реализовать сохранение важных регистров спец.-назначения и аккумулятора. Надо ли вообще это делать в СИ.
    Не смог найти в инете ответа.

    Делал как то так:
    __asm(“movwf W_TEMP”);
    __asm(“swapf STATUS,W”);
    __asm(“clrf STATUS”);
    __asm(“movwf STATUS_TEMP “);

    Но это как-то “коряво”.

Имя (обязательно)Email (обязательно)Веб-сайт

Добавить комментарий