stm32中断

比起51,32的中断类型更为丰富和细腻。

前置知识

STM32中断

STM32的中断有$16$个优先等级,可以设置抢占优先级和响应优先级。理论上有$68$种中段线路,具体取决于手册给出。
当中断来临时,由硬件自动调用相关函数。中断的类型既有内核的中断,例如复位、硬件失效等;也有外设的中断,如看门狗、PVD等等。
中断向量表由编译器给出。
当有多个外设同时触发中断时,中断机制会将这些触发排队或是嵌套

NVIC

管理32中断的系统:NVIC(嵌套中断向量控制器)

统一分配中断优先级和管理中断。是内核的外设。
NVIC按优先级处理中断,告诉CPU当前应该处理哪个中断。
CPU不知道中断优先级——除非谁告诉了它。


NVIC既管理中断的“排队”,也负责中断的嵌套。
NVIC的中断优先级由优先级寄存器的$4$位$(0-15)$决定,这四位可以进行切分,分为高$n$位抢占优先级和低$4-n$位的响应优先级

NVIC基本结构

优先级

抢占优先级和响应优先级相同的,按中断号进行排序

抢占优先级

pre-emption priority

抢占优先级高的可以中断嵌套

响应优先级

subpriority

响应优先级高的可以优先排队

EXTI简介

EXTI(Extern Interrupt)外部中断可以监测制定GPIO口的电平信号,档期制定的GPIO口产生电平变化时,EXTI将立即向NVIC发出终端申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序。

  • 支持的触发方式:上升沿/下降沿/双边沿/软件触发
  • 支持的GPIO口:所有的GPIO口,但相同的GPIO_Pin不能同时触发中断。例如PA0和PB0不能同时选为中断引脚。
  • 通道数:16个GPIO_Pin,PVD输出,RTC闹钟、USB唤醒、以太网唤醒
    在省电模式的停止模式下,需要借助外部中断唤醒STM32
    EXTI电路图

触发响应方式

中断响应/事件响应
如果选择事件响应,那么当引脚电平变化时,将不会触发中断,而是触发别的外设操作(例如由PVC触发ADC),属于外设之间的联合工作。

EXTI基本结构

GPIOn连接着AFIO(中断引脚选择)这个数据选择器,在各个Pin中选择一个连接到EXTI的通道上去。
再由EXTI边沿检测和控制电路上。
PVD、RTC、USB、ETH并接在EXTI边沿检测和控制电路上。
之后分为两种电路,一种连到NVIC上。外部中断的5~9和15~10会触发同一个中断函数~(ST偷工减料)~。
另有20条线路接到其它外设上,用来触发其它外设操作(事件响应)。
AFIO主要功能是引脚重映射、中断引脚选择等功能。
具体电路图如下:
AFIO

AFIO相关的函数

stm32f10x.h

  • GPIO_AFIODeInit():清除AFIO有关的设置
  • GPIO_PinLockConfig(GPIO_TypeDef*, uin16_t):没啥大用
  • GPIO_EventOutputConfig(uint8_t, uint8_t):配置AFIO事件输出功能
  • GPIO_EventOutputCmd(FunctionalState):配置AFIO事件输出功能
  • GPIO_PinRemapConfig(uint32_t, FunctionalState):进行引脚重映射
  • GPIO_EXTILineConfig(uint8_t, uint8_t):配置AFIO的数据选择器,选择需要的中断引脚
  • GPIO_ETH_MediaInterfaceConfig(uint32_t):与以太网外设有关

stm32f10x_exti.h

  • EXTI_DeInit()
  • EXTI_Init(EXTI_InitTypeDef*)
  • EXTI_StructInit(EXTI_InitTypeDef*):获得配置信息结构体
  • EXTI_GenerateSWInterrupt(uint32_t):软件触发外部中断,参数是一个指定的中断线
  • EXTI_GetFlagStatus(uint32_t):获取指定的标志位
  • EXTI_ClearFlag(uin32_t):清除标志位
  • EXTI_GetITStatus(uint32_t):(在中断函数中)获取与中断有关的标志位
  • EXTI_ClearITPendingBit():(在中断函数中)清除与中断有关的标志位
    其中,外设_Init()外设_StructInit()等在各种外设里都有类似的存在。

NVIC相关函数

misc.h中(怎么被发配到杂项里去了

  • NVIC_PriorityGroupConfig(uint32_t):用于中断分组
  • NVIC_Init(NVIC_InitTypeDef)
  • NVIC_SetVectorTable(uint32_t, uin32_t)
  • NVIC_SystemLPConfig(uint8_t, FunctionalState)
  • SysTick_CLKSourceConfig()

中断分组方式整个芯片只能用一种,NVIC_PriorityGroupConfig()调用一次即可。
若多次调用,请保证所有的调用作出同样的分组。

一些实例

这里使用了 江科大自化协 的例子,通过OLED显示屏显示结果

对射式红外传感器计次

电路连接图如下
面包板接线图
上代码!

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "stm32f10x.h"
#include "OLED.h"
#include "CountSensor.h"
#include "delay.h"

void main(void){
OLED_Init();
CounterSensor_Init();
while (1){
OLED_ShowNum(1, 1, CountSensor_Get());
}
}

CounterSensor.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "stm32f10x.h"

uin16_t CounterSensor_Count = 0;

void CounterSensor_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // AFIO属于APB2. 开启AFIO的时钟
// EXTI和NVIC的时钟一直开着
// RCC位于内核之外,管不到NVIC
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO+Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); // PB14

EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_Init(&EXTI_InitStructure);

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 注意:分组方式整个芯片只能用一种。最好放在main那里
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // 选择中断通道
NVIC_InitStructure.NVIC_IRQChanneCmd = ENABLE; // 中断通道是使能还是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_Init(&NVIC_InitStructure);
}

uin16_t CountSensor_Get(void){
return CounterSensor_Count;
}

// 中断函数名不能写错
// 建议去startup_stm32f10x_md.s启动文件里复制。
void EXTI15_10_IRQHandler(void){
// 中断标志位判断,确保是想要的中断进入此函数
if (EXTI_GetITStatus(EXTI_Line14) == SET){
CounterSensor_Count++;
// 中断程序结束后要手动清除标志位
EXTI_ClearITPendingBit)EXTI_Line14);
}
}

旋转编码器计次

电路连接图如下
面包板接线图
江科大自化协 的例子有所不同,我这里写的Encoder只写了个计数,并没有单位时间内计次功能。
上代码!

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "stm32f10x.h"
#include "OLED.h"
#include "Encoder.h"
#include "delay.h"

void main(void){
OLED_Init();
Encoder_Init();
while (1){
OLED_ShowNum(1, 1, Encoder_Get());
}
}

Encoder.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include "stm32f10x.h"

int16_t Encoder_Count;

void Encoder_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // AFIO属于APB2. 开启AFIO的时钟
// EXTI和NVIC的时钟一直开着
// RCC位于内核之外,管不到NVIC
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO+Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0); // PB0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); // PB1

EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_Init(&EXTI_InitStructure);

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 注意:分组方式整个芯片只能用一种。最好放在main那里
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 选择中断通道
NVIC_InitStructure.NVIC_IRQChanneCmd = ENABLE; // 中断通道是使能还是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 选择中断通道
NVIC_InitStructure.NVIC_IRQChanneCmd = ENABLE; // 中断通道是使能还是失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; // 响应优先级
NVIC_Init(&NVIC_InitStructure);
}

int16_t Encoder_Get(void){
return Encoder_Count;
}

// 中断函数
void EXTI0_IREHandler(void){
// 只是让中断的模式固定所以判断一下
if (EXTI_GetITStatus(EXTI_Line0) == SET){
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){
Encoder_Count--;
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
void EXTI1_IREHandler(void){
if (EXTI_GetITStatus(EXTI_Line1) == SET){
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){
Encoder_Count++;
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
}

杂记

可以考虑:在中断中对变量进行操作,当中断返回时,对中断变量进行显示和操作

中断里不要写耗时太长的代码

最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件

作者

勇敢梧桐树

发布于

2022-12-03

更新于

2023-05-29

许可协议

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×