4. STM32-NVIC 外部中断EXTI

NVIC介绍

NVIC(Nested vectored interrupt controller)为嵌套向量中断控制器,当中每个中断的优先级都是用暂存器当中的8 bit来做设置,也就是2^8=256,可以支持到256个中断。但实际上在使用时仅使用高四位[7:4]来做设定,低四位为0,表示支援2^4=16级中断。
https://ithelp.ithome.com.tw/upload/images/20220310/20146325CO8fXD0lZo.png
在NVIC当中分为两个优先级分组分别是Preemption Priority(主)与Sub Priority(子),字面上来看可以知道Preemption Priority比Sub Priority优先权来的高,在同一个优先级上数字越小越高。
有接触过8051应该会比较好理解暂存器的设定,8051中也有中断优先暂存器IP,可以依照bit去做中断优先权的设定!

设备 Preemption Priority Sub Priority
A 1 2
B 2 1

上表可以看到当B遇到中断时,由於A的主优先级较高,这时候B会立即停止。等待A执行完毕後再轮到B执行。

设备 Preemption Priority Sub Priority
A 1 2
B 1 1

当两设备Preemption Priority相同时,这时候就依照先後顺序来决定谁先执行。
但当A与B同时到来时,这时候会先比较Preemption Priority发现两者相同时,接着比较Sub Priority来决定何者先行执行。

优先级分组 Preemption Priority Sub Priority
NVIC_PriorityGroup_0 0 0~15
NVIC_PriorityGroup_1 0,1 0~7
NVIC_PriorityGroup_2 0~3 0~3
NVIC_PriorityGroup_3 0~7 0,1
NVIC_PriorityGroup_4 0~15 0

如何设定ioc档

进入到.ioc档後左侧选择Sydtem Core 当中的NVIC可以看到中断的设定~
https://ithelp.ithome.com.tw/upload/images/20220310/20146325ad7cvPlAIv.png
在上方的Priority Group 则是上方所提到的中断分组,点开可以看到分成五组,每组须按照规定给予中断分级。
https://ithelp.ithome.com.tw/upload/images/20220310/20146325GWqtz0yBuD.png
可以看到从最上方的是分组0最下方则是分组4,以分组0为例可以看到它是0 bits for pre-emption priority 4 bits for sub priority,意思也就是pre-emption priority只能设置为0,而sub priority可以设置0-15(2^4 = 16)。

  1. 要使用外部中断首先要先将脚位设定为GPIO_EXTIx
    https://ithelp.ithome.com.tw/upload/images/20220310/20146325Pmh2AVOsu6.png
  2. 将NVIC当中刚刚所选的脚位中断开启
    https://ithelp.ithome.com.tw/upload/images/20220310/20146325aOlaYBk4t8.png
    接着可以点回GPIO的介面并点选刚刚所设定的中断脚位,下方会出现可以配置的选项。
    https://ithelp.ithome.com.tw/upload/images/20220310/20146325kKrJ92X0xX.png
    第一个GPIO Mode可以设置中断的触发时机
    External Interrupt Mode with Rising edge trigger detection:上升缘触发~低电位变为高电位时会出发。
    External Interrupt Mode with Falling edge trigger detection:下降缘触发 ~高电位变为低电位时触发
    External Interrupt Mode with Rising/Falling edge trigger detection:上升与下降时都会触发
    下方三个当发生时会设置中断旗标,但不会产生中断
    External Event Mode with Rising edge trigger detection:上升缘触发
    External Event Mode with Falling edge trigger detection:下降缘触发
    External Event Mode with Rising/Falling edge trigger detection:上升与下降时都会触发

事件: 当检测到一个动作触发事件发生时,由硬体自动完成触发到解决的状况 。
中断: 当有某个事件发生并触发中断後,由CPU介入跳到中断服务常式中执行。

(中断有可能被优先权更高的中断抢先,而事件不会。事件可以在不需要CPU介入时执行动作)
第二个GPIO Pull-up/Pull-down可以看看上一篇的介绍,默认设置为No pull-up and no pull-down
第三个User Label可以设定比较好区分的名称


外部中断暂存器介绍

STM32的EXTI有20个中断/事件线,每个GPIO都可以设为一个中断(GPIO_0 ~ GPIO_15),另外4个为特殊用途。
同样也可以直接透过暂存器去对中断做设定,步骤大上至上如下:

硬体中断

  1. 设置20个中断线的屏蔽位→ EXTI_IMR
  2. 设置选定中断线的触发条件 → EXTI_RTSR EXTI FTSR
  3. 设置对应外部中断的NVIC的致能和屏蔽位。

硬体事件

  1. 设置20个事件线的屏蔽位 → EXTI_EMR
  2. 设置选定事件线的触发条件 → EXTI_RTSR EXTI FTSR

软件中断/事件

  1. 设置20个中断/事件线的屏蔽位 → EXTI_IMR EXTI_EMR
  2. 设置软件中断暂存器的请求位 EXTI_SWIER

这边说一下硬体中断与软件中断的区别:

  1. 硬体中断 : 由连接的外部设备产生的,当发生时可以直接触发中断,其余正在执行程序的暂时停止,直到中断结束。(会因为优先权而决定谁先执行) EX:按钮中断、定时器中断
  2. 软件中断: 由执行中断指令去产生的(不会有抢占的问题) EX: 除数为0、常用的逐行执行程序

AFIO_EXTICRx : 控制20条中断输入线,一共有4个暂存器分别控制0-3、4-7、8-11、12-15。
https://ithelp.ithome.com.tw/upload/images/20220310/20146325cIb6IQLJXu.png
EXTI_IMR : 只有当对应的位设定为1时才会产生中断,0-19共20条中断线。
https://ithelp.ithome.com.tw/upload/images/20220310/201463253cKtC2fKEx.png
EXTI_EMR :同上,只有设置为1才会产生事件
https://ithelp.ithome.com.tw/upload/images/20220310/20146325AaATKqflNW.png
EXTI_RTSR : 触发中断条件,这边是选择触发时是由低电位转为高电位时触发。
https://ithelp.ithome.com.tw/upload/images/20220310/20146325i826WCIqX0.png
EXTI_FTSR : 同上为触发条件,选择触发时是由高电位转为低电位时触发
https://ithelp.ithome.com.tw/upload/images/20220310/20146325nBXrRw8p2B.png
资料来源:STM32中文技术手册


函数介绍

下方是STM32xxxx_HAL_Driver中GPIO的定义,可以看到外部中断的回调函数。前面开头关键字是弱定义的意思,假如其他地方定义过了会优先选择。
__weak的介绍可以看连结这篇
https://ithelp.ithome.com.tw/upload/images/20220310/20146325P984IYdOEN.png
函数库当中所定义的回调函数,用在当触发了外部中断後要做什麽事情,像是8051当中的中断服务常式~ 两者的目的是一样的。

//8051 中断服务常式
void EXIT_ISR(void) interrupt 2 //外部中断向量为 2
{
   .......
}
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);

  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
}

HAL库当中还有个函数是用来检查中断旗标有没有被设定,如果有被设定了会清除中断旗标,之後继续呼叫上方的回调函数执行要做的事情!

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}
//使用方法
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_x);

板载LED灯外部中断

我的R476LG开发版上有颗蓝色的按钮已经接好线了,不用在外接线去做外部中断,可以直接利用这颗按钮做测试~一样查找DataSheet可以找到这颗按钮的连接脚位(B1 USER)。
https://ithelp.ithome.com.tw/upload/images/20220310/20146325clDZ3UDJqz.png
结合上一篇所提到的板载LED灯(LD2)可以去做LED灯模式的变化,LD2的脚位是PA5。
首先先找到main.c档下方的/* USER CODE BEGIN 4 / / USER CODE END 4 */ 将中断回调函数写在这,记得先在最上放宣告全域变数 i。

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_13)
	{
		i++;
	}
}
/* USER CODE END 4 */

接着可以到while(1)当中去写灯的模式变化,透过按下按钮对i+1,当i/2的余数为0时做第一种灯的变化,不为0时则做第二种变化。也可以在现场表达式中加入 i 去观察变数的变化,是不是跟程序所写的一样!

while (1)
{
	 if(i%2==0)
	 {
		  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, SET);
	  	HAL_Delay(1000);
	  	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, RESET);
	  	HAL_Delay(500);
	 }
	 else
	 {
	  	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	  	HAL_Delay(50);
	 }
 }

以上内容如果有误的话,麻烦各位通知我。感谢~


<<:  【JavaScript】变数

>>:  Lotus Notes to office 365

鬼故事 - 印表机最终还是挂了

鬼故事 - 印表机最终还是挂了 https://twitter.com/System32Comics...

Day 23 Flux

第 23 天 ! 使用 context 来传送,虽然达到了能跟 目标 component 直接对接的...

【Day 08】List 介绍!

前言 list 是 Python 中最常见的资料类型,有许多的应用都会用到 list 喔! 今天会先...

EAP-TLS身份验证协议最能支持零信任原则

-零信任网路安全范式 EAP-TLS、EAP-TTLS 和 PEAP 是 WPA2 中使用的合法身...

[DAY27]将Line讯息存入资料库(01)

再来就是我们要利用Line来记录我们的资料了,以下程序码我放在一个新增程序档 import psyc...