赞
踩
用C语言编程不需要考虑中断的现场保护和现场恢复,只需要配置触发中断的条件、开启中断、编写中断服务函数即可。因为编译器已经配置好了现场保护和现场恢复的程序,触发中断后会自行转入中断服务函数向量表,然后由中断向量再执行到编写的中断服务函数,执行完中断函数后自动返回主程序。中断服务子函数的最后一般往往是清除相关的中断标志位(挂起位),要不然就会一直进中断,造成程序的卡死。
基本上每个外设都可以产生中断。
NVIC:嵌套中断向量控制器,内核的中断处理器,用于管理中断,分配优先级的。
NVIC相当于一个叫号系统,配合cpu工作。
一个外设可能会同时占用多个中断通道,所以有n条线
外设_deInit(void):调用它,就会清除外设的所有配置,恢复成上电默认的状态。
- #ifndef __COUNT_SENSOR_H
- #define __COUNT_SENSOR_H
-
- void CountSensor_Init(void);
- uint16_t CountSensor_Get(void);
-
- #endif
- #include "stm32f10x.h" // Device header
-
- uint16_t CountSensor_Count; //全局变量,用于计数
-
- /**
- * 函 数:计数传感器初始化
- * 参 数:无
- * 返 回 值:无
- */
- void CountSensor_Init(void)
- {
- /*开启时钟*/
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
-
- /*GPIO初始化*/
- 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); //将PB14引脚初始化为上拉输入
-
- /*AFIO选择中断引脚*/
- GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
-
- /*EXTI初始化*/
- EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
- EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
- EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
- EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
- EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
- EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
-
- /*NVIC中断分组*/
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
- //即抢占优先级范围:0~3,响应优先级范围:0~3
- //此分组配置在整个工程中仅需调用一次
- //若有多个中断,可以把此代码放在main函数内,while循环之前
- //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
-
- /*NVIC配置*/
- NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
- NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
- }
-
- /**
- * 函 数:获取计数传感器的计数值
- * 参 数:无
- * 返 回 值:计数值,范围:0~65535
- */
- uint16_t CountSensor_Get(void)
- {
- return CountSensor_Count;
- }
-
- /**
- * 函 数:EXTI15_10外部中断函数
- * 参 数:无
- * 返 回 值:无
- * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
- * 函数名为预留的指定名称,可以从启动文件复制
- * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
- */
- void EXTI15_10_IRQHandler(void)
- {
- if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断
- {
- /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
- if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
- {
- CountSensor_Count ++; //计数值自增一次
- }
- EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }
1)使能对应 GPIO 口时钟。
2)设置 GPIO 工作模式,触发条件,开启 AFIO 时钟,设置 IO 口与中断线的映射关系。
这些步骤 HAL 库全部封装在 HAL_GPIO_Init 函数里面,我们只需要设置好对应的参数, 再调用 HAL_GPIO_Init 函数即可完成配置。
3)配置中断优先级(NVIC),并使能中断。
配置好 GPIO 模式以后,我们需要设置中断优先级和使能中断,中断优先级我们使用 HAL_NVIC_SetPriority 函数设置,中断使能我们使用 HAL_NVIC_EnableIRQ 函数设置。(中断分组设置已在HAL库初始化时配置,默认中断分组为2)
4)编写中断服务函数。
每开启一个中断,就必须编写其对应的中断服务函数,否则将导致死机(CPU 将找不到中断服务函数)。
中断服务函数接口厂家已经在 startup_stm32f103xe.s 中做好了,STM32F1 的 IO 口外部中断函数只有 7 个,分别为:
void EXTI0_IRQHandler();
void EXTI1_IRQHandler();
void EXTI2_IRQHandler();
void EXTI3_IRQHandler();
void EXTI4_IRQHandler();
void EXTI9_5_IRQHandler();
void EXTI15_10_IRQHandler();
中断线0-4,每个中断线对应一个中断函数,中断线5-9共用中断函数EXTI9_5_IRQHandler, 中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是 HAL 库把中断处理过程进行了简单封装(建议直接使用对应中断服务函数)。
5)编写中断处理回调函数 HAL_GPIO_EXTI_Callback。
HAL 库为了用户使用方便,提供了一个中断通用入口函数 HAL_GPIO_EXTI_IRQHandler, 在该函数内部直接调用回调函数 HAL_GPIO_EXTI_Callback。 我们先看一下 HAL_GPIO_EXTI_IRQHandler 函数定义:
- void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
-
- { if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00U)
-
- { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); /* 清中断标志位 */
- HAL_GPIO_EXTI_Callback(GPIO_Pin); /* 外部中断回调函数 */
- }
- }
该函数实现的作用非常简单,通过入口参数 GPIO_Pin 判断中断来自哪个 IO 口,然后清除相应的中断标志位,最后调用回调函数 HAL_GPIO_EXTI_Callback()实现控制逻辑。在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler,然后在回调函数 HAL_GPIO_EXTI_Callback 中通过判断中断是来自哪个 IO 口编写相应的中断服务控制逻辑。
- #ifndef __EXTI_H
- #define __EXTI_H
-
- #include "./SYSTEM/sys/sys.h"
-
- /******************************************************************************************/
- /* 引脚 和 中断编号 & 中断服务函数 定义 */
-
- #define KEY0_INT_GPIO_PORT GPIOE
- #define KEY0_INT_GPIO_PIN GPIO_PIN_4
- #define KEY0_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
- #define KEY0_INT_IRQn EXTI4_IRQn
- #define KEY0_INT_IRQHandler EXTI4_IRQHandler
-
- #define KEY1_INT_GPIO_PORT GPIOE
- #define KEY1_INT_GPIO_PIN GPIO_PIN_3
- #define KEY1_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
- #define KEY1_INT_IRQn EXTI3_IRQn
- #define KEY1_INT_IRQHandler EXTI3_IRQHandler
-
- #define KEY2_INT_GPIO_PORT GPIOE
- #define KEY2_INT_GPIO_PIN GPIO_PIN_2
- #define KEY2_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
- #define KEY2_INT_IRQn EXTI2_IRQn
- #define KEY2_INT_IRQHandler EXTI2_IRQHandler
-
- #define WKUP_INT_GPIO_PORT GPIOA
- #define WKUP_INT_GPIO_PIN GPIO_PIN_0
- #define WKUP_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
- #define WKUP_INT_IRQn EXTI0_IRQn
- #define WKUP_INT_IRQHandler EXTI0_IRQHandler
-
- /******************************************************************************************/
-
-
- void extix_init(void); /* 外部中断初始化 */
-
- #endif
- #include "./SYSTEM/sys/sys.h"
- #include "./SYSTEM/delay/delay.h"
- #include "./BSP/LED/led.h"
- #include "./BSP/BEEP/beep.h"
- #include "./BSP/KEY/key.h"
- #include "./BSP/EXTI/exti.h"
-
-
- /**
- * @brief KEY0 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void KEY0_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY0所在中断线 的中断标志位 */
- __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- }
-
- /**
- * @brief KEY1 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void KEY1_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY1所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
- __HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- }
-
- /**
- * @brief KEY2 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void KEY2_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(KEY2_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY2所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
- __HAL_GPIO_EXTI_CLEAR_IT(KEY2_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- }
-
- /**
- * @brief WK_UP 外部中断服务程序
- * @param 无
- * @retval 无
- */
- void WKUP_INT_IRQHandler(void)
- {
- HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY_UP所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
- __HAL_GPIO_EXTI_CLEAR_IT(WKUP_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
- }
-
- /**
- * @brief 中断服务程序中需要做的事情
- 在HAL库中所有的外部中断服务函数都会调用此函数
- * @param GPIO_Pin:中断引脚号
- * @retval 无
- */
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
- {
- delay_ms(20); /* 消抖 */
- switch(GPIO_Pin)
- {
- case KEY0_INT_GPIO_PIN:
- if (KEY0 == 0)
- {
- LED0_TOGGLE(); /* LED0 状态取反 */
- LED1_TOGGLE(); /* LED1 状态取反 */
- }
- break;
- case KEY1_INT_GPIO_PIN:
- if (KEY1 == 0)
- {
- LED0_TOGGLE(); /* LED0 状态取反 */
- }
- break;
- case KEY2_INT_GPIO_PIN:
- if (KEY2 == 0)
- {
- LED1_TOGGLE(); /* LED1 状态取反 */
- }
- break;
- case WKUP_INT_GPIO_PIN:
- if (WK_UP == 1)
- {
- BEEP_TOGGLE(); /* 蜂鸣器状态取反 */
- }
- break;
- }
- }
-
- /**
- * @brief 外部中断初始化程序
- * @param 无
- * @retval 无
- */
- void extix_init(void)
- {
- GPIO_InitTypeDef gpio_init_struct;
-
- KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
- KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
- KEY2_GPIO_CLK_ENABLE(); /* KEY2时钟使能 */
- WKUP_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
-
- gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLUP;
- HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct); /* KEY0配置为下降沿触发中断 */
-
- gpio_init_struct.Pin = KEY1_INT_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLUP;
- HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct); /* KEY1配置为下降沿触发中断 */
-
- gpio_init_struct.Pin = KEY2_INT_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLUP;
- HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &gpio_init_struct); /* KEY2配置为下降沿触发中断 */
-
- gpio_init_struct.Pin = WKUP_INT_GPIO_PIN;
- gpio_init_struct.Mode = GPIO_MODE_IT_RISING; /* 上升沿触发 */
- gpio_init_struct.Pull = GPIO_PULLDOWN;
- HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct); /* WKUP配置为下降沿触发中断 */
-
- HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2); /* 抢占0,子优先级2 */
- HAL_NVIC_EnableIRQ(KEY0_INT_IRQn); /* 使能中断线1 */
-
- HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2); /* 抢占1,子优先级2 */
- HAL_NVIC_EnableIRQ(KEY1_INT_IRQn); /* 使能中断线15 */
-
- HAL_NVIC_SetPriority(KEY2_INT_IRQn, 2, 2); /* 抢占2,子优先级2 */
- HAL_NVIC_EnableIRQ(KEY2_INT_IRQn); /* 使能中断线15 */
-
- HAL_NVIC_SetPriority(WKUP_INT_IRQn, 3, 2); /* 抢占3,子优先级2 */
- HAL_NVIC_EnableIRQ(WKUP_INT_IRQn); /* 使能中断线0 */
- }
1、回调函数void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
形参是uint16_t GPIO_Pin,把GPIO_PIN_x(x:0~15)作为参数,两者实质类型都是uint16_t。
回调函数会根据参数执行对应引脚的相关程序。
2、HAL库中AFIO时钟会随着GPIO模式的改变而自动使能
如果GPIO模式是外部中断模式,就自动使能AFIO时钟,而不需要单独开启AFIO的时钟
3、回调函数中 if (KEY0 == 0)与外部中断的GPIO配置是不冲突的,KEY0只是单纯的读取KEY0这个GPIOPIN的状态,此宏定义在key.h中
一般情况下中断什么时候清除标志:1.判断标志位 2.实现中断功能程序 3.清除相关中断标志位
规范的中断处理子程序流程图
1、现场保护和现场恢复
现场保护一定要位于中断处理程序的前面,现场恢复则要位于中断处理的后面。在中断处理前,为了使中断服务子程序的执行不破坏数据或状态,要将这些数据送入堆栈保存起来;中断处理结束后,再返回主程序前,需要把保存的程序内容从堆栈中弹出,恢复原内容。
2、关中断与开中断
在现场保护和现场恢复前关中断,是为了防止有更高优先级的中断进入,避免现场被破坏;在现场保护和现场恢复之后开中断,是为了下一次中断做准备,此时允许更高优先级的中断进入。
3、中断返回
中断服务子程序最后一条指令必须是返回指令RETI。该指令为中断程序的最后一条指令,CPU 执行这一条指令时,将响应中断时的优先级状态寄存器清零,然后从堆栈中弹出两个字节送入程序计数器PC,弹出的第一个字节送入PCH,第二个字节送入PCL。 CPU执行完该指令后,将从原先的断点处继续执行被中断的主程序。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。