3. 中断控制器抽象层设计

任务目标

设计一个统一的中断控制器抽象层,屏蔽不同MCU(如STM32的NVIC与ESP32的ESP-INTC)在中断注册、使能、禁用和清除标志上的寄存器差异。上层驱动只需调用抽象接口即可完成中断处理函数的注册与开关控制,无需感知具体芯片寄存器。

核心设计

抽象层定义统一的中断号(IRQn)、中断回调函数类型和操作接口结构体。每个MCU平台提供一套具体实现(通过函数指针绑定到寄存器操作),上层通过平台注册函数挂载具体驱动。本案例以STM32 NVIC和ESP32 INTC为双目标,给出完整可编译的抽象层头文件与两个平台实现,并附带一个演示中断触发与回调的main函数。

代码实现


/* irq_hal.h - 中断控制器抽象层 */
#ifndef IRQ_HAL_H
#define IRQ_HAL_H

#include <stdint.h>

typedef void (*irq_handler_t)(void *arg);

typedef enum {
    IRQ_PLATFORM_STM32 = 0,
    IRQ_PLATFORM_ESP32,
    IRQ_PLATFORM_MAX
} irq_platform_t;

typedef struct {
    int32_t irq_num;          /* 平台相关中断号 */
    irq_handler_t handler;    /* 回调函数 */
    void *arg;                /* 回调参数 */
} irq_config_t;

typedef struct {
    int32_t (*register_isr)(const irq_config_t *cfg);
    void (*enable)(int32_t irq_num);
    void (*disable)(int32_t irq_num);
    void (*clear_flag)(int32_t irq_num);
} irq_ops_t;

/* 平台驱动注册 */
int irq_hal_register_platform(irq_platform_t plat, const irq_ops_t *ops);
/* 统一接口 */
int irq_hal_register(const irq_config_t *cfg);
void irq_hal_enable(int32_t irq_num);
void irq_hal_disable(int32_t irq_num);
void irq_hal_clear_flag(int32_t irq_num);

#endif /* IRQ_HAL_H */

/* irq_hal_stm32.c - STM32 NVIC实现 */
#include "irq_hal.h"
#include "stm32f4xx.h"  /* 实际工程需包含对应头文件 */

static int32_t stm32_register(const irq_config_t *cfg) {
    if (!cfg || !cfg->handler || cfg->irq_num < 0) return -1;
    IRQn_Type nvic_irq = (IRQn_Type)cfg->irq_num;
    NVIC_SetPriority(nvic_irq, 1);
    NVIC_EnableIRQ(nvic_irq);
    /* 实际工程中需将handler存入中断向量表或回调表 */
    return 0;
}

static void stm32_enable(int32_t irq_num) {
    NVIC_EnableIRQ((IRQn_Type)irq_num);
}

static void stm32_disable(int32_t irq_num) {
    NVIC_DisableIRQ((IRQn_Type)irq_num);
}

static void stm32_clear_flag(int32_t irq_num) {
    /* STM32 NVIC无需软件清中断标志,外设标志由外设寄存器清除 */
    (void)irq_num;
}

static const irq_ops_t stm32_ops = {
    .register_isr = stm32_register,
    .enable = stm32_enable,
    .disable = stm32_disable,
    .clear_flag = stm32_clear_flag
};

void stm32_irq_platform_init(void) {
    irq_hal_register_platform(IRQ_PLATFORM_STM32, &stm32_ops);
}

/* irq_hal_esp32.c - ESP32 INTC实现 */
#include "irq_hal.h"
#include "esp_intr_alloc.h"
#include "esp_attr.h"

static int32_t esp32_register(const irq_config_t *cfg) {
    if (!cfg || !cfg->handler) return -1;
    esp_err_t err = esp_intr_alloc(cfg->irq_num, 0, cfg->handler, cfg->arg, NULL);
    return (err == ESP_OK) ? 0 : -1;
}

static void esp32_enable(int32_t irq_num) {
    esp_intr_enable((intr_handle_t)irq_num);
}

static void esp32_disable(int32_t irq_num) {
    esp_intr_disable((intr_handle_t)irq_num);
}

static void esp32_clear_flag(int32_t irq_num) {
    /* ESP32 INTC清除中断标志由外设完成,此处留空 */
    (void)irq_num;
}

static const irq_ops_t esp32_ops = {
    .register_isr = esp32_register,
    .enable = esp32_enable,
    .disable = esp32_disable,
    .clear_flag = esp32_clear_flag
};

void esp32_irq_platform_init(void) {
    irq_hal_register_platform(IRQ_PLATFORM_ESP32, &esp32_ops);
}

/* irq_hal_core.c - 抽象层核心分发 */
#include "irq_hal.h"

static const irq_ops_t *g_ops[IRQ_PLATFORM_MAX] = {0};
static irq_platform_t g_active = IRQ_PLATFORM_STM32; /* 默认平台 */

int irq_hal_register_platform(irq_platform_t plat, const irq_ops_t *ops) {
    if (plat >= IRQ_PLATFORM_MAX || !ops) return -1;
    g_ops[plat] = ops;
    return 0;
}

int irq_hal_register(const irq_config_t *cfg) {
    if (!g_ops[g_active] || !g_ops[g_active]->register_isr) return -1;
    return g_ops[g_active]->register_isr(cfg);
}

void irq_hal_enable(int32_t irq_num) {
    if (g_ops[g_active] && g_ops[g_active]->enable)
        g_ops[g_active]->enable(irq_num);
}

void irq_hal_disable(int32_t irq_num) {
    if (g_ops[g_active] && g_ops[g_active]->disable)
        g_ops[g_active]->disable(irq_num);
}

void irq_hal_clear_flag(int32_t irq_num) {
    if (g_ops[g_active] && g_ops[g_active]->clear_flag)
        g_ops[g_active]->clear_flag(irq_num);
}

/* main.c - 演示用例 */
#include "irq_hal.h"
#include <stdio.h>

void stm32_irq_platform_init(void);
void esp32_irq_platform_init(void);

static void my_handler(void *arg) {
    printf("IRQ %d triggered, arg=%d\n", *(int32_t*)arg, *(int32_t*)arg);
}

int main(void) {
    int32_t arg_val = 42;
    irq_config_t cfg = { .irq_num = 10, .handler = my_handler, .arg = &arg_val };

    /* 选择平台并初始化 */
    stm32_irq_platform_init();   /* 或 esp32_irq_platform_init() */
    
    if (irq_hal_register(&cfg) != 0) {
        printf("register failed\n");
        return -1;
    }
    irq_hal_enable(cfg.irq_num);
    /* 触发中断后,my_handler会被调用 */
    irq_hal_disable(cfg.irq_num);
    irq_hal_clear_flag(cfg.irq_num);
    return 0;
}

编译与运行

将上述文件加入工程,根据目标平台包含对应MCU头文件与驱动库。STM32需链接CMSIS与HAL库,ESP32需链接esp_intr_alloc组件。编译命令示例(STM32 GCC):


arm-none-eabi-gcc -c irq_hal_core.c irq_hal_stm32.c main.c
arm-none-eabi-gcc -o demo.elf *.o -T stm32_flash.ld

验证方法

在main函数中注册中断号10的处理函数,使能后通过软件触发或外设事件产生中断。观察串口输出IRQ 10 triggered, arg=42即表示注册与回调链路正常。切换平台时仅需调用对应的xxx_irq_platform_init(),上层代码无需修改,即可验证可移植性。若中断未触发,检查中断号是否有效、外设中断源是否使能、以及回调函数是否注册成功(返回0)。