Реализация очень сильно зависит от архитектуры и возможностей
контроллера прерываний. Например, для Cortex-R4 (TI, серия RM4x)
Дано:
1. имеется контроллер прерываний, в котором имеется таблица обработчиков
2. в нужный элемент таблицы заносишь адрес нужного тебе объекта, имеющего публичный метод handlerIrq
Пример кода (естесственно, всё разделено по разным модулям; я здесь всё в кучу свалил для демонстрации):
/* интерфейсный класс */
class IrqCapable
{
public:
IrqCapable(const IRQ irqnumber, const IRQTYPE irqtype);
virtual void handlerIrq() = 0;
virtual ~IrqCapable() {}
};
IrqCapable::IrqCapable(const IRQ irqNumber, const IRQTYPE irqtype) : irqnumber(irqNumber)
{
if (irqtype == IRQTYPE::IRQ)
{
registerIrqVectorInVim(irqNumber, reinterpret_cast<uint32_t>(this));
}
else
{
registerFiqVectorInVim(irqNumber, reinterpret_cast<uint32_t>(this));
}
}
static class Rti* rti = nullptr;
/* класс, реализующий требуемую функциональность по обработке */
class Rti : private ::irq::IrqCapable
{
virtual void handlerIrq() override
{
flagSystick = true;
++counterSysticks;
clearInterruptFlagForCompare0();
}
public:
Rti() : IrqCapable(irq::IRQ::RTI_COMPARE_0, IRQTYPE::IRQ) {/* прочее */}
}
/* функция, которая создаёт требуемые экземпляры класса */
/* при необходимости передаются параметры, которые транслируются в Rti::Rti(), например, номер IRQ для VIM, или номер периферийного блока */
void create()
{
rti = new Rti(); /* тут можно и без "кучи" обойтись, если использовать in-place new */
}
/* обработчик IRQ, прописываемый в таблице прерываний, выглядит как-то так: */
extern "C" __attribute__((target("arm"), interrupt("IRQ"))) void irqHandler();
extern "C" __attribute__((target("arm"), interrupt("IRQ"))) void irqHandler()
{
const auto irqvecreg = reinterpret_cast<uint32_t*>(0xFFFF'FE70U); /* сюда контроллер прерываний автоматически кладёт адрес из таблицы прерываний */
auto obj = reinterpret_cast<irq::IrqCapable*>(*irqvecreg);
obj->handlerIrq();
}
Как это работает для RM4x?
1. вызывается create()->Rti::Rti()->IrqCapable::IrqCapable()->registerIrqVectorInVim(), тем самым адрес объекта запоминается в таблице VIM, настраиваются приоритет, тип прерывания, разрешаются прерывания от этой периферии
2. при срабатывании IRQ: выполняется команда по адресу 0x18->в регистр PC загружается адрес irqHandler()->выполняется irqHandler()->из VIM считывается адрес объекта, вызвавшего IRQ->вызывается метод handlerIrq() данного объекта->выполняется Rti::handlerIrq(), в котором кодируется требуемая функциональность
Какие преимущества даёт (ИМХО)?
1. TDD, возможность вызвать Rti::handlerIrq() из тестового кода; я так несколько раз писал сложные драйверы
2. унификация обработчиков
3. лёгкость добавления статистики, например, в irqHandler() можно добавить статистику числа прерываний, или в интерфейсный класс добавить счётчик возникающих прерываний конкретного класса и время выполнения прерывания (также заполняемые в irqHandler()); тогда эта функциональность автоматом появится вообще для всех прерываний от всех периферийных блоков
4. уменьшение кода-спагетти для рабочего кода модуля Rti за счёт выноса настройки прерываний в отдельный модуль
5. унификация драйверов и обработчиков прерываний для разных проектов ПО для разных МК, я бОльшую часть тупо копирую в нужный проект, и подстраиваю немного
6. уменьшение связности модуля Rti
Всё это также легко решаемо на С, и в проектах средней и более высокой сложности так и делается, на сях.