目录
2.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY
2.3 configKERNEL_INTERRUPT_PRIORITY
2.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
2.5 configMAX_SYSCALL_INTERRUPT_PRIORITY
FreeRTOS 的中断配置需要根据所使用的 MCU 来具体配置。这需要结合 MCU 架构中有关中断的知识,本节结合 Cortex-M 的 NVIC 来讲解 STM32 平台下的 FreeRTOS 中断配置;
1. Cortex-M 中断
1.1 中断简介
中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。
Cortex-M3 和 M4 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个Systick(滴答定时器)定时器中断和多个系统异常。
1.2 中断管理简介
Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器,这些寄存器大多数都在 NVIC 和系统控制块(SCB)中,CMSIS 将这些寄存器定义为结构体。以 STM32F407 为例,打开core_cm4.h,有两个结构体,NVIC_Type 和 SCB_Type,如下:
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 (R/W) Interrupt Set Enable Register */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 (R/W) Interrupt Clear Enable Register */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 (R/W) Interrupt Set Pending Register */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 (R/W) Interrupt Clear Pending Register */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 (R/W) Interrupt Active bit Register */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 (R/W) Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 ( /W) Software Trigger Interrupt Register */
} NVIC_Type;
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x000 (R/ ) CPUID Base Register */
__IO uint32_t ICSR; /*!< Offset: 0x004 (R/W) Interrupt Control and State Register */
__IO uint32_t VTOR; /*!< Offset: 0x008 (R/W) Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x00C (R/W) Application Interrupt and Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x010 (R/W) System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x014 (R/W) Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x018 (R/W) System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x024 (R/W) System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x028 (R/W) Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x02C (R/W) HardFault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x030 (R/W) Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x034 (R/W) MemManage Fault Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x038 (R/W) BusFault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x03C (R/W) Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x040 (R/ ) Processor Feature Register */
__I uint32_t DFR; /*!< Offset: 0x048 (R/ ) Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x04C (R/ ) Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x050 (R/ ) Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x060 (R/ ) Instruction Set Attributes Register */
uint32_t RESERVED0[5];
__IO uint32_t CPACR; /*!< Offset: 0x088 (R/W) Coprocessor Access Control Register */
} SCB_Type;
NVIC 和 SCB 都位于系统控制空间(SCB)内,SCS 的地址从 0XE000E000 开始,SCB 和 NVIC 的地址也在 core_cm4.h 中有定义,如下:
#define SCS_BASE (0xE000E000UL) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100UL) /*!< NVIC Base Address */
#define SCB_BASE (SCS_BASE + 0x0D00UL) /*!< System Control Block Base Address
#define SCnSCB ((SCnSCB_Type*) SCS_BASE) /*!< System control Register not in SCB */
#define SCB ((SCB_Type *) SCB_BASE) /*!< SCB configuration struct */
#define NVIC ((NVIC_Type *) NVIC_BASE ) /*!< NVIC configuration struct *//
1.3 优先级分组定义
当多个中断来临的时候处理器应该响应哪一个中断是由中断的优先级来决定的,高优先级的中断(优先级编号小)肯定是首先得到响应,而且高优先级的中断可以抢占低优先级的中断,这个就是中断嵌套。Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、NMI(不可屏蔽中断)、HardFault,这些中断的优先级都是负数,优先级也是最高的。
Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级,最多有 128 个抢占等级,但是实际的优先级数量是由芯片厂商来决定的。在实际设计时,绝大多数的芯片都会精简设计,以致实际上支持的优先级数会更少,如 8级、16级、32级等,比如 STM32 就只有 16 级优先级。在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,所以不管用多少位来表达优先级,都是 MSB 对齐的;
在图 4.1.3.1 中,Bit0~Bit4 没有实现,所以读它们总是返回零,写它们的话也会忽略写入的值。因此,对于 3 个位的情况,可以使用的优先级就是 8 个:0x00(最高优先级)、0x20、0x40、0x60、0x80、0xA0、0xC0 和 0xE0。
注意:这个是芯片厂商来决定的!不是我们能决定的,比如说 STM32 就选择了 4 位作为优先级!
为了使抢占机能变得更可控,Cortex-M 处理器还把 256 个优先级按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),NVIC 中有一个寄存器是 "应用程序中断及复位控制寄存器(AIRCR)";AIRCR 寄存器里面有个位段名为 “优先级组”,
上图中 PRIGROUP 就是优先级分组,他把优先级分为两个位段:MSB 所在的位段(左边的)对应抢占优先级,LSB 所在的位段(右边的)对应亚优先级,也就是子优先级。
STM32 的优先级分组情况,STM32 使用了 4 位,因此最多有 5 组优先级分组设置,这 5 个分组在 msic.h 中有定义:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
STM32 有 5 个分组,一定要注意!STM32 中定义的分组 0 对应的值是 7!如果我们选择分组 4 ,即 NVIC_PriorityGroup_4 的话,那 4 位优先级就都全是抢占优先级了,没有亚优先级,那么就有 0~15 共 16 个优先级。移植 FreeRTOS 的时候我们配置的就是组 4 ;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化LED端口
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
1.4 优先级设置
每个外部中断都有一个对应的优先级寄存器,每个寄存器占 8 位,因此最大宽度是 8 位,但是最小为 3 位。4 个相邻的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组的设置,优先级又可以分为高、低两个位段 ,分别是抢占优先级和亚优先级。STM32 我们已经设置位组 4,所以就只有抢占优先级了。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问;
4 个相邻的寄存器可以拼成一个 32 位的寄存器,因此地址 0xE000_ED20 ~ 0xE000_ED23 这四个寄存器就可以拼接成一个地址为 0xE000_ED20 的 32 位寄存器。
这一点很重要!因为 FreeRTOS 在设置 PendSV 和 SysTick 的中断优先级的时候都是直接操作的地址 0xE000_ED20。
1.5 用于中断屏蔽的特殊寄存器
在 STM32 上移植 FreeRTOS 的时候 PRIMASK、FAULTMASK 和 BASEPRI 这三个寄存器发挥了重要的作用!
1.5.1 PRIMASK 和 FAULTMASK 寄存器
在许多应用中,需要暂时屏蔽所有的中断,执行一些对时序要求严格的任务,这个时候就可以使用 PRIMASK 寄存器,PRIMASK 用于禁止除 NMI 和 HardFalut 外的所有异常和中断,汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值:
CPSIE I; //清除 PRIMASK(使能中断)
CPSID I; //设置 PRIMASK(禁止中断)
PRIMASK 寄存器还可以通过 MRS 和 MSR 指令访问,如下:
MOVS R0, #1
MSR PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断
以及:
MOVS R0, #0
MSR PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断
UCOS 中的临界区代码保护就是通过开关中断实现的(UCOSIII 也可以使用禁止任务调度的方法来实现临界区代码保护),而开关中断就是直接操作 PRIMASK 寄存器的,所以在 UCOS 中关闭中断的时候关闭了除复位、NMI 和 HardFault 以外的所有中断!
FAULTMASK 比 PRIMASK 更狠,他可以连 HardFault 都屏蔽掉,使用方法和 PRIMASK 类似,FAULTMASK 会在退出时自动清零。
汇编编程的时候可以利用 CPS 指令修改 FAULTMASK 的当前状态:
CPSIE F ;清除 FAULTMASK
CPSID F ;设置 FAULTMASK
还可以利用 MRS 和 MSR 指令访问 FAULTMASK 寄存器:
MOVS R0, #1
MSR FAULTMASK, R0 ;将 1 写入 FAULTMASK 禁止所有中断
以及:
MOVS R0, #0
MSR FAULTMASK, R0 ;将 0 写入 FAULTMASK 使能中断
1.5.2 BASEPRI 寄存器
PRIMASK 和 FAULTMASK 寄存器过于简单粗暴,直接关闭了除复位、NMI 和 HardFault 以外的其他所有中断,但是在有些场合需要对中断屏蔽进行更加细腻的控制,比如只屏蔽优先级低于某一阈值的中断。那么这个作为阈值的优先级值存储在哪里呢?
在 BASEPRI 寄存器中,不过如果向 BASEPRI 写 0 的话就会停止屏蔽中断。
比如,我们要屏蔽优先级不高于 0x60 的中断,则可以使用如下汇编编程:
MOV R0, #0X60
MSR BASEPRI, R0
如果需要取消 BASEPRI 对中断的屏蔽,可以使用如下代码:
MOV R0, #0
MSR BASEPRI, R0
注意:FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈值的中断,高于这个阈值的中断就不会被关闭!
2. FreeRTOS 中断配置宏
2.1 configPRIO_BITS
此宏用来设置 MCU 使用几位优先级,STM32 使用的是 4 位,因此此宏为 4!
2.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用来设置最低优先级的,STM32 优先级使用了 4 位,而且 STM32 配置时使用组 4 ,也就是 4 位都是抢占优先级。因此优先级数就是 16 个,最低优先级就是 15 。所以此宏就是 15。
2.3 configKERNEL_INTERRUPT_PRIORITY
此宏用来设置内核中断优先级,此宏定义如下:
#define configKERNEL_INTERRUPT_PRIORITY
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
//宏 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 左移 (8 - configPRIO_BITS)位,也就是左移 4 位。
之前我们学过,STM32 使用了 4 位作为优先级,而这 4 位是高 4 位,因此要左移 4 位才是真正的优先级。
//当然也可以不用移位,直接将宏 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 定义为 0XF0!
宏 configKERNEL_INTERRUPT_PRIORITY 用来设置 PendSV 和滴答定时器的中断优先级,port.c 中有如下定义:
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) <<
16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) <<
24UL )
可以看出,portNVIC_PENDSV_PRI 和 portNVIC_SYSTICK_PRI 都使用了宏 configKERNEL_INTERRUPT_PRIORITY,PendSV 和 SysTick 的中断优先级设置是操作 0xE000_ED20 地址的,一次写入的是 32 位的数据,SysTick 和 PendSV 的优先级寄存器分别对应这个 32 位数据的最高 8 位和次高 8 位,所以就是一个左移 16 位,一个左移 24 位了。
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //设置 PendSV 中断优先级
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //设置 SysTick 中断优先级
上述代码就是设置 PendSV 和 SysTick 优先级的,它们是直接向地址 portNVIC_SYSPRI2_REG 写入优先级数据,portNVIC_SYSPRI2_REG 是个宏,在文件 port.c 中定义,如下:
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
可以看到宏 portNVIC_SYSPRI2_REG 就是地址 0XE000ED20!同时也可以看出在 FreeRTOS 中 PendSV 和 SysTick 的中断优先级都是最低的!
2.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
此宏用来设置 FreeRTOS 系统可管理的最大优先级,也就是 BASEPRI 寄存器说的那个阈值优先级,这个可以自由设置,这里我设置的是 5 。也就是高于 5 的优先级(优先级数小于 5 )不归 FreeRTOS 管理!
2.5 configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏是 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 4 位而来的,原因和宏 configKERNEL_INTERRUPT_PRIORITY 一样。此宏设置好以后,低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS 的 API 函数!
以 STM32 为例,有 16 个优先级,0 为最高优先级,15 为最低优先级,配置如下:
configMAX_SYSCALL_INTERRUPT_PRIORITY == 5
configKERNEL_INTERRUPT_PRIORITY == 15
由于高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的优先级不会被 FreeRTOS 内核屏蔽,因此那些对实时性要求严格的任务就可以使用这些优先级,比如四轴飞行器的避障检测。
3. FreeRTOS 的开关中断
FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS() 和 portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
可以看出开关中断实际上是通过函数 vPortSetBASEPRI(0) 和 vPortRaiseBASEPRI() 来实现的
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
/*-----------------------------------------------------------*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
函数 vPortSetBASEPRI() 是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传递进来,portENABLE_INTERRUPTS() 是开中断,它传递个 0 给 vPortSetBASEPRI(),根据我们前面讲解 BASEPRI 寄存器可知,结果就是开中断。
函数 vPortRaiseBASEPRI() 是向寄存器 BASEPRI 写入宏 configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!
4. 临界段代码
临界段代码也叫临界区,是指那些必须完整运行,不能被打断的代码段,比如说有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。
FreeRTOS 与临界段代码保护有关的函数有 4 个:taskENTER_CRITICAL()、taskEXIT_CRITICAL()、taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR(),这四个函数都是宏定义,在 task.h 文件中定义。这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。
4.1 任务级临界段代码保护
taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的,函数定义如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
而 portENTER_CRITICAL() 和 portEXIT_CRITICAL() 也是宏定义,在文件 portmacro.h 中有定义,如下:
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
函数 vPortEnterCritical() 和 vPortExitCritical() 在文件 port.c 中,函数如下:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
可以看出在进入函数 vPortEnterCritical() 以后会首先关闭中断,然后给变量 uxCriticalNesting 加一,uxCriticalNesting 是一个全局变量,用来记录临界段嵌套次数。函数 vPortExitCritical() 是退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 为 0 的时候才会调用函数 portENABLE_INTERRUPTS() 使能中断。这样保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断!
//任务级临界代码保护使用方法
void taskcritical_test(void)
{
while(1)
{
taskENTER_CRITICAL(); (1)
total_num = total_num + 0.01f;
printf("total_num 的值为:%.4f\r\n",total_num);
taskEXIT_CRITICAL(); (2)
vTaskDelay(1000);
}
}
(1)、进入临界区。
(2)、退出临界区。
(1)和(2)中间的代码就是临界区代码,注意临界区代码一定要精简!因为进入临界区会关闭中断,这样会导致优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时的响应!
4.2 中断级临界段代码保护
函数 taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR() 中断级别临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于 configMAX_SYSCALL_INTERRUPT_PRIORITY!这两个函数在文件 task.h 中有如下定义:
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
接着找 portSET_INTERRUPT_MASK_FROM_ISR() 和 portCLEAR_INTERRUPT_MASK_FROM_ISR(),这两个在文件 portmacro.h 中有如下定义:
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
函数 ulPortRaiseBASEPRI() 在文件 portmacro.h 中定义的,如下:
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
mrs ulReturn, basepri (1)
msr basepri, ulNewBASEPRI (2)
dsb
isb
}
return ulReturn; (3)
}
(1)、先读出 BASEPRI 的值,保存在 ulReturn 中。
(2)、将 configMAX_SYSCALL_INTERRUPT_PRIORITY 写入到寄存器 BASEPRI 中。
(3)、返回 ulReturn,退出临界区代码保护的时候要使用到此值!
//中断级临界代码保护使用方法如下:
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
status_value=taskENTER_CRITICAL_FROM_ISR(); (1)
total_num+=1;
printf("float_num 的值为: %d\r\n",total_num);
taskEXIT_CRITICAL_FROM_ISR(status_value); (2)
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
(1)、进入临界区。
(2)、退出临界区。
5. FreeRTOS 中断测试实验
5.1 实验目的
在 FreeRTOS 中,优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断会被屏蔽掉,高于的就不会;
本节,使用两个定时器,一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符串。然后在某个任务中关闭中断一段时间,查看两个定时器的输出情况。
5.2 实验设计
本实验设计了两个任务 start_task() 和 interrupt_task() ,这两个任务的任务功能如下:
start_task():创建另外一个任务。
interrupt_task():中断测试任务,任务中会调用 FreeRTOS 的关中断函数 portDISABLE_INTERRUPTS() 来将中断关闭一段时间。
5.3 实验程序与分析
//任务设置
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandler_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define INTERRUPT_TASK_PRIO 2 //任务优先级
#define INTERRUPT_STK_SIZE 256 //任务堆栈大小
TaskHandler_t INTERRUPTTask_Handler; //任务句柄
void interrupt_task(void *p_arg); //任务函数
// main 函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级分组 4
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED 端口
TIM3_Int_Init(10000-1,8400-1); //初始化定时器 3,定时器周期 1S
TIM5_Int_Init(10000-1,8400-1); //初始化定时器 5,定时器周期 1S
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler //任务句柄
);
vTaskStartScheduler(); //开启任务调度
}
//任务函数
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建中断测试任务
xTaskCreate((TaskFunction_t )interrupt_task, //任务函数 (1)
(const char* )"interrupt_task", //任务名称
(uint16_t )INTERRUPT_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )INTERRUPT_TASK_PRIO, //任务优先级
(TaskHandle_t* )&INTERRUPTTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//中断测试函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num=total_num+1;
if(total_num==5) (2)
{
printf("关闭中断…………\r\n");
portDISABLE_INTERRUPTS(); //关闭中断 (3)
delay_xms(5000); //延时5s (4)
printf("打开中断…………\r\n"); //打开中断
portENABLE_INTERRUPTS(); (5)
}
LED0=~LED0;
vTaskDelay(1000);
}
}
(1)创建一个任务来执行开关中断的动作,任务函数为 interrupt_task()。
(2)当任务 interrupt_task() 运行 5 次以后关闭中断。
(3)调用函数 portDISABLE_INTERRUPTS() 关闭中断。优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断都会被关闭,高于的不会受任何影响。
(4)调用函数 delay_xms() 延时 5S,此函数是对 delay_us() 的简单封装,delay_xms() 会用来模拟关闭中断一段时间,此函数不会引起任务调度!
(5)调用函数 portENABLE_INTERRUPTS() 重新打开中断。
//中断初始化及处理过程
//通用定时器 3 中断初始化
//arr:自动重装值
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器 3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能 TIM3 时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); //初始化TIM3
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许定时器 3 更新中断
TIM_Cmd(TIM3,ENABLE); //使能定时器 3
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //定时器 3 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x04; //抢占优先级 4 (1)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//通用定时器 5 中断初始化
//arr:自动重装值
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us
//Ft=定时器工作频率,单位:Mhz
//这里使用的是定时器 5!
void TIM5_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); //使能 TIM5 时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; //定时器分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure); //初始化TIM5
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE); //允许定时器 5 更新中断
TIM_Cmd(TIM5,ENABLE); //使能定时器 5
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //定时器 5 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x05; //抢占优先级 5 (2)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) //溢出中断
{
printf("TIM3 输出…………\r\n"); (3)
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Updata); //清除中断标志位
}
//定时器 5 中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update) == SET) //溢出中断
{
printf("TIM5 输出…………\r\n"); (4)
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Updata); //清除中断标志位
}
(1)设置定时器 3 的抢占优先级为 4 ,高于 configMAX_SYSCALL_INTERRUPT_PRIORITY,因此在调用函数 portDISABLE_INTERRUPTS() 关闭中断的时候定时器 3 是不会受影响的。
(2)设置定时器 5 的抢占优先级为 5 ,等于 configMAX_SYSCALL_INTERRUPT_PRIORITY,因此在调用函数 portDISABLE_INTERRUPTS() 关闭中断的时候定时器 5 肯定会被关闭的。
(3)和(4)定时器 3 和定时器 5 串口输出信息。
5.4 完整代码
试验现象:本实验设置两个任务,一个是开始任务,一个是中断任务,中断程序中设置 定时器 3 和 定时器 5 ,定时器 3 的抢占优先级为 4 ,定时器 5 的抢占优先级为 5 ;开始任务执行以后,执行开始任务函数,开始任务执行完成以后,删除开始任务,进入中断测试函数;
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num=total_num+1;
if(total_num==5)
{
printf("关闭中断…………\r\n");
portDISABLE_INTERRUPTS(); //关闭中断
delay_xms(5000); //延时5S
printf("打开中断…………\r\n"); //打开中断
portENABLE_INTERRUPTS();
}
LED0=~LED0;
vTaskDelay(1000);
}
}
//以上是中断测试函数的程序,首先开发板上 LED0 每隔一秒闪烁一次,提示程序正在运行;
//设置一个 static 修饰的静态变量 total_num,保证每一次进入中断测试函数以后,total_mum 的值
//都保留上一次函数结束时的值,而不是每一次进入中断测试函数以后 total_num 的值都为0
//第一次进入 interrupt_task 中断测试函数以后,因为 TIM3 和TIM5 定时器是每1秒输出一次;
//所以串口会首先打印5次 TIM3输出、TIM5输出;此时 total_num 的值会递增至5,进入 if 判断语句,
//串口会打印关闭中断,此时会调用宏 portDISABLE_INTERRUPTS(); ,FreeRTOS设置的中断优先级为 4
//TIM3 中断优先级为4,TIM5 中断优先级为5,所以关闭中断的时候,TIM3 不会受影响,TIM5 会受影响;
//此时串口只会打印 TIM3输出,延时5S,也就是打印5次;
//此时串口会打印打开中断,打开中断以后,串口会接着输出 TIM3输出、TIM5输出;
5.4.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "Timer.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define INTERRUPT_TASK_PRIO 2
//任务堆栈大小
#define INTERRUPT_STK_SIZE 256
//任务句柄
TaskHandle_t INTERRUPTTask_Handler;
//任务函数
void interrupt_task(void *p_arg);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级分组4
delay_init(168);
uart_init(115200);
LED_Init();
MyTIM3_Init(10000-1,8400-1); //初始化定时器3,定时器周期1S
MyTIM5_Init(10000-1,8400-1); //初始化定时器5,定时器周期1S
//创建开始任务
xTaskCreate((TaskFunction_t)start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开始任务调度
}
//开始任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建中断测试任务
xTaskCreate((TaskFunction_t)interrupt_task, //任务函数
(const char* )"interrupt_task", //任务名称
(uint16_t )INTERRUPT_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )INTERRUPT_TASK_PRIO, //任务优先级
(TaskHandle_t* )&INTERRUPTTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num=total_num+1;
if(total_num==5)
{
printf("关闭中断…………\r\n");
portDISABLE_INTERRUPTS(); //关闭中断
delay_xms(5000); //延时5S
printf("打开中断…………\r\n"); //打开中断
portENABLE_INTERRUPTS();
}
LED0=~LED0;
vTaskDelay(1000);
}
}
5.4.2 Timer.c
#include "sys.h"
#include "LED.h"
#include "Timer.h"
#include "usart.h"
//通用定时器 3 设置
//AutomaticArray:自动重装载值
//PresclarSendCount:时钟预分频数
//定时器溢出时间计算:Tout=((AutomaticArray+1)*(PresclarSendCount+1))/Ft us
//Ft:定时器工作频率,单位 MHz
void MyTIM3_Init(u16 AutomaticArray,u16 PresclarSendCount)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能TIM3时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_Period=AutomaticArray; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=PresclarSendCount; //时钟预分频值
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //TIM3更新中断使能
TIM_Cmd(TIM3,ENABLE); //使能定时器3
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; //定时器3中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x04; //抢占优先级4
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //子优先级0
NVIC_Init(&NVIC_InitStructure);
}
//通用定时器 5 设置
//AutomaticArray:自动重装载值
//PresclarSendCount:时钟预分频数
//定时器溢出时间计算:Tout=((AutomaticArray+1)*(PresclarSendCount+1))/Ft us
//Ft:定时器工作频率,单位 MHz
void MyTIM5_Init(u16 AutomaticArray,u16 PresclarSendCount)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE); //使能TIM5时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStructure.TIM_Period=AutomaticArray; //自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler=PresclarSendCount; //时钟预分频值
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE); //TIM5更新中断使能
TIM_Cmd(TIM5,ENABLE); //使能定时器5
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn; //定时器5中断
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x05; //抢占优先级5
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x00; //子优先级0
NVIC_Init(&NVIC_InitStructure);
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //TIM3 溢出中断
{
printf("TIM3输出……\r\n");
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
//定时器5中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //TIM5 溢出中断
{
printf("TIM5输出……\r\n");
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}
5.4.3 Timer.h
#ifndef _TIMER__H_
#define _TIMER__H_
#include "sys.h"
void MyTIM3_Init(u16 AutomaticArray,u16 PresclarSendCount);
void MyTIM5_Init(u16 AutomaticArray,u16 PresclarSendCount);
#endif