【STM32】HAL库-嵌套向量中断控制器NVIC

中断与异常

嵌套向量中断控制器,简称为NVIC,其支持为数众多的系统异常和外部中断。

Cortex-M3支持256个中断。

中断/异常类型表如下
编号为 1-15 的称为系统异常(注意:没有编号为 0 的异常),大于等于 16 的称为(外部)中断。
除了个别异常的优先级被定死外,其它中断/异常的优先级都是可编程的
在这里插入图片描述
在这里插入图片描述
外部中断表如下
在这里插入图片描述

优先级

  • 在 CM3 中,优先级对于异常来说很关键的,它会决定一个异常是否能被掩蔽,以及在未掩蔽的情况下何时可以响应。优先级的数值越小,则优先级越高。

  • CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。

  • 有3个系统异常的优先级是固定的,并且是负数,分别为:复位,NMI 以及硬 fault

  • 所有其它异常的先级则都是可编程的((但不能被编程为负数)

CM3 支持 3 个固定的高优先级(复位,NMI 以及硬 fault)和多达 256 级的可编程优先级,并且支持 128 级抢占式优先级
256级对应一个字节的大小,8位二进制,也就是每一个可编程的中断/异常有一个至少一个字节大小的寄存器来配置其优先级。并且是以 MSB 对齐的。
CM3 允许的最少使用位数为 3 个位,亦即至少要支持 8 级优先级

如使用3个位来定义优先级,则只有最高3位有效。
在这里插入图片描述

优先级优先级设置寄存器的值
00000 0000
10010 0000
20100 0000
30110 0000
71110 0000

优先级分组

为了使抢占机能变得更可控,CM3 还把 256 级优先级按位分成高低两段,分别称为抢占优先级子优先级

通过应用程序中断及复位控制寄存器(AIRCR)地址:0xE000_ED00来设置抢占式优先级与子优先级占用的位数。
在这里插入图片描述在这里插入图片描述
PRIGROUP的取值
在这里插入图片描述
子优先级至少是 1 个位。因此抢占优先级最多是 7 个位,造成了最多只有 128 级抢占的现象。

如使用最高3位来表达优先级,优先级分组选择5,则抢占式优先级用2个位表达,剩下的一个位则是子优先级。
在这里插入图片描述

使用最高3位来表达优先级,优先级分组选择1,则抢占式优先级占用3位,子优先级无效
在这里插入图片描述
如果两个中断的抢占式优先级和子优先级都相同,则先响应异常编号最小的那一个,例如,当 IRQ #3 的优先级与 IRQ #5 的优先级相等时,IRQ #3 会比 IRQ #5 先得到响应。

向量表

当发生中断或者异常时,需要定位其服务例程的入口地址(中断复位函数的入口地址)这些入口地址存储在所谓的“向量表”中。
也就是说向量表存储的是一个个函数的地址。默认情况下,,CM3 认为该表位于零地址处,且各向量占用 4 字节。
在这里插入图片描述
CM3支持动态重分发中断,CM3 允许向量表重定位——从其它地址处开始定位各异常向量。
为了实现这个功能,NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址 0xE000_ED08 处),通过修改它的值就能重定位向量表。
在这里插入图片描述

中断的触发与悬起(挂起)

当中断发生时候,该中断就被悬起。即使后来中断源撤消了中断请求,
已经被标记成悬起的中断也被记录下来。到了系统中它的优先级最高的时候,就会得到响应

悬起被置位:告诉CPU,有一个中断触发了,当该中断优先级最高时候,CPU将会根据向量表找到中断服务函数的入口地址,从而处理中断,当某中断的服务例程开始执行时,就称此中断进入了“活跃”状态,并且其悬起位会被硬件自动清除。

在这里插入图片描述
但是如果在某个中断得到响应之前,其悬起状态被清除了,则中断被取消。
在这里插入图片描述

如果中断源咬住请求信号不放,该中断就会在其上次服务例程返回后再次被置为悬起状态
在这里插入图片描述

Fault 类异常

CM3 中的 Faults 可分为以下几类:

  • 总线 faults
  • 存储器管理 faults
  • 用法 faults

每个faults都有其对应的faults状态寄存器,用来指示出错的原因与类型。

更多细节请看CM3权威指南。

NVIC

嵌套向量中断控制器(NVIC)

NVIC 的访问地址是 0xE000_E000。所有 NVIC 的中断控制/状态寄存器都只能在特权级下访问,仅软件触发中断寄存器可以在用户级下访问以产生软件中断。

每个外部中断都有在 NVIC 的下列寄存器

  • 使能与除能(失能)寄存器
  • 悬起与“解悬”寄存器
  • 优先级寄存器
  • 活动状态寄存器(中断是否正在被CPU响应)

使能与除能(失能)寄存器

  • SETENA(使能)/CLRENA(失能) 可以按字/半字/字节的方式来访问
  • 32 位寄存器,写1使能/失能,写0无效
  • CM3 中可以有 240 对使能位/除能位(SETENA 位/CLRENA 位),每个中断拥有一对。这 240 个对子分布在 8 对 32 位寄存器中(最后一对没有用完)
    在这里插入图片描述

悬起与解悬寄存器

如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。

中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄存器(CLRPEND)”来读取,还可以写它们来手工悬起中断。

  • 其用法与前面介绍的使能/除能寄存器完全相同

在这里插入图片描述

中断优先级寄存器

  • 每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位。
  • 优先级寄存器都可以按字节访问,当然也可以按半字/字来访问
  • 根据优先级组的设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级,
  • 通过应用程序中断及复位控制寄存器(AIRCR)地址:0xE000_ED00来设置抢占式优先级与子优先级占用的位数。
    在这里插入图片描述
    系统异常的优先级设置
    在这里插入图片描述

活动状态

每个外部中断都有一个活动状态位。在处理器执行了其 ISR(中断服务函数) 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。
哪怕中断被抢占,其活动状态也依然为 1。

  • 按字/半字/字节访问,但是只读的

在这里插入图片描述

在这里插入图片描述

软件中断

软件触发中断请求
在这里插入图片描述

STM32的中断

以STM32F103C8T6为例

  • 68个可屏蔽中断通道(不包含16个Cortex™-M3的中断线);
  • 16个可编程的优先等级(使用了4位来表示中断优先级);
    在这里插入图片描述

下面介绍HAL库的常用中断相关函数
其他的函数请查看stm32f1xx_hal_cortex.c文件

中断优先级分组

/**
  * @brief  Sets the priority grouping field (preemption priority and subpriority)
  *         using the required unlock sequence.
  * @param  PriorityGroup: The priority grouping bits length. 
  *         This parameter can be one of the following values:
  *         @arg NVIC_PRIORITYGROUP_0: 0 bits for preemption priority
  *                                    4 bits for subpriority
  *         @arg NVIC_PRIORITYGROUP_1: 1 bits for preemption priority
  *                                    3 bits for subpriority
  *         @arg NVIC_PRIORITYGROUP_2: 2 bits for preemption priority
  *                                    2 bits for subpriority
  *         @arg NVIC_PRIORITYGROUP_3: 3 bits for preemption priority
  *                                    1 bits for subpriority
  *         @arg NVIC_PRIORITYGROUP_4: 4 bits for preemption priority
  *                                    0 bits for subpriority
  * @note   When the NVIC_PriorityGroup_0 is selected, IRQ preemption is no more possible. 
  *         The pending IRQ priority will be managed only by the subpriority. 
  * @retval None
  */
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */
  NVIC_SetPriorityGrouping(PriorityGroup);
}

设置中断的抢占式优先级与子优先级

/**
  * @brief  Sets the priority of an interrupt.
  * @param  IRQn: External interrupt number.
  *         This parameter can be an enumerator of IRQn_Type enumeration
  *         (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xx.h))
  * @param  PreemptPriority: The preemption priority for the IRQn channel.
  *         This parameter can be a value between 0 and 15
  *         A lower priority value indicates a higher priority 
  * @param  SubPriority: the subpriority level for the IRQ channel.
  *         This parameter can be a value between 0 and 15
  *         A lower priority value indicates a higher priority.          
  * @retval None
  */
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
{ 
  uint32_t prioritygroup = 0x00U;
  
  /* Check the parameters */
  assert_param(IS_NVIC_SUB_PRIORITY(SubPriority));
  assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority));
  
  prioritygroup = NVIC_GetPriorityGrouping();
  
  NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));
}

使能中断

/**
  * @brief  Enables a device specific interrupt in the NVIC interrupt controller.
  * @note   To configure interrupts priority correctly, the NVIC_PriorityGroupConfig()
  *         function should be called before. 
  * @param  IRQn External interrupt number.
  *         This parameter can be an enumerator of IRQn_Type enumeration
  *         (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))
  * @retval None
  */
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
  /* Check the parameters */
  assert_param(IS_NVIC_DEVICE_IRQ(IRQn));

  /* Enable interrupt */
  NVIC_EnableIRQ(IRQn);
}

失能中断

/**
  * @brief  Disables a device specific interrupt in the NVIC interrupt controller.
  * @param  IRQn External interrupt number.
  *         This parameter can be an enumerator of IRQn_Type enumeration
  *         (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))  
  * @retval None
  */
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
{
  /* Check the parameters */
  assert_param(IS_NVIC_DEVICE_IRQ(IRQn));

  /* Disable interrupt */
  NVIC_DisableIRQ(IRQn);
}

清除中断的挂起位

/**
  * @brief  Clears the pending bit of an external interrupt.
  * @param  IRQn External interrupt number.
  *         This parameter can be an enumerator of IRQn_Type enumeration
  *         (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))  
  * @retval None
  */
void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
  /* Check the parameters */
  assert_param(IS_NVIC_DEVICE_IRQ(IRQn));

  /* Clear pending interrupt */
  NVIC_ClearPendingIRQ(IRQn);
}

STM32CubeMX中的中断优先级配置

在这里插入图片描述

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个完整的使用STM32G4HAL库实现串口空闲中断接收不定长数据的代码,供您参考: 首先,需要在CubeMX中配置串口和中断。在串口配置中,需要开启空闲中断,并设置接收缓冲区大小。在中断配置中,需要开启串口空闲中断,并设置中断优先级。 接下来是代码实现: ```c #include "main.h" #include "stm32g4xx_hal.h" #define RX_BUF_SIZE 64 // 接收缓冲区大小 UART_HandleTypeDef huart2; uint8_t rx_buf[RX_BUF_SIZE]; // 接收缓冲区 uint8_t rx_len = 0; // 接收数据长度 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART2) // 判断是哪个串口触发的中断 { if(rx_len < RX_BUF_SIZE) // 判断是否超出接收缓冲区大小 { rx_buf[rx_len++] = (uint8_t)(huart->Instance->RDR & 0xFF); // 读取接收数据 } else { rx_len = 0; // 超出接收缓冲区大小,清空缓冲区 } } HAL_UART_Receive_IT(&huart2, (uint8_t *)&rx_buf[rx_len], 1); // 重新开启接收中断 } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART2_UART_Init(); HAL_UART_Receive_IT(&huart2, (uint8_t *)&rx_buf[rx_len], 1); // 开启接收中断 while (1) { if(rx_len > 0) // 判断是否接收到数据 { // 处理接收到的数据,例如打印到串口或处理指令等操作 HAL_UART_Transmit(&huart2, rx_buf, rx_len, HAL_MAX_DELAY); // 回传接收到的数据 rx_len = 0; // 清空接收缓冲区 HAL_UART_Receive_IT(&huart2, (uint8_t *)&rx_buf[rx_len], 1); // 重新开启接收中断 } } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 4; RCC_OscInitStruct.PLL.PLLN = 32; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) { Error_Handler(); } } ``` 代码中使用了串口空闲中断,当接收到完整的数据包后会触发中断,然后在中断回调函数中读取接收缓冲区的数据并清空缓冲区,同时重新开启接收中断。在主循环中判断接收缓冲区是否有数据,如果有则进行处理,例如将数据回传到串口或进行其他操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值