zepher源码 中断interrupt

基本概念

ISR

1. 中断服务例程( ISR) 是响应硬件或软件中断而异步执行的函数。ISR 通常会抢占当前线程的执行,从而允许以非常低的开销进行响应。仅当所有 ISR 工作完成后,线程执行才会恢复;ISR的数量受底层硬件的限制。

2. ISR 具有以下关键属性:

    1)触发 ISR 的中断请求 (IRQ) 信号

    2)与 IRQ 相关的优先级。

    3)调用中断处理函数来处理中断。

    4)传递给该函数的参数值。

3. 中断嵌套是指在处理一个中断的过程中,如果发生了更高优先级的中断,处理器可以暂停当前的中断处理,转而去处理这个新的中断。

多级中断处理

 嵌套中断向量控制器(Nested Vectored Interrupt Controller,NVIC)是ARM Cortex-M系列处理器中用于处理中断的硬件

    如果支持嵌套中断控制器,CONFIG_MULTI_LEVEL_INTERRUPTS 则应设置为 1,CONFIG_2ND_LEVEL_INTERRUPTS根据 CONFIG_3RD_LEVEL_INTERRUPTS硬件架构进行配置。

    基于此,给每个硬件分配了唯一的32位中断号。(待补充)

IRQ锁

线程可以使用IRQ 锁暂时阻止系统中的所有 IRQ 处理。在内核再次处理中断之前,线程解锁IRQ锁次数要和设置IRD锁的次数相同。

IRQ 锁是线程特定的。具体来说,如果线程 A 锁定中断,然后执行使其自身进入休眠状态的操作(例如休眠 N 毫秒),则一旦线程 A 被换出并且下一个就绪线程 B 开始运行,该线程的 IRQ 锁就不再适用。

这意味着可以在线程 B 运行时处理中断,除非线程 B 也使用自己的 IRQ 锁锁定了中断。当内核在使用 IRQ 锁的两个线程之间切换时是否可以处理中断是特定于体系结构的。当线程A最终再次成为当前线程时,内核重新建立线程A的IRQ锁。这可确保线程 A 在显式解锁其 IRQ 锁之前不会被中断。如果线程 A 没有休眠,但让更高优先级的线程 B 准备就绪,则 IRQ 锁将禁止任何可能发生的抢占。线程 B 不会运行,直到释放 IRQ 锁后到达下一个重新调度点。

零延迟中断

IRQ锁会阻止中断IRQ执行,所以会增加中断延迟。对于一些对延迟时间有要求的中断用例来说,长时间的中断延迟是不可接受的;因此内核允许此类型的中断以无法被IRQ锁阻止的优先级来执行,这些中断被称为零延迟中断。

首先需要启使能CONFIG_ZERO_LATENCY_IRQS表示启用零延迟中断(整体支持零延迟中断);其次IRQ_ZERO_LATENCY标志必须被传递给IRQ_CONNECT或 IRQ_DIRECT_CONNECT宏以将特定中断配置为零延迟(将特定中断设置为零延迟中断)。

卸载ISR工作

offloading ISR working,又叫底半部处理。

1. ISR应快速执行确保预期的系统操作,提高系统的实时性。如果需要耗时的处理,ISR应将部分或全部处理卸载到线程,从而恢复内核相应其他中断的能力。

2. 内核支持多种将中断相关处理卸载到线程的机制:

  • ISR 可以使用内核对象(例如 FIFO、LIFO 或信号量)向辅助线程发出信号以执行与中断相关的处理。

  • ISR 可以指示系统工作队列线程执行工作项。

3. 当将ISR的部分或全部处理卸载到线程时,后续的这些线程处理通常是以系统线程的形式并行工作的,当有其他更高优先级的线程或中断来时,会在处理卸载的线程之前执行。

4. 补充说明:当一个中断发生时,处理器首先会执行ISR,这个过程被称为"顶半部(top half)"处理。在顶半部处理中,ISR通常会执行一些紧急的、必须立即完成的工作,比如读取设备的状态,清除中断标志,等等。然后,ISR可能会把一些可以稍后执行的工作卸载到一个线程中去执行,这个过程被称为"底半部"处理。这个线程可能是一个专门的内核线程,也可能是一个用户空间的线程,取决于操作系统的设计。

API

IRQ_CONNECT

IRQ_CONNECT( irq_p, priority_p, isr_p, isr_param_p, flags_p)  初始化中断处理程序,要求在构建时必须知道所有参数。

参数:

    irq_p:中断号

    priority_p:中断优先级

    isr_p:中断处理函数的地址

    isr_param_p:传递给中断处理函数的参数

    flags_p:特定架构的IRQ配置标志

irq_connect_dynamic

static inline int irq_connect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags)

配置动态中断。如果在构建时无法知道参数,用此函数代替IRQ_CONNECT()

irq_disconnect_dynamic

static inline int irq_disconnect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags)

断开动态中断。将其与共享中断结合使用,可以从使用同一中断线的客户端列表中删除例程/参数对。

IRQ_DIRECT_CONNECT

IRQ_DIRECT_CONNECT( irq_p, priority_p, isr_p, flags_p)初始化"直接"中断处理程序

这些 ISR 专为性能关键型中断处理而设计,不经过通用中断处理代码。它们的实现方式必须能够安全地将它们直接放入向量表中。对于用 C 语言编写的 ISR,ISR_DIRECT_DECLARE()宏将自动执行此操作。

(TODO:这段有一些内容不太理解)

与普通 Zephyr 中断相比,这种类型的中断目前有一些限制:

  • 没有参数传递给 ISR。

  • 不进行堆栈切换,ISR 将在中断上下文的堆栈上运行,除非体系结构自动在 HW 中进行堆栈切换。

  • 中断锁定状态与 ISR 运行时硬件设置的方式相同。在进入中断锁定的 ISR 的架构上,它们将保持锁定状态。

  • 调度决策现在是可选的,由使用ISR_DIRECT_DECLARE()宏实现的 ISR 的返回值控制

  • 现在可以选择调用操作系统退出电源管理空闲状态。普通中断总是在 ISR 运行之前执行此操作,但现在运行时由ISR_DIRECT_PM()宏的位置控制,或者完全省略。

ISR_DIRECT_HEADER 

ISR_DIRECT_HEADER ()

执行 ISR 主体之前的常见任务。

该宏必须位于所有直接中断的开头,并在 ISR 本身运行之前执行最少的特定于体系结构的任务。它不带任何参数,也没有返回值。

ISR_DIRECT_FOOTER

ISR_DIRECT_FOOTER ( check_reschedule )

退出 ISR 主体之前的常见任务。

该宏必须位于所有直接中断的末尾,并执行最少的特定于体系结构的任务,例如 EOI。它没有返回值。

在普通中断中,如果当前线程是可抢占的并且内核的就绪队列缓存中存在另一个线程准备运行,则会在中断结束时进行检查以调用 z_swap() 逻辑。现在这是可选的,并由 check_reschedule 参数控制。如果不确定,请设置为非零。在软件中执行堆栈切换和嵌套中断跟踪的系统上,仅当这是非嵌套中断时才应调用 z_swap()。

参数:

  • check_reschedule – 如果非零,则另外调用调度逻辑

ISR_DIRECT_PM 

ISR_DIRECT_PM ()

执行电源管理空闲退出逻辑。

该宏可以选择在 IRQ_DIRECT_HEADER() 和 IRQ_DIRECT_FOOTER() 调用之间的某个位置调用。它执行退出电源管理空闲状态所需的任务。它不接受任何参数,也不返回任何参数。

ISR_DIRECT_DECLARE

ISR_DIRECT_DECLARE(name)  name -- ISR的符合名称

用于声明直接中断服务例程的辅助宏。这将声明函数并自动包含ISR_DIRECT_FOOTER()和ISR_DIRECT_HEADER()宏。如果可能要做出调度决策,该函数应返回非零状态。

irq_lock

irq_lock()

锁定中断。该函数返回一个无符号整数"锁定密钥"(key)。"锁定密钥"必须传递给irq_unlock()以重新启用中断。

该例程可由 ISR 或线程调用。如果是线程调用,则中断锁是线程特定的;这意味着仅当线程运行时中断才保持禁用状态。如果线程执行允许另一个线程运行的操作(例如,给出信号量或休眠 N 毫秒),则中断锁不再适用,并且在其他处理发生时可以重新启用中断。当线程再次成为当前线程时,内核重新建立其中断锁;这可以确保线程在显式释放其建立的中断锁之前不会被中断。

irq_unlock

irq_unlock()

解锁中断。key值是由irq_lock()生成的锁定密钥。

irq_enable

irq_enable ( irq )

启用 IRQ。该例程启用来自源irq的中断。

参数:irq – IRQ 

irq_disable

irq_disable(irq)

禁用 IRQ。该例程禁用来自源irq的中断。

参数:irq – IRQ 

irq_is_enabled

irq_is_enabled ( irq )

获取 IRQ 启用状态。该例程指示是否启用来自源irq的中断。

参数:irq – IRQ 。

返回值:中断使能状态,true 或 false

示例

常规ISR

#define MY_DEV_IRQ  24       /* device uses IRQ 24 */
#define MY_DEV_PRIO  2       /* device uses interrupt priority 2 */
/* argument passed to my_isr(), in this case a pointer to the device */
#define MY_ISR_ARG  DEVICE_GET(my_device)
#define MY_IRQ_FLAGS 0       /* IRQ flags */

void my_isr(void *arg)
{
   ... /* ISR code */
}

void my_isr_installer(void)
{
   ...
   IRQ_CONNECT(MY_DEV_IRQ, MY_DEV_PRIO, my_isr, MY_ISR_ARG, MY_IRQ_FLAGS);
   irq_enable(MY_DEV_IRQ);
   ...
}

动态ISR

#include <stdio.h>
#include <rtems.h>

void first_interrupt_handler(rtems_vector_number vector)
{
    // 第一个中断处理程序的实现
    printf("Handling first dynamic interrupt number %d\n", vector);
}

void second_interrupt_handler(rtems_vector_number vector)
{
    // 第二个中断处理程序的实现
    printf("Handling second dynamic interrupt number %d\n", vector);
}

int main()
{
    rtems_status_code status;
    // 初始化和其他代码
    // 动态连接第一个中断处理程序
    status = irq_connect_dynamic(vector_number, 1, first_interrupt_handler, NULL, 0);
    if (status != RTEMS_SUCCESSFUL) {
        printf("Failed to connect dynamic interrupt handler\n");
    }
    
    // 某些条件下,需要更改中断处理程序为第二个处理程序
    if (some_condition) {
        // 先断开连接
        status = irq_disconnect(vector_number);  
        if (status != RTEMS_SUCCESSFUL) {
            printf("Failed to disconnect interrupt handler\n");
        }
        // 动态连接第二个中断处理程序
        status = irq_connect_dynamic(vector_number, 1, second_interrupt_handler, NULL, 0);
        if (status != RTEMS_SUCCESSFUL) {
            printf("Failed to connect dynamic interrupt handler\n");
        }
    }

    return 0;
}

直接ISR

#include <irq.h>
#include <arch/cpu.h>

// 定义一个直接的中断服务例程
ISR_DIRECT_DECLARE(my_isr)
{
    // 这里是处理中断的代码,假设我们只是简单地打印一条消息
    printk("Interrupt handled!\n");
    
    // 如果可能需要进行上下文切换,返回1
    return 1;
}

void main(void)
{
    // 这里是中断的IRQ号,你需要根据你的硬件来设置
    int irq_num = 0;
    // 这里是中断的优先级,你需要根据你的需求来设置
    int priority = 0;

    // 连接中断
    IRQ_CONNECT(irq_num, priority, my_isr, NULL, 0);
   
    // 启用中断
    irq_enable(irq_num);
    // 这里是主程序的其他代码...
}
### 如何在Zephyr操作系统中设置STM32的中断 为了配置和使用STM32微控制器上的中断,在Zephyr实时操作系统环境中,开发者通常遵循一系列特定的方法来确保硬件资源被有效地初始化并响应外部事件。下面提供了一个具体的实例说明如何实现这一目标。 #### 初始化设备驱动程序 首先,需要通过Kconfig选项启用相应的外设支持,并确认DTS(Device Tree Source)文件已正确定义了所需的GPIO引脚和其他必要的外围接口属性[^1]。这一步骤至关重要,因为它决定了哪些物理连接可以在软件层面得到识别和支持。 ```c // 在dts文件中定义设备树节点 &gpioa { pinctrl-0 = <&pa0_default>; status = "okay"; }; ``` #### 配置中断服务例程 (ISR) 接下来,编写一个函数作为中断处理程序,该函数将在触发指定类型的中断时执行。此过程涉及声明静态变量用于存储回调上下文以及调用`IRQ_CONNECT()`宏完成实际绑定操作。注意这里的参数顺序依次为:中断号、优先级级别、指向ISR的指针、传递给ISR的数据指针以及标志位。 ```c static void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) { printk("Button pressed!\n"); } static int init_button_interrupt(const struct device *port) { struct gpio_callback gpio_cb; // 设置输入模式 gpio_pin_configure(port, BUTTON_PIN, GPIO_INPUT); // 注册回调 gpio_init_callback(&gpio_cb, button_pressed, BIT(BUTTON_PIN)); gpio_add_callback(port, &gpio_cb); // 启用上升沿触发的中断 gpio_pin_interrupt_configure(port, BUTTON_PIN, GPIO_INT_EDGE_TO_ACTIVE); IRQ_CONNECT(DT_IRQN(DT_NODELABEL(gpio)), 0, button_pressed, DEVICE_DT_GET(DT_NODELABEL(gpio)), 0); irq_enable(DT_IRQN(DT_NODELABEL(gpio))); return 0; } ``` 上述代码片段展示了针对按键按下事件创建简单的ISR逻辑[^2]。值得注意的是,具体细节可能会因所选MCU型号的不同而有所变化;因此建议查阅官方文档获取最准确的信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值