当前位置:   article > 正文

STM32学习笔记(5_2)- EXTI外部中断代码

STM32学习笔记(5_2)- EXTI外部中断代码

无人问津也好,技不如人也罢,都应静下心来,去做该做的事。

最近在学STM32,所以也开贴记录一下主要内容,省的过目即忘。视频教程为江科大(改名江协科技),网站jiangxiekeji.com

本期介绍外部中断和中断系统,中断就是主程序执行时,发生了中断触发条件,当这些紧急事件处理完后,CPU继续执行程序。

外部中断的使用场景:外部驱动很快的突变信号

相关实验有对射式红外传感器计次和旋转编码器计次 

EXTI基本结构

主程序里查看和清除标志位:Flagstatus EXTI GetFlagstatus (uint32 t EXTI Line);
void EXTI ClearFlag(uint32 t EXTI Line);

中断程序里查看和清除标志位 :ITstatus EXTI GetITstatus (uint32 t EXTI Line);
void EXTI ClearITPendingBit (uint32 t EXTI Line);

对射式红外传感器计次

接线图

对射式红外传感器计次的DO为数字输出端,随意接一个GPIO,图上接到PB14。当挡光片或编码盘在对射式红外传感器的中间经过时,DO就会输出电平跳变的信号,进而触发PB14的中断,在中断函数里执行变量++的程序即可,然后在OLED上显示这个变量。

程序现象

操作步骤

首先清楚外部中断信号的流向,从GPIO到AFIO,再到EXTI,再到NVIC,最终流向CPU,这样就可让CPU从主程序跳到中断程序执行。

1、配置RCC(打开GPIOB时钟)

2、配置AFIO时钟

EXTI和NVIC(内核外设)不需要开启时钟

  1. /*开启时钟*/
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
  3. RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟

3、配置GPIO外设

选择端口为输入模式,具体选用哪种输入模式,需在STM32F10xxx参考手册-通用和复用功能I/O(GPIO和AFIO) 里查看

视频里采用输入上拉模式

  1. /*GPIO初始化*/
  2. GPIO_InitTypeDef GPIO_InitStructure;
  3. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  4. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
  5. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  6. GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB14引脚初始化为上拉输入

4、配置AFIO外设

GPlO_PinRemapConfig(重映射的方式,新的状态):引脚重映射,本期不用此函数

GPIO_EXTILineConfig(GPIO PortSourceGPIOB,GPIO PinSource14):本期外部中断需用到的函数,调用此函数可配置AFIO的数据选择器,选择想要的中断引脚 

  1. /*AFIO选择中断引脚*/
  2. GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚

 5、配置EXTI

在配置中断前,先指定中断的分组。然后用NVIC_Init函数初始化NVIC就行。

  1. /*EXTI初始化*/
  2. EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
  3. EXTI_InitStructure.EXTI_Line = EXTI_Line14; //选择配置外部中断的14号线
  4. EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
  5. EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
  6. EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
  7. EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
  8. /*NVIC中断分组*/
  9. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
  10. //即抢占优先级范围:0~3,响应优先级范围:0~3
  11. //此分组配置在整个工程中仅需调用一次
  12. //若有多个中断,可以把此代码放在main函数内,while循环之前
  13. //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
  14. /*NVIC配置*/
  15. NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
  16. NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; //选择配置NVIC的EXTI15_10线
  17. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
  18. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
  19. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
  20. NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设

6、配置中断函数

中断向量表里以IRQHandler结尾的字符串就是中断函数的名字,中断函数的名字最好直接从启动文件复制过来。

  1. /**
  2. * 函 数:EXTI15_10外部中断函数
  3. * 参 数:无
  4. * 返 回 值:无
  5. * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  6. * 函数名为预留的指定名称,可以从启动文件复制
  7. * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  8. */
  9. void EXTI15_10_IRQHandler(void)
  10. {
  11. if (EXTI_GetITStatus(EXTI_Line14) == SET) //判断是否是外部中断14号线触发的中断
  12. {
  13. /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
  14. if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
  15. {
  16. CountSensor_Count ++; //计数值自增一次
  17. }
  18. EXTI_ClearITPendingBit(EXTI_Line14); //清除外部中断14号线的中断标志位
  19. //中断标志位必须清除
  20. //否则中断将连续不断地触发,导致主程序卡死
  21. }
  22. }

7、需要一个数字统计中断触发次数

 在模块前定义一个变量CountSensor_Count,在中断函数里写CountSensor_Count++,最后还需在模块里写一个get函数,返回这个变量CountSensor_Count

  1. uint16_t CountSensor_Count; //全局变量,用于计数
  2. /**
  3. * 函 数:获取计数传感器的计数值
  4. * 参 数:无
  5. * 返 回 值:计数值,范围:0~65535
  6. */
  7. uint16_t CountSensor_Get(void)
  8. {
  9. return CountSensor_Count;
  10. }

完整代码展示 

和之前的操作一样,先在Hardware下新建一个文件(Countsensor)的.c、.h文件,把对射式红外传感器的驱动函数封装起来。

main函数
  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "CountSensor.h"
  5. int main(void)
  6. {
  7. /*模块初始化*/
  8. OLED_Init(); //OLED初始化
  9. CountSensor_Init(); //计数传感器初始化
  10. /*显示静态字符串*/
  11. OLED_ShowString(1, 1, "Count:"); //1行1列显示字符串Count:
  12. while (1)
  13. {
  14. OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不断刷新显示CountSensor_Get的返回值
  15. }
  16. }
 CountSensor.h函数
  1. #ifndef __COUNT_SENSOR_H
  2. #define __COUNT_SENSOR_H
  3. void CountSensor_Init(void);
  4. uint16_t CountSensor_Get(void);
  5. #endif

 CountSensor.s函数

  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. }

旋转编码器计次 

只有在B相下降沿和A相低电平时才识别为正转

接线图

操作步骤

首先清楚外部中断信号的流向,从GPIO到AFIO,再到EXTI,再到NVIC,最终流向CPU,这样就可让CPU从主程序跳到中断程序执行。

具体代码和对射式红外传感器大同小异,就直接展示完整代码

完整代码展示 

和之前的操作一样,先在Hardware下新建一个文件(Encoder)的.c、.h文件,把旋转编码器的驱动函数封装起来。

 main函数

  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "Encoder.h"
  5. int16_t Num; //定义待被旋转编码器调节的变量
  6. int main(void)
  7. {
  8. /*模块初始化*/
  9. OLED_Init(); //OLED初始化
  10. Encoder_Init(); //旋转编码器初始化
  11. /*显示静态字符串*/
  12. OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
  13. while (1)
  14. {
  15. Num += Encoder_Get(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
  16. OLED_ShowSignedNum(1, 5, Num, 5); //显示Num
  17. }
  18. }

Encoder.h函数

  1. #ifndef __ENCODER_H
  2. #define __ENCODER_H
  3. void Encoder_Init(void);
  4. int16_t Encoder_Get(void);
  5. #endif

Encoder.c函数

  1. #include "stm32f10x.h" // Device header
  2. int16_t Encoder_Count; //全局变量,用于计数旋转编码器的增量值
  3. /**
  4. * 函 数:旋转编码器初始化
  5. * 参 数:无
  6. * 返 回 值:无
  7. */
  8. void Encoder_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_0 | GPIO_Pin_1;
  17. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  18. GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入
  19. /*AFIO选择中断引脚*/
  20. GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
  21. GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
  22. /*EXTI初始化*/
  23. EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
  24. EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
  25. EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
  26. EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
  27. EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
  28. EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
  29. /*NVIC中断分组*/
  30. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
  31. //即抢占优先级范围:0~3,响应优先级范围:0~3
  32. //此分组配置在整个工程中仅需调用一次
  33. //若有多个中断,可以把此代码放在main函数内,while循环之前
  34. //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
  35. /*NVIC配置*/
  36. NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
  37. NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
  38. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
  39. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
  40. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
  41. NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
  42. NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线
  43. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
  44. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
  45. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
  46. NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
  47. }
  48. /**
  49. * 函 数:旋转编码器获取增量值
  50. * 参 数:无
  51. * 返 回 值:自上此调用此函数后,旋转编码器的增量值
  52. */
  53. int16_t Encoder_Get(void)
  54. {
  55. /*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
  56. /*在这里,也可以直接返回Encoder_Count
  57. 但这样就不是获取增量值的操作方法了
  58. 也可以实现功能,只是思路不一样*/
  59. int16_t Temp;
  60. Temp = Encoder_Count;
  61. Encoder_Count = 0;
  62. return Temp;
  63. }
  64. /**
  65. * 函 数:EXTI0外部中断函数
  66. * 参 数:无
  67. * 返 回 值:无
  68. * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  69. * 函数名为预留的指定名称,可以从启动文件复制
  70. * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  71. */
  72. void EXTI0_IRQHandler(void)
  73. {
  74. if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断
  75. {
  76. /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
  77. if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
  78. {
  79. if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
  80. {
  81. Encoder_Count --; //此方向定义为反转,计数变量自减
  82. }
  83. }
  84. EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
  85. //中断标志位必须清除
  86. //否则中断将连续不断地触发,导致主程序卡死
  87. }
  88. }
  89. /**
  90. * 函 数:EXTI1外部中断函数
  91. * 参 数:无
  92. * 返 回 值:无
  93. * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  94. * 函数名为预留的指定名称,可以从启动文件复制
  95. * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  96. */
  97. void EXTI1_IRQHandler(void)
  98. {
  99. if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断
  100. {
  101. /*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
  102. if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
  103. {
  104. if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
  105. {
  106. Encoder_Count ++; //此方向定义为正转,计数变量自增
  107. }
  108. }
  109. EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位
  110. //中断标志位必须清除
  111. //否则中断将连续不断地触发,导致主程序卡死
  112. }
  113. }

中断使用建议

1、在中断里不要执行耗时过长的代码,比如在中断里执行delay函数,中断为了执行突发的事情。

2、最好不要在中断函数和主函数调用相同的函数或者操作同一个硬件。比如OLED函数,如果你既在主程序里调用OLED,在中断里调用OLED,OLED就会显示错误。

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

闽ICP备14008679号