Как эффективно использовать один и тот же обработчик прерываний для каждого (идентичного) периферийного порта?

(Надеюсь) упрощенная версия моей проблемы:

Скажем, я использую каждый порт GPIO моего микроконтроллера Cortex M-4, чтобы делать одно и то же, например, читать порт при изменении уровня контактов. Я упростил свой код, чтобы он не зависел от порта, но у меня возникли проблемы с хорошим решением для повторного использования одной и той же функции обработчика прерываний.

  1. Есть ли способ использовать ту же функцию обработчика прерываний, имея метод определения того, какой порт вызвал прерывание? В идеале некоторые O(1)/не масштабируются в зависимости от количества портов на плате.
  2. Должен ли я просто иметь разные обработчики для каждого порта, которые вызывают одну и ту же функцию, которая принимает параметр порта? (Лучшее, что я смог придумать до сих пор)

Итак, как:

void worker (uint32_t gpio_id) {
    *work goes here*
}

void GPIOA_IRQ_Handler(void) { worker(GPIOA_id); }
void GPIOB_IRQ_Handler(void) { worker(GPIOB_id); }
void GPIOC_IRQ_Handler(void) { worker(GPIOC_id); }
...

Моя актуальная проблема:

Я изучаю и возился с FreeRTOS и создаю простые драйверы для отладки / стандартного UART, некоторые кнопки, которые есть на моем dev. доска и тд. До сих пор я делал драйверы для определенного периферийного устройства/порта.

Теперь я хочу создать драйвер I2C, не зная, какой интерфейс я буду использовать (в моем микроконтроллере 10 портов I2C), и потенциально разрешить использование кода драйвера на нескольких портах одновременно. Я бы знал все порты, используемые во время компиляции.

У меня есть довольно хорошая идея о том, как сделать драйвер независимым от порта, за исключением того, что я зациклен на выяснении хорошего способа найти, какой порт вызвал прерывание, используя единственную функцию-обработчик. (помимо циклического перебора регистра статуса прерывания каждого порта, поскольку это O (n)).

Как я уже сказал, лучшее, что я придумал, это не иметь единого обработчика, а вместо этого иметь разные обработчики в векторной таблице, которые все вызывают в ней одну и ту же рабочую функцию и передают параметр порта. Этот метод загромождает код драйвера, но это O(1) (если вы не принимаете во внимание сложность кода).

  1. Я делаю все это неправильно и должен просто сделать это простым и глупым и реализовать драйвер в соответствии с портом (ами) / вариантом использования, который мне действительно понадобится, самым простым способом? (даже не планирую использовать несколько шин I2C, хотя было бы интересно реализовать)

Заранее спасибо, надеюсь, пост не будет слишком двусмысленным или длинным (я чувствую, что он довольно длинный, извините).


person EmbeddedManuel    schedule 03.07.2020    source источник
comment
у вас может быть обработчик для каждого, этот обработчик устанавливает регистр, чтобы указать, какое прерывание было, а затем переходит к общему обработчику, который затем может использовать этот регистр или любую другую подсказку. Да, очевидно, вы можете установить один и тот же обработчик для любого/каждого прерывания, если хотите.   -  person old_timer    schedule 03.07.2020
comment
В чем здесь ваша настоящая проблема? Расход флэшки? Насколько велик ваш обработчик?   -  person old_timer    schedule 03.07.2020
comment
Моя проблема, по сути, состоит в том, как избежать дублирования кода, используя одну и ту же функцию-обработчик для нескольких прерываний, а также использовать метод постоянной сложности для поиска исходного периферийного устройства прерывания. Я также не был уверен, есть ли способ сделать это, что, вероятно, привело к запутанному сообщению/вопросу.   -  person EmbeddedManuel    schedule 03.07.2020
comment
Хендлер не очень большой. Для моей реальной проблемы с драйвером I2C это будет просто стандартная передача/получение следующего байта, если он есть; пробуждать задачу, когда больше не нужно обрабатывать байтов, что могло бы быть еще проще, если бы я использовал операцию прямого доступа к памяти, но сейчас я не собираюсь углубляться.   -  person EmbeddedManuel    schedule 03.07.2020
comment
Если вы можете исключить какие-либо отдельные параметры, перейдите к решению 2 вашего поста. Однако вам нужно написать все эти *handler() { ...} строки, потому что они предоставляют определенные аргументы и обычно устанавливают векторы. Использование хитрых приемов сборки сделало бы исходный код ужасным в обслуживании. -- Я бы начал с двух обработчиков, каждый из которых реализован в своем собственном обработчике. Затем я разработаю окончательный алгоритм, DMA или что-то еще. Только тогда я бы исследовал, в чем заключаются различия, и рассмотрел бы рефакторинг в смысле DRY.   -  person the busybee    schedule 03.07.2020
comment
@EmbeddedManuel DMA сам по себе может быть сложным в настройке, но в целом он может привести как к более быстрому, так и к более читаемому коду. Это особенно верно при работе с последовательными шинами, такими как I2C или UART, наличие триггера прерывания для каждого полученного байта довольно болезненно (хотя это старый школьный способ).   -  person Lundin    schedule 03.07.2020
comment
@thebusybee Да, это то, на что я собираюсь пойти. Этот пост был скорее подтверждением того, что я все делаю правильно, чем чем-то еще, что очень полезно, поскольку это сольный проект на более или менее новой территории. Спасибо за ответ!   -  person EmbeddedManuel    schedule 06.07.2020
comment
@Lundin Полностью согласен! Я определенно хочу в конечном итоге внедрить DMA, особенно потому, что я реализую это для FreeRTOS. Единственная реальная причина, по которой я не решаюсь пойти на это, заключается в том, что для моей платы (серия tiva C) вы можете включить DMA только для ведомого или ведущего в I2C, поэтому его реализация требует дополнительной работы для проверки и настройки. безопасно. Определенно в моих списках дел, лол   -  person EmbeddedManuel    schedule 06.07.2020


Ответы (1)


  1. Есть ли способ использовать ту же функцию обработчика прерываний, имея метод определения того, какой порт вызвал прерывание?

Только если разные прерывания очищаются одинаково, и вашему приложению все равно, какой вывод вызвал прерывание. Довольно маловероятный вариант использования.

  1. Должен ли я просто иметь разные обработчики для каждого порта, которые вызывают одну и ту же функцию, которая принимает параметр порта?

Да, я обычно так и делаю. Вы должны передать параметры из ISR в функцию, которые уникальны для конкретного прерывания. Важно: обратите внимание, что функция должна быть inline static! Быстрый ISR значительно важнее, чем экономия небольшого количества флэш-памяти за счет повторного использования одной и той же функции. Таким образом, в машинном коде у вас будет 4 разных ISR со встроенной рабочей функцией. (Возможно, вы захотите отключить встраивание в сборке отладки.)

Я делаю все это неправильно, и я должен просто говорить просто глупо

Похоже, вы делаете это правильно. Правильно написанный драйвер должен иметь возможность обрабатывать несколько экземпляров периферийного оборудования с одним и тем же кодом. При этом программисты на C склонны зацикливаться на том, чтобы избежать повторения кода. KISS часто гораздо эффективнее, чем предотвращение повторения кода. Избегать повторений, конечно, хорошо, но это не ваш главный приоритет.

Приоритеты в этом случае должны быть самые важные в первую очередь:

  1. Максимально быстрые и тонкие прерывания.
  2. Читаемый код.
  3. Используемый размер вспышки.
  4. Избегание повторения кода.
person Lundin    schedule 03.07.2020
comment
Спасибо, вы ответили на все, в чем у меня были сомнения, особенно на последний вопрос. К сожалению, это новая учетная запись, я не могу проголосовать за нее. +1 хотя - person EmbeddedManuel; 06.07.2020