Предыдущая статья (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 лет как. Возьмите нормальную книгу и почитайте по теме. И про прерывания и про прямой доступ к памяти.
Афтырь, я, как не владеющий темой, не понял смысла статьи. Ты вполне имеешь право постить такие статьи, но выводы будь любезен напиши такие, чтобы они были понятны обладателю среднего образования. Тогда и вопросов к тебе не будет, а спецы обсудят с тобой проблемы.
Нормально, пусть тоже будет.
Это не копипаста, а личное знание.