当前位置:   article > 正文

STM32的中断系统与EXTI_exti4_irqhandler

exti4_irqhandler

中断系统

C语言编程不需要考虑中断的现场保护和现场恢复,只需要配置触发中断的条件、开启中断、编写中断服务函数即可。因为编译器已经配置好了现场保护和现场恢复的程序,触发中断后会自行转入中断服务函数向量表,然后由中断向量再执行到编写的中断服务函数,执行完中断函数后自动返回主程序。中断服务子函数的最后一般往往是清除相关的中断标志位(挂起位),要不然就会一直进中断,造成程序的卡死

基本上每个外设都可以产生中断。

NVIC

NVIC:嵌套中断向量控制器,内核的中断处理器,用于管理中断,分配优先级的。

NVIC相当于一个叫号系统,配合cpu工作。

一个外设可能会同时占用多个中断通道,所以有n条线

EXTI

标准库EXTI设置步骤

外设_deInit(void):调用它,就会清除外设的所有配置,恢复成上电默认的状态。

标准库EXTI 配置具体程序

exti.h

  1. #ifndef __COUNT_SENSOR_H
  2. #define __COUNT_SENSOR_H
  3. void CountSensor_Init(void);
  4. uint16_t CountSensor_Get(void);
  5. #endif

exti.c

  1. #include "stm32f10x.h" // Device header
  2. uint16_t CountSensor_Count; //全局变量,用于计数
  3. /**
  4. * 函 数:计数传感器初始化
  5. * 参 数:无
  6. * 返 回 值:无
  7. */
  8. void CountSensor_Init(void)
  9. {
  10. /*开启时钟*/
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
  12. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟
  13. /*GPIO初始化*/
  14. GPIO_InitTypeDef GPIO_InitStructure;
  15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  16. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
  17. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  18. GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入
  19. /*AFIO选择中断引脚*/
  20. GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
  21. /*EXTI初始化*/
  22. EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
  23. EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
  24. EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
  25. EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
  26. EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
  27. EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
  28. /*NVIC中断分组*/
  29. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
  30. //即抢占优先级范围:0~3,响应优先级范围:0~3
  31. //此分组配置在整个工程中仅需调用一次
  32. //若有多个中断,可以把此代码放在main函数内,while循环之前
  33. //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
  34. /*NVIC配置*/
  35. NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
  36. NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
  37. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
  38. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
  39. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
  40. NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
  41. }
  42. /**
  43. * 函 数:获取计数传感器的计数值
  44. * 参 数:无
  45. * 返 回 值:计数值,范围:0~65535
  46. */
  47. uint16_t CountSensor_Get(void)
  48. {
  49. return CountSensor_Count;
  50. }
  51. /**
  52. * 函 数:EXTI15_10外部中断函数
  53. * 参 数:无
  54. * 返 回 值:无
  55. * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  56. * 函数名为预留的指定名称,可以从启动文件复制
  57. * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  58. */
  59. void EXTI15_10_IRQHandler(void)
  60. {
  61. if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断
  62. {
  63. /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
  64. if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
  65. {
  66. CountSensor_Count ++; //计数值自增一次
  67. }
  68. EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
  69. //中断标志位必须清除
  70. //否则中断将连续不断地触发,导致主程序卡死
  71. }
  72. }

HAL库EXTI 配置步骤

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 函数定义:

  1. void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
  2. { if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00U)
  3. { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); /* 清中断标志位 */
  4. HAL_GPIO_EXTI_Callback(GPIO_Pin); /* 外部中断回调函数 */
  5. }
  6. }

该函数实现的作用非常简单,通过入口参数 GPIO_Pin 判断中断来自哪个 IO 口,然后清除相应的中断标志位,最后调用回调函数 HAL_GPIO_EXTI_Callback()实现控制逻辑。在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler,然后在回调函数 HAL_GPIO_EXTI_Callback 中通过判断中断是来自哪个 IO 口编写相应的中断服务控制逻辑。

具体程序EXTI 配置程序

exti.h

  1. #ifndef __EXTI_H
  2. #define __EXTI_H
  3. #include "./SYSTEM/sys/sys.h"
  4. /******************************************************************************************/
  5. /* 引脚 和 中断编号 & 中断服务函数 定义 */
  6. #define KEY0_INT_GPIO_PORT GPIOE
  7. #define KEY0_INT_GPIO_PIN GPIO_PIN_4
  8. #define KEY0_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
  9. #define KEY0_INT_IRQn EXTI4_IRQn
  10. #define KEY0_INT_IRQHandler EXTI4_IRQHandler
  11. #define KEY1_INT_GPIO_PORT GPIOE
  12. #define KEY1_INT_GPIO_PIN GPIO_PIN_3
  13. #define KEY1_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
  14. #define KEY1_INT_IRQn EXTI3_IRQn
  15. #define KEY1_INT_IRQHandler EXTI3_IRQHandler
  16. #define KEY2_INT_GPIO_PORT GPIOE
  17. #define KEY2_INT_GPIO_PIN GPIO_PIN_2
  18. #define KEY2_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0) /* PE口时钟使能 */
  19. #define KEY2_INT_IRQn EXTI2_IRQn
  20. #define KEY2_INT_IRQHandler EXTI2_IRQHandler
  21. #define WKUP_INT_GPIO_PORT GPIOA
  22. #define WKUP_INT_GPIO_PIN GPIO_PIN_0
  23. #define WKUP_INT_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
  24. #define WKUP_INT_IRQn EXTI0_IRQn
  25. #define WKUP_INT_IRQHandler EXTI0_IRQHandler
  26. /******************************************************************************************/
  27. void extix_init(void); /* 外部中断初始化 */
  28. #endif

exti.c

  1. #include "./SYSTEM/sys/sys.h"
  2. #include "./SYSTEM/delay/delay.h"
  3. #include "./BSP/LED/led.h"
  4. #include "./BSP/BEEP/beep.h"
  5. #include "./BSP/KEY/key.h"
  6. #include "./BSP/EXTI/exti.h"
  7. /**
  8. * @brief KEY0 外部中断服务程序
  9. * @param 无
  10. * @retval 无
  11. */
  12. void KEY0_INT_IRQHandler(void)
  13. {
  14. HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY0所在中断线 的中断标志位 */
  15. __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  16. }
  17. /**
  18. * @brief KEY1 外部中断服务程序
  19. * @param 无
  20. * @retval 无
  21. */
  22. void KEY1_INT_IRQHandler(void)
  23. {
  24. HAL_GPIO_EXTI_IRQHandler(KEY1_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY1所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
  25. __HAL_GPIO_EXTI_CLEAR_IT(KEY1_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  26. }
  27. /**
  28. * @brief KEY2 外部中断服务程序
  29. * @param 无
  30. * @retval 无
  31. */
  32. void KEY2_INT_IRQHandler(void)
  33. {
  34. HAL_GPIO_EXTI_IRQHandler(KEY2_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY2所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
  35. __HAL_GPIO_EXTI_CLEAR_IT(KEY2_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  36. }
  37. /**
  38. * @brief WK_UP 外部中断服务程序
  39. * @param 无
  40. * @retval 无
  41. */
  42. void WKUP_INT_IRQHandler(void)
  43. {
  44. HAL_GPIO_EXTI_IRQHandler(WKUP_INT_GPIO_PIN); /* 调用中断处理公用函数 清除KEY_UP所在中断线 的中断标志位,中断下半部在HAL_GPIO_EXTI_Callback执行 */
  45. __HAL_GPIO_EXTI_CLEAR_IT(WKUP_INT_GPIO_PIN); /* HAL库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
  46. }
  47. /**
  48. * @brief 中断服务程序中需要做的事情
  49. 在HAL库中所有的外部中断服务函数都会调用此函数
  50. * @param GPIO_Pin:中断引脚号
  51. * @retval 无
  52. */
  53. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  54. {
  55. delay_ms(20); /* 消抖 */
  56. switch(GPIO_Pin)
  57. {
  58. case KEY0_INT_GPIO_PIN:
  59. if (KEY0 == 0)
  60. {
  61. LED0_TOGGLE(); /* LED0 状态取反 */
  62. LED1_TOGGLE(); /* LED1 状态取反 */
  63. }
  64. break;
  65. case KEY1_INT_GPIO_PIN:
  66. if (KEY1 == 0)
  67. {
  68. LED0_TOGGLE(); /* LED0 状态取反 */
  69. }
  70. break;
  71. case KEY2_INT_GPIO_PIN:
  72. if (KEY2 == 0)
  73. {
  74. LED1_TOGGLE(); /* LED1 状态取反 */
  75. }
  76. break;
  77. case WKUP_INT_GPIO_PIN:
  78. if (WK_UP == 1)
  79. {
  80. BEEP_TOGGLE(); /* 蜂鸣器状态取反 */
  81. }
  82. break;
  83. }
  84. }
  85. /**
  86. * @brief 外部中断初始化程序
  87. * @param 无
  88. * @retval 无
  89. */
  90. void extix_init(void)
  91. {
  92. GPIO_InitTypeDef gpio_init_struct;
  93. KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
  94. KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
  95. KEY2_GPIO_CLK_ENABLE(); /* KEY2时钟使能 */
  96. WKUP_GPIO_CLK_ENABLE(); /* WKUP时钟使能 */
  97. gpio_init_struct.Pin = KEY0_INT_GPIO_PIN;
  98. gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
  99. gpio_init_struct.Pull = GPIO_PULLUP;
  100. HAL_GPIO_Init(KEY0_INT_GPIO_PORT, &gpio_init_struct); /* KEY0配置为下降沿触发中断 */
  101. gpio_init_struct.Pin = KEY1_INT_GPIO_PIN;
  102. gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
  103. gpio_init_struct.Pull = GPIO_PULLUP;
  104. HAL_GPIO_Init(KEY1_INT_GPIO_PORT, &gpio_init_struct); /* KEY1配置为下降沿触发中断 */
  105. gpio_init_struct.Pin = KEY2_INT_GPIO_PIN;
  106. gpio_init_struct.Mode = GPIO_MODE_IT_FALLING; /* 下升沿触发 */
  107. gpio_init_struct.Pull = GPIO_PULLUP;
  108. HAL_GPIO_Init(KEY2_INT_GPIO_PORT, &gpio_init_struct); /* KEY2配置为下降沿触发中断 */
  109. gpio_init_struct.Pin = WKUP_INT_GPIO_PIN;
  110. gpio_init_struct.Mode = GPIO_MODE_IT_RISING; /* 上升沿触发 */
  111. gpio_init_struct.Pull = GPIO_PULLDOWN;
  112. HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct); /* WKUP配置为下降沿触发中断 */
  113. HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2); /* 抢占0,子优先级2 */
  114. HAL_NVIC_EnableIRQ(KEY0_INT_IRQn); /* 使能中断线1 */
  115. HAL_NVIC_SetPriority(KEY1_INT_IRQn, 1, 2); /* 抢占1,子优先级2 */
  116. HAL_NVIC_EnableIRQ(KEY1_INT_IRQn); /* 使能中断线15 */
  117. HAL_NVIC_SetPriority(KEY2_INT_IRQn, 2, 2); /* 抢占2,子优先级2 */
  118. HAL_NVIC_EnableIRQ(KEY2_INT_IRQn); /* 使能中断线15 */
  119. HAL_NVIC_SetPriority(WKUP_INT_IRQn, 3, 2); /* 抢占3,子优先级2 */
  120. HAL_NVIC_EnableIRQ(WKUP_INT_IRQn); /* 使能中断线0 */
  121. }

HAL库相关注意点

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执行完该指令后,将从原先的断点处继续执行被中断的主程序。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/479312
推荐阅读
相关标签
  

闽ICP备14008679号