stm32-2-INTERRUPT-1


1 中断系统

  1. 定义:主程序运行过程中,出现了特定得中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而处理中断程序,处理完成后再返回主程序中暂停的位置继续运行。

    STM32F10x芯片有84个中断通道,包括16个内核中断和68个可屏蔽中断, 包括EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设.

  2. 嵌套向量中断控制器(Nested Vector Interrupt Controller, NVIC)属于M3内核的一个外设,控制着芯片的中断相关功能.

    中断控制相关寄存器在固件库core_cm3.h文件NVIC结构体内。

  3. 中断优先级:每个中断通道都拥有16个可编程的优先等级,而优先级可以分为组成抢占式优先级和响应式优先级。高抢占优先级可以进行中断嵌套,高响应优先级则可以优先响应。比如,当前正在执行中断函数,此时有一个抢占优先级更高的中断信号过来,那么程序将进行中断嵌套;而当这个中断信号的抢占优先级和当前正在执行的中断函数相同,则需要等待当前的中断完成后再进行;而当两个抢占优先级相同的中断同时到达,那么响应优先级高的中断优先响应;而当两个抢占优先级和响应优先级均相同的中断同时到达,则按中断表中的序号决定响应的顺序。

2 外部中断

  1. EXTI可以监测指定的GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将向NVIC发出中断申请,通过NVIC的裁决后即可执行中断程序。

  2. 触发方式

    • 上升沿:由低电平变为高电平瞬间。
    • 下降沿:由高电平变化低电平瞬间。
    • 双边沿:产生电平变化瞬间,这里的电平变化可以指高电平变为低电平和低电平变为高电平。
    • 软件触发:当程序执行了某个函数时自动调用中断程序。
  3. 外部中断/事件线映射: STM32F10x的EXTI具有20个中断/事件线,即16个GPIO_PIN和PVD输出、RTC闹钟、USB唤醒、以太网唤醒.

    所有的GPIO口均可以支持外部中断,但是相同的PIN脚不能同时触发中断。每个GPIO口相同PIN接入了AFIO中的对应位置用于触发对应的外部中断,当不同GPIO口的相同PIN脚均产生中断信号,实际上经过AFIO选择后只会响应其中一个中断。

  4. 响应方式:中断响应/事件响应.

    • 中断响应:申请中断,请求执行对应的中断函数。
    • 事件响应:不触发中断,而是触发其他外设进行操作,属于外设间的联合工作。

3 外部中断初始化

  1. 时钟使能:包括GPIO口时钟、AFIO时钟.(EXTI不需要使能时钟,而NVIC属于内核外设,同样不需要使能时钟)(配置RCC_APB2ENR寄存器)
  2. 配置GPIO:设置为输入/输出模式(具体的外设对应的配置模式需要查询参考手册).
  3. 配置AFIO:选择中断引脚
  4. 配置EXTI的触发方式和响应方式
  5. 配置NVIC选择中断优先级

4 外部中断库函数实现

  1. 使能时钟
  • 函数原型:void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
  • 输入:RCC_APB2Periph门控APB2外设时钟,也就是指定对应的外设来选定其对应的时钟,GPIOx口对应为RCC_APB2Periph_GPIOARCC_APB2Periph_AFIO; NewState: 指定使能(ENABLE)还是禁用(DISABLE)该外设的时钟。
  1. 设置IO口模式
  • 函数原型:void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
  • 输入:GPIOx用于指定使用的GPIO口(注意不是PIN); GPIO_InitStruct为指向GPIO_InitTypeDef类型结构体的指针,其成员为GPIO口的状态(输入/输出模式、输出速度、PIN脚).
  1. 配置AFIO
  • 函数1
    • 函数原型:void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
    • 输入:GPIO_Remap用于指定具体的PIN来进行重映射; NewState: 指定重映射功能的状态.
  • 函数2
    • 函数原型:void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
    • 输入:GPIO_PortSource用于指定GPIO口; GPIO_PinSource用于指定具体的PIN脚.
  1. 配置EXTI
  • 函数1

    • 函数原型:void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
    • 输入: EXTI_InitStruct为指向外部中断初始化结构体的指针.

      typedef struct
      {
          uint32_t EXTI_Line;     // 指定中断线,即将GPIO口的PIN挂到AFIO的输出上
          
          EXTIMode_TypeDef EXTI_Mode;     // 配置响应方式为中断响应还是事件响应 
      
          EXTITrigger_TypeDef EXTI_Trigger;   // 配置触发方式
      
          FunctionalState EXTI_LineCmd;   // 指定EXTI的初始状态
      }EXTI_InitTypeDef;
    • 函数功能:初始化EXTI
  • 函数2
    • 函数原型:void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
    • 输入:EXTI_InitStruct为指向外部中断初始化结构体的指针
    • 功能:
  • 函数3
    • 函数原型:void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
    • 输入:EXTI_Line为指定中断线
    • 功能:用于软件触发
  1. 配置NVIC
  • 函数1
    • 函数原型:void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
    • 输入: NVIC_PriorityGroup为指定中断分组的形式.
    • 功能:中断分组
  • 函数2

    • 函数原型:void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
    • 输入: NVIC_InitStruct为指向NVIC_InitTypeDef初始化结构体的指针.

      typedef struct
      {
          uint8_t NVIC_IRQChannel;  // 指定通道
      
          uint8_t NVIC_IRQChannelPreemptionPriority;  // 指定抢占优先级
      
          uint8_t NVIC_IRQChannelSubPriority;        // 指定响应优先级
      
          FunctionalState NVIC_IRQChannelCmd;    // 指定通道启用或禁用       
      } NVIC_InitTypeDef;
    • 功能:初始化NVIC

  1. 代码实现

  指定PB12为中断通道,配置为下降沿触发、中断响应.

void EXTI_Init(void){
    // 使能GPIOB时钟和AFIO时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    // 配置GPIO口为输入模式
    GPIO_InitStruct GPIO_InitStructrue;
    GPIO_InitStructrue.GPIO_Mode = GPIO_MODE_IPU;
    GPIO_InitStructrue.GPIO_Pin = GPIO_Pin_12;
    GPIO_InitStructrue.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructrue);

    // 配置AFIO,选择PB12作为中断通道
    GPIO_EXTILineConfig(GPIOB_PortSource, GPIO_PinSource_12);

    // 配置EXTI,通道为12,中断响应,下降沿触发,并使能该通道
    EXTI_InitTypeDef EXTI_InitStructrue;
    EXTI_InitStructrue.EXTI_Line = EXTI_Line12;
    EXTI_InitStructrue.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructrue.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructrue.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructrue);

    // 配置NVIC优先级分组,只有一个中断所以随便配置
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    // 初始化NVIC,EXTI10-15共用一个通道,抢占优先级和响应优先级随便,并使能该通道
    NVIC_InitTypeDef NVIC_InitStructtrue;
    NVIC_InitStructtrue.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructtrue.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructtrue.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructtrue.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructtrue)
}

// 中断处理函数
void EXTI15_10_IRQHandler(void){
    // 由于该通道是共有通道,先判断是否为我们设置的通道申请了中断
    if(EXTI_GetITStatus(EXTI_Line_12) == SET){
        // 清除中断标志位,否则会一直进入中断函数
        EXTI_ClearITPendingBit(EXTI_Line_12);

        // 进行中断处理
        ...
    }
}

5 定时器中断

  1. TIM(Timer)定时器可以对输入的时钟进行计数,当计数值达到设定值时触发中断。定时器的计数器为16位,定时最大为59.65s。除了基本的定时中断功能外,还有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能,TIM可以分为高级定时器、通用定时器和基本定时器三种类型。

  1. 对于基本定时器,其时钟只能选择内部时钟,频率为内部时钟频率72MHz,通过预分频器分频来到计数器,当计数器得到一个方波信号则自增1(仅支持向上计数),当计数器的值达到预先设定好的值时就会产生中断,而预先设定的值则是存放在自动重装载寄存器中。预分频器为16位,为0则进行1分频,此时输出频率等于输入频率;为1时进行2分频,输出频率等于输入频率的一半,以此类推;分频器的作用其实就是改变到达计数器的方波间的时间间隔。因此,当预分频器设置为65535、自动重装载寄存器(也是16位)也设置为65535,则达到最大计时时间,为:

  1. CNT_EN未使能时计数器CK_CNT不工作,当CNT_EN使能后CK_CNT才开始计数。而预分频控制分为两个寄存器,一个未预分频控制寄存器,是提供给用户进行写操作来修改分频,另一个则是预分频缓冲器,是定时器中实际操控分频的寄存器;当用户通过预分频控制寄存器修改分频时,分频会写入预分频控制寄存器而不会直接写入预分频缓冲器,即定时器并不会马上对分频进行修改,而是在中断产生后才修改分频,这可以避免同个定时周期内前后频率不一致的问题(可以通过修改ARPE寄存器来控制是否预装时序)。根据下面的时序图可以知道,当定时器的计数器CK_CNT达到设定值时并不会马上触发中断,而是需要等到下一个上升沿到来之后才进行中断并重装。而分频的实现实际上也是通过计数器实现,即预分频计数器,当预分频计数器重装时才会输出一个方波信号。

6 定时器中断初始化

  1. 使能时钟:RCC使能时钟
  2. 配置时钟源: 选择使用内部时钟源还是外部时钟源
  3. 配置时基单元:包括PSC、CNT、ARR
  4. 配置输出中断控制
  5. 配置NVIC
  6. 使能计数器

7 定时器中断库函数实现

  1. 使能时钟

  2. 配置时钟源

  • 函数1
    • 函数原型:void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
    • 输入:TIMx选择使用的定时器编号
    • 功能:时钟源选择内部时钟
  • 函数2
    • 函数原型:void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
    • 输入:TIMx选择使用的定时器编号, TIM_InputTriggerSource选择接入的定时器
    • 功能:选择ITRx其他定时器的时钟,即实现定时器的串联
  • 函数3
    • 函数原型:void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);
    • 输入:TIMx选择使用的定时器编号, TIM_TIxExternalCLKSource选择TIx具体的引脚,TIM_ICPolarity选择极性,ICFilter选择滤波器
    • 功能:选择TIx捕获通道的时钟
  • 函数4
    • 函数原型:void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
    • 输入:TIMx选择使用的定时器编号,TIM_ExtTRGPrescaler对ETR外部时钟进行预分频,TIM_ExtTRGPolarity选择极性,ExtTRGFilter选择滤波器。
    • 功能:使用ETR通过外部时钟模式1输入的时钟
  • 函数5
    • 函数原型:void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
    • 输入:TIMx选择使用的定时器编号,TIM_ExtTRGPrescaler对ETR外部时钟进行预分频,TIM_ExtTRGPolarity选择极性,ExtTRGFilter选择滤波器。
    • 功能:使用ETR通过外部时钟模式2输入的时钟
  • 函数6
    • 函数原型:void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
    • 输入:TIMx选择使用的定时器编号,TIM_ExtTRGPrescaler选择预分频,TIM_ExtTRGPolarity选择极性,ExtTRGFilter选择滤波器。
    • 功能:配置ETR引脚的预分频器、极性、滤波器等参数。
  1. 配置时基单元
  • 函数1
    • 函数原型:void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
    • 输入:TIMx选择使用的定时器编号,TIM_TimeBaseInitStruct为指向定时器初始化结构体TIM_TimeBaseInitTypeDef类型的指针, 结构体定义为:
      typedef struct
      {
      uint16_t TIM_Prescaler;    // 设置预分频器  
      
      uint16_t TIM_CounterMode;  // 设置计数模式
      
      uint16_t TIM_Period;       // ARR自动重装器的值
      
      uint16_t TIM_ClockDivision;     // 设置时钟分频,即采样频率
      
      uint8_t TIM_RepetitionCounter;  // 重复计数器的值
      } TIM_TimeBaseInitTypeDef; 
    • 功能:配置时基单元
  • 函数2
    • 函数原型:void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
    • 输入:TIMx选择使用的定时器编号,Prescaler选择预分频,TIM_PSCReloadMode配置是否采用影子寄存器.
    • 功能:配置分频
  • 函数3
    • 函数原型:void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
    • 输入:TIMx选择使用的定时器编号,TIM_CounterMode选择计数模式(向上、向下、中央对称).
    • 功能:配置计数器
  • 函数4
    • 函数原型:void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
    • 输入:TIMx选择使用的定时器编号,NewState选择是否有预装功能
    • 功能: 自动重装载寄存器预装功能配置
  • 函数5
    • 函数原型:void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
    • 功能:给计数器写入一个值
  • 函数6
    • 函数原型:void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
    • 功能:给自动重装器写入一个值
  • 函数7
    • 函数原型:uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
    • 功能:读取计数器当前的值
  • 函数8
    • 函数原型:uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
    • 功能:读取预分频器的值
  • 函数9-12
    • FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);:获取定时器的中断标志位
    • void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);:清除定时器的中断标志位
  1. 配置输出中断控制
  • 函数原型:void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
  • 输入:TIMx选择使用的定时器编号,TIM_IT选择中断输出,NewState为使能/禁用定时器中断
  1. 配置NVIC
  2. 使能计数器
  • 函数原型:void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
  • 输入:TIMx选择使用的定时器编号,NewState为使能/禁用定时器
  1. 代码实现

  配置初始化定时器TIM2, 定时为1s.

void TIM_Init(void){
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    TIM_InternalClockConfig(TIM2);  // 默认使用内部时钟,可不写

    // 配置时基单元
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructrue;
    TIM_TimeBaseInitStructrue.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructrue.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructrue.TIM_Prescaler = 7200 - 1;;
    TIM_TimeBaseInitStructrue.TIM_Period = 10000 - 1;
    TIM_TimeBaseInitStructrue.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructrue);
    
    // 配置完时基单元后会将中断标志位置1,这里需要将标志位清空
    TIM_ClearFlag(TIM2, TIM_IT_Update);
    // 使能更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); 

    // 配置NVIC
    // 配置NVIC优先级分组,只有一个中断所以随便配置
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    // 初始化NVIC,EXTI10-15共用一个通道,抢占优先级和响应优先级随便,并使能该通道
    NVIC_InitTypeDef NVIC_InitStructtrue;
    NVIC_InitStructtrue.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructtrue.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructtrue.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructtrue.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructtrue)

    // 使能定时器中断
    TIM_Cmd(TIM2, ENABLE);
}

void TIM2_IRQHandler(void){
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
        // 中断操作
        ...
    }
}

文章作者: Vyron Su
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vyron Su !