Ещё раз про внешние прерывания в CH32V003

Аватар пользователя zhevak

Предыдущая статья (https://zhevak.wordpress.com/2023/07/12/ch32v003-exti/) не получилась по причине того, что я не разобравшись в теме бросился сразу писать статью по прерываниям. Проблема работы с микросхемой TP4056 и старыми литиевыми аккумуляторами оказалась на столько тяжёлой, что до описания внешних прерываний в CH32V003 дело даже и не дошло.

Я сейчас постараюсь устранить этот технический долг -- описать как работать с внешними прерываниями.

Назначение системы внешних прерываний крайне простое -- вызывать прерывание при изменении сигнала на ножке микроконтроллера. Прерывания можно на строить как на изменение сигнала с 1 на 0, так и с 0 на 1. Иногда говорят о фронте сигнала -- нарастающий или спадающий. Можно запрограммировать оба изменения. В общем, что вам нужно, то и "заказывайте".

Внешние прерывания используют так называемые каналы (каналы прерываний). Что это значит?

Это означает, что, например, канал прерывания номер 2 будет обслуживать все изменения, которые приходят со 2-го бита (биты считаем с 0), независимо от какого порта они пришли. Другими словами, изменение сигнала на выводе 2-го бита порта GPIOA, а так же на выводе 2-го бита порта GPIOC и на выводе 2-го бита порта GPIOD -- все попадут во 2-ой канал. Да, это накладывает определённые ограничения и иногда неудобства.

В микроконтроллерах CH32V003 три 8-разрядных порта: GPIOA, GPIOC, GPIOD. Порт GPIOB отсутствует.

Для обслуживания внешних прерываний создан специальный модуль -- EXTI. В состав модуля входят несколько регистров. К счастью, номера битов регистров совпадают с номерами битов портов. Все регистры у модуля, вообще говоря, -- 32-разрядные, но рабочими битами считаются биты с 0-го по 7-ой. Биты с 8-го по 31 -- нерабочие, в них обычно записывают нули.

Рассмотрим состав регистров, их немного и они крайне простые.

Регистр INTERN -- отвечает за включение прерывания от соответствующего канала (Ещё раз напомню -- каналы соответствуют битам портов и имеют нумерацию с 0-го бита по 7-ой. И другие регистры имеют точно такую же конфигурацию.) Если вам нужно включит канал номер два, отвечающий за прерывание от 2-го бита какого-либо порта, запишите в этот регистр значение 0x04. (Не ступите! Не 0x02, а 0x04, поскольку 0x04 -- это есть 0b00000100 в двоичном представлении.)

Регистр RTENR -- отвечает за возникновение прерывания, если на выводе порта был обнаружен перепад с 0 на 1 (В разных коллективах это называют по разному -- нарастающий фронт сигнала, передний фронт, ...)

Регистр FTENR -- отвечает за возникновение прерывания, если на выводе порта был обнаружен перепад с 1 на 0 (Опять же, в разных коллективах это называют -- спадающий фронт сигнала, задний фронт, ...)

Вы можете установить соответствующий бит в том или другом регистре, в зависимости от того на какой фронт сигнала вы хотите реагировать. Можете установить бит в обоих регистрах, тогда прерывание будет возникать как по переднему фронт, так и по заднему.

Когда возникает прерывание в регистре INTFR в (соответствующем каналу) бите устанавливается флаг (бит переходит в единичное состояние), и в системе возникает прерывание. Этот флаг не снимается автоматически. В обработчике прерывания нужно сначала определить, от какого канала пришло прерывание -- может это сразу несколько каналов сработало, а затем в ручную снять соответствующие флажки записью единицы (не нуля!) в регистр INTFR. Ну и, разумеется, нужно выполнить какие-то полезные действия -- зачем-то ведь вам нужно было это прерывание.

Иногда бывает нужно выполнить какое-либо прерывание в ручную (то есть -- программно). Для такого случая предназначен регистр SWIEVR. Запись в его какой-либо бит логической 1 приведёт к возникновению прерывания по соответствующему каналу. Это точно такое же прерывание, как от изменения в порту, только сэмулированно программным способом. Не забывайте, что и в этом случае тоже нужно точно так же снимать флаг прерывания.

Вот пример обработчика внешнего прерывания.

__attribute__((interrupt("WCH-Interrupt-fast")))
void EXTI7_0_IRQHandler(void)
{
  // Сначала определяю по какому каналу возникло прерывание
  if (((EXTI->INTFR & M2IN) != 0) && ((EXTI->INTENR & M2IN ) != 0))
  {
    EXTI->INTFR = M2IN; // Очищаю флаг прерывания
    
    // Выполняю полезную работу
    ...
  }

  // Обработка прерываний от других каналов, если есть
  ...
}
Здесь M2IN -- это  номер бита порта (и, соответственно, номер канала), по которому поступает сигнал. Он у меня в программе определен следующим образом:
#define M2IN   (0x04)
Ну, вот, полдела сделано! Теперь вы умеете обрабатывать прерывания.
Сейчас займёмся настройкой модулей для того, чтобы прерывания заработали. Фокус заключается в том, что в работе по внешнему прерыванию участвует несколько модулей.
Нам нужно запрограммировать модуль порта. Если мы собирается работать с портом, то нам как минимум нужно подать на него тактирование. За это отвечает модуль RCC. Нам так же нужно правильно сконфигурировать ножки порта. К счастью, если мы собирается только принимать на ножку (например, как в моём случае -- M2IN), то ножка порта уже находится в нужной конфигурации.
В модуле EXTI нужно правильно настроить регистры. А чтобы сигналы с ножек порта пошли в модуль EXTI, нужно сконфигурировать матрицу сигналов в модуле AFIO. К стати, модуль AFIO тоже нужно затактировать. Ну, и наконец, после всей этой подготовки разрешить модулю прерываний NVIC принимать прерывания от модуля EXTI.
Ну и чтобы жизнь не была так прекрасна -- не забудьте разрешить прерывания глобально. Но это уже делается перед входом в бесконечный цикл в функции main().
Итак. Код инициализации внешних прерываний
void init_m2in(void)
{
  // Конфигурирую порт
  RCC->APB2PCENR |= RCC_IOPDEN;  // Тактирование порта
  GPIOD->CFGLR &= 0xFFFFF0FF;    // Подготавливаю конфигурацию бита порта
  GPIOD->CFGLR |= 0x00000400;    // Назначаю конфигурацию биту 2 (4 - обычный вход)

  // Конфигурирую AFIO
  RCC->APB2PCENR |= RCC_AFIOEN;  // Подаю тактирование
  AFIO->EXTICR &= 0xFFCF;        // Подготавливаю канал 2 матрицы
  AFIO->EXTICR |= 0x0030;        // Назначаю канал 2 на работу с портом GPIOD

  // Настраиваю модуль внешний прерываний
  EXTI->INTENR |= M2IN;          // Разрешаю прерывания по каналу 2
  EXTI->RTENR  |= M2IN;          // Разрешаю прерывания по нарастающему фронту
  EXTI->FTENR  |= M2IN; // Разрешаю прерывания по спадающему фронту

  NVIC_EnableIRQ(EXTI7_0_IRQn);  // Разрешаю модулю прерываний реагировать на внешние прерывания
}

Теперь система готова реагировать на внешние прерывания. Осталось только разрешить ей только глобально разрешить это делать

int main(void)

{

  ...

  init_m2in();

  ... 

  __enable_irq();

  while (true)

  {
    ...
  }
}

Сложного в программировании на уровне регистров ничего нет. Программирование на уровне фреймворков не сильно уменьшает сложность работы. Когда нужно сделать что-то простое и быстро, и при этом не влезать "под капот" (как там устроен и работает двигатель?) то фреймфорки наверно и помогают. Я бы сказал так -- они помогают не ошибиться при делитантском походе.

Но стоит разработчику, поднимающему свой проект на фрейморке, столкнуться с проблемой или каким-либо нестандартным решением, и вот тут-то его жизнь резко усложняется. Ему нужно "открыть капот" и начать изучать то, от чего он убежал в начале. Получается, что теперь помимо знаний самого фремворка ему нужно прокачать знания самого "мотора". Кто-то закусывает удила и решает задачу, а кто-то и капитулирует. Всяко бывает.

Можно как угодно, например, "вытанцовывать" конфигурирование портов. Но если ты понимаешь, как они устроены, то что тебе мешает их сразу программировать?

Вот, смотрите:

GPIOD->CFGLR &= 0xFFFFF0FF; // Подготавливаю конфигурацию бита порта GPIOD->CFGLR |= 0x00000400; // Назначаю конфигурацию биту 2 (4 - обычный вход)

Я знаю, что на конфигурирование одного бита порта отводится четыре бита в регистре CFGLR.

Сначала я обнуляю все биты, отвечающие за конфигурацию бита номер. Легко определит (посчитать), что это бит с номером два. Второй командой я устанавливаю конфигурацию. Значение 4 -- это есть конфигурация порта на ввод цифровых данных.

Табличка конфигураций не является чем-то секретным

Значение CNFx MODEx Конфигурация Fmax
0 00 00 Аналоговый вход
1 00 01 Обычный выход (Push/Pull) 10 МГц
2 00 10 2 МГц
3 00 11 50 МГц
4 01 00 Обычный цифровой вход
5 01 01 Выход с открытым стоком 10 МГц
6 01 10 2 МГц
7 01 11 50 МГц
8 10 00 Вход с подтяжкой (вниз Pull-Down или вверх Pull-Up)
9 10 01 Обычный выход для функций (Push/Pull) 10 МГц
A 10 10 2 МГц
B 10 11 50 МГц
C 11 00 (Не используется)
D 11 01 Выход с открытым стоком для функций 10 МГц
E 11 10 2 МГц
F 11 11 50 МГц

Авторство: 
Авторская работа / переводика
Комментарий автора: 

Уважаемые несогласные!

Я понимаю, что эта публикация не отвечает тематике портала. Пожалуйста, выражайте свои эмоции более скромно. Не пытайтесь меня морально побить и тем более унизать.

Я действительно ищу удобную площадку для подобных статей. До недавнего времени я публиковал свои статьи на wordpress.com. С какого-то момента я понял, что оттуда нужно съезжать. Вопрос -- куда? Вот я и ищу подходящий ресурс.

Если вам есть что посоветовать по делу, то это будет намного лучше, чем бесполезная критика и истерики. Пожалуйста, относитесь к моей проблеме с пониманием.

Полагаюсь на вашу мудрость.

Комментарии

Аватар пользователя Море
Море(8 лет 6 месяцев)

smile8.gif

Аватар пользователя eprst
eprst(12 лет 7 месяцев)

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

Аватар пользователя Тигр Шрёдингера

А как тогда люди узнают? Если вам не интересно можно же не читать, разве нет?

Аватар пользователя eprst
eprst(12 лет 7 месяцев)

Лента пухнет, приходится прокручивать. А люди, если им надо, узнают.

Аватар пользователя Тигр Шрёдингера

Ладно вам не лопнет

Аватар пользователя Тихон
Тихон(10 лет 3 недели)

smile127.gif

Аватар пользователя DioDao
DioDao(10 лет 8 месяцев)

Это не та площадка, что Вы ищете. 

Аватар пользователя Astro_Graf
Astro_Graf(9 лет 4 месяца)

Очень даже та площадка, давно хотел, но все не решался!
Единственное - на пульс тащить не надо, а в блогах - пригодится!

Аватар пользователя просто пользователь

Ну, если пишут про сверхпроводники, то почему бы и про прерывания не пописать? Чем тема хуже вирусов или жоповозок?

Аватар пользователя Assert
Assert(9 лет 3 недели)

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

А для микроконтроллеров, нормальные разработчики фреймворки пишут сами. Благо почти для всего сейчас можно компилить современный c++ и делать очень удобные и быстрые конструкции на шаблонах. А не писать этой хренотой из хекса и непонятных макросов. 

Аватар пользователя IMHO
IMHO(12 лет 10 месяцев)

Это что за детский сад здесь?

"Автор", убей себя

Аватар пользователя Ярослав2
Ярослав2(9 лет 3 месяца)

Автор, вы этот бред полуграмотный зачем сюда тащите? Архитектура и программирование семейства stm32 разобраны по косточкам уже 12 лет как. Возьмите нормальную книгу и почитайте по теме. И про прерывания и про прямой доступ к памяти. 

Аватар пользователя купорос
купорос(6 лет 4 месяца)

Афтырь, я, как не владеющий темой, не понял смысла статьи. Ты вполне имеешь право постить такие статьи, но выводы будь любезен напиши такие, чтобы они были понятны обладателю среднего образования. Тогда и вопросов к тебе не будет, а спецы обсудят с тобой проблемы.

Аватар пользователя офисный планктон с Авантюры
спасибо!
заинтересовался
почему то
отпуск, есть время
погуглил, почитал
интересно

Аватар пользователя vadim144
vadim144(12 лет 11 месяцев)

Нормально, пусть тоже будет.

Это не копипаста, а личное знание.