当前位置: 首页 > news >正文

Diving into the HAL-----Interrupts

        硬件管理就是处理异步事件。其中大部分来自硬件外围设备。例如,计时器达到配置的 period 值,或者 UART 在数据到达时发出警告。

        中断是一个异步事件,它会导致按优先级停止执行当前代码(中断越重要,其优先级越高;这将导致优先级较低的中断被挂起)。为中断提供服务的代码称为 Interrupt Service Routine (ISR)。

        中断可以由硬件和软件本身产生。ARM 架构区分了两种类型:由硬件发起的中断,由软件发起的异常(例如,访问无效的内存位置)。在 ARM 术语中,中断是一种异常。Cortex-M 处理器提供了一个专门用于异常管理的单元。这称为嵌套向量中断控制器 (NVIC)。

1、NVIC中断控制器

        NVIC 是基于 Cortex-M 的微控制器内部的专用硬件单元,负责异常处理。下图是NVIC 单元、处理器内核和外设之间的关系。必须区分两种类型的外设:CortexM 内核外部同时也是STM32 MCU 内部的外设(例如定时器、UARTS 等),以及 MCU 外部的外设(中断源是 MCU I/O)。

       ARM 区分源自 CPU 内核内部的系统异常和来自外部外设的硬件异常,也称为中断请求 (IRQ)。程序员只需要使用特定的 ISR 管理异常(通常使用 C 语言编写)。处理器知道在哪里找到这些例程,这要归功于一个包含 Interrupt Service Routines 内存中地址的间接表。这个表通常称为矢量表,每个 STM32 微控制器都定义了自己的表。异常类型如下:

        向量表包含处理程序例程的地址(实际上是一个间接表),Cortex-M 内核需要一种方法来在内存中查找向量表。按照惯例,在所有基于 Cortex-M 的处理器中,向量表都从硬件地址 0x0000 0000 开始。如果我们的固件设计为矢量表驻留在内部闪存中(一种非常常见的情况),那么矢量表将从所有 STM32 MCU 中的 0x0800 0000 地址开始放置。然而,在第 1 章中,我们看到当 CPU 启动时,0x0800 0000 地址会自动别名到 0x0000 0000。

2、使能中断

        当 STM32 MCU 启动时,默认情况下仅启用 Reset、NMI 和 Hard Fault 异常。其余的异常和外设中断被禁用,并且必须根据请求启用它们。
        为了启用 IRQ,CubeHAL 提供了以下功能:

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);

        其中 IRQn_Type 是为该特定 MCU 定义的所有异常和中断的枚举。IRQn_Type 枚举是 ST 驱动程序 HAL 的一部分,它在 stm32XXxx.h头文件中定义。

        需要注意的是,上面两个函数在 NVIC 控制器级别启用/禁用中断。从前面中可以看出,连接到该线路的外设断言了一条中断线路。例如,USART2 外设断言与 NVIC 控制器内部的 USART2_IRQn 中断线相对应的中断线。这意味着必须正确配置单个外设才能在中断模式下工作。大多数 STM32 外设都设计为在中断模式下工作。通过使用特定的 HAL 例程,我们可以在外设级别启用中断。例如,使用 HAL_USART_Transmit_IT(), 我们在中断模式下隐式配置 USART 外设。显然,还需要通过调用 HAL_NVIC_EnableIRQ() 在 NVIC 级别启用相应的中断。

2.1、中断线

        如下图,所有 Px0 引脚都连接到 EXTI0,所有 Px10 引脚都连接到 EXTI10,所有 Px15 引脚都连接到 EXTI15。但是,EXTI 10 和 15 线路在 NVIC 内共享相同的 IRQ(因此由相同的 ISR 提供服务)。

例如,PC13按键手动控制PA5 LED闪烁:

int main(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};/* MCU Configuration----------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* GPIOA and GPIOC Configuration----------------------------------------------*//* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin : PC13 */GPIO_InitStruct.Pin = GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;GPIO_InitStruct.Pull = GPIO_PULLDOWN;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);/*Configure GPIO pin : PA5 */GPIO_InitStruct.Pin = GPIO_PIN_5;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);/* EXTI interrupt init*/HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);while (1);
}void EXTI15_10_IRQHandler(void) {if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) {__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);}
}

        首先,GPIO PC13 配置为每次从低电平变为高电平时触发中断。这是通过设置 GPIO 来实现的。Mode 设置为 GPIO_MODE_IT_RISING。

        接下来,启用与 Px13 引脚关联的 EXTI 线的中断,即 EXTI15_10_IRQn。

        最后,定义函数 void EXTI15_10_IRQHandler(),这是与向量表中 EXTI15_10  IRQ 关联的 ISR 例程。ISR 的内容非常简单。由于 EXTI15_10 线连接到不同的引脚,检查 PC13 是否是触发中断的引脚。如果是这样,切换 PA5 I/O 并清除与 EXTI 线关联的pending处理位。

        幸运的是,ST HAL 提供了一种抽象机制,除非我们需要处理它们,否则我们无需处理所有这些细节。前面的例子可以按以下方式重写:

int main(void) {GPIO_InitTypeDef GPIO_InitStruct = {0};/* MCU Configuration----------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* GPIOA and GPIOC Configuration----------------------------------------------*//* GPIO Ports Clock Enable */__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin : PC13 */GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;GPIO_InitStruct.Pull = GPIO_PULLDOWN;HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);/*Configure GPIO pin : PA5 */GPIO_InitStruct.Pin = GPIO_PIN_5;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);/* EXTI interrupt init*/HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);while (1);
}void EXTI15_10_IRQHandler(void) {HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {if(GPIO_Pin == GPIO_PIN_13)HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, SET);else if(GPIO_Pin == GPIO_PIN_12)HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, RESET);
}

        这次我们将引脚 PC13 和 PC12 都配置为中断源。当调用 EXTI15_10_IRQHandler() ISR 时,我们将控制权转移到 HAL 内的 HAL_GPIO_EXTI_IRQHandler() 函数。这将为我们执行所有与中断相关的活动,并且它将调用 HAL_GPIO_EXTI_Callback() 例程,该例程传递生成 IRQ 的实际 GPIO(请记住,PC12 和 PC13 连接到同一条外线中断线)。

        下图清楚地显示了从 IRQ生成的调用序列。HAL 中几乎所有的 IRQ 处理程序例程都使用这种机制。

2.2、使用 CubeMX 启用中断

        CubeMX 会自动将已启用的 ISR 添加到 Core/Src/stm32XXxx_it.c 文件中,并负责启用 IRQ。此外,它还为我们添加了要调用的相应 HAL 处理程序例程,如下所示:

/**
* @brief This function handles EXTI line[15:10] interrupts.
*/
void EXTI15_10_IRQHandler(void) {/* USER CODE BEGIN EXTI15_10_IRQn 0 *//* USER CODE END EXTI15_10_IRQn 0 */HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);/* USER CODE BEGIN EXTI15_10_IRQn 1 *//* USER CODE END EXTI15_10_IRQn 1 */
}

我们只需要在应用程序代码中添加相应的回调函数(例如 HAL_GPIO_EXTI_Callback() 例程)。

        注意:stm32XX_hal_cortex.c 模块清楚地展示了 ST HAL 和 CMSIS 包之间的交互,因为它完全依赖官方的 ARM 包来处理底层的 Cortex-M NVIC 控制器。每个 HAL_NVIC_xxx() 函数都是相应 CMSIS NVIC_xxx() 函数的包装。这意味着我们可以使用 CMSIS API 对 NVIC 控制器进行编程。

3、中断生命周期

        一旦处理了中断,了解它们的生命周期就非常重要了。尽管 Cortex-M 内核会自动为我们完成大部分工作,但我们必须注意在中断管理过程中可能造成混淆的某些方面。

        中断可以:

        1. 禁用 (默认行为) 或启用:我们调用 HAL_NVIC_EnableIRQ()/HAL_NVIC_DisableIRQ()函数启用/禁用它;

        2. 处于待处理状态(请求正在等待送达)或未待处理;

        3. 处于 Active (正在服务) 或 Inactive 状态。

        我们已经在上一段中看到过第一种情况。现在,研究发生中断时会发生什么很重要。当中断触发时,它会被标记为 pending (挂起),直到处理器可以处理它。如果当前没有其他中断正在处理,则处理器会自动清除其 pending 状态,处理器几乎立即开始为其提供服务。

        上图显示了其工作原理。中断 A 在时间 t0 触发,由于 CPU 没有为另一个中断提供服务,因此其待处理位被清除,其执行立即开始⁸(中断变为活动状态)。在时间 t1 触发 B 中断,但在这里我们假设它的优先级低于 A。因此,它将处于待处理状态,直到 A ISR 结束其操作。发生这种情况时,待处理位会自动清除,ISR 变为活动状态。

        上图显示了另一个重要情况。在这里,我们发现 A 中断触发,CPU 可以立即为它提供服务。中断 B 在 A 提供服务时触发,因此它保持待处理状态,直到 A 完成。发生这种情况时,B interrupt 的 pending bit 将被清除,并且它变为活动状态。但是,一段时间后,A 中断再次触发,由于它具有更高的优先级,因此 B 中断被暂停(变为非活动状态),并立即开始执行 A。完成此操作后,B 中断将再次变为活动状态,并完成其作业。


http://www.mrgr.cn/news/61332.html

相关文章:

  • AutoDIR: Automatic All-in-One Image Restoration with Latent Diffusion论文阅读笔记
  • 线上Bug排查清单,测试小哥拿走不谢!
  • Docker快速安装Grafana
  • 2807. 在链表中插入最大公约数 辗转相除和BigDecimal自带求公约数实现
  • Docker Compose一键部署Spring Boot + Vue项目
  • IDEA使用Maven Helper查看整个项目的jar冲突
  • Javaee:单例模式
  • linux 查看磁盘和内存的使用情况
  • 大模型提示词简介 举例
  • VBA技术资料MF221:删除给定工作簿的指定模块
  • Java-I/O框架06:常见字符编码、字符流抽象类
  • 论文学习 | 《锂离子电池健康状态估计及剩余寿命预测研究》
  • DBeaver如何导出insert的sql数据
  • 配合数据库进行网页的动态数据上传
  • 四款图片编辑软件,P图更轻松
  • 芯片固件加密方式
  • 晓羽扫码点餐快销版系统源码
  • 简易SQL注入原理及注入失败原因
  • k8s 二进制部署安装(三)
  • 玉石渲染用什么渲染软件最好?单品渲染用哪个软件?