文章目录
- [1. Cortex - M3中断管理](#1. Cortex - M3中断管理)
-
- [1.1 异常响应系统](#1.1 异常响应系统)
-
- [1.1.1 优先级分组的定义](#1.1.1 优先级分组的定义)
- [1.1.2 关于中断优先级寄存器 IP](#1.1.2 关于中断优先级寄存器 IP)
- [1.1.3 中断屏蔽寄存器组 PRIMASK、FAULTMASK、BASEPRI](#1.1.3 中断屏蔽寄存器组 PRIMASK、FAULTMASK、BASEPRI)
- [1.2 NVIC ------ 嵌套向量中断控制器](#1.2 NVIC —— 嵌套向量中断控制器)
-
- [1.2.1 中断的使能与除能](#1.2.1 中断的使能与除能)
- [1.2.2 中断的悬起与解悬](#1.2.2 中断的悬起与解悬)
- [1.2.3 中断优先级](#1.2.3 中断优先级)
- [1.2.4 活动状态](#1.2.4 活动状态)
- [1.3 【实验】中断测试实验](#1.3 【实验】中断测试实验)
- [2. 列表与列表项](#2. 列表与列表项)
-
- [2.1 列表结构体](#2.1 列表结构体)
- [2.2 列表项](#2.2 列表项)
- [2.3 迷你列表项](#2.3 迷你列表项)
- [2.4 列表项相关API函数](#2.4 列表项相关API函数)
-
- [2.4.1 列表初始化](#2.4.1 列表初始化)
- [2.4.2 列表项初始化](#2.4.2 列表项初始化)
- [2.4.3 列表项插入](#2.4.3 列表项插入)
-
- [2.4.3.1 列表项插入图解](#2.4.3.1 列表项插入图解)
- [2.4.3.2 关于环形列表](#2.4.3.2 关于环形列表)
- [2.4.4 列表项末尾插入](#2.4.4 列表项末尾插入)
-
- [2.4.4.1 列表项末尾插入图解](#2.4.4.1 列表项末尾插入图解)
- [2.4.5 列表项的删除](#2.4.5 列表项的删除)
- [2.4.6 列表的遍历](#2.4.6 列表的遍历)
- [2.5 【实验】列表插入与删除](#2.5 【实验】列表插入与删除)
关于FreeRTOS 的API函数,在FreeRTOS官网文档中都有详细介绍;关于CMSIS OS 所有的API 函数,则在Arm 的CMSIS-RTOS2文档中有详细介绍;下面不一一详细展开;
1. Cortex - M3中断管理
📕参考资料:《Cortex - M3 权威指南》------ 异常、NVIC与中断控制 两节
在STM32芯片中,中断由硬件产生,当中断产生后 ,CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC).
Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个 Systick(滴答定时器)定时器中断和多个系统异常。
NVIC:嵌套向量中断控制器
1.1 异常响应系统
Cortex‐M3 在内核水平上搭载了一个异常响应系统,支持为数众多的系统异常和外部中断。其中,编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断(一共240个)。除了个别异常的优先级被定死外,其它异常的优先级都是可编程的(但不能编程为负数)。另外,优先级数值越小,优先级越高。
注:所有能打断正常执行流的事件都称为异常



可见1、2、3号的优先级为负数,其优先级是写定的,不可修改。而表7.2 中可见,系统外部中断一共有240个。
在FreeRTOS中重点关注第14、15号的异常。
1.1.1 优先级分组的定义
由上一节可知,优先级的数值越小,优先级越高。CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。但其中有 3 个系统异常(见表7.2):复位,NMI 以及硬 fault,它们具有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。
Cortex-M 处理器有三个固定优先级和 256 个(见下图,中断优先级寄存器被定义为8位长,2^8=256)可编程的优先级,最多有 128 个抢占等级(优先级分组:为了使抢占机能变得更加可控,CM3把256个可编程优先级分成高低两段,分别是抢占优先级、亚优先级 ),但实际的优先级数量是由芯片厂商来决定的,为了精简设计,芯片厂商在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,但不管用多少位来表达优先级,都是 MSB 对齐的,如图 4.1.3.1 就是使用三位来表达优先级。不管用多少位来表达优先级,都是 MSB(最高有效位) 对齐的,如下图就是使用前三位来表达优先级。
关于中断优先级寄存器 IP的定义,查看1.5.1.2节
如上图,Bit0~Bit4 没有实现,所以读值总是为零,写入操作也无效。因此,对于高 3 位的情况,可是使用的优先级就是 8个(2^3=8):0X00(最高优先级)、0X20(0010 0000)、0X40(0100 0000)、0X60(0110 0000)、0X80(1000 0000)、0XA0(1010 0000)、0XC0(1100 0000) 和 0XE0(1110 0000) .
❗注意:多少个有效位是芯片厂商来决定的!比如 STM32M3 就选择了 高4 位作为优先级!那么它可配置的优先级数量就是16个(2^4=16)
如下图为CM3中表达抢占优先级和亚优先级的关系,默认设置为分组0,即第0位表示亚优先级,第1~7位表示抢占优先级。
其中,这些分组可以表达的优先级分组最多为128个,即默认分组0下,1~7位表示抢占优先级,其能表达的数量为128个抢占优先级(2^7=128)
❗❗❗ 但是,STM32M3 处理器只选用了高4位 作为优先级,其可配置的可编程优先级数量只有16个,那么对于下表,分组0到分组2是无效的!其他芯片类同。

c
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4,将设置写入到AIRCR(应用程序中断及复位控制寄存器)中
c
// 在misc.h中,有关于中断优先级分组的定义
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority 抢占优先级 对应上图中的分组7
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 抢占优先级 对应上图中的分组3
0 bits for subpriority */
1.1.2 关于中断优先级寄存器 IP
在头文件startup_stm32f10x_hd.s文件中,有对中断优先级寄存器IP(Interrupt Priority Register (8Bit wide))的定义。
c
__Vectors DCD __initial_sp ; Top of Stack // IP[0]
DCD Reset_Handler ; Reset Handler // IP[1] 同理以下以此类推
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
// 上面16个是系统默认寄存器,固定不可编程
// 下面是外部中断寄存器,可编程
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
1.1.3 中断屏蔽寄存器组 PRIMASK、FAULTMASK、BASEPRI
- PRIMASK:用于除能在 NMI 和硬 fault 之外的所有异常。
- FAULTMASK:用于除能在 NMI之外的所有异常。
- BASEPRI(FreeRTOS中最常用):已知在FreeRTOS中,优先级号越小,优先级越高。BASEPRI 寄存器可实现只掩蔽优先级低于某一阈值的中断,被屏蔽的中断其优先级在数字上大于等于某个数,这个数就存储在BASEPRI 寄存器中。也即是说,如果BASEPRI中写0,将取消屏蔽任何中断。下面只讲解BASEPRI寄存器。
- FreeRTOS中对中断开关操作使用的函数是:
portDISABLE_INTERRUPTS()和portENABLE_INTERRUPTS(),它们定义在头文件portmacro.h中,
c
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
//其中
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; // configMAX_SYSCALL_INTERRUPT_PRIORITY参数定义在FreeRTOSConfig.h中
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI // 往BASEPRI寄存器中写值
dsb
isb
}
}
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
在头文件FreeRTOSConfig.h中,有如下以config定义的与中断有关的配置选项,如下面所设置,那么中断优先级0~4是系统不可管理的,用户不可以使用FreeRTOS中的关于中断优先级0 ~4的API函数,而中断优先级5 ~15是系统可管理的。
c
/***************************************************************************************************************/
/* FreeRTOS与中断有关的配置选项 */
/***************************************************************************************************************/
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS // stm32 使用4位优先级,即 __NVIC_PRIO_BITS = 4
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 // 中断最低优先级,stm32 是0-15共16个中断优先级,所以这里写15,此数值根据芯片而定
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级,这里设置为5,即高于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) // 设置 PendSV 和滴答定时器的中断优先级
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) // 低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不可管理的,中断服务函数也不能调用 FreeRTOS 的 API 函数
以 STM32 为例,有 16 (2^4=16)个优先级,0 为最高优先级,15 为最低优先级,配置如下:
configMAX_SYSCALL_INTERRUPT_PRIORITY==5
configKERNEL_INTERRUPT_PRIORITY==15那么实现的中断配置如下:
1.2 NVIC ------ 嵌套向量中断控制器
在core_cm3.h头文件中,可以找到 NVIC 的定义如下:
c
#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */
#define NVIC ((NVIC_Type *) NVIC_BASE) /*!< NVIC configuration struct */
可知 NVIC_BASE 的基础地址为 0xE000E000 + 0x0100 = 0xE000 E100 ,
其中结构体NVIC_Type 定义为:
c
/** @addtogroup CMSIS_CM3_NVIC CMSIS CM3 NVIC
memory mapped structure for Nested Vectored Interrupt Controller (NVIC)
@{
*/
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register,中断使能寄存器,其开始地址为 0xE000 E100 */
uint32_t RESERVED0[24]; /* 保留的24个32位数据 */
/* 上面一共8+24=32个数组元素,每个数组元素占4个字节,则32个数组元素一共占32*4=128个字节,128的十六进制就是 0x80 */
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register,中断除能寄存器,其开始地址为0xE000 E100 + 0x80 = 0xE000 E180 */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register,中断悬起寄存器 */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register 中断解悬寄存器 */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register 活动状态寄存器 */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
} NVIC_Type;
/*@}*/ /* end of group CMSIS_CM3_NVIC */
1.2.1 中断的使能与除能
由1.5.1节中的表7.2外部中断清单可知,一共有240个外部中断,每个中断拥有一对使能位/除能位,这 240 对使能位/除能位分布在 8 对 32 位寄存器中(32*8=256,足够存储240对数据位)。
在中断使能寄存器__IO uint32_t ISER[8]; 中,该数组大小定义为8,由于数据类型被定义为uint32_t ,即每个数组元素为32位,8个数组元素就是256位(32*8=256,足够存放240个数据位)。
想要使能一个中断,就写 1 到对应 SETENA 的位中;欲除能一个中断,就写 1 到对应的 CLRENA 位中;若往它们中写 0,不会有任何效果。通过这种方式,使能/除能中断时只需把"当事位"写成1,其它的位可以全部为0。

1.2.2 中断的悬起与解悬
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过"中断设置悬起寄存器(SETPEND)"和"中断悬起清除寄存器(CLRPEND)"来读取,还可以手动写它们来悬起中断。
悬起寄存器和"解悬"寄存器也可以有 8 对,其用法和用量都与前面介绍的使能/除能寄存器完全相同


1.2.3 中断优先级
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位。那么4 个相临的优先级寄存器就拼成一个 32 位寄存器。根据优先级组设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问。

系统异常优先级寄存器如下表所示,重点常用的为PRI_14和PRI_15.
注意:由于0xE000 ED20到0xE000 ED234个字节拼成一个32位寄存器,故在操作PRI_14和PRI_15时,其操作地址从0xE000 ED20开始。

1.2.4 活动状态
每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个ISR。活动状态寄存器的定义,与前面讲的使能/除能和悬起/解悬寄存器相同,只是不再成对出现。它们也能按字/半字/字节访问,但他们是只读的,如下表所示:

1.3 【实验】中断测试实验
在 FreeRTOS 中优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断会被屏蔽掉,高于的就不会,那么下面就写个简单的例程测试一下,使用两个定时器,一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符串。然后在某个任务中关闭中断一段时间,再查看两个定时器的输出情况。
- main文件
c
#include "main.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define TASK2_TASK_PRIO 2 //任务优先级
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task2Task_Handler; //任务句柄
void task2_task(void *pvParameters); //任务函数
#define INTERRUPT_TASK_PRIO 2 //任务优先级
#define INTERRUPT_STK_SIZE 128 //任务堆栈大小
TaskHandle_t INTERRUPTTask_Handler; //任务句柄
void interrupt_task(void *p_arg); //任务函数
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
TIM3_Int_Init(10000-1,7200-1); //初始化定时器3,定时器周期1S
TIM5_Int_Init(10000-1,7200-1); //初始化定时器5,定时器周期1S
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
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); //删除开始任务
// 任务创建完成后,即刻开始运行,即任务1和任务2同时运行,直到其被删除
taskEXIT_CRITICAL(); //退出临界区
}
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
total_num+=1;
if(total_num==5)
{
sprintfU4(".............关闭中断.............\r\n");
portDISABLE_INTERRUPTS(); //关闭中断
delay_xms(5000); //延时5s
sprintfU4(".............打开中断.............\r\n"); //打开中断
portENABLE_INTERRUPTS();
total_num = 0;
}
PCA9554_OUT(7,ON);
vTaskDelay(500);
PCA9554_OUT(7,OFF);
vTaskDelay(500); // 延时共1s后循环
}
}
定时器文件
c
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定义参数结构体
NVIC_InitTypeDef NVIC_InitStructure; // 定义参数结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //定时器3时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //先占优先级4级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//通用定时器5中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器5!
void TIM5_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 定义参数结构体
NVIC_InitTypeDef NVIC_InitStructure; // 定义参数结构体
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
//定时器TIM5初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5; //先占优先级5级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM5, ENABLE); //使能TIM5
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
sprintfU4("TIM3输出.......\r\n");
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
//定时器5中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
{
sprintfU4(".......TIM5输出\r\n");
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}
串口打印结果:

2. 列表与列表项
列表是 FreeRTOS 中的一个数据结构,列表用来跟踪 FreeRTOS中的任务。与列表相关的全部东西都在文件 list.c 和 list.h 中,其原理类似于数据结构中的链表。
2.1 列表结构体
在 list.h 中定义了一个叫 List_t 的结构体如下:
c
/*
* Definition of the type of queue used by the scheduler.
*/
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex; /*< Used to walk through the list. Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
MiniListItem_t xListEnd; /*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;
其中:
listFIRST_LIST_INTEGRITY_CHECK_VALUE、listSECOND_LIST_INTEGRITY_CHECK_VALUE:用于检查列表的完整性,其使用的前提是在FreeRTOSConfig.h文件中,对变量configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES进行置1,但是默认不开启,基本不使用uxNumberOfItems:用于记录列表中的列表项数量pxIndex:用于记录当前列表中各列表项的索引号,以用于遍历列表,它所指向的列表项就是要遍历的开始列表项,也就是说它所指向的列表项就代该表列的表头xListEnd:表示列表中最后一个列表项,用来表示列表结束
2.2 列表项
列表项就是存放在列表中的项,其在文件 list.h 中有定义:
c
/*
* Definition of the only type of object that a list can contain.
*/
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue; /*< The value being listed. In most cases this is used to sort the list in descending order. */
struct xLIST_ITEM * configLIST_VOLATILE pxNext; /*< Pointer to the next ListItem_t in the list. */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< Pointer to the previous ListItem_t in the list. */
void * pvOwner; /*< Pointer to the object (normally a TCB) that contains the list item. There is therefore a two way link between the object containing the list item and the list item itself. */
void * configLIST_VOLATILE pvContainer; /*< Pointer to the list in which this list item is placed (if any). */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t; /* For some reason lint wants this as two separate definitions. */
其中:
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE、listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE:用于检查列表的完整性,其使用的前提是在FreeRTOSConfig.h文件中,对变量configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES进行置1,但是默认不开启,基本不使用xItemValue: 列表项的值pxNext:指向下一个列表项pxPrevious:指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能pvOwner:记录此列表项归谁拥有,通常是任务控制块pvContainer:用于记录此列表项属于哪个列表
关于
pvOwner与pvContainer的区别,举个通俗的例子:小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的
pvOwner属性值就是老王,小王的pvContainer属性值就是二年级。
2.3 迷你列表项
FreeRTOS中的另一种列表项类型,其在文件 list.h 中有定义,其功能与列表项一样,相当于列表项的简化版,目的是防止列表项占用太多内存:
c
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
2.4 列表项相关API函数

2.4.1 列表初始化
列表的初始化其实就是初始化列表结构体List_t 中的各个成员变量,列表的初始化通过使函数 vListInitialise()来完成,此函数定义在 list.c 中:
c
/*-----------------------------------------------------------
* PUBLIC LIST API documented in list.h
*----------------------------------------------------------*/
void vListInitialise( List_t * const pxList )
{
/* The list structure contains a list item which is used to mark the
end of the list. To initialise the list the list end is inserted
as the only list entry. */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
/* The list end value is the highest possible value in the list to
ensure it remains at the end of the list. */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* The list end next and previous pointers point to itself so we know
when the list is empty. */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
/* Write known values into the list if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
/*-----------------------------------------------------------*/
其中:
pxIndex:列表项的索引,因为在列表初始化时,列表中只有一个列表项xListEnd,所以在列表初始化时,pxIndex指向xListEnd
注:
xListEnd的数据类型是迷你列表项,同理,由于xListEnd是一个迷你列表项,那么它本身也需要进行初始化,初始化方法一样采样对其结构体进行赋值的方式:
xListEnd.xItemValue:xListEnd的列表项值初始化为portMAX_DELAY,portMAX_DELAY是个宏,在头文件portmacro.h中有定义。根据不同的 MCU ,portMAX_DELAY值不相同,可以为 0xffff(16位MCU)或者 0xffffffffUL(32位MCU),这里使用的STM32F1的是32位MCUxListEnd.pxNext:指向下一个列表项,因为列表中只有一个列表项xListEnd,因此pxNext只能指向自身xListEnd.pxNext:与xListEnd.pxNext同理uxNumberOfItems:当前列表中有几个列表项,初始化是0个
注:迷你列表项
xListEnd不计入列表中的列表项
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );、listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );:用于检查初始化列表项的完整性, 它们只有宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES为 1 时有效。同样的根据所选的MCU 不同其写入的值也不同,可以为 0x5a5a(16位MCU) 或者 0x5a5a5a5aUL(32位MCU),这里使用的STM32F1的是32位MCU

2.4.2 列表项初始化
与列表一样,列表项在使用前也需要初始化,列表项初始化由函数 vListInitialiseItem()来完成:
c
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* Make sure the list item is not recorded as being on a list. */
pxItem->pvContainer = NULL;
/* Write known values into the list item if
configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
其中:
pvContainer:表示该列表项属于那个列表,由于列表项初始化时未属于任何一个列表,故配置为NULLlistSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );、listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );:同理是用于检查完整性的
2.4.3 列表项插入
列表项的插入操作通过函数 vListInsert()来完成:
c
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; // 获取要插入的列表项值,即获取列表项成员变量 xItemValue 的值,因为要根据这个值来确定列表项要插入的位置
/* Only effective when configASSERT() is also defined, these tests may catch
the list data structures being overwritten in memory. They will not catch
data errors caused by incorrect configuration or use of FreeRTOS. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem ); //用于检查列表和列表项的完整性
/* Insert the new list item into the list, sorted in xItemValue order.
If the list already contains a list item with the same item value then the
new list item should be placed after it. This ensures that TCB's which are
stored in ready lists (all of which have the same xItemValue value) get a
share of the CPU. However, if the xItemValue is the same as the back marker
the iteration loop below will not end. Therefore the value is checked
first, and the algorithm slightly modified if necessary. */
/* 如果要插入的列表项的值等于 portMAX_DELAY,也就是说列表项值为最大值,直接将列表项插入到列表的末尾 */
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* *** NOTE ***********************************************************
If you find your application is crashing here then likely causes are
listed below. In addition see http://www.freertos.org/FAQHelp.html for
more tips, and ensure configASSERT() is defined!
http://www.freertos.org/a00110.html#configASSERT
1) Stack overflow -
see http://www.freertos.org/Stacks-and-stack-overflow-checking.html
2) Incorrect interrupt priority assignment, especially on Cortex-M
parts where numerically high priority values denote low actual
interrupt priorities, which can seem counter intuitive. See
http://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition
of configMAX_SYSCALL_INTERRUPT_PRIORITY on
http://www.freertos.org/a00110.html
3) Calling an API function from within a critical section or when
the scheduler is suspended, or calling an API function that does
not end in "FromISR" from an interrupt.
4) Using a queue or semaphore before it has been initialised or
before the scheduler has been started (are interrupts firing
before vTaskStartScheduler() has been called?).
**********************************************************************/
/*否则,就需要在列表中找到自己的位置,这个 for 循环就是找位置的过程,当找到合适列表项的位置的时候就会跳出。由
于这个 for 循环是用来寻找列表项插入点的,所以 for 循环体里面没有任何东西。这个查找过程
是按照升序的方式查找列表项插入点的。*/
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM. This is checked and valid. */
{
/* There is nothing to do here, just iterating to the wanted
insertion position. */
}
}
/*下面四行代码就是将列表项插入到列表中,插入过程和数据结构中双向链表的插入类似。 */
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* Remember which list the item is in. This allows fast removal of the
item later. */
pxNewListItem->pvContainer = ( void * ) pxList; // 到此步,列表项已插入到列表中,列表项的成员变量 pvContainer 用于记录此列表项属于哪个列表
( pxList->uxNumberOfItems )++; // 列表的成员变量 uxNumberOfItems 加一,表示又添加了一个列表项
}
其中输入参数:
pxList: 列表项要插入的列表pxNewListItem: 要插入的列表项,列表项的插入根据xItemValue的值按照升序的方式排列,而xItemValue的值根据pxNewListItem而得
2.4.3.1 列表项插入图解
往一个空列表中插入一个值为40的列表项:(这里的值表示xItemValue,下同)
- 列表中的
uxNumberOfItems由NULL变为1,表示空列表中增加了一个列表项 - 列表项中的
pvContainer变成了 List,表示该列表项归属于列表List

继续往列表中插入一个值为60的列表项,他插在列表项ListItem1之后:
- 列表中的
uxNumberOfItems由1变为2,表示列表中列表项的数量为2 - 同理ListItem2列表项中的
pvContainer变成了 List,表示该列表项归属于列表List - 它位于ListItem1之后,xListEnd之前

继续往列表中插入一个值为50的列表项,他插在列表项ListItem1之后、ListItem2之前:
- 列表中的
uxNumberOfItems由2变为3,表示列表中列表项的数量为3 - 同理ListItem3列表项中的
pvContainer变成了 List,表示该列表项归属于列表List

2.4.3.2 关于环形列表
以图7.3.2.3 为例,由于pxIndex指向迷你列表项xListEnd,故列表的表头为迷你列表项xListEnd,而迷你列表项xListEnd的pxNext指向ListItem1,表示它的下一个列表项是ListItem1,如此类推,最后表尾ListItem3的pxNext指回迷你列表项xListEnd,形成一个环形列表

2.4.4 列表项末尾插入
列表末尾插入列表项的操作通过函数 vListInsertEnd()来完成:
c
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
/* Only effective when configASSERT() is also defined, these tests may catch
the list data structures being overwritten in memory. They will not catch
data errors caused by incorrect configuration or use of FreeRTOS. */
listTEST_LIST_INTEGRITY( pxList ); //用于检查列表和列表项的完整性
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* Insert a new list item into pxList, but rather than sort the list,
makes the new list item the last item to be removed by a call to
listGET_OWNER_OF_NEXT_ENTRY(). */
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* Remember which list the item is in. */
pxNewListItem->pvContainer = ( void * ) pxList;
( pxList->uxNumberOfItems )++;
}
其中输入参数:
pxList: 列表项要插入的列表pxNewListItem: 要插入的列表项
2.4.4.1 列表项末尾插入图解
先准备一个默认的列表,注意与 6.4.3.1节中不同的是,列表的pxIndex指向的不是迷你列表项xListEnd,而是ListItem1,表示列表List的表头列表项为ListItem1,末尾列表项就是迷你列表项xListEnd

既然已知末尾列表项是迷你列表项xListEnd,那么使用列表项末尾插入函数vListInsertEnd()去插入列表项,当然是在迷你列表项xListEnd之后插入新的列表项

2.4.5 列表项的删除
列表项的删除通过函数 uxListRemove()来完成:
c
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in. Obtain the list from the list
item. */
List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pvContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;
}
其中
- 输入函数:
pxItemToRemove: 要删除的列表项 - 返回值:返回删除列表项以后的列表剩余列表项数目
注:列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉
2.4.6 列表的遍历
在6.1节中说过列表结构体 List_t 中的成员变量 pxIndex 是用来遍历列表的,FreeRTOS中用于完成列表的遍历函数是listGET_OWNER_OF_NEXT_ENTRY() ,此函数的用途是从多个同优先级的就绪任务中查找下一个要运行的任务, 每调用一次这个函数,列表的 pxIndex 变量就会指向下一个列表项,并且返回这个列表项的 pxOwner值。这个函数是一个宏,这个宏在头文件 list.h 中定义:
c
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* Increment the index to the next item and return the item, ensuring */ \
/* we don't return the marker used at the end of the list. */ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; // 列表的 pxIndex 变量指向下一个列表项
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) //如果 pxIndex 指向了列表的 xListEnd 成员变量,表示到了列表末尾
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; //如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表项,这样就完成了一次对列表的遍历
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; //将 pxIndex 所指向的新列表项的 pvOwner 赋值给 pxTCB
}
其中输入参数:
pxTCB: 用于保存pxIndex所指向的列表项的pvOwner值,也就是这个列表项属于谁的,它通常是一个任务的任务控制块pxList: 表示要遍历的列表
2.5 【实验】列表插入与删除
c
#include "main.h"
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define TASK1_TASK_PRIO 3 //任务优先级
#define TASK1_STK_SIZE 128 //任务堆栈大小
TaskHandle_t Task1Task_Handler; //任务句柄
void task1_task(void *pvParameters); //任务函数
#define LIST_TASK_PRIO 3//任务优先级
#define LIST_STK_SIZE 128 //任务堆栈大小
TaskHandle_t ListTask_Handler;//任务句柄
void list_task(void *pvParameters);//任务函数
//定义一个测试用的列表和3个列表项
List_t TestList; //测试用列表
ListItem_t ListItem1; //测试用列表项1
ListItem_t ListItem2; //测试用列表项2
ListItem_t ListItem3; //测试用列表项3
int main(void)
{
bsp_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
/* FreeRTOS 创建开始任务 开始 */
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(); //开启任务调度器
/* FreeRTOS 创建开始任务 结束 */
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建LIST任务
xTaskCreate((TaskFunction_t )list_task,
(const char* )"list_task",
(uint16_t )LIST_STK_SIZE,
(void* )NULL,
(UBaseType_t )LIST_TASK_PRIO,
(TaskHandle_t* )&ListTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
// 任务创建完成后,即刻开始运行,即任务1和任务2同时运行,直到其被删除
taskEXIT_CRITICAL(); //退出临界区
}
//task1任务函数
void task1_task(void *pvParameters)
{
while(1)
{
PCA9554_OUT(8,ON);
vTaskDelay(250); //延时250ms
PCA9554_OUT(8,ON);
vTaskDelay(250); //延时250ms
}
}
//list任务函数
void list_task(void *pvParameters)
{
//第一步:初始化列表和列表项
vListInitialise(&TestList);
vListInitialiseItem(&ListItem1);
vListInitialiseItem(&ListItem2);
vListInitialiseItem(&ListItem3);
ListItem1.xItemValue=40; //ListItem1列表项值为40
ListItem2.xItemValue=60; //ListItem2列表项值为60
ListItem3.xItemValue=50; //ListItem3列表项值为50
//第二步:打印列表和其他列表项的地址
printf("\r\n");
printf("/*******************列表和列表项地址*******************/\r\n");
printf("项目 地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd %#x \r\n",(int)(&TestList.xListEnd));
printf("ListItem1 %#x \r\n",(int)&ListItem1);
printf("ListItem2 %#x \r\n",(int)&ListItem2);
printf("ListItem3 %#x \r\n",(int)&ListItem3);
printf("/************************结束**************************/\r\n");
printf("按下INPUT1继续!\r\n\r\n\r\n");
while(INPUT1==1) //等待INPUT1按下
{delay_ms(10);}
//第三步:向列表TestList添加列表项ListItem1,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem1); //插入列表项ListItem1
printf("/******************添加列表项ListItem1*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT2继续!\r\n\r\n\r\n");
while(INPUT2==1) //等待INPUT2键按下
{delay_ms(10);}
//第四步:向列表TestList添加列表项ListItem2,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem2); //插入列表项ListItem2
printf("/******************添加列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT3继续!\r\n\r\n\r\n");
while(INPUT3==1) //等待INPUT3键按下
{delay_ms(10);}
//第五步:向列表TestList添加列表项ListItem3,并通过串口打印所有
//列表项中成员变量pxNext和pxPrevious的值,通过这两个值观察列表
//项在列表中的连接情况。
vListInsert(&TestList,&ListItem3); //插入列表项ListItem3
printf("/******************添加列表项ListItem3*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT4继续!\r\n\r\n\r\n");
while(INPUT4==1) //等待INPUT3键按下
{delay_ms(10);}
//第六步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
uxListRemove(&ListItem2); //删除ListItem2
printf("/******************删除列表项ListItem2*****************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n");
printf("按下INPUT5继续!\r\n\r\n\r\n");
while(INPUT5==1) //等待INPUT3键按下
{delay_ms(10);}
//第七步:删除ListItem2,并通过串口打印所有列表项中成员变量pxNext和
//pxPrevious的值,通过这两个值观察列表项在列表中的连接情况。
TestList.pxIndex=TestList.pxIndex->pxNext; //pxIndex向后移一项,这样pxIndex就会指向ListItem1。
vListInsertEnd(&TestList,&ListItem2); //列表末尾添加列表项ListItem2
printf("/***************在末尾添加列表项ListItem2***************/\r\n");
printf("项目 地址 \r\n");
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/*******************前后向连接分割线********************/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/************************结束**************************/\r\n\r\n\r\n");
while(1)
{
PCA9554_OUT(8,ON);
vTaskDelay(1000);
PCA9554_OUT(8,OFF);
vTaskDelay(1000);
}
}
运行结果如下:



过程图解如下:



