在STM32中执行中断主要分三部分:
1.配置NVIC_Config()函数
2.配置EXTI_Config()函数
3.编写中断服务函数
(注:本文章所用代码为中断按键代码,实现了按键进入中断从而控制LED亮灭)
配置NVIC_Config()函数
NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。
NVIC_Config()函数代码如下:
static void NVIC_Config(void) /* 主要是配置中断源的优先级与打开使能中断通道 */
{
NVIC_InitTypeDef NVIC_InitStruct ;
/* 配置中断优先级分组(设置抢占优先级和子优先级的分配),在函数在misc.c */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1) ;
/* 配置初始化结构体 在misc.h中 /
/ 配置中断源 在stm32f10x.h中 /
NVIC_InitStruct.NVIC_IRQChannel = KEY1_EXTI_IRQN ;
/ 配置抢占优先级 /
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1 ;
/ 配置子优先级 /
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0 ;
/ 使能中断通道 /
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE ;
/ 调用初始化函数 */
NVIC_Init(&NVIC_InitStruct) ;
/* 对key2执行相同操作 */
NVIC_InitStruct.NVIC_IRQChannel = KEY2_EXTI_IRQN ;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1 ;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1 ;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE ;
NVIC_Init(&NVIC_InitStruct) ;
}
配置NVIC_Config()的目的是选择中断源的优先级以及打开中断通道,主要功能通过配置NVIC初始化结构体NVIC_InitStruct来完成。通俗的讲,STM32中有很多中断,而当有多个中断同时发生时就涉及到中断执行的先后问题了,所以引入了中断优先级的概念,中断优先级越高中断就越先执行。在这里我们只讨论外部中断的优先级,在 NVIC 有一个专门的寄存器:中断优先级寄存器 NVIC_IPRx,用来配置外部中断的优先级。优先级高低的比较包括抢占优先级和子优先级,先比较抢占优先级,如果抢占优先级相同就比较子优先级,从而得出中断之间的优先级高低。NVIC的主要任务就是给对应的中断源分配中断优先级。 中断优先级分配的原理繁杂,但固件库编程的好处就是化繁为简,我们只需要按照NVIC_InitStruct()中的内容进行配置就行。
接下来简单讲解一下NVIC_Config()函数的内容:
1.首先设置中断优先级分组
中断优先级分组其实是确立一个大纲,中断优先级寄存器 NVIC_IPRx中有4个位用来确定优先级,中断优先级的分组就是把这4个位分配在抢占优先级和子优先级中。比如设定一个位配置抢占优先级,其余三个位配置子优先级。通过函数NVIC_PriorityGroupConfig() ; 实现分组,详细代码如下:
1 /**
2 * 配置中断优先级分组:抢占优先级和子优先级
3 * 形参如下:
4 * @arg NVIC_PriorityGroup_0: 0bit for 抢占优先级
5 * 4 bits for 子优先级
6 * @arg NVIC_PriorityGroup_1: 1 bit for 抢占优先级
7 * 3 bits for 子优先级
8 * @arg NVIC_PriorityGroup_2: 2 bit for
9 * 2 bits for 子优先级
10 * @arg NVIC_PriorityGroup_3: 3 bit for 抢占优先级
11 * 1 bits for 子优先级
12 * @arg NVIC_PriorityGroup_4: 4 bit for 抢占优先级
13 * 0 bits for 子优先级
14 * @注意 如果优先级分组为 0,则抢占优先级就不存在,优先级就全部由子优先级控制
15 */
16 void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
17 {
18 // 设置优先级分组
19 SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
20 }
2.优先级分组完毕后,是配置NVIC初始化结构体
typedef struct {
2 uint8_t NVIC_IRQChannel; // 中断源
3 uint8_t NVIC_IRQChannelPreemptionPriority; // 抢占优先级
4 uint8_t NVIC_IRQChannelSubPriority; // 子优先级
5 FunctionalState NVIC_IRQChannelCmd; // 中断使能或者失能
6 } NVIC_InitTypeDef;
初始化结构体的作用是,收集中断源的信息(包括配置的是哪一个中断源、中断源的抢占优先级是多少、中断源的子优先级是多少、中断源的使能是否开启)。
NVIC_IROChannel:用来设置中断源,不同的中断中断源不一样,且不可写错,即使写错了程序也不会报错,只会导致不响应中断。 stm32f10x.h 头文件里面的 IRQn_Type 结构体定义,这个结构体包含了所有的中断源。
NVIC_IRQChannelPreemptionPriority和NVIC_IRQChannelSubPriority 分别设置抢占优先级和子优先级,具体的值要根据中断优先级分组来确定。
NVIC_IRQChannelCmd:设置中断使能(ENABLE)或者失能(DISABLE),相当于一个电源总开关。
3.最后借助NVIC初始化函数将NVIC初始化结构体中的信息写入相应的寄存器中 (体现了固件库编程的优点,不需要我们深入到寄存器层次去,只需要掌握相应函数的配置即可)
配置EXTI_Config()函数
EXTI(External interrupt/event controller):外部中断/事件控制器,管理了控制器的 20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可以实现对每个中断/事件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
按我的理解,EXTI是一个有着多达20个接口的控制器,它可以为每一个接入接口的信号源配置中断(或事件)线、设置信号的检测方式、设置触发事件的性质,也就是说,传入EXTI的仅仅是一个信号,EXTI的功能就是根据信号传入的“线”对信号做出相应的处理,然后将处理后的信号转向NVIC。 就像一个分拣机器,传入的东西经过筛选处理被送往不同的地方,只是EXTI分拣的是信号罢了。 如果说NVIC是配置中断源,那么EXTI就是向NVIC传送中断信号。
EXTI功能框图:
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,线路1-2-4-5是产生中断的流程,20/代表着有20条相同的线路。
接下来讲解一下EXTI_Config()函数代码:
void EXTI_Config() /* 主要是连接EXTI与GPIO */
{
GPIO_InitTypeDef GPIO_InitStruct ;
EXTI_InitTypeDef EXTI_InitStruct ;
NVIC_Config();
/* 初始化要与EXTI连接的GPIO /
/ 开启GPIOA与GPIOC的时钟 */
RCC_APB2PeriphClockCmd(KEY1_EXTI_GPIO_CLK | KEY2_EXTI_GPIO_CLK, ENABLE) ;
GPIO_InitStruct.GPIO_Pin = KEY1_EXTI_GPIO_PIN ;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
GPIO_Init(KEY1_EXTI_GPIO_PORT , &GPIO_InitStruct) ;
GPIO_InitStruct.GPIO_Pin = KEY2_EXTI_GPIO_PIN ;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
GPIO_Init(KEY2_EXTI_GPIO_PORT , &GPIO_InitStruct) ;
/* 初始化EXTI外设 /
/ EXTI的时钟要设置AFIO寄存器 /
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE) ;
/ 选择作为EXTI线的GPIO引脚 /
GPIO_EXTILineConfig( KEY1_GPIO_PORTSOURCE , KEY1_GPIO_PINSOURCE) ;
/ 配置中断or事件线 /
EXTI_InitStruct.EXTI_Line = KEY1_EXTI_LINE ;
/ 使能EXTI线 /
EXTI_InitStruct.EXTI_LineCmd = ENABLE ;
/ 配置模式:中断or事件 /
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt ;
/ 配置边沿触发 上升or下降 */
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising ;
EXTI_Init(&EXTI_InitStruct) ;
GPIO_EXTILineConfig( KEY2_GPIO_PORTSOURCE , KEY2_GPIO_PINSOURCE) ;
EXTI_InitStruct.EXTI_Line = KEY2_EXTI_LINE ;
EXTI_InitStruct.EXTI_LineCmd = ENABLE ;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt ;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling ;
EXTI_Init(&EXTI_InitStruct);
}
代码可大体分为三部分:
配置GPIO相应引脚、配置EXTI并连接GPIO引脚、传入NVIC_Config()
1.配置GPIO相应引脚
该代码是通过按键产生一个电平信号,然后经EXTI处理传入NVIC产生中断的,所以要配置连接按键的GPIO引脚,主要是设置相应的引脚模式为浮空输入 。老规矩,先开启相应GPIO的时钟,然后配置引脚初始化结构体,再利用初始化函数将初始化结构体写入寄存器中。
2.配置EXTI并连接GPIO引脚
要操作外设,首先要打开相关的时钟,EXTI挂载在APB2总线上,并且开启时钟时要操作AFIO寄存器 ,准备工作就绪后连接GPIO相应的引脚到EXTI中,前面说了EXTI有20个接口,所以特定的引脚有特定的接口,所以要根据GPIO_EXTILineConfig();函数选择用作EXTI线的GPIO引脚,函数说明如下
/**
- @brief Selects the GPIO pin used as EXTI Line.
- @param GPIO_PortSource: selects the GPIO port to be used as source for EXTI lines.
- This parameter can be GPIO_PortSourceGPIOx where x can be (A…G).
- @param GPIO_PinSource: specifies the EXTI line to be configured.
- This parameter can be GPIO_PinSourcex where x can be (0…15).
- @retval None
/
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
{
uint32_t tmp = 0x00;
/ Check the parameters */
assert_param(IS_GPIO_EXTI_PORT_SOURCE(GPIO_PortSource));
assert_param(IS_GPIO_PIN_SOURCE(GPIO_PinSource));
tmp = ((uint32_t)0x0F) << (0x04 * (GPIO_PinSource & (uint8_t)0x03));
AFIO->EXTICR[GPIO_PinSource >> 0x02] &= ~tmp;
AFIO->EXTICR[GPIO_PinSource >> 0x02] |= (((uint32_t)GPIO_PortSource) << (0x04 * (GPIO_PinSource & (uint8_t)0x03)));
}
其实对应的EXTI线就对应GPIO引脚号,这样看起来还比较直观。
连接好GPIO引脚与EXTI后就该配置EXTI的初始化结构体了,结构体如下:
typedef struct
{
uint32_t EXTI_Line; // 中断/事件线
EXTIMode_TypeDef EXTI_Mode; // EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; // 触发类型
FunctionalState EXTI_LineCmd; // EXTI 使能
} EXTI_InitTypeDef;
配置此结构体主要是:选择相应的EXTI线 、选择触发模式、选择产生的结果(中断还是事件)、是否使能EXTI线。
EXTI_Line:中断线选择,可选 EXTI_0 至 EXTI_19(一共20个)。既然刚才配置好了与GPIO引脚对应的EXTI线,所以初始化结构体中的EXTI线就是与GPIO连接的那个线。
EXTI_Mode: EXTI 模式选择,可选为产生中断或者产生事件。就是决定信号的发展方向,是产生中断呢?还是产生事件呢?此处是中断。
EXTI_Trigger: EXTI 边沿触发模式,可选上升沿触发、下降 沿 触 发 或 者 上 升 沿 和 下 降 沿 都 触 发。触发信号。
EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线或禁用。
初始化结构体配置完毕后交由初始化函数写入相应的寄存器中。
3.传入NVIC_Config()
之后就自动传入NVIC中了。。。
编写中断服务函数
到这里就万事俱备只欠东风了,中断的触发与处理及优先级定义都已经安排上了,最后一步就是编写中断函数的内容了,只要进入中断就会执行中断函数中的代码,所以这是收尾工作。STM32的中断服务函数不同于51单片机中的中断服务函数,STM32的所有中断函数都被偷偷安排了,每个中断都有其固定的名字,只有找到这个名字,在这个固定的函数名下编写中断服务函数才是有效的,所有中断函数的编写都要在stm32f10x_it.c 中,如示:
从所给的信息可得知外设的中断服务函数的名字都存放在startup_stm32f10x_xx.s 中,而且是由汇编语言编写,如示:
可知EXTI线0到EXTI线4线都是单独的中断函数名、EXTI线5到EXTI线9共用一个中断函数名、EXTI线10线到EXTI线15线共用一个中断函数名。
我们要做的就是以相应的EXTI线的中断函数名字在stm32f10x_it.c中编写中断函数 如下:
void EXTI0_IRQHandler(void)
{
if( EXTI_GetITStatus(KEY1_EXTI_LINE)!=RESET)
{
LED1_TOGGLE; //LED1的亮灭状态反转
}
EXTI_ClearITPendingBit(KEY1_EXTI_LINE);
}
void EXTI15_10_IRQHandler(void)
{
if( EXTI_GetITStatus(KEY2_EXTI_LINE)!=RESET)
{
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
!**
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-FQ6qi5yB-1712915848295)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!