Внешние прерывания и приоритеты прерываний

Система внешних прерываний в STM8 устроена довольно хитро. Разработчики дали нам возможность ловить прерывания с любого пина, но при этом выделять по вектору на каждый пин не стали. В результате эта часть STM8L (в S- с этим как-то получше) просто утыкана разными костылями и хитростями.
Разберемся, как все устроено.

До кучи, кроме внешних прерываний рассмотрим настройку приоритетов прерываний.

Нам обещали прерывания на всех ножках. Вот с них, то-есть с ножек, и начнем.

Разрешить или запретить прерывание для конкретного пина можно через регистр GPIOx_CR2 (где x — порт, для которого настраивается прерывание). Если ножка настроена на вход, то запись 1 в соответствующий бит регистра GPIOx_CR2 разрешит прерывание для неё.

Например вот-так можно настроить кнопку на STM8L-Discovery (пин C1):
GPIOC->DDR |= GPIO_Pin _1; //На вход
GPIOC->CR1 |= GPIO_Pin _1; //Подтягивающий резистор (хотя он тут и не нужен)
GPIOC->CR2 |= GPIO_Pin _1; //Прерывание разрешено


За исключением настройки ножек, система внешних прерываний STM8 выглядит немного по-разному в разных семействах и линейках. Сначала подробно рассмотрим, как она организована в STM8L15xx. Там хитроумные выверты разработчиков достигают предельной концентрации, а в других сериях все устроено проще. Особенно в STM8S — там система внешних прерываний очень похожа на PCINT в AVR.

Первой неожиданностью (для меня, после AVR) является то, что есть две группы прерываний: прерывание порта (EXTIB, EXTID...) без указания пина, куда прилетают прерывания с каждой ножки порта (как PCINT); и прерывание пина (EXTI0, EXTI1… EXTI7) без указания порта, куда слетаются прерывания с нескольких портов, но с одинаковых пинов. Сначала это немного выносит мозг, но потом привыкаешь.

Каждую половину порта можно настроить на генерацию 1го прерывания для любого пина (EXTID, EXTIG — по названию порта) или генерацию отдельного прерывания для каждого пина (EXTI1,EXTI2...). Настраивается это через регистры EXTI_CONF1 и 2, а точнее, через биты PxLIS и PxHIS в них.



PxLIS отвечает за младшие 4 бита, а PxHIS — за старшие. Если бит установлен (1), то эта половина порта будет генерировать одно прерывание на все пины. А если бит сброшен (0) — отдельные прерывания на каждый пин: EXTI0..3 для младшей половины и EXTI4..7 для старшей.

Для некоторых портов общие прерывания (которые по штуке на порт) не доступны. С ними можно работать только через прерывания пинов. К таким обделенным относятся порты А и С в STM15x. А в STM101 своими прерываниями обладают только порты B и D.

Для STM8S наоборот, есть только прерывания портов, но нет никаких EXTI0, EXTI1. Это сильно снижает возможности по настройке — фронт, по которому сработает прерывание, настраивается для целого порта. Но об этом чуть ниже.

Так-как в старших STM8L15x векторов на все порты трагически не хватает, было придумано переключение. Например, вектор EXTIB/G может обрабатывать прерывания с порта B или G. За переключение векторов между портами отвечают биты PFES, PHDS, PBGS в регистрах EXTI_CONFx. Если в бит PFES записана 1, то вектор EXTIE/F будет ловить прерывания с порта F, иначе — с порта E. Аналогично с битами PHDS и PBGS (Они есть только в старших моделях, на дискавери нет).



Фронт или уровень, по которому сработает прерывание, настраивается через регистры EXTI_CRx. В STM8L15x таких регистров 4 штуки, в STM8L101 — 3.

Биты в них зовутся PxIS и собраны парами. Например пара P0IS[1:0] отвечает за фронт, по которому сработает прерывание EXTI0. Здесь возможны четыре варианта:

00 — Падение напряжения и низкий уровень
01 — Переход из низкого уровня в высокий (__/)
10 — Переход из высокого в низкий (\__)
11 — Любое изменение уровня

Фанаты библиотек, для настройки прерывания, могут написать что-то типа этого:
EXTI_SetPinSensitivity(EXTI_Pin_1,EXTI_Trigger_Falling);


Для примера, вот так можно заставить кнопку на STM8L-Discovery (пин C1) генерировать прерывание EXTI1 по падению напряжения (т.е. когда лог уровень меняется с 1 на 0):

GPIOC->DDR |= GPIO_Pin _1; //На вход
GPIOC->CR1 |= GPIO_Pin _1; //Подтягивающий резистор (хотя он тут и не нужен)
GPIOC->CR2 |= GPIO_Pin _1; //Прерывание разрешено
EXTI->CR2 |= 1<<3; //Устанавливаем 1 бит в P1IS


А вот так, кнопка, висящая на B3, будет настроена на генерацию прерывания EXTIB/G по любому изменению уровня:

GPIOB->DDR |= GPIO_Pin _3; //На вход
GPIOB->CR2 |= GPIO_Pin _3; //Прерывание разрешено
EXTI->CR3 |= 3; //Устанавливаем 1 и 2 бит в PBIS
EXTI->CONF1 |= EXTI_CONF1_PBLIS; //Переключаем младшую половину порта B на генерацию EXTIB/G


Теперь посмотрим, как правильно оформить обработчик прерывания.
В IAR шаблон обработчика выглядит вот так:

INTERRUPT_HANDLER(Interrupt_Name, Interrupt_Number)
{
 //Код-код-код-код
 //Не забудь сбросить флаг прерывания перед выходом
}


Interrupt_Name это название прерывания. Оно может быть любым.
Interrupt_Number — номер прерывания в таблице. Таблица в даташите — «Interrupt vector mapping»

Например, обработчик прерывания EXTI1, в котором тупо переключается светодиод на E7:

INTERRUPT_HANDLER(EXTI1_IRQHandler, 9)
{
 GPIOE->ODR ^= GPIO_Pin_7;
 EXTI->SR1 |= 1<<1;
}


Обратите внимание, что перед выходом из прерывания надо сбросить его флаг. Иначе — день сурка — МК будет вечно крутится в этом обработчике.

Флаги разбросаны в регистрах EXTI_SR1 и EXTI_SR2. Для сброса флага в него надо записать 1. Любители библиотек могут просто написать:

EXTI_ClearITPendingBit(EXTI_IT_Pin1);


В данном случае так и надо делать, ибо констант для сброса флагов прерываний все-равно нет, что приводит к появлению в коде волшебных чисел (1<<1).

Эти же флаги позволяют определять, из-за чего случилось прерывание. В STM8 несколько источников прерывания на один вектор — вполне обычное дело, поэтому без флагов можно потеряться.

Приоритеты прерываний

Приоритеты — полезность, которой так не хватало в AVR — позволяет не беспокоится о том, что критичное ко времени прерывание запаздает из-за того, что обрабатывается другое, менее важное.

Эту тему уже поднимал ZiB в одной из своих статей. Чтобы не пришлось искать сотни информации в разных местах, расскажу и про приоритеты.

Все прерывания имеют два типа приоритетов — программный и аппаратный. Самый высокий аппаратный приоритет имеет RESET, затем TRAP и далее вниз по таблице прерываний. Программный приоритет по умолчанию одинаковый у всех прерываний. Настраивать его можно через регистры ITC_SPRx, которых в STM8L15x аж целых 8 штук. На каждый вектор прерывания отводится два бита, при этом приоритет 00 установить нельзя.

Опять-же фанатам библиотек хватит и одной строчки:

ITC_SetSoftwarePriority(EXTI2_IRQn, ITC_PriorityLevel_1);


Пока программные приоритеты у всех прерываний одинаковы, коллизии между прерываниями разруливаются по принципу «кто первый встал — тому и тапки». Если во время обработки одного прерывания возникнут другие, то они будут обработаны после завершения первого. Первым полезет то, у которого выше аппаратный приоритет.



Стоит нам поменять программный приоритет хотя-бы у одного прерывания, как контроллер переключается в режим вложенных прерываний (nested interrupts). Теперь выполнение обработчика может быть невозбранно прервано другим прерыванием, если у него выше программный приоритет. Если программные приоритеты одинаковые, то в дело вступает аппаратный приоритет: если у нового прерывания он выше, то оно перехватывает управление на себя.



Программный приоритет 3 самый высокий (он-же установлен по-умолчанию), а 1 — самый низкий (0 поставить нельзя).

Для примера напишем программку, которая при нажатии на кнопку на пине C2 (её придется прицепить к дискавери самому) вызывает прерывание. В этом прерывании будет в бесконечном цикле (sic!) мигать синий светодиод. Параллельно с этим беспределом, нажатие на User-кнопку (C1) будет переключать зеленый светодиод — приоритет у этого прерывания выше, поэтому бесконечный цикл ему не помешает.

Вот, как это выглядит:


Исходник программы (проект для IAR) как обычно во вложении.

  • +2
  • 19 августа 2011, 19:46
  • dcoder
  • 1
Файлы в топике: EXTI.zip

Комментарии (9)

RSS свернуть / развернуть
Отличное описание. Так держать!!!
0
Спасибо!
На очереди АЦП
0
Давай, пиши побольше, я тоже STM-щиком скоро стану.
0
Ага, а для S там вообще работает не так как хочеться. И не нашел сброса флага прерывания.
0
Классное описание!
Есть небольшая неточность в примере по настройке кнопки наверное не EXTI->CR2 |= 1<<3,
а EXTI->CR1 |= 1<<3
0
Не могу понять в чём дело. Помогите кто знает.
Настраиваю кнопку на 8Л-Дискавери так
bres PC_DDR,#1; на вход
bset PC_CR1,#1; вкл подтяжку
bset PC_CR2,#1; разреш прерывания на кнопку
bset EXTI_CR1,#3; прерывание по спаду

в обработке прерывания переключаю светодиод так

EXTI1_Interrupt.l; Внешнее прерывание по фронту — кнопка
blink.l
clrw X
loop1.l
incw X
cpw X, #6000
jrult loop1
bcpl PC_ODR,#7; меняем состояние светодиода на РС7
bset EXTI_SR1,#1; сбрасываем флаг прерывания
iret
Ассемблер от ST. В итоге все работает, но если кнопку нажать и держать, то прерывание
срабатывает постоянно!!? Почему????? Причём не важно настроено прерывание по фронту или по спаду и вход с подтяжкой или плавающий… Результат один.
Заранее спасибо.
0
У меня это происходит из-за дребезга (смотрел осциллографом)
0
Странно, еще STM себя так ведет если настроено прерывание по низкому уровню.
0
а можно поподробнее про сброс флага прерывания от внешней ножки?
EXTI->SR1 |= 1<<1;

— я так понимаю это в STM8L???

ибо в STM8S всего два регистра в пространстве EXTI:
EXTI->СR1
EXTI->СR2
т.о. сбрасывать ничего не надо?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.