STM32理论 —— FreeRTOS:中断管理、列表

文章目录

  • [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_14PRI_15.

注意:由于0xE000 ED200xE000 ED234个字节拼成一个32位寄存器,故在操作PRI_14PRI_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.clist.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_VALUElistSECOND_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_VALUElistSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE:用于检查列表的完整性,其使用的前提是在FreeRTOSConfig.h文件中,对变量configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES进行置1,但是默认不开启,基本不使用
  • xItemValue: 列表项的值
  • pxNext :指向下一个列表项
  • pxPrevious :指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能
  • pvOwner :记录此列表项归谁拥有,通常是任务控制块
  • pvContainer :用于记录此列表项属于哪个列表

关于pvOwnerpvContainer的区别,举个通俗的例子:小王在上二年级,他的父亲是老王。如果把小王比作列表项,那么小王的
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.xItemValuexListEnd 的列表项值初始化为 portMAX_DELAYportMAX_DELAY 是个宏,在头文件portmacro.h 中有定义。根据不同的 MCU ,portMAX_DELAY 值不相同,可以为 0xffff(16位MCU)或者 0xffffffffUL(32位MCU),这里使用的STM32F1的是32位MCU
  • xListEnd.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:表示该列表项属于那个列表,由于列表项初始化时未属于任何一个列表,故配置为NULL
  • listSET_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,下同)

  1. 列表中的uxNumberOfItems由NULL变为1,表示空列表中增加了一个列表项
  2. 列表项中的pvContainer 变成了 List,表示该列表项归属于列表List

继续往列表中插入一个值为60的列表项,他插在列表项ListItem1之后:

  1. 列表中的uxNumberOfItems由1变为2,表示列表中列表项的数量为2
  2. 同理ListItem2列表项中的pvContainer 变成了 List,表示该列表项归属于列表List
  3. 它位于ListItem1之后,xListEnd之前

继续往列表中插入一个值为50的列表项,他插在列表项ListItem1之后、ListItem2之前:

  1. 列表中的uxNumberOfItems由2变为3,表示列表中列表项的数量为3
  2. 同理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); 
	}
}

运行结果如下:


过程图解如下:

相关推荐
Hello_Embed3 小时前
嵌入式上位机开发入门(二):常用 API
笔记·stm32·嵌入式·信息与通信
Zevalin爱灰灰3 小时前
零基础入门学用物联网(ESP8266) 第二部分 MQTT基础篇(四)
单片机·物联网·mqtt·嵌入式·esp8266
贺小涛4 小时前
STM32学习
stm32·单片机·学习
LXY_BUAA4 小时前
《嵌入式操作系统》_GPIOLIB前置知识_20260328
驱动开发·嵌入式硬件
DA02214 小时前
系统移植-STM32MP1_TF-A概述
单片机·系统移植·stm32mp1
17(无规则自律)4 小时前
深度剖析Linux Input子系统(2):驱动开发流程与现代 Multi-touch 协议
linux·驱动开发·嵌入式硬件
yugi9878384 小时前
基于STM32F107和DP83848的TCP服务器数据收发方案
服务器·stm32·tcp/ip
BackCatK Chen5 小时前
STM32保姆级入门教程|第5章:GPIO内部结构 + 8种模式 + 功能详解
stm32·嵌入式硬件·gpio·推挽输出·开漏输出·gpio内部结构·上拉输入
2301_822782826 小时前
C语言利用EasyX实现图形化界面的小游戏
c语言·单片机·图形化界面·lcd菜单·接口实现