STM32 HAL库中的定时器中断简介

STM32 HAL库-定时器中断

  • <font color=red>STM32 关闭所有中断
  • 一、STM32F407定时器介绍
  • 定时器计算公式
  • 二、CubeMX配置定时器
  • 三、基本定时器中断配置流程
  • 1)开启定时器时钟
  • 2)初始化定时器参数,设置自动重装值,分频系数,计数方式等
  • 3)使能定时器更新中断,开启定时器计数,配置定时器中断优先级
  • 4)编写中断服务函数(主要是重写回调函数,覆盖原来的)
  • 注意:
  • 四、参考资料
  • 1 HAL库的中断处理
  • 1.1 HAL 库的中断封装
  • 1.2 外部中断处理流程
  • 五、示例

  • STM32 关闭所有中断

    关闭或开启所有中断;代码如下:

    __set_PRIMASK(1);//关总中断
    
    __set_PRIMASK(0);//开总中断
    

    一、STM32F407定时器介绍

    STM32F407 有众多的定时器,其中包括 2 个基本定时器(TIM6 和 TIM7)、10 个通用定时
    器(TIM2 ~ TIM5、TIM9 ~TIM14)、2 个高级控制定时器(TIM1 和 TIM8),这些定时器彼此完
    全独立,不共享任何资源。

    选择定时器,配置时钟源,设置预分频系数,计数模式,设置自动重装载值

    定时器计算公式

    不过一般情况下定时时间都是毫秒级别。
    时钟频率是84Mhz这里进行84分频,就是1Mhz,也就是每秒10的6次方个脉冲。而重装载值是1000,每秒可以触发1000个中断,也就是定时时间是1ms。

    PSC = 84 -1 = 83;
    ARR = 1000 -1 = 999;

    二、CubeMX配置定时器

    详见正点原子视频教程

    三、基本定时器中断配置流程

    1)开启定时器时钟

    HAL 中定时器使能是通过宏定义标识符来实现对相关寄存器操作的,方法如下:
    __HAL_RCC_TIMx_CLK_ENABLE(); /* x=1~14 */

    2)初始化定时器参数,设置自动重装值,分频系数,计数方式等

    定时器的初始化参数是通过定时器初始化函数 HAL_TIM_Base_Init 实现的。
    注意:该函数会调用:HAL_TIM_Base_MspInit 函数,我们可以通过后者存放定时器时钟
    和中断等初始化的代码。

    3)使能定时器更新中断,开启定时器计数,配置定时器中断优先级

    通过 HAL_TIM_Base_Start_IT 函数使能定时器更新中断和开启定时器计数。

    通过 HAL_NVIC_EnableIRQ 函数使能定时器中断,通过 HAL_NVIC_SetPriority 函数设置
    中断优先级。

    4)编写中断服务函数(主要是重写回调函数,覆盖原来的)

    定时器中断服务函数为:TIMx_IRQHandler 等,当发生中断的时候,程序就会执行中断服
    务函数。
    HAL 库提供了一个定时器中断公共处理函数 HAL_TIM_IRQHandler,该函数又会调用HAL_TIM_PeriodElapsedCallback 等一些回调函数,需要用户根据中断类型选择重定义对应的中
    断回调函数来处理中断程序。

    注意:

    1、中断处理最好写在回调函数里面。

    往往不止一种情况触发同一个中断,各个情况都有对应回调函数,库函数会去检查相关标志位并调用对应的回调函数,这样就不需要我们自己去检查中断标志来确定中断来源以及清除标志位,比如上面的中断程序可以写在void HAL_TIM_PeriodElapsedCallback里面。
    库的回调函数都有__weak修饰,我们可以自己定义回调函数。

    2、时钟与它的中断使能可以用函数HAL_TIM_Base_Start_IT()。

    四、参考资料

    这篇文章介绍的非常不错: 【STM32】基于HAL库的中断详细学习

    1 HAL库的中断处理

    1.1 HAL 库的中断封装

    在上一节中我们介绍了中断的处理过程:当中断发生时,处理器暂停当前程序,根据中断类型号在中断向量表中查找中断服务程序的入口地址,进而执行中断服务程序。执行完成后,返回断点处执行原程序。虽然上述的操作大部分是由硬件完成,但是在针对具体外设的中断处理中,还需要用户完成以下的编程步骤:

    ① 设置中断触发条件。如外部中断的边沿触发,定时器更新中断的时间间隔。

    ② 设置中断优先等级。系统中存在多个中断时,需要根据任务的紧迫程度,设置不同的中断优先级。

    ③ 使能外设的某个中断。微控制器片内集成的外设一般都有多个中断源,这些中断源的使能由外设的寄存器控制。例如,串口通信的发送数据寄存器空中断TXE和发送完成中断TC的使能,就是由串口控制寄存器CR1中的TXEIE位和TCIE位控制。

    ④ 判断中断源。对于有多个中断源的外设,进入中断服务程序后,需要根据中断标志来判断具体发生了哪一种中断。

    ⑤ 清除中断标志。发生中断后,相应的中断标志位置位,以便MCU查询。这些中断标志要注意及时清除,避免重复进入中断。

    ⑥ 编写程序。用户根据实际项目的需求来编写中断发生时应该执行的操作。

    在上述的六个中断编程步骤中,借助CubeMX软件可以通过图形化的设置方式完成前三个步骤,而后三个步骤可以借助HAL库提供的接口函数进一步简化。

    在HAL库中,将中断的处理过程进行了重新的封装,主要有以下几点:

    1.统一定义了各个外设的中断通用处理所两数HAL_PPP_IRQHandler()(PPP代表外设名称)。

    2.在中断通用处理函数HAL_PPP_IRQHandler()中完成中断标志的判断和清除。

    3.将中断中需要执行的操作以回调函数的形式提供给用户,回调函数由中断通用处理函数调用。

    回调函数是一个通过函数指针调用的函数。如果把一个函数的指针(即函数的地址)作为另一个函数的参数时,当这个指针被用来调用其所指向的函数时,这个被调用的函数就称为回调函数。

    我们通过一个例子来进一步说明回调函数的概念。假设有三个函数:Func1、Func2和 Func3。函数Func1调用函数Func2,同时将函数Func3作为形参传递给Func2,此时,Fun1 可以看作是应用层函数,Func2可以看作是中间层函数,Func3则称为回调函数。

    在实际的程序设计中,回调函数一般位于用户程序中,和主程序同属于应用层,而回调函数的调用方通常是第三方提供的库函数。回调函数的执行,相当于从库函数调用应用层的函数,因此称为回调。

    回调函数一般用于执行具体的操作,需要用户根据实际项目的需求来编写。无法封装到第三方的库函数里面。因此,库函数提供一个函数指针作为入口参数,主程序将回调函数像参数一样传入库函数。这样一来,只要改变传进库函数的参数,就可以实现不同的功能,并且不需要修改库函数的代码,确保了应用层和库函数的解耦。

    以中断服务程序的处理为例:判断中断源,清除中断标志这些流程对任何一个中断都相同的。但是,某一个中断发生之后执行什么具体的操作,却各不相同。比如,1s的定时中断到了,是开启一个指示灯,还是使蜂鸣器鸣响,都由用户根据实际项目的需求来编写。因此,HAL库将中断中需要执行的操作以回调函数的形式提供给用户,简化了中断服务程序的设计。

    HAL库在设计回调函数的时候,采用了更为巧妙的方法:在需要使用回调函数的地方,预定义了一个默认的回调函数,该函数的属性设计为“weak”,函数内部不执行任何操作。weak属性的函数表示:如果该函数没有在其他文件中定义,则使用该函数;如果用户在其他地方定义了该函数,则使用用户定义的函数。当用户需要使用回调函数时,可以在应用程序中重新定义一个同名的回调函数,在函数中实现具体的操作。这样,编译器在编译的时候,就会选择用户重新定义的回调函数。

    在利用CubeMX软件生成的MDK工程中,与中断处理相关的文件一共有两个:

    1.启动文件:startup_stm32fxxx.s (xxx表示芯片型号)

    在启动文件中,创建了中断向量表,并预先为每一个中断编写了中断服务程序。这些断服务程序的内部都是死循环,不执行任何具体操作,其目的只是为了初始化中断向量表。中断服务程序的属性设置为“weak”,用户可以在用户文件中重新定义一个同名函数作为实际执行的中断服务程序。

    2.中断服务程序文件:stm32fxxx_it.c (xxx表示芯片型号)

    中断服务程序文件用于存放各个中断实际执行的中断服务程序,这些中断服务程序可以分成两个部分:

    第一部分是Cortex-M内核处理器的异常处理程序,包括不可屏蔽中断(NMI)类型错误(HardFault)以及系统节拍定时器中断(Systick)等。

    第二部分是微控制器片内外设的中断服务程序。在使用CubeMX软件进行初始化配置时,如果使能了某一个外设的中断功能,那么在生成代码时,对应外设的中断服务程序将会自动添加到stm32fxxx_it.c文件中,在中断服务程序中再调用该外设所对应的中断通用处理函数HAL_PPP_IRQHandler()。

    注意:中断服务程序的函数名在中断向量表中已经由ST公司预先规定,用户必须按照这个规定的函数名来编写中断服务程序。发生中断时,中断服务程序才能被正确调用。

    1.2 外部中断处理流程

    下面我们以GPIO引脚触发的外部中断为例,来详细分析一下HAL库的中断处理流程。假设芯片型号为STM32F411,设置引脚PC0和PC13为外部中断功能(即PC0和PC13与对应的外部中断线EXTI0和EXTI13相连),并使能对应的外部中断。当引脚PC0或PC13出现电平变化时,将触发外部中断,具体的执行过程如下:

    1. 查找中断向量表,跳转到中断服务程序

    微控制器暂停当前程序的执行,根据中断向量表跳转到EXTIO_IRQHandler()(外部中断线0的中断服务程序)或者EXTI15 10_IRQHandler()(外部中断线10~15的中断服务程序)执行,具体执行哪一个中断服务程序由中断发生的先后顺序及中断的优先级决定。启动文件startup_stm32f411xe.s的中断向量表中预先定义了外部中断线所对应的中断服务程序,如表所示。这些定义的函数都加入了weak属性。

    2.执行中断服务程序

    启动文件startup_stm32f411xe.s中定义的中断服务程序默认为死循环,不执行任何具体操作。由于这些函数定义为weak属性,因此用户可以在stm32f4xx_it.c文件中重新定义与该函数同名的中断服务程序。当中断发发生时,将执行在stm32f4xx_it.c文件中重新定义的中断服务程序。

    我们在CubeMX软件中使能外部中断线发的中断功能后,将在stm32f4xx_it.c文件中自动添加相关的外部中断服务程序,具体代码支如程序清单所示。

    /*
    * @brief This function handles EXTI line0 interrupt.
    */
    void EXTI0_IRQHandler(void) // 外部中断线日的中断服务程序 
    {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 调用外部中断通用处理函数
    }
    /*
    *@brief This function handles EXTI line[15:10] interrupts.
    */
    void EXTI15_10_IRQHandler(void) // 外部中断线 10~15 的中断服务程序 
    {
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); // 调用外部中断通用处理函数
    }
    

    3. 执行外部中断通用处理函数

    进入外部中断服务程序EXTIO_IRQHa andler()或EXTI15_10_IRQHandler()后,都将调用HAL库提供的外部中断通用处理函数数HAL_GPIO_EXTI_IRQHandler()。该函数作为HAL库提供的外部中断接口函数,在stm32f4xx_hal_gpio.c文件中定义。

    由于所有通过GPIO引脚触发的外部中断都会调用外部中断通用处理函数,因此在函数内部需要判断本次中断具体是由哪一个GPIO引脚触发的,并清除对应的中断标志最后再调用外部中断回调函数HAL_GPIO_ EXTI_Callback()完成具体的中断处理任务。

    外部中断通用处理函数的代码如程序清单所示。

    /*
    * @brief This function handle s EXTI interrupt request.
    * @param GPIO_Pin Specifes the pins connected EXTI line
    * @retval None
    */
    void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
    {
    /* EXTI line interrupt detected */
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin)!= RESET)// 检测对应的中断标志是否置位
    {
    _HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); // 清除对应的中断标志 
    HAL_GPIO_EXTI_Callback(GPIO_Pin); // 调用回调函数执行具体任务 
    }
    }
    

    4.默认的外部中断回调函数

    外部中断回调函数HAL_GPIO_EXTI Callback()用于完成具体的中断处理任务。该函数也是HAL库提供的外部中断接口函数数,在stm32f4xx_hal_gpio.c文件中定义。默认的外部中断回调函数添加了weak属性,函数内部没有任何可执行代码,仅仅有一个避免编译器警告的语句:UNUSED(GPIO_Pin)。

    默认的外部中断回调函数的代码如程序清单所示。

    /*
    * @brief EXTI line detection callbacks.
    * @param GPIO_Pin Specifes the pins connected EXTI line
    * @retval None
    */
    _weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
    /* Prevent unused argument(s) compilation warning */
    UNUSED(GPIO_Pin);
    /*NOTE:This function Should not be modifed, when the callback is needed,
    the HAL_GPIO_EXTI_Callback could be implemented in the user file*/
    }
    

    5.执行用户编写的外部中断回调函数

    用户需要在main.c文件中重新编写外部中断回调函数HAL_GPIO_EXTI_Callback(),来完成具体的中断处理任务。中断回调函数一般添加在/* USER CODE BEGIN4*/和/* USER CODE END4*/之间。由于所有通过于GPIO引脚触发的外部中断都会调用该回调函数,因此在回调函数内部,需要根据入口参参数GPIO_Pin判断是哪一个GPIO引脚触发的本次外部中断,然后再执行不同的中断处理任务。如果用户设置了多个GPIO引脚产生外部中断,则可以使用switch-case多分支语句进行判断。

    外部中断回调函数的示例代码如程序法清单所示。

    /* USER CODE BEGIN 4 */
    void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
    {
    switch(GPIO_Pin)
    {
    case GPIO_PIN_0: // 引脚 PCO 对应的中断处理任务 
    /* GPIO_PIN_0 EXTI handling */
    break;
    case GPIO_PIN_13:
    /*GPIO_PIN_13 EXTI handling */ // 引脚 PC13 对应的中断处理任务 
    break;
    case GPIO_PIN_X; // 引脚x对应的中断处理任务 
    /* GPIO_PIN_X EXTI handling */ 
    break;
    ……
    default:
    break;
    }
    }
    /* USER CODE END 4 */
     
    

    根据上述的分析,我们可以用下图来更加清晰地展示HAL库的外部中断处理流程。

    从下图可以看到,发生外部中断时处理器在中断向量表找到所对应的中断服务程序,在中断服务程序内部调用外部中断通通用处理函数HAL_GPIO_EXTI_IRQHandler( ),最后再调用外部中断回调函数HAL_GP O_EXTICallback()来完成具体的中断处理任务。

    在HAL库的外部中断处理流程中,外部中断回调函数HAL_GPIO_EXTI_Callback()是放在main.c文件中,作为应用程序的一部分,而它的调用却是由HAL库提供的接口函数触发的,即库函数反过来调用应用层的函类数,因此称为回调函数。

    借助CubeMX软件和HAL库,用户的中中断编程步骤大大简化,只需要利用CubeMX软件进行中断的相关配置,包括中断触发条件、中断优先级以及中断使能等,最后在中断回调函数中编写具体的中断处理任务即可。

    五、示例

    借助CubeMX软件和HAL库,定时器中断调用HAL_TIM_IRQHandler函数,发现它内部是这么实现中断处理的:

    由于我们只使用了最基本的定时器更新中断,所以在这个函数里,只会进入TIM Update event这个处理内部,清除中断标志后,还会调用HAL_TIM_PeriodElapsedCallback这个函数。


    而HAL_TIM_PeriodElapsedCallback这个函数是这么实现的:

    它有一个__weak关键词修饰,函数内部是空的,没做任何处理。

    这种有__weak修饰的函数,是可以被用户重写的。HAL_TIM_PeriodElapsedCallback这类函数一般称为回调函数,是留给用户的接口函数,用于给用户重写实现,以加入自己的功能。

    //定时器开启的时候记得设置CUBE里面的NVIC
    uint16_t t3_count;
    uint16_t t3_count2;
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *hitm)
    {
    	if(hitm ->Instance ==TIM1)
    	{
    	}
    	if(hitm ->Instance ==TIM3)//定时器3,定时时间10ms
    	{
    		t3_count++;
    		if(t3_count>=100)//1s
    		{
    			t3_count=0;
    			Led0_Toggle;
    			t3_count2++;
    			if(t3_count2>=10)//10s
    			{
    				Led1_Toggle;
    				t3_count2=0;
    			}
    		}
    	}
    
    }
    
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库中的定时器中断简介

    发表评论