【STM32】中断
目录
- 1. 中断相关概念
- 1.1 什么是中断?
- 1.2 中断的意义
- 1.3 中断优先级
- 1.4 中断嵌套
- 1.5 中断执行流程
- 2. STM32中断
- 2.1 STM32F103C8T6中断数量
- 2.2 中断向量表
- 2.3 STM32中断框图
- 3. NVIC
- 3.1 NVIC介绍
- 3.2 NVIC工作原理
- 3.3 中断优先级基本概念
- 3.4 NVIC寄存器
- 3.5 NVIC相关函数介绍
- 3.6 NVIC配置方法
- 4. EXTI
- 4.1 EXTI简介
- 4.2 中断/事件
- 4.3 EXTI基本结构
- 4.4 EXTI框图
- 4.5 EXTI寄存器
- 4.6 EXTI相关函数介绍
- 5. AFIO
- 5.1 AFIO简介
- 5.2 AFIO与IO口对应关系
- 5.3 AFIO寄存器
- 5.4 AFIO相关函数介绍
- 6. EXTI配置流程
- 7. 电动车报警器项目
- 7.1 项目需求
- 7.2 硬件清单
- 7.3 硬件接线
- 7.4 项目流程图
- 7.5 项目代码
1. 中断相关概念
1.1 什么是中断?
中断是单片机正在执行程序时,由于内部或外部事件的触发,打断当前程序,转而去处理这一事件,当处理完成后再回到原来被打断的地方继续执行原程序的过程。在ARM体系结构中,中断通常由外设或外部输入产生,有时也可以由软件触发。中断是单片机系统处理紧急或突发事件的重要方式,如定时器溢出、按键输入、串口数据到达等。
1.2 中断的意义
中断的主要意义在于提高CPU的效率,而不会一直占用CPU,实现对突发事件的实时处理,以及实现程序的并行化和嵌入式系统进程之间的切换。相较于轮询方式(即按照一定的频率和周期不断地检测某些事件的发生),中断在处理一些偶然发生的事情时效率更高。
1.3 中断优先级
中断具有优先级高低之分,两个中断同时触发,则优先响应高优先级中断,再响应低优先级中断。
1.4 中断嵌套
如果一个高优先级的中断发生,它会立即打断当前正在处理的中断(如果其优先级较低),并首先处理这个高优先级的中断,这就是所谓的中断嵌套。
1.5 中断执行流程
当中断发生时,STM32的执行流程如下:首先,由外设发出中断请求;然后,处理器暂停当前执行的任务,保护现场(如将当前位置的PC地址压栈);接着,程序跳转到对应的中断服务程序(ISR)并执行;中断服务程序执行完毕后,恢复现场(如将栈顶的值送回PC);最后,处理器返回到被中断的位置,继续执行下一个指令。
2. STM32中断
2.1 STM32F103C8T6中断数量
STM32F103C8T6 支持的中断共有 70 个,其中包括 10 个内核中断和 60 个外部中断。外部中断包含 EXTI、TIM、USART、ADC、I2C、SPI 等等。
2.2 中断向量表
STM32的中断向量表是一个存储中断处理函数地址的数组,位于Flash区的起始位置。每个数组元素对应一个中断源,其地址指向相应的中断服务程序。当中断发生时,处理器会根据中断号查找向量表,然后跳转到对应的中断服务程序执行。
中断向量表的主要作用是解决中断函数地址不固定与中断必须跳转到固定地方执行程序之间的矛盾。由于编译器每次编译都会为中断函数随机分配地址,但硬件要求中断必须跳转到固定的位置,因此,中断向量表就作为这样一个固定的地址列表,其中包含了中断函数的地址以及跳转到这些地址的程序。当中断发生时,处理器会跳转到这个固定的中断向量表,然后根据其中的信息跳转到相应的中断处理函数,从而执行中断。
2.3 STM32中断框图
3. NVIC
3.1 NVIC介绍
NVIC,即Nested Vectored Interrupt Controller(嵌套向量中断控制器),是STM32中的中断控制器。它负责管理和协调处理器的中断请求,是STM32中处理异步事件的重要机制。
NVIC提供了灵活、高效、可扩展的中断处理机制,支持多级优先级、多向中断、嵌套向量中断等特性。当一个中断请求到达时,NVIC会确定其优先级并决定是否应该中断当前执行的程序,以便及时响应和处理该中断请求。这种设计有助于提高系统的响应速度和可靠性,特别是在需要处理大量中断请求的实时应用程序中。
NVIC 支持:256个中断(16内核+240外部),支持:256个优先级,允许裁剪。
3.2 NVIC工作原理
3.3 中断优先级基本概念
NVIC可以管理多个中断请求,并按优先级处理它们。在STM32中,中断优先级被划分为抢占式优先级和响应优先级,可以根据具体的应用需求进行配置。不同的优先级分组方式会影响中断的响应和处理顺序。
- 抢占优先级
如果一个中断的抢占优先级高于当前正在执行的中断,那么它可以打断当前中断,优先得到执行。数值越小,优先级越高。
- 响应优先级
如果两个中断同时到达,且它们的抢占优先级相同,那么响应优先级高的中断将首先得到响应。数值越小,优先级越高。
- 自然优先级
自然优先级是由硬件固定并预先设定的,用户无法更改。当抢占优先级和响应优先级都相同时,自然优先级将决定哪个中断先得到处理。
- 优先级执行顺序
当多个中断同时发生时,执行顺序首先由抢占优先级决定。如果抢占优先级相同,则进一步由响应优先级决定。如果响应优先级也相同,则最终由自然优先级决定。
在中断嵌套的情况下,高抢占优先级的中断可以打断低抢占优先级的中断,但高响应优先级的中断不能打断低响应优先级的中断(当它们具有相同的抢占优先级时)。
- 优先级分组
优先级寄存器 IPR 有 8 位,但实际只使用到高 4 位,用于决定抢占优先级、响应优先级的等级。具体这 4 位如何切割?由又由 AIRCR 寄存器控制。
优先级分组 | AIRCR[10:8] | 抢占优先级 | 响应优先级 |
---|---|---|---|
0 | 111 | 0位,取值为0 | 4位,取值为0~15 |
1 | 110 | 1位,取值为0~1 | 3位,取值为0~7 |
2 | 101 | 2位,取值为0~3 | 2位,取值为0~3 |
3 | 100 | 3位,取值为0~7 | 1位,取值为0~1 |
4 | 011 | 4位,取值为0~15 | 0位,取值为0 |
3.4 NVIC寄存器
寄存器名称 | 位数 | 寄存器个数 | 说明 |
---|---|---|---|
中断使能寄存器(ISER) | 32 | 8 | 每个位控制一个中断 |
中断失能寄存器(ICER) | 32 | 8 | 每个位控制一个中断 |
应用程序中断及复位控制寄存器(AIRCR) | 32 | 1 | 位[10:8]控制优先级分组 |
中断优先级寄存器(IPR) | 8 | 240 | 8个位对应一个中断,而STM32只使用高4位 |
3.5 NVIC相关函数介绍
3.6 NVIC配置方法
设置中断分组 → 设置中断优先级 → 使能中断
设置中断分组一般在 HAL_Init 函数中进行。
4. EXTI
4.1 EXTI简介
EXTI 是 External Interrupt 的缩写,表示外部中断事件控制器。EXTI 可以监测指定 GPIO 口的电平信号变化,并在检测到指定条件时,向内核的中断控制器 NVIC 发出中断申请。NVIC 在裁决后,如果满足条件,会中断CPU的主程序,使 CPU 转而执行 EXTI 对应的中断服务程序。EXTI 支持的触发方式:上升沿、下降沿、双边沿或软件触发。EXTI 支持所有的 GPIO 口,但需要注意的是,相同的 Pin 不能同时触发中断。例如,PA0 和 PB0 不能同时被配置为中断源。EXTI 提供了 16 个 GPIO_Pin 的中断线,以及额外的中断线如 PVD 输出、RTC 闹钟、USB 唤醒和以太网唤醒。通过适当的配置,EXTI可以实现丰富多样的功能,如响应按键的按下、传感器的状态变化等外部事件。
4.2 中断/事件
中断会打断CPU当前正在执行的程序,转而去执行中断服务程序,待中断服务程序执行完毕后,CPU会返回到原来的程序执行点继续执行。
事件只是简单地表示某个动作或状态的变化,而不会打断CPU当前正在执行的程序。当事件发生时,它会根据配置来决定是否触发相应的中断。如果开放了对应的中断屏蔽位,事件就可以触发相应的中断,否则事件只会作为一个信号存在,不会被CPU处理。
4.3 EXTI基本结构
4.4 EXTI框图
4.5 EXTI寄存器
4.6 EXTI相关函数介绍
5. AFIO
5.1 AFIO简介
AFIO 是 Alternate Function Input/Output 的缩写,表示复用功能 IO,主要用于实现 I/O 端口的复用功能以及外部中断的控制。
STM32上有很多 I/ O口以及内置外设(如I2C、ADC、ISP、USART等)。为了节省引出管脚的数量,这些内置外设通常与 I/O 口共用管脚,即 I/O 管脚具有复用功能。例如,一个 GPIO 管脚除了可以作为普通的 I/O端口外,还可以被复用为某个内置外设的功能引脚。
然而,为了优化64脚或100脚封装的外设数量,有时需要将一些复用功能重新映射到其他引脚上。这时,就可以使用AFIO的复用重映射功能。通过设置复用重映射和调试I/O配置寄存器(AFIO_MAPR),可以实现引脚的重新映射,使得复用功能不再映射到它们的原始分配上。
此外,AFIO 还用于控制外部中断,用来配置 EXTI 中断线 0~15 对应哪个具体 IO 口。
当需要使能外部中断线或进行外部中断线的映射时,通常需要开启AFIO的时钟。
5.2 AFIO与IO口对应关系
5.3 AFIO寄存器
5.4 AFIO相关函数介绍
6. EXTI配置流程
7. 电动车报警器项目
7.1 项目需求
点击遥控器 A 按键,系统进入警戒模式,一旦检测到震动(小偷偷车),则喇叭发出声响报警,吓退小偷。点击遥控器 B 按键,系统退出警戒模式,再怎么摇晃系统都不会报警,否则系统一直发出尖叫,让车主尴尬。
7.2 硬件清单
振动传感器
继电器
高功率喇叭
433M无线接收发射模块
杜邦线
STM32
ST-Link
7.3 硬件接线
7.4 项目流程图
7.5 项目代码
main.c
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "exti.h"
#include "alarm.h"int main(void)
{HAL_Init(); /* 初始化HAL库 */stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */led_init(); /* 初始化LED灯 */exti_init(); /* 初始化EXTI */alarm_init();uint8_t alert_mode = FALSE;while(1){ //A按键是否被按下if(buttonA_flag_get() == TRUE){ //如果检测到A键按下alarm_on();delay_ms(2000);alarm_off();alert_mode = TRUE;}//B按键是否被按下if(buttonB_flag_get() == TRUE){ //如果检测到B键按下if(alarm_status_get() == ALARM_STATUS_ON){alarm_off();}else{alarm_on();delay_ms(1000);alarm_off();}alert_mode = FALSE;} //如果处于警戒模式if(alert_mode == TRUE){if(vibrate_flag_get() == TRUE){alarm_on();}}else{vibrate_flag_set(FALSE);}}
}
exit.c
#include "exti.h"
#include "sys.h"
#include "delay.h"
#include "led.h"uint8_t buttonA_flag = FALSE; // 按键A按下的标志位
uint8_t buttonB_flag = FALSE; // 按键B按下的标志位uint8_t vibrate_flag = FALSE;void exti_init(void)
{GPIO_InitTypeDef gpio_initstruct;//打开时钟__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟__HAL_RCC_GPIOB_CLK_ENABLE();//按键B中断配置gpio_initstruct.Pin = GPIO_PIN_12; // 按键B对应的引脚gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发gpio_initstruct.Pull = GPIO_PULLDOWN; // 下拉HAL_GPIO_Init(GPIOA, &gpio_initstruct);HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0); // 设置EXTI15_10中断线的优先级HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); // 使能中断//按键A中断配置gpio_initstruct.Pin = GPIO_PIN_5; // 按键A对应的引脚gpio_initstruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发gpio_initstruct.Pull = GPIO_PULLDOWN; // 下拉HAL_GPIO_Init(GPIOB, &gpio_initstruct);HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0); // 设置EXTI9_5中断线的优先级HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // 使能中断//震动传感器终端配置gpio_initstruct.Pin = GPIO_PIN_4; //震动传感器引脚gpio_initstruct.Mode = GPIO_MODE_IT_FALLING; //输入信号下降沿检测gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; //上拉gpio_initstruct.Pull = GPIO_PULLUP; //高速HAL_GPIO_Init(GPIOA, &gpio_initstruct);HAL_NVIC_SetPriority(EXTI4_IRQn,2,0); //设置中断线为0,抢占优先级2,响应优先级0HAL_NVIC_EnableIRQ(EXTI4_IRQn); //使能中断
}void EXTI4_IRQHandler(void)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
}void EXTI15_10_IRQHandler(void)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
}void EXTI9_5_IRQHandler(void)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
}void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{//delay_ms(20);if (GPIO_Pin == GPIO_PIN_12) //检测到B键按下{if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == GPIO_PIN_SET)//led2_toggle();buttonB_flag = TRUE;}else if (GPIO_Pin == GPIO_PIN_5) //检测到A键按下{if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET)//led1_toggle();buttonA_flag = TRUE;}else if(GPIO_Pin == GPIO_PIN_4){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_4) == GPIO_PIN_RESET){//led1_toggle();vibrate_flag = TRUE;}}
}uint8_t vibrate_flag_get(void)
{return vibrate_flag;
}void vibrate_flag_set(uint8_t value)
{vibrate_flag = value;;
}uint8_t buttonA_flag_get(void)
{uint8_t temp = buttonA_flag;buttonA_flag = FALSE;return temp;
}void buttonA_flag_set(uint8_t value)
{buttonA_flag = value;
}uint8_t buttonB_flag_get(void)
{uint8_t temp = buttonB_flag;buttonB_flag = FALSE;return temp;
}void buttonB_flag_set(uint8_t value)
{buttonB_flag = value;
}
exit.h
#ifndef __EXTI_H__
#define __EXTI_H__#include "stdint.h"#define TRUE 1
#define FALSE 0void exti_init(void);
uint8_t buttonA_flag_get(void);
void buttonA_flag_set(uint8_t value);
uint8_t buttonB_flag_get(void);
void buttonB_flag_set(uint8_t value);
uint8_t vibrate_flag_get(void);
void vibrate_flag_set(uint8_t value);#endif
alarm.c
#include "alarm.h"
#include "sys.h"//初始化GBIO口函数
void alarm_init(void)
{GPIO_InitTypeDef gpio_initstruct;//使能GPIOB时钟__HAL_RCC_GPIOB_CLK_ENABLE();//调用GPIO初始化函数gpio_initstruct.Pin = GPIO_PIN_7; //LED1对应引脚gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; //上拉gpio_initstruct.Pull = GPIO_PULLUP; //高速HAL_GPIO_Init(GPIOB, &gpio_initstruct);//关闭alarmHAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);
}//点亮alarm的函数
void alarm_on(void)
{HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); //拉低LED1引脚,点亮LED1
}//熄灭alarm的函数void alarm_off(void)
{HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); //拉高LED1引脚,熄灭LED1
}//翻转alarm的函数
void alarm_toggle(void)
{HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_7); //翻转LED1引脚电平
}//返回继电器的状态
uint8_t alarm_status_get(void)
{return (uint8_t)HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_7);
}
alarm.h
#ifndef __ALARM_H__
#define __ALARM_H__#include "stdint.h"#define ALARM_STATUS_ON 0
#define ALARM_STATUS_OFF 0//初始化GBIO口函数
void alarm_init(void);//点亮alarm的函数
void alarm_on(void);//熄灭alarm的函数void alarm_off(void);//翻转alarm的函数
void alarm_toggle(void);//返回继电器的状态
uint8_t alarm_status_get(void);#endif