STM32之中断
Posted YellowMax2001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32之中断相关的知识,希望对你有一定的参考价值。
1、NVIC
Nested vectored interrupt controller :可嵌套向量中断控制器 (NVIC)
- NVIC 特性
- 82个可屏蔽中断 ##不包括内核的16个中断
- 16个可编程优先级 ##适用于全部中断
- 低延迟异常和中断处理
- 电源管理控制
- 系统控制寄存器的实现
NVIC与处理器内核接口紧密耦合, 实现了高效快速的中断响应。所有的中断,包括内核异常都被 NVIC 所管理.
2、中断向量表
其实中断向量表在STM32F4XX启动文件里面就可以看出来,详情可看 :STM32F4XX启动文件分析
3、EXTI(External interrupt/event controller) 外部中断/事件控制器
主要特性
- 在每个中断/事件线都有独立的触发和屏蔽功能
- 每个中断线有专用的状态标志位
- 可产生高达 23 个软件事件/中断请求
- 以比APB2时钟周期更短的脉冲宽度检测外部信号
中断与事件配置
硬件中断选择
配置23线为中断源可参考以下配置步骤 :
- 配置 23 中断线的屏蔽位 (EXTI_IMR)
- 配置中断线的触发选择位 (EXTI_RTSR and EXTI_FTSR)
- 配置控制 NVIC IRQ 通道的使能与屏蔽位来使来自 23 线的一个中断可以被正确的应答,NVIC IRQ 被映射到外部中断控制器(EXTI)
硬件事件选择
配置23线为事件源可参考以下配置步骤 :
- 配置 23 事件线的屏蔽位 (EXTI_EMR)
- 配置事件线的触发选择位 (EXTI_RTSR and EXTI_FTSR)
软件 中断/事件 选择
23 线可以被配置为软件 中断/事件 线。下面的操作步骤可以产生一个软件中断.
- 配置 23 事件/中断 线的屏蔽位 (EXTI_IMR, EXTI_EMR)
- 设置软件中断寄存器的应答位 (EXTI_SWIER)
外部 中断/事件 映射
STM32F407ZG 的 140 个 GPIO 引脚都与一个外部中断线相连,具体如图所示:
上述一共用到了16根 EXTI 线,其余 7 根 EXTI 线的连接使用如下:
- EXTI 16 连接到 PVD 输出(PVD:掉电检测)
- EXTI 17 连接到 RTC Alarm 事件
- EXTI 18 连接到 USB OTG FS Wakeup 事件
- EXTI 19 连接到 Ethernet Wakeup 事件
- EXTI 20 连接到 USB OTG HS (configured in FS) Wakeup 事件
- EXTI 21 连接到 RTC Tamper and TimeStamp 事件
- EXTI 22 连接到 RTC Wakeup 事件
中断与事件的区别
一个硬件中断/事件的产生:
- input line 输入外部信号
- 边缘检测电路检测电平变化(电平变化检测可以人为配置,并且上升沿检测与下降沿检测是独立的)
- 经过一个或门,此或门连接电平检测电路的输出与软件事件/中断寄存器。也因此任意一条线的值为真,那么输出就为真,所以可以产生软触发中断或者事件
- 或门输入信号分别经过两个与门,另个与门分别再与中断屏蔽寄存器与事件屏蔽寄存器连接,控制中断或者事件的产生。这两个也是独立的,所以可以同时产生中断以及事件
- 如果是中断的话,输出信号会再经过中断挂起请求寄存器,如果此时芯片正处于不可被中断打断的时候,可以配置中断挂起寄存器来暂时挂起一个中断。需要软件参与
- 如果是事件的话,输出信号直接输出到一个脉冲发生器里面,脉冲发生器可以产生一个脉冲,调动相应的硬件完成此次事件响应。无需软件参与
DMA传输的例子:
- 如果配置为中断的话,需要在中断产生之后,进入中断处理函数,在中断处理函数中触发DMA操作,然后进行DMA。
- 如果配置为事件的话,直接由事件最终输出脉冲来触发DMA操作,不需要经过中断处理函数进行DMA的触发。
事件可以降低CPU的负荷,提高了响应速度
4、内核最重要的两个模块(SCB:System controller block NVIC:Nested vectored interrupt controller)
内核的外设
SCB
地址 | 寄存器名 | 读写权限 | 特权 | 复位值 | 作用描述 |
---|---|---|---|---|---|
0xE000E008 | ACTLR | RW | Privileged | 0x00000000 | 辅助控制寄存器 |
0xE000ED00 | CPUID | RO | Privileged | 0x410FC240 | CPU的ID号码 |
0xE000ED04 | ICSR | RW | Privileged | 0x00000000 | 中断控制与状态寄存器 |
0xE000ED08 | VTOR | RW | Privileged | 0x00000000 | 中断向量表偏移,一般只取两个值,第29位为1表示SRAM区,为0表示code区 |
0xE000ED0C | AIRCR | RW | Privileged | 0xFA050000 | 应用程序中断以及复位 |
0xE000ED10 | SCR | RW | Privileged | 0x00000000 | 系统控制 |
0xE000ED14 | CCR | RW | Privileged | 0x00000200 | 配置与控制 |
0xE000ED18 | SHPR1 | RW | Privileged | 0x00000000 | 系统中断处理函数优先级寄存器1 |
0xE000ED1C | SHPR2 | RW | Privileged | 0x00000000 | 系统中断处理函数优先级寄存器2 |
0xE000ED20 | SHPR3 | RW | Privileged | 0x00000000 | 系统中断处理函数优先级寄存器3 |
0xE000ED24 | SHCRS | RW | Privileged | 0x00000000 | 系统中断处理函数控制与状态 |
0xE000ED28 | CFSR | RW | Privileged | 0x00000000 | 配置异常状态寄存器 |
0xE000ED28 | MMSRb | RW | Privileged | 0x00 | 内存管理异常状态寄存器 |
0xE000ED29 | BFSRb | RW | Privileged | 0x00 | 总线异常状态寄存器 |
0xE000ED2A | UFSRb | RW | Privileged | 0x0000 | 使用异常状态寄存器 |
0xE000ED2C | HFSR | RW | Privileged | 0x00000000 | 硬件异常状态寄存器 |
0xE000ED34 | MMAR | RW | Privileged | Unknown | 内存管理异常地址寄存器 |
0xE000ED38 | BFAR | RW | Privileged | Unknown | 总线异常地址寄存器 |
0xE000ED3C | AFSR | RW | Privileged | 0x00000000 | 辅助异常状态寄存器 |
NVIC
地址 | 寄存器名 | 读写权限 | 特权 | 复位值 | 作用描述 |
---|---|---|---|---|---|
0xE000E100-0xE000E11C | NVIC_ISER0-NVIC_ISER7 | RW | Privileged | 0x00000000 | 中断使能 |
0XE000E180-0xE000E19C | NVIC_ICER0-NVIC_ICER7 | RW | Privileged | 0x00000000 | 中断禁止 |
0XE000E200-0xE000E21C | NVIC_ISPR0-NVIC_ISPR7 | RW | Privileged | 0x00000000 | 中断挂起 |
0XE000E280-0xE000E29C | NVIC_ICPR0-NVIC_ICPR7 | RW | Privileged | 0x00000000 | 中断恢复 |
0xE000E300-0xE000E31C | NVIC_IABR0-NVIC_IABR7 | RW | Privileged | 0x00000000 | 中断激活 |
0xE000E400-0xE000E4EF | NVIC_IPR0-NVIC_IPR59 | RW | Privileged | 0x00000000 | 中断优先级 |
0xE000EF00 | STIR | WO | Configurable | 0x00000000 | 软件触发中断 |
5、优先级分组的概念
Cortex M4 的优先级分组如下图所示
内核优先级的分组:
要注意的是,在 STM32F407ZG 只使用了 4bits 的位(高4位),也就是说分组情况如下
STM32组编号 | PRIGROUP | Binary point | Group priority bits | Subpriority bits | Group priorities | subpriorities |
---|---|---|---|---|---|---|
0 | 0b111 | b.yyyyyyyy | none | [7:4] | 1 | 16 |
1 | 0b110 | bx.yyyyyyy | [7] | [6:4] | 2 | 8 |
2 | 0b101 | bxx.yyyyyy | [7:6] | [5:4] | 4 | 4 |
3 | 0b100 | bxxx.yyyyy | [7:5] | [4] | 8 | 2 |
4 | 0b011 | bxxxx.yyyy | [7:4] | none | 16 | 1 |
在STM32中组编号恰好与内核手册中的是反的,这样设计的原因是为了兼容性,也就是说如果程序移植到了只支持3位优先级设置的系统中也能够运行。另外有三种设计方式分别是:使用高 4bits,组编号不反转;使用低 4bits,组编号不反转;使用低 4bits,组编号反转。这三种方法如果按照内核分组写出来之后会发现会有优先级完全一样的情况出现,所以不可取。
- 要了解优先级分组,就要明确两个概念:抢占优先级(组优先级)、响应优先级(子优先级)
- 抢占优先级:可以被中断嵌套。也就是在一个中断发生的时候,另一个抢占优先级比此中断级别高的中断可以打断正在进行的中断,直到更高优先级的中断执行完毕之后,才会返回来继续执行这个被打断的中断
- 响应优先级:不可以被中断嵌套。也就是说在多个中断同时发生的时候,只能够优先相应较高优先级的中断,并且如果在中断过程中有更高优先级中断发生的时候,正在进行的中断也不能够被打断。
抢占优先级与响应优先级的关系有点像 TCP/IP 协议中的网络号与子网号的区别,两个中断也是先比较抢占优先级然后才是比较响应优先级
6、程序编写
#define SUM_NVIC_PRIOTITY_BITS 4 //一共用了4个位
/* 中断优先级分组
* group_num : 分组号,上面有列出各个分组对应的优先级
*/
static void set_priority_group(u8 group_num)
{
u32 temp = 0, temp1 = 0;
group_num = group_num % (SUM_NVIC_PRIOTITY_BITS + 1); //因为只有5个组,所以限制数量
temp1 = (((~group_num) & 0x07) << 8); //取反区低三位
temp = SCB->AIRCR;
temp &= 0x0000F8FF; //清除8-10位
temp |= 0x05FA0000; //必须写5FA位,是作为钥匙的作用,不写的话写入的分组是无效的
temp |= temp1;
SCB->AIRCR = temp;
}
/* 设置优先级分组
* g_priority :抢占优先级 sub_priority :响应优先级
* irq_num :中断号 prioritygroup :优先级组
*/
void NVIC_set_priority(u8 g_priority, u8 sub_priority, IRQn_Type irq_num, u8 prioritygroup)
{
int32_t sub_priority_bits = 0;
sub_priority_bits = SUM_NVIC_PRIOTITY_BITS - prioritygroup;
ASSERT(sub_priority_bits >= 0); //断言,如果小于0就报错
/* 原型
#define ASSERT(x) while(!(x)){ \\
printf("Assert failed!!! File:%s Function:%s Line:%d\\r\\n", __FILE__, __FUNCTION__, __LINE__); \\
delay_ms(1000); \\
}
*/
set_priority_group(prioritygroup); //设置优先级分组
/* 参考内核头文件写的 */
if(irq_num < 0) //要判断是否小于0原因是:内核头文件中把内核的中断设置为小于0的枚举类型,而其他的都是大于0的,参照内核头文件
{
/* 根据内核头文件中的 SCB 结构体对应内核手册部分推算 */
SCB->SHP[((uint32_t)(irq_num) & 0xF)-4] = ((g_priority << sub_priority_bits) | sub_priority) << (8 - SUM_NVIC_PRIOTITY_BITS);
}
else
{
NVIC->IP[(uint32_t)(irq_num)] = ((g_priority << sub_priority_bits) | sub_priority) << (8 - SUM_NVIC_PRIOTITY_BITS);
}
}
/* 使能相应的中断,为必须的 */
void NVIC_enable_irq(IRQn_Type irq)
{
NVIC->ISER[(uint32_t)((int32_t)irq) >> 5] |= (uint32_t)(1 << ((uint32_t)((int32_t)irq) % 32));
}
void EX_irq_config(GPIOx_SELECT gpiox,GPIOx_pn_SELECT gpiox_n,GPIO_IRQ_TRIGGER trigger)
{
RCC->APB2ENR |= (1 << 14); //使能SYSCFG模块,只有使能之后对SYSCFG寄存器的设置才会有效
gpio_init(gpiox, gpiox_n, BYM_PULL_UP, BYM_GPI, BYM_HIGH_LEVEL, BYM_PUSH_PULL); //初始化GPIO为输入,内部上拉
SYSCFG->EXTICR[gpiox_n/4] &= ~(0xF << ((gpiox_n % 4) * 4)); //
SYSCFG->EXTICR[gpiox_n/4] |= (gpiox << ((gpiox_n % 4) * 4));//映射 Px_n 到 EXTIn中断线
EXTI->IMR |= (1 << gpiox_n); //解除屏蔽
EXTI->RTSR |= ((trigger & 0x01) << gpiox_n); //设置触发沿
EXTI->FTSR |= ((trigger >> 1) << gpiox_n);
}
/* 只支持外部中断的中断标志清除 */
void EX_irq_clear(u8 irq)
{
EXTI->PR |= (1 << irq);
}
7、测试
测试抢占优先级不同的情况
主程序
NVIC_set_priority(1, 2, EXTI3_IRQn, 2);
NVIC_set_priority(2, 1, EXTI2_IRQn, 2);
EX_irq_config(BYM_GPIOE, BYM_Px3, IRQ_BOTHEDGE);
EX_irq_config(BYM_GPIOE, BYM_Px2, IRQ_BOTHEDGE);
NVIC_enable_irq(EXTI3_IRQn);
NVIC_enable_irq(EXTI2_IRQn);
中断服务程序
void EXTI3_IRQHandler(void)
{
if(0 == gpio_get(BYM_GPIOE, BYM_Px3))
{
printf("Key 1 down \\r\\n");
delay_ms(1000);
printf("Key 1 end \\r\\n");
}
EX_irq_clear(3);
}
void EXTI2_IRQHandler(void)
{
if(0 == gpio_get(BYM_GPIOE, BYM_Px2))
{
printf("Key 2 down \\r\\n");
delay_ms(1000);
printf("Key 2 end \\r\\n");
}
EX_irq_clear(2);
}
先按下 GPE3(对应EXTI3),立马按下 GPE2(对应EXTI2),有下面的输出结果(不会被打断)
Key 1 down Key 1 end
先按下 GPE2(对应EXTI2),立马按下 GPE3(对应EXTI3),有下面的输出结果(EXTI2的中断被EXTI3打断了)
Key 2 down Key 1 down Key 2 end Key 1 end
测试抢占优先级相同而响应优先级不同的情况
主程序改变如下
NVIC_set_priority(1, 2, EXTI3_IRQn, 2);
NVIC_set_priority(1, 1, EXTI2_IRQn, 2);
EX_irq_config(BYM_GPIOE, BYM_Px3, IRQ_BOTHEDGE);
EX_irq_config(BYM_GPIOE, BYM_Px2, IRQ_BOTHEDGE);
NVIC_enable_irq(EXTI3_IRQn);
NVIC_enable_irq(EXTI2_IRQn);
先按下 GPE3(对应EXTI3),立马按下 GPE2(对应EXTI2),有下面的输出结果(不会被打断)
Key 1 down Key 1 end
先按下 GPE2(对应EXTI2),立马按下 GPE3(对应EXTI3),有下面的输出结果(不会被打断)
Key 2 down Key 2 end
8、程序编写思路
- 设置优先级分组,使用 SCB 的 AIRCR 寄存器(重要的是写入钥匙,5FA)
- 具体规划分组内部抢占与响应优先级,如果中断是内核的,使用 SCB 3. 控制模块,如果是外部的,使用 NVIC 模块
- 使能 SYSCFG 模块时钟,映射相应的中断线,如果是IO口中断还要设置跳变沿以及初始化IO管脚
- 解除中断或者事件屏蔽(EXTI寄存器)
- NVIC使能相应的中断
- 编写中断服务程序
以上是关于STM32之中断的主要内容,如果未能解决你的问题,请参考以下文章