1. 中断的概念
在主程序运行过程中,出现了特定事件,使得CPU暂停当前正在运行的程序,转而去处理这个事件,等这个事件处理完成之后,CPU再回到刚才被打断的位置继续处理,这就是中断。
那个打断CPU执行的特定事件,我们一般称之为中断源。被中断源打断的位置我们称为断点。处理特定事件的过程,我们称为执行中断处理程序。
![](https://img-blog.csdnimg.cn/img_convert/29069278f7a7b23ca08098f0fa709079.png)
2. 中断嵌套概念
正在执行中断程序的时候,这个时候有可能被另外一个中断源给中断,CPU转而去执行另外一个中断源的中断处理程序,这叫中断嵌套。
中断B能否打断中断A,要看他们的优先级,优先级高的可以打断优先级低的,优先级低的无法打断优先级高的。中断源可以是外部的,也可以是内部的。外部的叫外部中断源,内部的叫内部中断源(有时候也叫异常)。
2.1. 为什么需要中断
2.1.1. STM32的中断体系架构
STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
STM32F103系列70个中断(咱们目前使用的芯片)有10个内部中断和60个可编程的外部中断。
3. NVIC嵌套向量中断控制器
嵌套向量中断控制器管理着包括内核异常,外部中断等所有中断。由NVIC决定哪个中断的处理程序交给CPU来执行。
![](https://img-blog.csdnimg.cn/img_convert/b8ef258eabbbc475b591be06d5cf62b9.png)
3.1.1. 中断优先级
NVIC为了方便管理中断,可以通过软件给每个中断设置优先级。
NVIC用4个位来控制优先级,值小的优先级高。把优先级分为两种:抢占优先级和响应优先级。
3.1.2. 中断优先级规则
Ø 优先级值越小,优先级越高。
Ø 如果不设置优先级,则默认优先级为0。
Ø 先比较抢占优先级。抢占优先级高的可以打断抢占优先级低的。
Ø 若抢占优先级一样,再比较响应优先级。但是响应优先级不会导致中断嵌套。
Ø 若抢占优先级一样的同时挂起,则优先处理响应抢占优先级高的。
Ø 若挂起的优先级(抢占和响应)都一样,则查找中断向量表,值小的先响应。
分组 | 抢占优先级 | 响应优先级 |
0 | 0位 取值范围:0 | 4位 取值范围:0-15 |
1 | 1位 取值范围:0-1 | 3位 取值范围:0-7 |
2 | 2位 取值范围:0-3 | 2位 取值范围:0-3 |
3 | 3位 取值范围:0-7 | 1位 取值范围:0-1 |
4 | 4位 取值范围:0-15 | 0位 取值范围:0 |
3.1. 外部中断控制器 (EXTI)
3.1.1. 按键控灯(基于寄存器开发)
#include "Driver_LED.h"
#include "Delay.h"
#include "Driver_Key.h"
int main()
{
/* 1. 初始化LED */
Driver_LED_Init();
/* 2. 初始化按键 */
Driver_Key_Init();
while (1)
{
}
}
#include "Driver_Key.h"
#include "Driver_LED.h"
#include "Delay.h"
/**
* @description: 初始化按键.
* 1. 给按键对应的io口设置工作模式: 下拉输入
* 2. 配置复用为外部中断
* 3. 配置外部中断控制器 EXTI
* 4. 配置NVIC
*/
void Driver_Key_Init(void)
{
/* 1. 开启时钟 */
/* 1.1 GPIOF*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* 1.2 AFIO*/
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
/* 2. 配置 PA0 为下拉输入: MODE=00 CNF=10 ODR=0 */
GPIOA->CRL &= ~GPIO_CRL_MODE0;
GPIOA->CRL |= GPIO_CRL_CNF0_1;
GPIOA->CRL &= ~GPIO_CRL_CNF0_0;
GPIOA->ODR &= ~GPIO_ODR_ODR0;
/* 3. 配置AFIO 配置 PA0 引脚为外部中断 EXTICR3 0101*/
//AFIO->EXTICR[0] &= ~AFIO_EXTICR1_EXTI0;
AFIO->EXTICR[0] |= AFIO_EXTICR1_EXTI0_PA;
/* 4. 配置EXTI */
/* 4.1. 配置上升沿触发 RTSR TR10=1*/
EXTI->RTSR |= EXTI_RTSR_TR0;
/* 4.2 开启 LINE10, 配置的中断屏蔽寄存器 */
EXTI->IMR |= EXTI_IMR_MR0;
/* 5. 配置 NVIC */
/* 5.1 配置优先级组 (3-7) 配置3表示4个二进制位全部用于表示抢占优先级*/
NVIC_SetPriorityGrouping(3);
/* 5.2 配置优先级 参数1:中断号*/
NVIC_SetPriority(EXTI0_IRQn, 3);
/* 5.3 使能Line10 */
NVIC_EnableIRQ(EXTI0_IRQn);
}
/**
* @description: line 15-10的中断服务函数.
* 一旦按键下按键1,则会执行一次这个函数
* @return {*}
*/
void EXTI0_IRQHandler(void)
{
/* 务必一定必须要清除中断标志位 */
EXTI->PR |= EXTI_PR_PR0;
Delay_ms(5);
if ((GPIOA->IDR & GPIO_IDR_IDR0) != 0)
{
Drviver_LED_Toggle(LED_1);
Drviver_LED_Toggle(LED_2);
}
}
#ifndef __DRIVER_KEY_H
#define __DRIVER_KEY_H
#include "stm32f10x.h"
void Driver_Key_Init(void);
#endif
#include "Driver_LED.h"
/**
* @description: 对LED进行初始化
*/
void Driver_LED_Init(void)
{
/* 1. 打开GPIOA的时钟 */
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
/* 2. 给用到的端口的所有 PIN (PA0 PA1 PA8) 设置工作模式: 通用推挽输出 MODE:11 CNF:00 */
GPIOB->CRL |= (GPIO_CRL_MODE0 | GPIO_CRL_MODE1);
GPIOB->CRL &= ~(GPIO_CRL_CNF0 | GPIO_CRL_CNF1);
/* 3. 关闭所有灯 */
Drviver_LED_Off(LED_1);
Drviver_LED_Off(LED_2);
}
/**
* @description: 点亮指定的LED
* @param {uint32_t} led 要点亮的LED
*/
void Drviver_LED_On(uint32_t led)
{
GPIOB->ODR &= ~led;
}
/**
* @description: 关闭指定的LED
* @param {uint32_t} led 要关闭的LED
*/
void Drviver_LED_Off(uint32_t led)
{
GPIOB->ODR |= led;
}
/**
* @description: 翻转LED的状态
* @param {uint32_t} led 要翻转的LED
*/
void Drviver_LED_Toggle(uint32_t led)
{
/* 1. 读取引脚的电平,如果是1(目前是关闭), 打开, 否则就关闭 */
if ((GPIOB->IDR & led) == 0)
{
Drviver_LED_Off(led);
}
else
{
Drviver_LED_On(led);
}
}
/**
* @description: 打开数组中所有的灯
* @param {uint32_t} leds 所有灯
* @param {uint8_t} size 灯的个数
*/
void Drviver_LED_OnAll(uint32_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
Drviver_LED_On(leds[i]);
}
}
/**
* @description: 关闭数组中所有的灯
* @param {uint32_t} leds 所有灯
* @param {uint8_t} size 灯的个数
*/
void Drviver_LED_OffAll(uint32_t leds[], uint8_t size)
{
for (uint8_t i = 0; i < size; i++)
{
Drviver_LED_Off(leds[i]);
}
}
#ifndef __DRIVER_LED_H
#define __DRIVER_LED_H
#include "stm32f10x.h"
#define LED_1 GPIO_ODR_ODR0
#define LED_2 GPIO_ODR_ODR1
void Driver_LED_Init(void);
void Drviver_LED_On(uint32_t led);
void Drviver_LED_Off(uint32_t led);
void Drviver_LED_Toggle(uint32_t led);
void Drviver_LED_OnAll(uint32_t leds[], uint8_t size);
void Drviver_LED_OffAll(uint32_t leds[], uint8_t size);
#endif
#include "Delay.h" // Device header
void Delay_us(uint16_t us)
{
/* 定时器重装值 */
SysTick->LOAD = 72 * us;
/* 清除当前计数值 */
SysTick->VAL = 0;
/*设置内部时钟源(2位->1),不需要中断(1位->0),并启动定时器(0位->1)*/
SysTick->CTRL = 0x5;
/*等待计数到0, 如果计数到0则16位会置为1*/
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG));
/* 关闭定时器 */
SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}
void Delay_ms(uint16_t ms)
{
while (ms--)
{
Delay_us(1000);
}
}
void Delay_s(uint16_t s)
{
while (s--)
{
Delay_ms(1000);
}
}
#ifndef __delay_h
#define __delay_h
#include "stm32f10x.h" // Device header
void Delay_us(uint16_t us);
void Delay_ms(uint16_t ms);
void Delay_s(uint16_t s);
#endif
3.1.2. 按键控灯(基于HAL库开发)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KEY0_Pin)
{
HAL_Delay(15);
// 防抖: 延迟15ms之后再次检测是否仍然是高电平,
if (HAL_GPIO_ReadPin(GPIOA, GPIO_Pin) == GPIO_PIN_SET)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
}
}
}