一、简介
中断是 CPU 的一种常见特性,中断一般由硬件产生,当中断发生后,会中断 CPU 当前正
在执行的程序而跳转到中断对应的服务程序种去执行,ARM Cortex-M 内核的 MCU 具有一个
用于中断管理的嵌套向量中断控制器(NVIC,全称:Nested vectored interrupt controller)。
ARM Cortex-M 的 NVIC 最大可支持 256 个中断源,其中包括 16 个系统中断和 240 个外部
中断。然而芯片厂商一般情况下都用不完这些资源,以正点原子的战舰开发板为例,所使用的
STM32F103ZET6 芯片就只用到了 10 个系统中断和 60 个外部中断。
二、中断优先级分组设置
ARM Cortex-M 使用 NVIC 对不同优先级的中断进行管理,首先看一下 NVIC 在 CMSIS 中
的结构体定义,如下所示:
typedef struct { __IOM uint32_t ISER[8U]; /* 中断使能寄存器 */ uint32_t RESERVED0[24U]; __IOM uint32_t ICER[8U]; /* 中断除能寄存器 */ uint32_t RSERVED1[24U]; __IOM uint32_t ISPR[8U]; /* 中断使能挂起寄存器 */ uint32_t RESERVED2[24U]; __IOM uint32_t ICPR[8U]; /* 中断除能挂起寄存器 */ uint32_t RESERVED3[24U]; __IOM uint32_t IABR[8U]; /* 中断有效位寄存器 */ uint32_t RESERVED4[56U]; __IOM uint8_t IP[240U]; /* 中断优先级寄存器 */ uint32_t RESERVED5[644U]; __OM uint32_t STIR; /* 软件触发中断寄存器 */ } NVIC_Type;
复制
在 NVIC 的相关结构体中,成员变量 IP 用于配置外部中断的优先级,成员变量 IP 的定义
如下所示:
__IOM uint8_t IP[240U]; /* 中断优先级寄存器 */
复制
可以看到成员变量 IP 是一个 uint8_t 类型的数组,数组一共有 240 个元素,数组中每一个
8bit 的元素就用来配置对应的外部中断的优先级。
综上可知,ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就
是中断优先级配置寄存器,因此最大中断的优先级配置范围位 0~255。但是芯片厂商一般用不
完这些资源,对于 STM32,只用到了中断优先级配置寄存器的高 4 位[7:4],低四位[3:0]取零处
理,因此 STM32 提供了最大 2^4=16 级的中断优先等级,如下图所示:
中断优先级配置寄存器的值与对应的优先等级成反比,即中断优先级配置寄存器的值越小,
中断的优先等级越高。
STM32 的中断优先级可以分为抢占优先级和子优先级,抢占优先级和子优先级的区别如下:
**抢占优先级:**抢占优先级高的中断可以打断正在执行但抢占优先级低的中断,即中断嵌套。
**子优先级:**抢占优先级相同时,子优先级高的中断不能打断正在执行但子优先级低的中的
中断,即子优先级不支持中断嵌套。
注意:中断优先级数值越小越优先
STM32 中每个中断的优先级就由抢占优先级和子优先级共同组成,使用中断优先级配置寄
存器的高 4 位来配置抢占优先级和子优先级,那么中断优先级配置寄存器的高 4 位是如何分配
设置抢占优先级和子优先级的呢?一共由 5 种分配方式,对应这中断优先级分组的 5 个组,优
先级分组的 5 种分组情况在 HAL 中进行了定义,如下所示:
#define NVIC_PRIORITYGROUP_0 0x00000007U /* 优先级分组 0 */ #define NVIC_PRIORITYGROUP_1 0x00000006U /* 优先级分组 1 */ #define NVIC_PRIORITYGROUP_2 0x00000005U /* 优先级分组 2 */ #define NVIC_PRIORITYGROUP_3 0x00000004U /* 优先级分组 3 */ #define NVIC_PRIORITYGROUP_4 0x00000003U /* 优先级分组 4 */
复制
优先级分组对应的抢占优先级和子优先级分配方式如下表所示:
STM32 的中断即要设置中断优先级分组又要设置抢占优先级和子优先级,看起来十分复杂。
其实对于 FreeRTOS,FreeRTOS 的官方强烈建议 STM32 在使用 FreeRTOS 的时候,使用中断优先级分组 4(NVIC_PriorityGroup_4)即优先级配置寄存器的高 4 位全部用于抢占优先级,不使用子优先级,这么一来用户就只需要设置抢占优先级即可,本教程配套的例程源码也全部将中断优先级分组设置为中断优先级分组 4(NVIC_PriorityGroup_4),如下所示:
/* Set Interrupt Group Priority */ HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
复制
三、相关寄存器
1. 三个系统中断优先级配置寄存器
除了外部中断,系统中断有独立的中断优先级配置寄存器,分别为 SHPR1、SHPR2、SHPR3,下面就分别来看一下这三个寄存器的作用。
1. SHPR1
SHPR1 寄存器的地址为 0xE000ED18,用于配置 MemManage、BusFault、UsageFault 的中
断优先级,各比特位的功能描述如下表所示:
2. SHPR2
SHPR2 寄存器的地址为 0xE000ED1C,用于配置 SVCall 的中断优先级,各比特位的功能
描述如下表所示:
3. SHPR3
SHPR3 寄存器的地址为 0xE000ED20,用于配置 PendSV、SysTick 的中断优先级,各比特
位的功能描述如下表所示:
FreeRTOS 在配置 PendSV 和 SysTick 中断优先级的时,就使用到了 SHPR3 寄存器。
2.三个中断屏蔽寄存器
ARM Cortex-M 有三个用于屏蔽中断的寄存器,分别为 PRIMASK、FAULTMASK 和
BASEPRI。
1. PRIMASK
作用:PRIMASK 寄存器有 32bit,但只有 bit0 有效,是可读可写的,将 PRIMASK 寄存器
设置为 1 用于屏蔽除 NMI 和 HardFault 外的所有异常和中断,将 PRIMASK 寄存器清 0 用于使
能中断。
2. FAULTMASK
作用:FAULTMASK 寄存器有 32bit,但只有 bit0 有效,也是可读可写的,将 FAULTMASK
寄存器设置为 1 用于屏蔽除 NMI 外的所有异常和中断,将 FAULTMASK 寄存器清零用于使能
中断。
3. BASEPRI
作用:BASEPRI 有 32bit,但只有低 8 位[7:0]有效,也是可读可写的。BASEPRI 寄存器比
起 PRIMASK 和 FAULTMASK 寄存器直接屏蔽掉大部分中断的方式,BASEPRI 寄存器的功能显得更加细腻,BASEPRI 用于设置一个中断屏蔽的阈值,设置好 BASEPRI 后,中断优先级低
于 BASEPRI 的中断就都会被屏蔽掉,FreeRTOS 就是使用 BASEPRI 寄存器来管理受 FreeRTOS管理的中断的,而不受 FreeRTOS 管理的中断,则不受 FreeRTOS 的影响。
3.中断控制状态寄存器
中断状态状态寄存器(ICSR)的地址为 0xE000ED04,用于设置和清除异常的挂起状态,
以及获取当前系统正在执行的异常编号,各比特位的功能描述如下表所示:
这个寄存器主要关注 VECTACTIVE 段[8:0],通过读取 VECTACTIVE 段就能够判断当前执行
的代码是否在中断中。
四、 中断配置项
FreeRTOSConfig.h 文件中有 6 个与中断相关的 FreeRTOS 配置项,
1. configPRIO_BITS
此宏是用于辅助配置的宏,主要用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY
和宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的,此宏应定义为 MCU 的 8 位优先级配置寄存器实际使用的位数,因为 STM32 只使用到了中断优先级配置寄存器的高 4 位,因此,此宏应配置为 4。
2. configLIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY 的,此宏应设置为 MCU
的最低优先等级,因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此 MCU 的最低优先等级就是 2^4-1=15,因此,此宏应配置为 15。
3. configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
此宏是用于辅助配置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的,此宏适用于配
置 FreeRTOS 可管理的最高优先级的中断,此功能就是操作 BASEPRI 寄存器来实现的。此宏的值可以根据用户的实际使用场景来决定,本教程的配套例程源码全部将此宏配置为 5,即中断优先级高于 5 的中断不受 FreeRTOS 影响,如下图所示:
4. configKERNEL_INTERRUPT_PRIORITY
此宏应配置为 MCU 的最低优先级在中断优先级配置寄存器中的值,在 FreeRTOS 的源码
中,使用此宏将 SysTick 和 PenSV 的中断优先级设置为最低优先级。因为 STM32 只使用了中
断优先级配置寄存器的高 4 位,因此,此宏应配置为最低中断优先级在中断优先级配置寄存器
高 4 位的表示,即(configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。
5. configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏用于配置 FreeRTOS 可管理的最高优先级的中断,在 FreeRTOS 的源码中,使用此宏来
打开和关闭中断。因为 STM32 只使用了中断优先级配置寄存器的高 4 位,因此,此宏应配置为(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。
6. configMAX_API_CALL_INTERRUPT_PRIORITY
此宏为宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的新名称,只被用在 FreeRTOS
官方一些新的移植当中,此宏于宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 是等价的。
五、中断管理详解
1.PendSV 和 SysTick 中断优先级
FreeRTOS 使用 SHPR3 寄存器配置 PendSV 和 SysTick 的中断优先级,那么 FreeRTOS 是如何配置的呢?在 FreeRTOS 的源码中有如下定义:
#define portNVIC_SHPR3_REG \ ( *( ( volatile uint32_t * ) 0xe000ed20 ) ) #define portNVIC_PENDSV_PRI \ ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL ) #define portNVIC_SYSTICK_PRI \ ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
复制
可以看到宏 portNVIC_SHPR3_REG 被定义成了一个指向 0xE000ED20 地址的指针,而
0xE000ED20 就是 SHPR3 寄存器地址的指针,因此只需通过宏 portNVIC_SHPR3_REG 就能够访问 SHPR3 寄存器了。
接 着 是 宏 portNVIC_PENDSV_PRI 和 宏 portNVIC_SYSTICK_PRI 分 别 定 义 成 了 宏
configKERNEL_INTERRUPT_PRIORITY 左 移 16 位 和 24 位 , 其 中 宏
configKERNEL_INTERRUPT_PRIORITY 在 FreeRTOSConfig.h 文件中被定义成了系统的最低优先等级,而左移的 16 位和 24 位,正好是 PendSV 和 SysTick 中断优先级配置在 SHPR3 寄存器中的位置,因此只需将宏 portNVIC_PENDSV_PRI 和宏 portNVIC_SYSTICK_PRI 对应地写入SHPR3 寄存器,就能将 PendSV 和 SysTick 的中断优先级设置为最低优先级。
接着 FreeRTOS 在启动任务调度器的函数中设置了 PendSV 和 SysTick 的中断优先级,代码
如下所示:
BaseType_t xPortStartScheduler( void ) { /* 忽略其他代码 */ /* 设置 PendSV 和 SysTick 的中断优先级为最低中断优先级 */ portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI; portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI; /* 忽略其他代码 */ }
复制
2.FreeRTOS 开关中断
FreeRTOS 使用 BASEPRI 寄存器来管理受 FreeRTOS 管理的中断,而不受FreeRTOS 管理的中断不受 FreeRTOS 开关中断的影响,那么 FreeRTOS 开关中断是如何操作的呢?首先来看一下 FreeRTOS 开关中断的宏定义,代码如下所示:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) #define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS() #define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()
复制
根据上面代码,再来看一下函数 vPortRaiseBASEPRI()和函数 vPortSetBASEPRI(),具体的
代码如下所示:
1. 函数 vPortRaiseBASEPRI()
{ uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* 设置 BasePRI 寄存器 */ msr basepri, ulNewBASEPRI dsb isb } }
复制
可以看到,函数 vPortRaiseBASEPRI() 就是将 BASEPRI 寄 存 器 设 置 为 宏
configMAX_SYSCALL_INTERRUPT_PRIORITY 配置的值。
这里再简单介绍一下 DSB 和 ISB 指令,DSB 和 ISB 指令分别为数据同步隔离和指令同步
隔离,关于 DSB 和 ISB 指令更详细内容,感兴趣的读者请自行查阅相关资料。
2. 函数 vPortSetBASEPRI()
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ) { __asm { /* 设置 BasePRI 寄存器 */ msr basepri, ulBASEPRI } }
复制
可以看到,函数 vPortSetBASEPRI()就是将 BASEPRI 寄存器设置为指定的值。
下面再来看看 FreeRTOS 中开关中断的两个宏定义:
1. 宏 portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
复制
从上面的宏定义可以看出,FreeRTOS 关闭中断的操作就是将 BASEPRI 寄存器设置为宏
configMAX_SYSCALL_INTERRUPT_PRIORITY 的值,以此来达到屏蔽受 FreeRTOS 管理的中断,而不影响到哪些不受 FreeRTOS 管理的中断。
2. 宏 portENABLE_INTERRUPTS()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
复制
从上面的宏定义可以看出,FreeRTOS 开启中断的操作就是将 BASEPRI 寄存器的值清零,
以此来取消屏蔽中断。
3.FreeRTOS 进出临界区
临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。例
如一些使用软件模拟的通信协议,通信协议在通信时,必须严格按照通信协议的时序进行,不
能被打断。FreeRTOS 在进出临界区的时候,通过关闭和打开受 FreeRTOS 管理的中断,以保护临界区中的代码。FreeRTOS 的源码中就包含了许多临界区的代码,这部分代码都是用临界区进行保护,用户在使用 FreeRTOS 编写应用程序的时候,也要注意一些不能被打断的操作,并为这部分代码加上临界区进行保护。
对于进出临界区, FreeRTOS 的源码中 有 四 个 相关的宏定义 , 分 别 为
taskENTER_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 、 taskEXIT_CRITICAL() 、taskEXIT_CRITICAL_FROM_ISR(x),这四个宏定义分别用于在中断和非中断中进出临界区,定义代码如下所示:
/* 进入临界区 */ #define taskENTER_CRITICAL() portENTER_CRITICAL() #define portENTER_CRITICAL() vPortEnterCritical() /* 中断中进入临界区 */ #define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR() #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI() /* 退出临界区 */ #define taskEXIT_CRITICAL() portEXIT_CRITICAL() #define portEXIT_CRITICAL() vPortExitCritical() /* 中断中退出临界区 */ #define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x) #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
复制
下面分别来看一下这四个进出临界区的宏定义。
1. 宏 taskENTER_CRITICAL()
此宏用于在非中断中进入临界区,此宏展开后是函数 vPortEnterCritical() ,函数
vPortEnterCritical()的代码如下所示:
void vPortEnterCritical( void ) { /* 关闭受 FreeRTOS 管理的中断 */ portDISABLE_INTERRUPTS(); /* 临界区支持嵌套 */ uxCriticalNesting++; if( uxCriticalNesting == 1 ) { /* 这个函数不能在中断中调用 */ configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); } }
复制
从上面的代码中可以看出,函数 vPortEnterCritical()进入临界区就是关闭中断,当然了,不
受 FreeRTOS 管理的中断是不受影响的。还可以看出,FreeRTOS 的临界区是可以嵌套的,意思就是说,在程序中可以重复地进入临界区,只要后续重复退出相同次数的临界区即可。
在上面的代码中还有一个断言,代码如下所示:
if( uxCriticalNesting == 1 ) { /* 这个函数不能在中断中调用 */ configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); }
复制
断言中使用到的两个宏定义在 FreeRTOS 的源码中都有定义,定义如下所示:
#define portNVIC_INT_CTRL_REG ( *( ( volatile uint32_t * ) 0xe000ed04 ) ) #define portVECTACTIVE_MASK ( 0xFFUL )
复制
可以看出,宏 portNVIC_INT_CTRL_REG 就是指向中断控制状态寄存器(ICSR)的指针,
而宏 portVECTACTIVE_MASK 就是 ICSR 寄存器中 VECTACTIVE 段对应的位置,因此这个断
言就是用来判断当第一次进入临界区的时候,是否是从中断服务函数中进入的,因为函数
vportEnterCritical()是用于从非中断中进入临界区,如果用户错误地在中断服务函数中调用函数
vportEnterCritical(),那么就会通过断言报错。
2. 宏 taskENTER_CRITICAL_FROM_ISR()
此宏用于从中断中进入临界区,此宏展开后是函数 ulPortRaiseBASEPRI(),函数
ulPortRaiseBASEPRI()的代码如下所示:
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void ) { uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* 读取 BASEPRI 寄存器 */ mrs ulReturn, basepri /* 设置 BASEPRI 寄存器 */ msr basepri, ulNewBASEPRI dsb isb } return ulReturn; }
复制
可 以 看 到 函 数 ulPortRaiseBASEPRI() 同 样 是 将 BASEPRI 寄 存 器 设 置 为 宏
configMAX_SYSCALL_INTERRUPT_PRIORITY 的值,以达到关闭中断的效果,当然了,不受FreeRTOS 管理的中断是不受影响的。只不过函数 ulPortRaiseBASEPRI()在设置 BASEPRI 寄存器之前,先读取了 BASEPRI 的值,并在函数的最后返回这个值,这是为了在后续从中断中退出临界区时,恢复 BASEPRI 寄存器的值。
从上面的代码中也可以看出,从中断中进入临界区时不支持嵌套的。
3. 宏 taskEXIT_CRITICAL()
此 宏 用 于 从 非 中 断 中 退 出 临 界 区 , 此 宏 展 开 后 是 函 数 vPortExitCritical() ,函数
vPortExitCritical()的代码如下所示:
void vPortExitCritical( void ) { /* 必须是进入过临界区才能退出 */ configASSERT( uxCriticalNesting ); uxCriticalNesting--; if( uxCriticalNesting == 0 ) { /* 打开中断 */ portENABLE_INTERRUPTS(); } }
复制
这个函数就很好理解了,就是将用于临界区嵌套的计数器减 1,当计数器减到 0 的时候,
说明临界区已经没有嵌套了,于是调用函数 portENABLE_INTERRUPT()打开中断。在函数的一开始还有一个断言,这个断言用于判断用于临界区嵌套的计数器在进入此函数的不为 0,这样就保证了用户不会在还未进入临界区时,就错误地调用此函数退出临界区。
4. taskEXIT_CRITICAL_FROM_ISR(x)
此宏用于从中断中退出临界区,此宏展开后是调用了函数 vPortSetBASEPRI(),并将参数 x
传入函数 vPortSetBASEPRI()。其中参数 x 就是宏 taskENTER_CRITICAL_FROM_ISR()的返回值,用于在从中断中对出临界区时,恢复 BASEPRI 寄存器。
读者在使用 FreeRTOS 进行开发的时候,应适当并合理地使用临界区,以让设计的程序更
加可靠。
六、相关实验
#include "freertos_demo.h" #include "./SYSTEM/usart/usart.h" #include "./BSP/LED/led.h" #include "./BSP/LCD/lcd.h" #include "./BSP/KEY/key.h" #include "./SYSTEM/delay/delay.h" /*FreeRTOS*********************************************************************************************/ #include "FreeRTOS.h" #include "task.h" /******************************************************************************************************/ /*FreeRTOS配置*/ /* START_TASK 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */ #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task( void * pvParameters ); /* TASK1 任务 配置 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务 */ #define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1( void * pvParameters ); /******************************************************************************************************/ /** * @brief FreeRTOS例程入口函数 * @param 无 * @retval 无 */ void freertos_demo(void) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task", (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL, (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task( void * pvParameters ) { taskENTER_CRITICAL(); /* 进入临界区 */ xTaskCreate((TaskFunction_t ) task1, (char * ) "task1", (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL, (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); vTaskDelete(NULL); taskEXIT_CRITICAL(); /* 退出临界区 */ } /* 任务一,实现LED0每500ms翻转一次 */ void task1( void * pvParameters ) { uint8_t task1_num = 0; while(1) { if(++task1_num == 5) { task1_num = 0; printf("关中断!!\r\n"); portDISABLE_INTERRUPTS(); delay_ms(5000); printf("开中断!!!\r\n"); portENABLE_INTERRUPTS(); } vTaskDelay(1000); } }
复制
#include "./BSP/LED/led.h" #include "./BSP/TIMER/btim.h" #include "./SYSTEM/usart/usart.h" TIM_HandleTypeDef g_timx_handle; /* 定时器参数句柄 */ TIM_HandleTypeDef g_tim7_handle; /* 定时器参数句柄 */ /** * @brief 基本定时器TIMX定时中断初始化函数 * @note * 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候 * 基本定时器的时钟为APB1时钟的2倍, 而APB1为45M, 所以定时器时钟 = 90Mhz * 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us. * Ft=定时器工作频率,单位:Mhz * * @param arr : 自动重装值。 * @param psc : 时钟预分频数 * @retval 无 */ void btim_timx_int_init(uint16_t arr, uint16_t psc) { g_timx_handle.Instance = BTIM_TIMX_INT; /* 定时器x */ g_timx_handle.Init.Prescaler = psc; /* 分频 */ g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */ g_timx_handle.Init.Period = arr; /* 自动装载值 */ HAL_TIM_Base_Init(&g_timx_handle); HAL_TIM_Base_Start_IT(&g_timx_handle); /* 使能定时器x和定时器更新中断 */ } /* TIM7初始化函数 */ void btim_tim7_int_init(uint16_t arr, uint16_t psc) { g_tim7_handle.Instance = BTIM_TIM7_INT; /* 定时器x */ g_tim7_handle.Init.Prescaler = psc; /* 分频 */ g_tim7_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */ g_tim7_handle.Init.Period = arr; /* 自动装载值 */ HAL_TIM_Base_Init(&g_tim7_handle); HAL_TIM_Base_Start_IT(&g_tim7_handle); /* 使能定时器x和定时器更新中断 */ } /** * @brief 定时器底层驱动,开启时钟,设置中断优先级 此函数会被HAL_TIM_Base_Init()函数调用 * @param 无 * @retval 无 */ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { if (htim->Instance == BTIM_TIMX_INT) { BTIM_TIMX_INT_CLK_ENABLE(); /* 使能TIMx时钟 */ HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 6, 0); /* 抢占6,子优先级0 */ HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn); /* 开启ITMx中断 */ } if(htim->Instance == BTIM_TIM7_INT) { BTIM_TIM7_INT_CLK_ENABLE(); /* 使能TIM7时钟 */ HAL_NVIC_SetPriority(BTIM_TIM7_INT_IRQn, 4, 0); /* 抢占4,子优先级0 */ HAL_NVIC_EnableIRQ(BTIM_TIM7_INT_IRQn); /* 开启ITM7中断 */ } } /** * @brief 基本定时器TIMX中断服务函数 * @param 无 * @retval 无 */ void BTIM_TIMX_INT_IRQHandler(void) { HAL_TIM_IRQHandler(&g_timx_handle); /* 定时器回调函数 */ } /* TIM7中断服务函数 */ void BTIM_TIM7_INT_IRQHandler(void) { HAL_TIM_IRQHandler(&g_tim7_handle); /* 定时器回调函数 */ } /** * @brief 回调函数,定时器中断服务函数调用 * @param 无 * @retval 无 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == BTIM_TIMX_INT) { printf("TIM6优先级为6的正在运行!!!\r\n"); }else if(htim->Instance == BTIM_TIM7_INT) { printf("TIM7优先级为4的正在运行!!!!!\r\n"); } }
复制
#ifndef __BTIM_H #define __BTIM_H #include "./SYSTEM/sys/sys.h" /******************************************************************************************/ /* 基本定时器 定义 */ /* TIMX 中断定义 * 默认是针对TIM6/TIM7 * 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器. */ #define BTIM_TIMX_INT TIM6 #define BTIM_TIMX_INT_IRQn TIM6_DAC_IRQn #define BTIM_TIMX_INT_IRQHandler TIM6_DAC_IRQHandler #define BTIM_TIMX_INT_CLK_ENABLE() do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0) /* TIM6 时钟使能 */ #define BTIM_TIM7_INT TIM7 #define BTIM_TIM7_INT_IRQn TIM7_IRQn #define BTIM_TIM7_INT_IRQHandler TIM7_IRQHandler #define BTIM_TIM7_INT_CLK_ENABLE() do{ __HAL_RCC_TIM7_CLK_ENABLE(); }while(0) /* TIM6 时钟使能 */ /******************************************************************************************/ void btim_timx_int_init(uint16_t arr, uint16_t psc); /* 基本定时器 定时中断初始化函数 */ void btim_tim7_int_init(uint16_t arr, uint16_t psc); /* 基本定时器 定时中断初始化函数 */ #endif
复制