Предыдущая статья (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; // Очищаю флаг прерывания
// Выполняю полезную работу
...
}
// Обработка прерываний от других каналов, если есть
...
}
#define M2IN (0x04)
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 МГц |
Комментарии
Что-то надо менять в структуре сайта. Разнотемье в одной ленте, даже, и тем более, если это Блоги - не есть хорошо. Надо создать возможность публиковаться только в личных блогах без выноса в ленту.
А как тогда люди узнают? Если вам не интересно можно же не читать, разве нет?
Лента пухнет, приходится прокручивать. А люди, если им надо, узнают.
Ладно вам не лопнет
Это не та площадка, что Вы ищете.
Очень даже та площадка, давно хотел, но все не решался!
Единственное - на пульс тащить не надо, а в блогах - пригодится!
Ну, если пишут про сверхпроводники, то почему бы и про прерывания не пописать? Чем тема хуже вирусов или жоповозок?
Не понял, что тут интересного, чего нет в даташите. Я вот думаю опубликовать статью по архитектуре современных суперскалярных процессоров и сложностях их реализации в кремнии, но...
А для микроконтроллеров, нормальные разработчики фреймворки пишут сами. Благо почти для всего сейчас можно компилить современный c++ и делать очень удобные и быстрые конструкции на шаблонах. А не писать этой хренотой из хекса и непонятных макросов.
Это что за детский сад здесь?
"Автор", убей себя
Автор, вы этот бред полуграмотный зачем сюда тащите? Архитектура и программирование семейства stm32 разобраны по косточкам уже 12 лет как. Возьмите нормальную книгу и почитайте по теме. И про прерывания и про прямой доступ к памяти.
Афтырь, я, как не владеющий темой, не понял смысла статьи. Ты вполне имеешь право постить такие статьи, но выводы будь любезен напиши такие, чтобы они были понятны обладателю среднего образования. Тогда и вопросов к тебе не будет, а спецы обсудят с тобой проблемы.
Нормально, пусть тоже будет.
Это не копипаста, а личное знание.