中断分配

概述

ESP32 有两个核(core),每个核有 32 个中断。每个中断有一个确定的优先级,大多数(而非全部)中断被连接到中断复用矩阵中。由于中断源的数量大于中断的数量,因此在必要时可以在多个驱动程序中共享同一个中断。ESP-IDF 中提供了一个 esp_intr_alloc 抽象,它的作用就是隐藏这些实现细节。

驱动程序可以调用 esp_intr_alloc(或 esp_intr_alloc_sintrstatus)为某个外设分配一个中断。它可以通过一个传递给该函数的标志设置所分配中断的类型,指明指定的等级或者触发方法。然后中断分配代码会知道一个可用的中断,使用中断复用矩阵将它与外设挂在一起,并安装驱动程序传递给它的中断 hander 和 ISR。

该代码有两种处理方式不同的中断:共享中断和非共享中断。二者中最简单的是非共享中断:一个独立的中断会在调用 esp_intr_alloc 时被分配,且该中断仅能用于附着到它上面的外设,仅由一个 ISR 会被调用。共享中断可以被多个外设触发,当附着到它上面的某一个外设发送中断信号时,多个 ISR 都会被调用。因此,共享中断的 ISR 应当检查它们所服务的外设的中断状态,以查看是否需要执行任何动作。

非共享中断即可以由电平触发,也可以是边沿触发。共享中断仅能被电平触发(因为使用边沿触发时可能错过中断)。(它内部的逻辑是这样的:设备 A 和设备 B 共享一个中断。设备 B 发出一个中断信号。中断线为高。ISR handler 调用设备 A 的代码 -> 什么也不做。ISR handler 调用设备 B 的代码,但是在此期间,设备 A 发送了一个信号。设备 B 完成处理,清除设备 B 的中断,退出中断代码。现在,设备 A 的中断处于 pending 状态,但是由于中断线没有变为低(即使设备 B 的中断被清除了,设备 A 仍然保持为高),中断永远不会被服务)

多核问题

可以产生中断的外设可以被分为两类:

  • 外部外设,在 ESP32 上面但是在 Xtensa 核外面。大多数 ESP32 外设都是这种类型。
  • 内部外设,属于 Xtensa CPU 核自身的设备。

这两种外设的中断处理有一点点区别。

内部外设中断

每个 Xtensa CPU 核都有六个内部外设:

  • 三个 timer 比较器(comparator)
  • 一个性能监视器(monitor)
  • 两个软件中断

内部中断源在 esp_intr_alloc.h 中定义为 ETS_INTERNAL_*_INTR_SOURCE

这些外设只能由它们所关联的核进行配置。当产生中断时,中断是通过硬连线(hard-wire)连接到它所关联的核;一个核中的中断源(例如内部 timer 比较器)不能在另一个核中产生中断。这就是为什么只能被运行在该核上面的某个任务管理。内部中断源仍然是使用 esp_intr_alloc 进行分配的,但是它们不能进行共享,且总是具有一个固定的中断等级。

外部外设中断

剩下的中断源来自外部设备,它们在 soc/soc.h 中定义为 ETS_*_INTR_SOURCE

两个 CPU 核的非内部中断槽被连接到一个中断复用器上面。中断复用器可以将任何外部中断源引导到它上面的任意中断槽。

  • 分配外部中断时总是会将它分配到对它进行分配的那个核上面。
  • 释放外部中断时必须发送在它所分配的同一个核上面。
  • 可以从另一个核使能/禁止外部中断。
  • 多个外部中断源可以通过将 ESP_INTR_FLAG_SHARED 作为标志参数传递给 esp_intr_alloc() 来共享一个中断槽。

从某个没有固定到一个具体核的任务中调用 esp_intr_alloc() 将不会有效果。在任务切换期时,这些任务可能会在两个核之间迁移。因此,它不能辨别中断被分配到哪个 CPU 上了,从而导致很难释放中断处理,且可能让调试变得更加困难。建议在创建需要分配中断的任务时,使用 xTaskCreatePinnedToCore() 函数,且指定一个参数 CoreID。在内部中断源的时候,这也是需要的。

IRAM-Safe 中断 Handler

使用 ESP_INTR_FLAG_IRAM 标志注册的中断 handler 将移植在 IRAM 中运行(从 DRAM 中读取它的所有数据),因此不需要在 flash

这对于那些需要保证最小执行延迟的中断来说是非常有用的,因为 falsh 的写和擦除操作可能比较慢(擦除可能哟啊花几十或几百微秒才能完成)。

如果中断 handler 的调用非常频繁,可以将它放到 IRAM 中,从而避免 flash cache 的遗漏。

更多细节请cake SPI flash API 文档

应用程序示例

API 参考手册

ESP_INTR_FLAG_LEVEL1

Interrupt allocation flags.

These flags can be used to specify which interrupt qualities the code calling esp_intr_alloc* needs.Accept a Level 1 interrupt vector

ESP_INTR_FLAG_LEVEL2

Accept a Level 2 interrupt vector.

ESP_INTR_FLAG_LEVEL3

Accept a Level 3 interrupt vector.

ESP_INTR_FLAG_LEVEL4

Accept a Level 4 interrupt vector.

ESP_INTR_FLAG_LEVEL5

Accept a Level 5 interrupt vector.

ESP_INTR_FLAG_LEVEL6

Accept a Level 6 interrupt vector.

ESP_INTR_FLAG_NMI

Accept a Level 7 interrupt vector.

ESP_INTR_FLAG_LOWMED

Low and medium prio interrupts. These can be handled in C.

ESP_INTR_FLAG_HIGH

High level interrupts. Need to be handled in assembly.

ESP_INTR_FLAG_SHARED

Interrupt can be shared between ISRs.

ESP_INTR_FLAG_EDGE

Edge-triggered interrupt.

ESP_INTR_FLAG_IRAM

ISR can be called if cache is disabled.

ESP_INTR_FLAG_INTRDISABLED

Return with this interrupt disabled.

函数

esp_err_t esp_intr_mark_shared(int intno, int cpu, bool is_in_iram)

Mark an interrupt as a shared interrupt.

This will mark a certain interrupt on the specified CPU as an interrupt that can be used to hook shared interrupt handlers to.

Return
ESP_ERR_INVALID_ARG if cpu or intno is invalid ESP_OK otherwise
Parameters
  • intno: The number of the interrupt (0-31)
  • cpu: CPU on which the interrupt should be marked as shared (0 or 1)
  • is_in_iram: Shared interrupt is for handlers that reside in IRAM and the int can be left enabled while the flash cache is disabled.

esp_err_t esp_intr_reserve(int intno, int cpu)

Reserve an interrupt to be used outside of this framewoek.

This will mark a certain interrupt on the specified CPU as reserved, not to be allocated for any reason.

Return
ESP_ERR_INVALID_ARG if cpu or intno is invalid ESP_OK otherwise
Parameters
  • intno: The number of the interrupt (0-31)
  • cpu: CPU on which the interrupt should be marked as shared (0 or 1)

esp_err_t esp_intr_alloc(int source, int flags, intr_handler_t handler, void *arg, intr_handle_t *ret_handle)

Allocate an interrupt with the given parameters.

This finds an interrupt that matches the restrictions as given in the flags parameter, maps the given interrupt source to it and hooks up the given interrupt handler (with optional argument) as well. If needed, it can return a handle for the interrupt as well.

The interrupt will always be allocated on the core that runs this function.

If ESP_INTR_FLAG_IRAM flag is used, and handler address is not in IRAM or RTC_FAST_MEM, then ESP_ERR_INVALID_ARG is returned.

Return
ESP_ERR_INVALID_ARG if the combination of arguments is invalid. ESP_ERR_NOT_FOUND No free interrupt found with the specified flags ESP_OK otherwise
Parameters
  • source: The interrupt source. One of the ETS_*_INTR_SOURCE interrupt mux sources, as defined in soc/soc.h, or one of the internal ETS_INTERNAL_*_INTR_SOURCE sources as defined in this header.
  • flags: An ORred mask of the ESP_INTR_FLAG_* defines. These restrict the choice of interrupts that this routine can choose from. If this value is 0, it will default to allocating a non-shared interrupt of level 1, 2 or 3. If this is ESP_INTR_FLAG_SHARED, it will allocate a shared interrupt of level 1. Setting ESP_INTR_FLAG_INTRDISABLED will return from this function with the interrupt disabled.
  • handler: The interrupt handler. Must be NULL when an interrupt of level >3 is requested, because these types of interrupts aren’t C-callable.
  • arg: Optional argument for passed to the interrupt handler
  • ret_handle: Pointer to an intr_handle_t to store a handle that can later be used to request details or free the interrupt. Can be NULL if no handle is required.

esp_err_t esp_intr_alloc_intrstatus(int source, int flags, uint32_t intrstatusreg, uint32_t intrstatusmask, intr_handler_t handler, void *arg, intr_handle_t *ret_handle)

Allocate an interrupt with the given parameters.

This essentially does the same as esp_intr_alloc, but allows specifying a register and mask combo. For shared interrupts, the handler is only called if a read from the specified register, ANDed with the mask, returns non-zero. By passing an interrupt status register address and a fitting mask, this can be used to accelerate interrupt handling in the case a shared interrupt is triggered; by checking the interrupt statuses first, the code can decide which ISRs can be skipped

Return
ESP_ERR_INVALID_ARG if the combination of arguments is invalid. ESP_ERR_NOT_FOUND No free interrupt found with the specified flags ESP_OK otherwise
Parameters
  • source: The interrupt source. One of the ETS_*_INTR_SOURCE interrupt mux sources, as defined in soc/soc.h, or one of the internal ETS_INTERNAL_*_INTR_SOURCE sources as defined in this header.
  • flags: An ORred mask of the ESP_INTR_FLAG_* defines. These restrict the choice of interrupts that this routine can choose from. If this value is 0, it will default to allocating a non-shared interrupt of level 1, 2 or 3. If this is ESP_INTR_FLAG_SHARED, it will allocate a shared interrupt of level 1. Setting ESP_INTR_FLAG_INTRDISABLED will return from this function with the interrupt disabled.
  • intrstatusreg: The address of an interrupt status register
  • intrstatusmask: A mask. If a read of address intrstatusreg has any of the bits that are 1 in the mask set, the ISR will be called. If not, it will be skipped.
  • handler: The interrupt handler. Must be NULL when an interrupt of level >3 is requested, because these types of interrupts aren’t C-callable.
  • arg: Optional argument for passed to the interrupt handler
  • ret_handle: Pointer to an intr_handle_t to store a handle that can later be used to request details or free the interrupt. Can be NULL if no handle is required.

esp_err_t esp_intr_free(intr_handle_t handle)

Disable and free an interrupt.

Use an interrupt handle to disable the interrupt and release the resources associated with it.

Return
ESP_ERR_INVALID_ARG if handle is invalid, or esp_intr_free runs on another core than where the interrupt is allocated on. ESP_OK otherwise
Parameters
  • handle: The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus

int esp_intr_get_cpu(intr_handle_t handle)

Get CPU number an interrupt is tied to.

Return
The core number where the interrupt is allocated
Parameters
  • handle: The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus

int esp_intr_get_intno(intr_handle_t handle)

Get the allocated interrupt for a certain handle.

Return
The interrupt number
Parameters
  • handle: The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus

esp_err_t esp_intr_disable(intr_handle_t handle)

Disable the interrupt associated with the handle.

Note
For local interrupts (ESP_INTERNAL_* sources), this function has to be called on the CPU the interrupt is allocated on. Other interrupts have no such restriction.
Return
ESP_ERR_INVALID_ARG if the combination of arguments is invalid. ESP_OK otherwise
Parameters
  • handle: The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus

esp_err_t esp_intr_enable(intr_handle_t handle)

Ensable the interrupt associated with the handle.

Note
For local interrupts (ESP_INTERNAL_* sources), this function has to be called on the CPU the interrupt is allocated on. Other interrupts have no such restriction.
Return
ESP_ERR_INVALID_ARG if the combination of arguments is invalid. ESP_OK otherwise
Parameters
  • handle: The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus

void esp_intr_noniram_disable()

Disable interrupts that aren’t specifically marked as running from IRAM.

void esp_intr_noniram_enable()

Re-enable interrupts disabled by esp_intr_noniram_disable.