中断分配
概述
ESP32-S2 有一个核,32 个中断。每个中断都有一个确定的优先级别,大多数中断(但不是全部)都连接到中断矩阵。
由于中断源数量多于中断,有时多个驱动程序可以共用一个中断。esp_intr_alloc()
抽象隐藏了这些实现细节。
驱动程序可以通过调用 esp_intr_alloc()
、esp_intr_alloc_bind()
、 esp_intr_alloc_intrstatus()
或 esp_intr_alloc_intrstatus_bind()
为某个外设分配中断。通过向此函数传递 flag,可以指定中断类型、优先级和触发方式。然后,中断分配代码会找到适用的中断,使用中断矩阵将其连接到外设,并为其安装给定的中断处理程序和 ISR。
中断分配器提供两种不同的中断类型:共享中断和非共享中断,这两种中断需要不同处理方式。非共享中断在每次调用 esp_intr_alloc()
时,都会分配一个单独的中断,该中断仅用于与其相连的外设,只调用一个 ISR。共享中断则可以由多个外设触发,当其中一个外设发出中断信号时,会调用多个 ISR。因此,针对共享中断的 ISR 应检查对应外设的中断状态,以确定是否需要采取任何操作。
非共享中断可由电平或边缘触发。共享中断只能由电平触发,因为使用边缘触发可能会错过中断。
要解释为什么共享中断只能由电平触发,以外设 A 和外设 B 共用一个边缘触发中断为例进行说明:当外设 B 触发中断时,会将其中断信号设置为高电平,产生一个从低到高的边缘,进而锁存 CPU 中断位,并触发 ISR。接着,ISR 开始执行,检查到此时外设 A 没有触发中断,于是继续处理外设 B 的中断信号,最后将外设 B 的中断状态清除。最后,CPU 会在 ISR 返回之前清除中断位锁存器。因此在整个中断处理过程中,如果外设 A 触发了中断,该中断会因 CPU 清除中断位锁存器而丢失。
IRAM 安全中断处理程序
在执行 SPI flash 的写入和擦除操作时,ESP32-S2 会禁用 cache,中断处理程序将无法访问 SPI flash 和 SPIRAM。因此,ESP-IDF 中存在两种中断处理程序,它们各有优缺点:
IRAM 安全中断处理程序 - 只能访问内部内存中的代码和数据(代码存储在 IRAM 中,数据存储在 DRAM 中)。
+ 延迟:flash 写入和擦除操作相对缓慢,例如擦除可能需要几十或几百毫秒才能完成,但这些中断处理程序不受影响,执行速度较快且延迟较低,能够保证最小执行延迟。
- 占用内部内存:中断处理程序占用了宝贵的内部内存,这些内存本可以用于其他目的。
+ cache 未命中:中断处理程序不依赖 cache, 因此就不会出现 cache 未命中带来的不确定性,因为代码和数据已存储在内部存储器中了。
使用场景:请使用
ESP_INTR_FLAG_IRAM
标志,通过中断分配器 API 注册此类中断。
非 IRAM 安全中断处理程序 - 可能会访问 flash 中的代码和(只读)数据。
- 延迟:在进行 flash 操作时,中断处理程序会被推迟,因此平均延迟较高且难以预测。
+ 占用内部内存:该中断处理程序不使用内部 RAM,或者使用的内存比 IRAM 安全中断要少。
使用场景:通过中断分配器 API 注册此类中断时,请 不要 使用
ESP_INTR_FLAG_IRAM
标志。
请注意,没有任何显式标记将中断处理程序标识为 IRAM 安全。 当且仅当要访问的代码和数据存储在内部内存中时,中断处理程序才被隐式标记为 IRAM 安全。“IRAM 安全”这个术语实际上有点误导性,因为除了将处理程序的代码放在 IRAM 中之外,还有更多其他要求。以下是 不属于 IRAM 安全的中断处理程序示例:
部分代码放置在 flash 中。
放置在 IRAM 中但调用了存储在 flash 里的函数。
代码放置在 IRAM 中但访问了位于 flash 中的只读变量。
关于如何将代码和数据放置在 IRAM 或 DRAM 中,请参见 如何将代码放入 IRAM。
有关 SPI flash 操作及其与中断处理程序交互的更多详细信息,请参见 SPI flash API 文档。
备注
如果不能 100% 确定中断处理程序访问的所有代码和数据都位于 IRAM(代码)或 DRAM(数据)中,切勿使用 ESP_INTR_FLAG_IRAM
标志注册中断处理程序。忽略这一点将导致(有时是偶发性的):ref:cache 错误 <cache_error>。通过调用函数间接访问代码和数据时也需要注意这点。
排除中断分配故障
CPU 中断在大多数 Espressif SoC 上都是有限的资源。因此,一个运行的程序有可能耗尽 CPU 中断,例如初始化多个外设驱动程序的情况。这通常导致驱动程序的初始化函数返回 ESP_ERR_NOT_FOUND
错误。
这种情况下,可使用 esp_intr_dump()
函数打印中断列表及其状态。此函数输出通常如下:
CPU 0 interrupt status:
Int Level Type Status
0 1 Level Reserved
1 1 Level Reserved
2 1 Level Used: RTC_CORE
3 1 Level Used: TG0_LACT_LEVEL
...
输出列含义如下:
Int
:CPU 中断输入编号。通常不直接在软件中使用,仅作为参考。Level
:CPU 中断的优先级(1-7)。此优先级固定在硬件上,无法更改。Type
:CPU 中断的中断类型(电平或边缘中断)。此类型在硬件上固定,无法更改。Status
:中断的可能状态:Reserved
:中断在硬件层面保留,或由 ESP-IDF 的某些部分保留。不能使用esp_intr_alloc()
分配。Used: <source>
:中断已分配并连接到单个外设。Shared: <source1> <source2> ...
:中断已分配并连接到多个外设。参见本文档 多个处理程序共用一个中断源 章节。Free
:中断未分配,可以由esp_intr_alloc()
使用。
Free (not general-use)
:中断未分配,但它是高优先级中断(级别 4-7)或边缘触发中断。高优先级中断可以使用esp_intr_alloc()
分配,但要求处理程序必须用汇编语言编写,参见 高优先级中断处理程序。低优先级和中优先级的边缘触发中断也可以用esp_intr_alloc()
分配,但很少使用,因为大多数外设中断是电平触发的。
如果已确认应用程序的确用完了中断,可组合采用下列建议解决问题:
找到可接受更高延迟的中断,并用
ESP_INTR_FLAG_SHARED
flag (或与ESP_INTR_FLAG_LOWMED
进行 OR 运算)分配这些中断。对两个或更多外设使用此 flag 能让它们使用单个中断输入,从而为其他外设节约中断输入。参见 多个处理程序共用一个中断源。一些外设驱动程序可能默认使用
ESP_INTR_FLAG_LEVEL1
flag 来分配中断,因此默认情况下不会使用优先级为 2 或 3 的中断。如果esp_intr_dump()
显示某些优先级为 2 或 3 的中断可用,尝试在初始化驱动程序时将中断分配 flag 改为ESP_INTR_FLAG_LEVEL2
或ESP_INTR_FLAG_LEVEL3
。检查是否有些外设驱动程序不需要一直启用,并按需将其初始化或取消初始化。这样可以减少同时分配的中断数量。
API 参考
Header File
This header file can be included with:
#include "esp_intr_types.h"
Macros
-
ESP_INTR_CPU_AFFINITY_TO_CORE_ID(cpu_affinity)
Convert esp_intr_cpu_affinity_t to CPU core ID.
Type Definitions
-
typedef void (*intr_handler_t)(void *arg)
Function prototype for interrupt handler function
-
typedef struct intr_handle_data_t *intr_handle_t
Handle to an interrupt handler
Enumerations
-
enum esp_intr_cpu_affinity_t
Interrupt CPU core affinity.
This type specify the CPU core that the peripheral interrupt is connected to.
Values:
-
enumerator ESP_INTR_CPU_AFFINITY_AUTO
Install the peripheral interrupt to ANY CPU core, decided by on which CPU the interrupt allocator is running.
-
enumerator ESP_INTR_CPU_AFFINITY_0
Install the peripheral interrupt to CPU core 0.
-
enumerator ESP_INTR_CPU_AFFINITY_1
Install the peripheral interrupt to CPU core 1.
-
enumerator ESP_INTR_CPU_AFFINITY_AUTO
Header File
This header file can be included with:
#include "esp_intr_alloc.h"