6.中断管理

news2024/11/20 1:23:12

一、简介

中断是 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1691934.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt 在windows下显示中文

Qt在windows平台上显示中文&#xff0c;简直是一门玄学&#xff0c;经过测试&#xff0c;有如下发现&#xff1a; 1&#xff0c; 环境&#xff1a;Qt 5.15.2 vs2019 64位 win11系统 默认用Qt 创建的文件使用utf-8编码格式&#xff0c;此环境下 中文没有问题 ui->textE…

运用HTML、CSS设计Web网页——“西式甜品网”图例及代码

目录 一、效果展示图 二、设计分析 1.整体效果分析 2.头部header模块效果分析 3.导航及banner模块效果分析 4.分类classify模块效果分析 5.产品展示show模块效果分析 6.版权banquan模块效果分析 三、HTML、CSS代码分模块展示 1. 头部header模块代码 2.导航及bann…

一条命令安装Metasploit Framework

做安全渗透的人都或多或少的使用kali-Linux系统中msfconsole命令启动工具&#xff0c;然而也经常会有人遇到这样那样的问题无法启动 今天我们就用一条命令来重新安装这个工具 curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/met…

Proteus仿真小技巧(隔空连线)

用了好几天Proteus了.总结一下使用的小技巧. 目录 一.隔空连线 1.打开添加网络标号 2.输入网络标号 二.常用元件 三.运行仿真 四.总结 一.隔空连线 引出一条线,并在末尾点一下. 1.打开添加网络标号 选择添加网络标号, 也可以先点击按钮,再去选择线(注意不要点端口) 2.…

PTT票据传递攻击

一. PTT票据传递攻击原理 1.PTT介绍 PTT(Pass The Ticket)&#xff0c;中文叫票据传递攻击&#xff0c;PTT 攻击只能用于kerberos认证中,NTLM认证中没有&#xff0c; PTT是通过票据进行认证的。 进行票据传递&#xff0c;不需要提权&#xff0c;域用户或者system用户就可以 …

2024上海初中生古诗文大会倒计时4个月:单选题真题解析(持续)

现在距离2024年初中生古诗文大会还有4个多月时间&#xff0c;我们继续来看10道选择题真题和详细解析&#xff0c;以下题目截取自我独家制作的在线真题集&#xff0c;都是来自于历届真题&#xff0c;去重、合并后&#xff0c;每道题都有参考答案和解析。 为帮助孩子自测和练习&…

【JS】并发控制

需求 控制网络请求并发数控制并发按顺序返回结果 码 /** * 控制并发 * param {Function} fn 逻辑处理函数 * param {Array} arr 发送的数据 * param {Number} [max3] 并发数 默认3 * param {Number} [orderfalse] 按顺序返回执行结果 默认false * param {Number} [retry1] 重试…

javaAPI文档中文版(JDK11在线版)java帮助文档,掌握文档java学习事半功倍。

&#x1f320;个人主页 : 赶路人- - &#x1f30c;个人格言 : 要努力成为梧桐&#xff0c;让喜鹊在这里栖息。 要努力成为大海&#xff0c;让百川在这里聚积。 11.by,prep.凭&#xff0c;靠&#xff0c;沿 [baɪ] 12.press,v.按&#xff0c;压 [prɛs] 菜鸟教程javaAPI文档中文…

python水果分类字典构建指南

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、理解需求 三、构建字典 1. 数据结构选择 2. 代码实现 3. 结果展示 四、总…

深入编程逻辑:从分支到循环的奥秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、编程逻辑的基石&#xff1a;分支与循环 分支逻辑详解 代码案例&#xff1a;判断整数是…

速卖通测评揭秘:如何选择安全的渠道操作

许多商家对测评存在误解&#xff0c;认为只需进行几次测评就能迅速打造爆款。实际上&#xff0c;测评是一个需要计划和持久性的过程&#xff0c;以便让平台检测到产品的受众程度并提高产品的曝光和权重。 在进行测评时&#xff0c;安全是首要考虑的问题。平台可以通过设备、网…

【数据结构】数据结构中的隐藏玩法——栈与队列

前言&#xff1a; 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我…

二叉树——进阶(递归创建,非递归,广度优先,翻转,深度,对称)

二叉树——进阶 二叉树的递归创建非递归前中后序遍历非递归前序遍历非递归中序遍历非递归后序遍历 广度优先遍历二叉树&#xff08;层序遍历&#xff09;翻转二叉树 二叉树深度最大深度最小深度 对称二叉树 二叉树的递归创建 1&#xff0c;二叉树是一种结构相对固定的数据&…

SpringBoot接入Knife4j接口文档

0.介绍 1&#xff09; Knife4j是什么 Knife4j是Java MVC框架集成Swagger生成Api文档的增强解决方案&#xff0c;前身是swagger-bootstrap-ui&#xff0c;有着比Swagger更为美观的UI以及功能。 例如以下效果图&#xff1a; 2&#xff09; 官方链接 官网&#xff1a;Knife4j …

Mujava 工具的简单使用

首先下载openjava.jar和mujava.jar&#xff0c;以及自己手写一个mujava.config指向存放mujava的目录&#xff0c;并将这些文件放在mujava目录下。此时&#xff0c;基本的mujava环境就搭建好了。 分别创建src&#xff08;存放源码文件&#xff09;、classes&#xff08;存放源码…

使用pygame绘制图形

参考链接&#xff1a;https://www.geeksforgeeks.org/pygame-tutorial/?reflbp 在窗口中绘制单个图形 import pygame from pygame.locals import * import sys pygame.init()window pygame.display.set_mode((600,600)) window.fill((255,255,255))# pygame.draw.rect(wind…

针对上一篇微信同声传译语音播报部分坑的解决和优化

1. 上一篇语音播报其实是不完美的&#xff0c;就是如何停止上一个音频开始下一个音频的问题&#xff0c;我在此做一下修改 比如说&#xff1a;现在正在播放1&#xff0c;我点击2让2开始播放&#xff0c;1停止播放&#xff0c;我上面的写法是有问题的&#xff1a; 通过 innerAu…

编译器 编译过程 compiling 动态链接库 Linking 接口ABI LTO PGO inline bazel增量编译

编译器 编译过程 compiling 动态链接库 Linking 接口ABI LTO PGO Theory Shared Library Symbol Conflicts (on Linux) 从左往右查找:Note that the linker only looks further down the line when looking for symbols used by but not defined in the current lib.Linux 下…

【全部更新完毕】2024电工杯B题详细思路代码成品文章教学:大学生平衡膳食食谱的优化设计及评价

大学生平衡膳食食谱的优化设计及评价 摘要 大学阶段是学生获取知识和身体发育的关键时期&#xff0c;也是形成良好饮食习惯的重要阶段。然而&#xff0c;当前大学生中存在饮食结构不合理和不良饮食习惯的问题&#xff0c;主要表现为不吃早餐或早餐吃得马虎&#xff0c;经常食用…