STM32
直播中

孔朱磊

7年用户 911经验值
私信 关注
[问答]

STM32是怎么把16个中断线和IO口一一对应起来呢

外部中断有何功能?
如何去配置GPIO与中断线映射关系的函数?
STM32是怎么把16个中断线和IO口一一对应起来呢?

回帖(1)

席毖庸

2021-8-10 15:52:48
  外部中断
  通过中断的功能,达到通过板载的3个按键,控制板载的两个LED的亮灭以及蜂鸣器的发声。
  STM32的每个I/O口都可以作为外部中断的中断输入口,这点也是STM32的强大之处。STM32F103的中断控制器支持19个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103的19个外部中断为:
  线0-15:对应外部IO口的输入中断
  线16:连接到PVD输出
  线17:连接到RTC闹钟事件
  线18:连接到USB唤醒事件
  通过上述发现,STM32供IO口使用的中断线只有16个,但是IO口远远不止16个,那么STM32是怎么把16个中断线和IO口一一对应起来呢?中断线每次只能连接到1个IO口上,需要通过配置来决定对应的中断线配置到哪个GPIO口上,也就是说线0对应(GPIOA.0,GPIOB.0,GPIOC.0,GPIOD.0,GPIOE.0,GPIOF.0,GPIOG.0)这样,一直对应到引脚15,如下图所示
  
  在库函数中,配置GPIO与中断线的映射关系的函数如下来实现
  void GPIO_EXTILineConfig(uint8_t GPIO_PortSource,uint8_t GPIO_PinSource)
  //该函数将GPIO端口与中断线映射起来,使用范例是:
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
  //将中断线2与GPIOE银蛇起来。设置好之后,那么这个IO口中断通过什么方式触发呢?我们需要进行中断的初始化函数操作
  void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
  EXTI_InitStructure.EXTI_Line=EXTI_Line4; //中断线4
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式,可选中断和事件两种
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发,1-0 ,可选上升沿触发和任意电平触发
  EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能中断线
  EXTI_Init(&EXTI_InitStructure); //根据 EXTI_InitStruct 中指定的
  中断服务函数的名字是在MDK事先有定义的,STM32的IO口外部中断函数只有7个,分别如下:
  EXPORT EXTI0_IRQHandler
  EXPORT EXTI1_IRQHandler
  EXPORT EXTI2_IRQHandler
  EXPORT EXTI3_IRQHandler
  EXPORT EXTI4_IRQHandler
  EXPORT EXTI9_5_IRQHandler
  EXPORT EXTI15_10_IRQHandler
  中断线0-4每个中断线对应一个中断函数,中断线5-9公用函数EXTI9_5_IRQHandler,中断线10-15公用函数EXPORT EXTI15_10_IRQHandler。
  //判断某个中断线上的中断是否发生(标志位是否置位)
  ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
  //这个函数一般使用在中断服务函数的开头判断中断是否发生。
  //下面是清除中断线上的中断标志位
  void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
  除此之外,库函数还提供两个函数用来判断外部中断状态以及清除外部状态标志位的函数EXTI_GetFlagStatus和EXTI_ClearFlag,它们的作用和前面两个函数的作用类似。只能在EXTI_GetFlagStatus会先判断这种中断是否使能,使能了才去判断中断标志位。
  一般步骤
  初始化IO口为输入
  开启AFIO时钟
  设置IO口与中断线的映射关系
  初始化线上中断,设置触发条件等
  配置中断分组(NVIC),并使能中断
  编写中断服务函数
  代码实现
  #include “led.h”
  #include “delay.h”
  #include “key.h”
  #include “sys.h”
  #include “usart.h”
  #include “beep.h”
  int main(void)
  {
  delay_init(); //延时函数初始化
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
  uart_init(115200); //设置波特率为115200
  LED_Init(); //LED灯初始化
  BEEP_Init(); //蜂鸣器初始化
  EXTIX_Init(); //外部中断初始化
  LED0=0; //先点亮红灯
  while(1)
  {
  printf(“OKrn”); //串口打印ok
  delay_ms(1000);
  }
  }
  //外部中断0服务程序
  void EXTI0_IRQHandler(void)
  {
  delay_ms(10);//消抖
  if(WK_UP==1) //WK_UP按键
  {
  BEEP=!BEEP;
  }
  EXTI_ClearITPendingBit(EXTI_Line0); //清除LINE0上的中断标志位
  }
  //外部中断3服务程序
  void EXTI3_IRQHandler(void)
  {
  delay_ms(10);//消抖
  if(KEY1==0) //按键KEY1
  {
  LED1=!LED1;
  }
  EXTI_ClearITPendingBit(EXTI_Line3); //清除LINE3上的中断标志位
  }
  void EXTI4_IRQHandler(void)
  {
  delay_ms(10);
  if(KEY0==0)
  {
  LED0=!LED0;
  LED1=!LED1;
  }
  EXTI_ClearITPendingBit(EXTI_Line4); //清除LINE4上的中断标志位
  }
  void EXTIX_Init(void)
  {
  EXTI_InitTypeDef EXTI_InitStructure; //初始化
  NVIC_InitTypeDef NVIC_InitStructure; //中断优先级
  KEY_Init(); // 按键端口初始化
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟
  //GPIOE.3 中断线以及中断初始化配置 下降沿触发 KEY1
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
  EXTI_InitStructure.EXTI_Line=EXTI_Line3;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
  //GPIOE.4 中断线以及中断初始化配置 下降沿触发 //KEY0
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);
  EXTI_InitStructure.EXTI_Line=EXTI_Line4;
  EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
  //GPIOA.0 上升沿触发 WK_UP
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0);
  EXTI_InitStructure.EXTI_Line=EXTI_Line0;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  EXTI_Init(&EXTI_InitStructure); //根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能按键WK_UP所在的外部中断函数
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  NVIC_Init(&NVIC_InitStructure);
  NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn; //使能按键KEY1所在的外部中断函数
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; //子优先级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //使能按键KEY0所在的外部中断函数
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
  NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  }
  独立看门狗
  STM32自带两个看门狗:独立看门狗(IWDG)和窗口看门狗(WWDG)
  STM32的独立看门狗由内部专门的40khz低速时钟驱动,即使主时钟发生故障,它也仍然有效。这里需要注意独立看门狗的时钟是一个内部RC时钟,所以并不是一个准确的40khz,而是在30-60khz之间。
  原理
  单片机系统在外界的干扰下会出现程序跑飞的现象导致出现死循环,看门狗威廉希尔官方网站 就是为了避免这种情况的发声。看门狗的作用就是在一段时间内(通过定时计数器实现)没有接收喂狗信号(表示MCU已经挂了),便实现处理器的自动复位重启(发送复位信号)。
  键值寄存器IWDG_KR
  
  写入0XCCCC,开始启用独立看门狗;此时计数器开始从其复用值0XFFF递减计数,当计数器计数到末尾0x000时,会产生一个复位信号(IWDG_RESET)。
  IWDG_PR和IWDG_RLR寄存器具有写保护功能。
  启动看门狗
  取消寄存器写保护(向IWDG_KR写入0x5555)
  通过这步,我们取消IWDG_PR和IWDG_RLR的写保护,使后面可以操作这两个寄存器
  IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //开启/取消写保护
  设置独立看门狗的预分频系数和重装载值
  设置看门狗的分频系数函数是:
  void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //设置IWDG预分频值 void IWDG_SetReload(uint16_t Reload); //设置IWDG重装载值 看门狗的喂狗时间,其中prer是分频系数,rlr为看门狗的重装载值
  Tout=((4×2^prer) ×rlr) /40 (ms)
  设定prer = 4,rlr = 625,那么Tout = 64 * 625/40 = 1000ms,这样,看门狗的溢出时间就是1s,也就是说在1s内,只要写入0xAAAA到IWDG_KR,就不会导致看门狗复位。
  重载计数值喂狗(向IWDG_KR写入0xAAAA)
  IWDG_ReloadCounter(); //按照IWDG重装载寄存器的值装载IWDG计数器
  启动看门狗(向IWDG_KR写入0xCCCC)
  IWDG_Enable(); //使能IWDG 通过这句,来启动 STM32 的看门狗。注意 IWDG 在一旦启用,就不能再被关闭!想要关 闭,只能重启,并且重启之后不能打开 IWDG,否则问题依旧,所以在这里提醒大家,如果不 用 IWDG 的话,就不要去打开它,免得麻烦。
  通过上面 4 个步骤,我们就可以启动 STM32 的看门狗了,使能了看门狗,在程序里面就 必须间隔一定时间喂狗,否则将导致程序复位。利用这一点,我们本章将通过一个 LED 灯来指 示程序是否重启,来验证 STM32 的独立看门狗。
  在配置看门狗后,DS0 将常亮,如果 WK_UP 按键按下,就喂狗,只要 WK_UP 不停的按, 看门狗就一直不会产生复位,保持 DS0 的常亮,一旦超过看门狗定溢出时间(Tout)还没按, 那么将会导致程序重启,这将导致 DS0 熄灭一次。
  相关代码
  #include “led.h”
  #include “delay.h”
  #include “key.h”
  #include “sys.h”
  #include “usart.h”
  #include “wdg.h”
  int main(void)
  {
  delay_init();
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  uart_init(115200);
  LED_Init();
  KEY_Init();
  delay_ms(500); //让人看到灭
  IWDG_Init(4,625); //与分频系数64,重载值625,溢出时间1s
  LED0=0; //点亮
  while(1)
  {
  if(KEY_Scan(0)==WKUP_PRES)
  {
  IWDG_Feed();//喂狗
  }
  delay_ms(10);
  };
  }
  //时间计算,大概Tout=((4*2^prer)*rlr)/40 (ms)。
  void IWDG_Init(u8 prer,u16 rlr)
  {
  IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //ʹÄܶԼĴæÆ÷IWDG_PRºÍIWDG_RLRµÄд²Ù×÷
  IWDG_SetPrescaler(prer); //ÉèÖÃIWDGÔ¤·ÖƵֵ:ÉèÖÃIWDGÔ¤·ÖƵֵΪ64
  IWDG_SetReload(rlr); //ÉèÖÃIWDGÖØ×°ÔØÖµ
  IWDG_ReloadCounter(); //°´ÕÕIWDGÖØ×°ÔؼĴæÆ÷µÄÖµÖØ×°ÔØIWDG¼ÆÊýÆ÷
  IWDG_Enable(); //ʹÄÜIWDG
  }
  //喂独立看门狗
  void IWDG_Feed(void)
  {
  IWDG_ReloadCounter();//reload
  }
  小结
  外部中断,在以后的开发过程中,应该会涉及多一点,对于看门狗,我认为仅做参考处理。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分