FREERTOS学习笔记

  1. INCLUDE_XX
    使能或者除能函数,定义了后,某些函数才会被编译
  2. configXXX
    用来完成FREERTOS的裁剪

任务四种状态

  1. 运行态
  2. 就绪态
  3. 阻塞态
    等待事件的输入
  4. 挂起态

任务优先级

0~最大优先级数-1,
优先级0最小,优先级随数字依次递增
多个任务可以使用同一个优先级(configUSE_TIME_SLICING 为 1 时)

任务控制模块

存储任务的属性,FREERTOS把这些属性结合到一起用一个结构体表示,这个结构体就叫:TCB_t,会存储任务优先级,任务堆栈起始地址等,多数成员和功能裁剪有关,哪些功能不适用,控制块的大小也随之减小。

创建任务可以通过

a. 动态创建方法 xTaskCreate()

使用的内存是根据任务的使用情况申请的,需要传入一个内存指针
I. 函数原型
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )

II. 参数:
pxTaskCode:要执行的任务的函数
const pcName:任务的名字,是一串字符串,长度不能超过最大名字长度
usStackDepth:任务堆栈大小,申请到为参数的四倍大小,注:空闲任务的堆栈大小为configMINIMAL_STACK_SIZE
pvParameters: 要传递的参数,可以为字符串
uxPriority: 任务优先级,范围在0~最高优先级-1
pxCreatedTask: 任务句柄,其他API函数会使用到这个任务句柄
动态方法在参数中返回任务句柄,而静态方法在函数返回值中返回任务句柄(参数中无任务句柄)

III. 返回值:
pdPASS: 如果任务成功创建
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 由于堆内存不足,任务创建失败。
@return pdPASS if the task was successfully created and added to a ready
list, otherwise an error code defined in the file projdefs.h

任务创建函数功能:

  1. 根据栈空间增长方向,portSTACK_GROWTH>0为向上增长,portSTACK_GROWTH<0为向下增长,使用pvPortMalloc申请任务Stack的内存
  2. 使用pvPortMalloc申请任务控制块的内存TCB
  3. 如果控制块TCB申请成功,将TCB成员ucStaticallyAllocated标记为自动申请
  4. 调用prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL )初始化任务
  5. 调用prvAddNewTaskToReadyList( pxNewTCB ); 将新创建任务添加到就绪列表中

prvInitialiseNewTask()功能

  1. 假如开启了configCHECK_FOR_STACK_OVERFLOW > 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 )初始化任务stack为 0xa5
  2. 获取任务栈底的地址
  3. 将任务名字符串保存到TCB的pcTaskName
  4. 保存任务优先级,若优先级超过最大优先级,则设置为最大优先级
  5. 若使能了互斥信号量,则设置基优先级
  6. 通过vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );,初始化 状态列表项xStateListItem 和 事件列表项xEventListItem,并将创建的两个列表项设置owner为TCB
  7. 设置事件列表项值,按照优先级排列(数字由小到大),所以用最大优先级减去当前任务优先级,这样优先级小的在队列后部,优先级大的在前部
  8. 如果使能了任务通知,初始化任务通知成员
  9. 初始化任务控制块其余的成员
  10. 调用pxPortInitialiseStack(),初始化栈
  11. 将任务控制块转换成任务句柄(一个指针),作为参数保存

pxPortInitialiseStack()的作用

  1. 栈底第一个位置存放portINITIAL_XPSR

  2. 接下来存放任务函数指针

  3. prvTaskExitError入栈

    StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void pvParameters )
    {
    /
    Simulate the stack frame as it would be created by a context switch
    interrupt. */

     /* Offset added to account for the way the MCU uses the stack on entry/exit
     of interrupts, and to ensure alignment. */
     pxTopOfStack--;
    
     *pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
     pxTopOfStack--;
     *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
     pxTopOfStack--;
     *pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */
    
     /* Save code space by skipping register initialisation. */
     pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
     *pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */
    
     /* A save method is being used that requires each task to maintain its
     own exec return value. */
     pxTopOfStack--;
     *pxTopOfStack = portINITIAL_EXEC_RETURN;
    
     pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */
    
     return pxTopOfStack;//最后返回真正的栈顶地址
    

    }

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

*prvAddNewTaskToReadyList( TCB_t pxNewTCB )的作用

  1. uxCurrentNumberOfTasks++,记录系统中的任务
  2. 判断当前运行的控制块,若为NULL(第一个任务创建时),初始化延时列表等
  3. 若不为NULL,则为任务调度器开始前,第一个任务创建后的情况,根据任务优先级,判断是否要将pxCurrentTCB 赋值为 新的任务控制块
  4. 任务控制块中任务创建的序号加一,用来调试用
  5. 调用prvAddTaskToReadyList( pxNewTCB );将任务添加到就绪列表中
  6. 若新创建的任务优先级比当前任务优先级高,那么进行任务切换

prvAddTaskToReadyList( pxNewTCB ) 的作用

  1. 将对应的uxTopReadyPriority相应的bit置1,表示相应的优先级有就绪任务
  2. 将创建的任务添加到对应的就绪列表中,末尾插入,比如优先级5就插入到pxReadyTaskLists[5]

b. 静态创建方法 xTaskCreateStatic()
要先在 FreeRTOSConfig.h中

 #define configSUPPORT_STATIC_ALLOCATION			1	

然后定义vApplicationGetIdleTaskMemory,vApplicationGetTimerTaskMemory这两个函数,因为一旦使用了静态分配模式,就要用户自行实现这两个函数
不需要传入控制块的指针,要设置任务的堆栈大小
注:堆栈大小单位为UINT类型,所以设置堆栈大小50的话,实际大小为200字节
I. 函数原型
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )

II. 参数:
pxTaskCode:要执行的任务的函数
const pcName:任务的名字,是一串字符串,长度不能超过最大名字长度
usStackDepth:任务堆栈大小,申请到为参数的四倍大小,注:空闲任务的堆栈大小为configMINIMAL_STACK_SIZE
pvParameters: 要传递的参数,可以为字符串
uxPriority: 任务优先级,范围在0~最高优先级-1
puxStackBuffer: 任务堆栈,一般为一个数组,数组类型要是StackType_t类型,当为动态创建时,这个栈空间由函数自行申请。
pxTaskBuffer: 任务控制块,必须要是StaticTask_t类型,当为动态创建时,这个任务控制块由函数自行申请。

动态方法在参数中返回任务句柄,而静态方法在函数返回值中返回任务句柄(参数中无任务句柄)

III. 返回值:
NULL:任务创建失败,puxStackBuffer 和 pxTaskBuffer 为 NULL会导致这个错误的发生
其他值:任务创建成功,返回任务的任务句柄

@return If neither pxStackBuffer or pxTaskBuffer are NULL, then the task will
be created and pdPASS is returned. If either pxStackBuffer or pxTaskBuffer
are NULL then the task will not be created and errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned.

例子:

// Structure that will hold the TCB of the task being created.
    StaticTask_t xTaskBuffer;

// Buffer that the task being created will use as its stack.  Note this is
// an array of StackType_t variables.  The size of StackType_t is dependent on
// the RTOS port.
	StackType_t xStack[ STACK_SIZE ];

// Function that implements the task being created.
void vTaskCode( void * pvParameters )
{
    // The parameter value is expected to be 1 as 1 is passed in the
    // pvParameters value in the call to xTaskCreateStatic().
    configASSERT( ( uint32_t ) pvParameters == 1UL );

    for( ;; )
    {
        // Task code goes here.
    }
}

// Function that creates a task.
void vOtherFunction( void )
{
    TaskHandle_t xHandle = NULL;

    // Create the task without using any dynamic memory allocation.
    xHandle = xTaskCreateStatic(
                  vTaskCode,       // Function that implements the task.
                  "NAME",          // Text name for the task.
                  STACK_SIZE,      // Stack size in words, not bytes.
                  ( void * ) 1,    // Parameter passed into the task.
                  tskIDLE_PRIORITY,// Priority at which the task is created.
                  xStack,          // Array to use as the task's stack.
                  &xTaskBuffer );  // Variable to hold the task's data structure.

    // puxStackBuffer and pxTaskBuffer were not NULL, so the task will have
    // been created, and xHandle will be the task's handle.  Use the handle
    // to suspend the task.
    vTaskSuspend( xHandle );
}

c. 创建一个使用MPU进行限制的任务
xTaskCreateRestricted()
相关内存使用动态内存分配

删除任务可以通过

a. vTaskDelete()
删除一个用以上方法创建的任务,任务删除后不会再进入运行态,所以再也不能使用这个任务的句柄,

  1. 若该任务由动态方法创建,堆栈和控制块会在空闲任务中释放掉,所以调用 vTaskDelete()后要给空闲任务一些运行时间。
  2. 若该任务是用静态方法创建的,则需要程序员自己释放内存,否则会导致内存泄漏

I. 函数原型
void vTaskDelete( TaskHandle_t xTaskToDelete )

II. 参数
xTaskToDelete: 要删除的任务的任务句柄

III. 返回值

IV. 任务删除函数功能:

  1. 获取任务控制块,若传入的参数为null,则获取当前current控制块
  2. 从相应的列表(Ready, Block, Suspend)中移除该任务的列表项,做列表的元素删除操作
  3. 若对应优先级的就绪列表组中,没有任务的话,就将该优先级的bit清零
  4. 如果任务还在等待事件的话,那么将此任务从对应的事件列表中移除
  5. 如果删除的是自身,那么将任务添加到等待结束的任务列表中xTasksWaitingTermination,任务内存将在空闲任务释放
  6. 如果不是删除自身的话,–uxCurrentNumberOfTasks; 当前系统任务减1
  7. 释放任务控制块和栈空间内存
  8. 假如延时列表中还有其他任务,因为这个任务被删除了,所以下个恢复运行的任务时间要提前
  9. 如果删除的是自身,还要主动发起任务切换

任务的挂起和恢复

a. vTaskSuspend()
挂起一个任务
I. 函数原型
void vTaskSuspend ( TaskHandle_t xTaskToSuspend )

II. 参数
xTaskToSuspend: 要挂起的任务的任务句柄,传入NULL的话,会将调用vTaskSuspend函数的任务挂起

III. 返回值

IV. 任务挂起函数功能:

  1. 获取任务控制块,若传入的参数为null,则获取当前current控制块
  2. 从相应的列表(Ready, Block, Suspend)中移除该任务的列表项,做列表的元素删除操作
  3. 将任务从对应的事件列表中移除
  4. 将要挂起的任务添加到xSuspendedTaskList中
  5. 更新下个任务的解锁时间,防止其他任务的解锁时间参考了这个被挂起的任务
  6. 假如挂起的是当前运行的任务,且任务调度器在运行,则进行任务切换
  7. 假如任务调度器没有运行,切所有任务(包括空闲任务)都被挂起的话,则寻找下一个要运行的任务

b. vTaskResume()
恢复一个任务的运行,无论一个任务调用了多少次vTaskSuspend,恢复任务值用调用一次vTaskResume
注: INCLUDE_vTaskSuspend must be defined as 1 for this function to be available.

I. 函数原型
void vTaskResume ( TaskHandle_t xTaskToResume )

II. 参数
xTaskToResume: 要恢复的任务的句柄

III. 返回值

IV. 任务恢复函数功能:

  1. 判断要恢复的任务是否为挂起的任务
  2. 将要恢复的任务从挂起列表xSuspendedTaskList中移除
  3. 调用prvAddTaskToReadyList()将任务添加到就绪列表中
  4. 假如恢复的任务优先级大于正在运行的任务,则进行任务切换

c. xTaskResumeFromISR()
在中断服务函数中恢复一个任务的运行,与vTaskResume()相同,无论一个任务调用了多少次vTaskSuspend,恢复任务值用调用一次xTaskResumeFromISR()
注:INCLUDE_xTaskResumeFromISR must be defined as 1 for this function to be available
I. 函数原型
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

II. 参数
xTaskToResume: 要恢复的任务的句柄

III. 返回值
pdTRUE:恢复的任务优先级比当前运行的任务更高,所以要进行任务切换
pdFALSE:恢复的任务优先级比当前任务低,所以不用进行任务切换
例如:

extern TaskHandle_t Task2Task_Handler;

void EXTI3_IRQHandler(void)
{
	BaseType_t YieldRequired;

	delay_xms(50);						//消抖
	if(KEY0==0)
	{
		YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务2
		printf("恢复任务2的运行!\r\n");
		if(YieldRequired==pdTRUE)
		{
			/*如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
			任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
			退出中断的时候一定要进行上下文切换!*/
			portYIELD_FROM_ISR(YieldRequired);
		}
	}
	__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3);	//清除中断标志位
}

列表和列表项

列表和列表项有完整性检查的成员,原理是在结构体的起始成员和末尾成员,设置两个固定值的变量,一旦有数据越界,必定会改变头尾数据的值,每次插入前检查一次值,就可以确认成员值是否被更改,以下为代码

#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
#define listTEST_LIST_ITEM_INTEGRITY( pxItem )		configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#define listTEST_LIST_INTEGRITY( pxList )			configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )

a. 列表

/*
 * 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;

注:最后一个成员变量为 Mini列表项类型

b. 列表项

/*
 * 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. */

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;

列表的相关API函数

a. vListInitialise()
列表初始化

#define portMAX_DELAY ( TickType_t ) 0xffffffffUL

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. 确保listEnd处于最尾端*/
	
	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;  //当前列表项为0

	/* Write known values into the list if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */完整性检查
	 
	 //16_Bit_Mcu use 0x5a5a, and 32_Bit one use 0x5a5a5a5aUL
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTE  GRITY_CHECK_2_VALU E( pxList );
}

b. vListInitialiseItem()
列表项 初始化,在xTaskCreate(任务创建)的时候,函数会对其初始化

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 );
}

c. vListInsert
列表项插入,由于列表是双向的,所以结构为环状

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	const TickType_t xValueOfInsertion = pxNewListItem->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. */
	
	if( xValueOfInsertion == portMAX_DELAY )//如果插入的列表项是最大值,直接插入到列表最尾
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else//若不是最尾项,则找到要插入的位置,以下是按xItemValue 来寻找插入的位置
	{
		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; //将列表项插入到列表中

	( pxList->uxNumberOfItems )++; //列表的成员数加1
}

d. vListInsertEnd
列表项末尾插入 ,这里并不是指插入到列表尾,而是插入到pxIndex所指列表项的前面,因为pxIndex指向的是列表头,所以pxIndex所指项的前一个就是尾巴

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(). */
	//以下同样是在双向链表中插入元素的操作,只不过是在pxIndex所指项的前面插入,pxIndex所指的项是任意的,pxIndex所指的列表项就代表列表头
	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 )++; //列表的成员数加1
}

f. uxListRemove
列表项删除
返回值:删除列表项后,列表剩余的列表项的数量

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 )//如果删掉的是pxIndex所指的节点的话,则把pxIndex指向删除的节点的前一个节点
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();//用来输出调试信息
	}

	pxItemToRemove->pvContainer = NULL; //被删除的列表项要清除 所归属的列表
	( pxList->uxNumberOfItems )--; //该列表的项目数减少

	return pxList->uxNumberOfItems; //返回这个列表当前列表项数目
}

g. listGET_OWNER_OF_NEXT_ENTRY
列表的遍历
此函数用来用于从多个优先级的就绪任务中查找下一个要运行的任务
FREE_RTOS提供了一个宏定义形式的函数

#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指向了列表的END项时,则代表到了列表的末尾,到了末尾的话,就跳过END项,指向列表头的列表项																					\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;  //将指向的列表项的owner赋给TCB											\
}

任务查询和信息统计API函数

  • 1. UBaseType_t uxTaskPriorityGet ( TaskHandle_t xTask )
    功能: 获取对应任务的任务优先级
    参数: 任务的句柄
    返回值: 该任务的优先级

    #if ( INCLUDE_uxTaskPriorityGet == 1 )

     UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
     {
     TCB_t *pxTCB;
     UBaseType_t uxReturn;
    
     	taskENTER_CRITICAL();
     	{
     		/* If null is passed in here then it is the priority of the that
     		called uxTaskPriorityGet() that is being queried. */
     		pxTCB = prvGetTCBFromHandle( xTask );
     		uxReturn = pxTCB->uxPriority;
     	}
     	taskEXIT_CRITICAL();
    
     	return uxReturn;
     }
    
  • 2. void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
    功能: 将任务设定为对应的优先级
    参数:
    a. xTask:任务的句柄
    b. uxNewPriority :新的任务优先级

  • 3. UBaseType_t uxTaskGetSystemState ( TaskStatus_t * const pxTaskStatusArray,
    const UBaseType_t uxArraySize,
    uint32_t * const pulTotalRunTime )
    功能: 获取系统中所有任务的任务状态
    参数:
    a. pxTaskStatusArray:存储任务状态的数组

    typedef struct xTASK_STATUS
     {
     	TaskHandle_t xHandle;			/* The handle of the task to which the rest of the information in the structure relates. */
     	const char *pcTaskName;			/* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
     	UBaseType_t xTaskNumber;		/* A number unique to the task. */
     	eTaskState eCurrentState;		/* The state in which the task existed when the structure was populated. */
     	UBaseType_t uxCurrentPriority;	/* The priority at which the task was running (may be inherited) when the structure was populated. */
     	UBaseType_t uxBasePriority;		/* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
     	uint32_t ulRunTimeCounter;		/* 任务总共运行时间 The total run time allocated to the task so far, as defined by the run time stats clock.  See http://www.freertos.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
     	StackType_t *pxStackBase;		/* 指向任务堆栈 Points to the lowest address of the task's stack area. */
     	uint16_t usStackHighWaterMark;	/* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */栈空间历史剩余最小的容量
     } TaskStatus_t;
    

    b. uxArraySize:保存任务状态组的数组的大小
    c. pulTotalRunTime :若configGENERATE_RUN_TIME_STATS为1 时,此参数用来保存系统的总的运行时间
    返回值: 统计到的任务状态的个数

第一个创建的任务为vTaskStartScheduler()开始之前起创建的任务,在vTaskStartScheduler()中,会创建IDLE任务,以及定时器服务任务,所以任务的编号如下:
在这里插入图片描述

  • 4. UBaseType_t uxTaskGetNumberOfTasks( void )
    功能: 返回当前有多少个任务
    参数:
    返回值: 当前系统中存在的任务数量 = 挂起态 + 阻塞态 + 就绪态 + 空闲任务 + 运行态 的任务
    UBaseType_t uxTaskGetNumberOfTasks( void )
    {
    	/* A critical section is not required because the variables are of type
    	BaseType_t. */
    	return uxCurrentNumberOfTasks;
    }
  • 5. void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState )
    功能: 获取单个任务的任务状态,
    #if( configUSE_TRACE_FACILITY == 1 ) 宏定义成立才能使用
    参数:
    a. xTask: 要查找的任务的任务句柄
    b. pxTaskStatus: 存储任务状态的结构体
    c. xGetFreeStackSpace: 值为pdTRUE或者pdFALSE,为TRUE时会消耗一些时间来计算任务堆栈剩余历史最小大小,为FALSE时将跳过这个统计步骤
    d. eState : TaskStatus_t 中有成员eCurrentState是用来保存任务运行状态的,如下,此参数可以直接赋值(减少执行时间),或者将eState设为eInvalid,这样函数会去获取任务的状态。
     /* Task states returned by eTaskGetState. */
    typedef enum
    {
    	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
    	eReady,			/* The task being queried is in a read or pending ready list. */
    	eBlocked,		/* The task being queried is in the Blocked state. */
    	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
    	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
    	eInvalid			/* Used as an 'invalid state' value. */
    } eTaskState;
  • 6. TaskHookFunction_t xTaskGetApplicationTaskTag ( TaskHandle_t xTask )
    功能: 获取任务的Tag值,任务句柄中有个pxTaskTag变量来保存任务标签值的,标签的功能由用户自己决定,这个函数获取的是任务的标签值的(系统内核不会使用该标签值),如果要使用下面的宏定义要开启

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    	TaskHookFunction_t pxTaskTag;
    #endif
    

    参数: xTask获取的任务的任务句柄,若为NULL就获取正在运行的任务标签值
    返回值: 任务的标签值

  • 7. TaskHandle_t xTaskGetHandle( const char *pcNameToQuery )
    功能: 获取当前任务的任务句柄,使用的话,下述的宏定义要打开
    参数:
    返回值: 当前任务的任务句柄

     #if ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) )
    TaskHandle_t xTaskGetCurrentTaskHandle( void )
    {
    	TaskHandle_t xReturn;
    
    	/* A critical section is not required as this is not called from
    	an interrupt and the current TCB will always be the same for any
    	individual execution thread. */
    	
    	xReturn = pxCurrentTCB;
    
    	return xReturn;
    }
    
    
    
          #endif /* ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) ) */
    
    
  • 8. TaskHandle_t xTaskGetHandle( const char *pcNameToQuery )
    功能: 根据任务名称获取任务句柄
    参数: 任务名,字符串
    返回值: 返回任务句柄,若为NULL,则说明没有这个任务

  • 9. TaskHandle_t xTaskGetIdleTaskHandle ( void )
    功能: 获取空闲任务的任务句柄,使用时宏定义要打开
    参数:
    返回值: 返回空闲任务的任务句柄

        #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
        
       	TaskHandle_t xTaskGetIdleTaskHandle( void )
       	{
       		/* If xTaskGetIdleTaskHandle() is called before the scheduler has been
       		started, then xIdleTaskHandle will be NULL. */
       		configASSERT( ( xIdleTaskHandle != NULL ) );
       		return xIdleTaskHandle;
       	}
        
        #endif /* INCLUDE_xTaskGetIdleTaskHandle */
    
    
  • 10. UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    功能: 检查任务创建完成到现在栈空间历史剩余最小值,值越小溢出的可能越大,可以用于代码调试阶段,出货时不用
    参数: 要查询的任务的任务句柄,使用NULL的话,则查询调用此函数的任务本身
    返回值: 返回剩余量最小值

    #if ( INCLUDE_uxTaskGetStackHighWaterMark == 1 )
    
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    {
    	TCB_t *pxTCB;
    	uint8_t *pucEndOfStack;
    	UBaseType_t uxReturn;
    
    	pxTCB = prvGetTCBFromHandle( xTask );
    
    	#if portSTACK_GROWTH < 0
    	{
    		pucEndOfStack = ( uint8_t * ) pxTCB->pxStack;
    	}
    	#else
    	{
    		pucEndOfStack = ( uint8_t * ) pxTCB->pxEndOfStack;
    	}
    	#endif
    
    	uxReturn = ( UBaseType_t ) prvTaskCheckFreeStackSpace( pucEndOfStack );
    
    	return uxReturn;
        	
        #endif /* INCLUDE_uxTaskGetStackHighWaterMark */
    
    
  • 11. eTaskState eTaskGetState( TaskHandle_t xTask )
    功能: 查询任务的运行状态
    参数: 要查询的任务的任务句柄
    返回值: 返回为eTaskState类型,如下

     typedef enum
          {
          	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
          	eReady,			/* The task being queried is in a read or pending ready list. */
          	eBlocked,		/* The task being queried is in the Blocked state. */
          	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
          	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
          	eInvalid			/* Used as an 'invalid state' value. */
          } eTaskState;
    
  • 12. char *pcTaskGetName( TaskHandle_t xTaskToQuery )
    功能: 查询对应任务的任务名
    参数: 要查询的任务的任务句柄,NULL的话表示查询调用此函数的任务的名字
    返回值: 返回对应任务的任务名

  • 13. TickType_t xTaskGetTickCount( void )
    功能: 查询任务调度器启动到现在,计数器xTickCount的值。xTickCount是系统的时钟节拍,每次定时器中断,xTickCount就会加1,定时器一秒中断几次取决于宏定义 configTICK_RATE_HZ。
    参数:
    返回值: 时间计数器xTickCount的值

  • 14. void vTaskGetRunTimeStats( char *pcWriteBuffer )
    功能: 提供一个表格,记录了每个任务运行的时间(不是秒,是由定时器的周期决定的变量,比如一个中断周期是50us,那么5就是 5 x 50us) 和 所占总时间的比例,
    要使用的话,要打开一下几个宏定义:
    configGENERATE_RUN_TIME_STATS,configUSE_STATS_FORMATTING_FUNCTIONS,
    并且要实现一下几个宏定义:
    portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏定义要初始化一个定时器(中断频率要比系统时钟高,为系统时钟的10~20倍),比如:


void ConfigureTimeForRunTimeStats(void)
{
	//定时器3初始化,定时器时钟为108M,分频系数为108-1,所以定时器3的频率
	//为108M/108=1M,自动重装载为50-1,那么定时器周期就是50us
	FreeRTOSRunTimeTicks=0;
	TIM3_Init(50-1,108-1);	//初始化TIM3
}

以及
portGET_RUN_TIME_COUNTER_VALUE()

    #define portGET_RUN_TIME_COUNTER_VALUE()		Runtime

或者portALT_GET_RUN_TIME_COUNTER_VALUE(Time)
这两个宏定义用来获取当前时基的时间值

参数:
pcWriteBuffer:用来保存任务时间信息的buffer

返回值:

系统内核控制函数

  • 1. taskYIELD()
    任务切换
  • 2. taskENTER_CRITICAL()
    任务中的临界区进入,支持多级嵌套,注:临界区内的代码不要太长,否则屏蔽其他中断太久,其他中断得不到响应
    #define taskENTER_CRITICAL()		portENTER_CRITICAL()
    #define portENTER_CRITICAL()	    vPortEnterCritical()
    void vPortEnterCritical( void )
    {
    	portDISABLE_INTERRUPTS(); //除能5~15级的中断
    	uxCriticalNesting++;  //使用该全局变量,是因为防止有多层临界区嵌套的时候,一旦调用退出临界区,就打乱了其他临界段的保护,只有全部临界段代码退出后才打开中断
    
    	/* This is not the interrupt safe version of the enter critical function so
    	assert() if it is being called from an interrupt context.  Only API
    	functions that end in "FromISR" can be used in an interrupt.  Only assert if
    	the critical nesting count is 1 to protect against recursive calls if the
    	assert function also uses a critical section. */
    	if( uxCriticalNesting == 1 )
    	{
    		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); //断言函数
    	}
    }
  • 3. taskEXIT_CRITICAL()
    任务中的临界区退出
    void vPortExitCritical( void )
    {
    	configASSERT( uxCriticalNesting );
    	uxCriticalNesting--;
    	if( uxCriticalNesting == 0 )
    	{
    		portENABLE_INTERRUPTS(); //使能5~15级的中断
    	}
    }
  • 4. taskENTER_CRITICAL_FROM_ISR()
    中断中的任务中的临界区进入
#define taskENTER_CRITICAL_FROM_ISR()  portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		mrs ulReturn, basepri   //先读出BASEPRI的值,保存到ulReturn中
		cpsid i
		msr basepri, ulNewBASEPRI //将屏蔽的优先级数写到BASEPRI中,比如写入4的话,0~3优先级的中断会执行,而4~15级的中断将被屏蔽
		dsb
		isb
		cpsie i
	}

	return ulReturn;  //返回之前的优先级,退出时将使用到这个值
}
  • 5. taskEXIT_CRITICAL_FROM_ISR()
    中断中的任务中的临界区退出
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )	vPortSetBASEPRI( x )

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 //将进入临界区时保存的值,重新写到BASEPRI寄存器中,恢复进入之前的状态
	}
}
ISR中使用的临界区函数示例:
void TIMCallBack()
{
	uint32_t uiState;
	uiState = taskENTER_CRITICAL_FROM_ISR();
	/********要执行的函数********/
	taskEXIT_CRITICAL_FROM_ISR(uiState);
}
  • 6. taskDISABLE_INTERRUPTS()
    屏蔽5~15级的中断,见CORTEX M中断管理
#define taskDISABLE_INTERRUPTS()	portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		cpsid i
		msr basepri, ulNewBASEPRI
		dsb
		isb
		cpsie i
	}
}
  • 7. taskENABLE_INTERRUPTS()
    使能5~15级的中断,见CORTEX M中断管理
#define portENABLE_INTERRUPTS()	   vPortSetBASEPRI( 0 ) //写0即打开所有中断
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
	}
}
  • 8. taskStartScheduler()
  • 9. taskEndSchelduler()
    给x86结构使用
  • 10. vTaskSuspendAll()
    功能: 挂起所有任务,调用多少次挂起函数,就要调用多少个恢复的函数
  • 11. xTaskResumeAll()
    功能: 恢复所有任务的运行
    返回值: 有没进行任务切换,TRUE为已切换,FALSE为未切换(因为恢复的任务优先级没当前优先级高)
  • 12. vTaskStepTick()
    低功耗TickLess会用到

系统延时函数

  • 1. void vTaskDelay( const TickType_t xTicksToDelay )
    功能:从函数被调用的时刻开始,延时若干个节拍
#if ( INCLUDE_vTaskDelay == 1 )

void vTaskDelay( const TickType_t xTicksToDelay )// 延时多少个时间节拍
{
	BaseType_t xAlreadyYielded = pdFALSE;

	/* A delay time of zero just forces a reschedule. */
	if( xTicksToDelay > ( TickType_t ) 0U )
	{
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll();
		{
			traceTASK_DELAY();

			/* A task that is removed from the event list while the
			scheduler is suspended will not get placed in the ready
			list or removed from the blocked list until the scheduler
			is resumed.

			This task cannot be in an event list as it is the currently
			executing task. */
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); //此时就绪列表发生了变化
		}
		xAlreadyYielded = xTaskResumeAll();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* Force a reschedule if xTaskResumeAll has not already done so, we may
	have put ourselves to sleep. */
	if( xAlreadyYielded == pdFALSE )
	{
		portYIELD_WITHIN_API();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

#endif /* INCLUDE_vTaskDelay */

相关函数:

if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
	/* The current task must be in a ready list, so there is no need to
	check, and the port reset macro can be called directly. */
	portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );//重置任务的优先级
}

  • 2. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
    功能: 延时绝对的时间(除非中断打断)
    参数:
    a. pxPreviousWakeTime: 第一次调用时,要传入进入循环的系统计数(用xTaskGetTickCount),以后由系统自动更新这个值
    b. xTimeIncrement: 循环执行的时间,包括功能函数 加上 延时的时间,如下图所示
    在这里插入图片描述

FREERTOS 系统节拍

SysTick为倒计数类型的计数器
在这里插入图片描述
a. 先初始化SysTick

//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
	u32 reload;
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
	fac_us=SYSCLK;						    //不论是否使用OS,fac_us都需要使用
	reload=SYSCLK;					        //每秒钟的计数次数 单位为K	   
	reload*=1000000/configTICK_RATE_HZ;		//根据delay_ostickspersec设定溢出时间,周期50ms转换为频率为20HZ(1秒20个周期),被除数为一秒钟的计数,这个值不能大于77ms
											//reload为24位寄存器,最大值:16777216,在216M下,约合77.7ms左右	
	fac_ms=1000/configTICK_RATE_HZ;			//代表OS可以延时的最少单位,OS的系统节拍	   
	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
	SysTick->LOAD=reload; 					//每1/OS_TICKS_PER_SEC秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}		

b. 注册中断服务函数

//systick中断服务函数,使用OS时用到

void xPortSysTickHandler( void )
{
	/* The SysTick runs at the lowest interrupt priority, so when this interrupt
	executes all interrupts must be unmasked.  There is therefore no need to
	save and then restore the interrupt mask value as its value is already
	known - therefore the slightly faster vPortRaiseBASEPRI() function is used
	in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
	vPortRaiseBASEPRI();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();
}


void SysTick_Handler(void)
{	
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();	
    }
    HAL_IncTick();
}

c. xTaskIncrementTick()的功能
I. 若任务调度器未被挂起
xTickCount系统节拍加1
若系统节拍计数溢出,则交换两个延时列表(overflow和正常的列表)
若系统节拍计数大于 延时列表中最近要被唤醒的任务的节拍数,判断各个任务是不是到了退出阻塞态,就绪态(State)的时候,判断任务是否要退出各个event(队列,信号量),到了就退出对应的状态,并且把这个任务从对应的列表移除出来,并把合适的任务放到readylist中。

II. 若任务调度器被挂起
++uxPendedTicks
在xTaskResumeAll中,会调用这个变量次数的xTaskIncrementTick(),这样xTickCount就会恢复,并且取消任务的阻塞

队列

用于任务和任务,任务和中断之间的传递消息,
队列长度:数据条数,创建时要指定大小和队列的长度
发送数据到队列是通过拷贝
出队阻塞: 任务从队列中读取消息可指定一个阻塞时间,阻塞时间设定为0的话,读不到数据,就马上返回执行其他程序;设定为0~portMAX_DELAY的话,任务没获取到消息就等待阻塞时间,若设置为portMAX_DELAY的话,就会一直等待,直到收到数据
入队阻塞: 当队列满时,等待有没位置,与出队阻塞类似

队列操作过程:

  1. 创建队列
  2. 向队列发送消息
  3. 从队列读取消息,数据先进先出
typedef struct QueueDefinition
{
	int8_t *pcHead;					/*< Points to the beginning of the queue storage area. */
	int8_t *pcTail;					/*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
	int8_t *pcWriteTo;				/*< Points to the free next place in the storage area. 空闲的块 */

	union							/* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
	{
		int8_t *pcReadFrom;			/*< Points to the last place that a queued item was read from when the structure is used as a queue. */
		UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
	} u;

	List_t xTasksWaitingToSend;		/*< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. 存储处于因队列满而无法发送消息进来的任务*/
	List_t xTasksWaitingToReceive;	/*< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. 存储处于因队空而无法接收到消息进来的任务*/

	volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. 目前队列中有多少消息*/
	UBaseType_t uxLength;			/*< The length of the queue defined as the number of items it will hold, not the number of bytes. 队列的长度*/
	UBaseType_t uxItemSize;			/*< The size of each items that the queue will hold. 每个消息的大小*/

	volatile int8_t cRxLock;		/*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */
	volatile int8_t cTxLock;		/*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	/*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

队列的创建:
QueueHandle_t xQueueGenericCreate ( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
功能: 创建队列的动态方法,内核自动分配内存

参数:
a. uxQueueLength: 最大的消息数量
b. uxItemSize: 每条消息的字节数

返回值: 队列的句柄,0为失败

#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
	Queue_t *pxNewQueue;
	size_t xQueueSizeInBytes;
	uint8_t *pucQueueStorage;

configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

if( uxItemSize == ( UBaseType_t ) 0 )
{
/* There is not going to be a queue storage area. */
	xQueueSizeInBytes = ( size_t ) 0;
}
else
{
/* Allocate enough space to hold the maximum number of items that
can be in the queue at any time. */
	xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}

pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

if( pxNewQueue != NULL )
{
/* Jump past the queue structure to find the location of the queue
storage area. */
	pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );

#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Queues can be created either statically or dynamically, so
note this task was created dynamically in case it is later
deleted. */
	pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */

prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}

	return pxNewQueue;
}

#endif /* configSUPPORT_STATIC_ALLOCATION */

创建队列的静态方法
QueueHandle_t xQueueGenericCreateStatic ( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t * pucQueueStorage,
StaticQueue_t *pxStaticQueue,
const uint8_t ucQueueType )

入队函数:

  1. INCLUDE_XX
    使能或者除能函数,定义了后,某些函数才会被编译
  2. configXXX
    用来完成FREERTOS的裁剪

任务四种状态

  1. 运行态
  2. 就绪态
  3. 阻塞态
    等待事件的输入
  4. 挂起态

任务优先级

0~最大优先级数-1,
优先级0最小,优先级随数字依次递增
多个任务可以使用同一个优先级(configUSE_TIME_SLICING 为 1 时)

任务控制模块

存储任务的属性,FREERTOS把这些属性结合到一起用一个结构体表示,这个结构体就叫:TCB_t,会存储任务优先级,任务堆栈起始地址等,多数成员和功能裁剪有关,哪些功能不适用,控制块的大小也随之减小。

创建任务可以通过

a. 动态创建方法 xTaskCreate()

使用的内存是根据任务的使用情况申请的,需要传入一个内存指针
I. 函数原型
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )

II. 参数:
pxTaskCode:要执行的任务的函数
const pcName:任务的名字,是一串字符串,长度不能超过最大名字长度
usStackDepth:任务堆栈大小,申请到为参数的四倍大小,注:空闲任务的堆栈大小为configMINIMAL_STACK_SIZE
pvParameters: 要传递的参数,可以为字符串
uxPriority: 任务优先级,范围在0~最高优先级-1
pxCreatedTask: 任务句柄,其他API函数会使用到这个任务句柄
动态方法在参数中返回任务句柄,而静态方法在函数返回值中返回任务句柄(参数中无任务句柄)

III. 返回值:
pdPASS: 如果任务成功创建
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 由于堆内存不足,任务创建失败。
@return pdPASS if the task was successfully created and added to a ready
list, otherwise an error code defined in the file projdefs.h

b. 静态创建方法 xTaskCreateStatic()
要先在 FreeRTOSConfig.h中

 #define configSUPPORT_STATIC_ALLOCATION			1	

然后定义vApplicationGetIdleTaskMemory,vApplicationGetTimerTaskMemory这两个函数,因为一旦使用了静态分配模式,就要用户自行实现这两个函数
不需要传入控制块的指针,要设置任务的堆栈大小
注:堆栈大小单位为UINT类型,所以设置堆栈大小50的话,实际大小为200字节
I. 函数原型
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )

II. 参数:
pxTaskCode:要执行的任务的函数
const pcName:任务的名字,是一串字符串,长度不能超过最大名字长度
usStackDepth:任务堆栈大小,申请到为参数的四倍大小,注:空闲任务的堆栈大小为configMINIMAL_STACK_SIZE
pvParameters: 要传递的参数,可以为字符串
uxPriority: 任务优先级,范围在0~最高优先级-1
puxStackBuffer: 任务堆栈,一般为一个数组,数组类型要是StackType_t类型,当为动态创建时,这个栈空间由函数自行申请。
pxTaskBuffer: 任务控制块,必须要是StaticTask_t类型,当为动态创建时,这个任务控制块由函数自行申请。

动态方法在参数中返回任务句柄,而静态方法在函数返回值中返回任务句柄(参数中无任务句柄)

III. 返回值:
NULL:任务创建失败,puxStackBuffer 和 pxTaskBuffer 为 NULL会导致这个错误的发生
其他值:任务创建成功,返回任务的任务句柄

@return If neither pxStackBuffer or pxTaskBuffer are NULL, then the task will
be created and pdPASS is returned. If either pxStackBuffer or pxTaskBuffer
are NULL then the task will not be created and errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned.

例子:

// Structure that will hold the TCB of the task being created.
    StaticTask_t xTaskBuffer;

// Buffer that the task being created will use as its stack.  Note this is
// an array of StackType_t variables.  The size of StackType_t is dependent on
// the RTOS port.
	StackType_t xStack[ STACK_SIZE ];

// Function that implements the task being created.
void vTaskCode( void * pvParameters )
{
    // The parameter value is expected to be 1 as 1 is passed in the
    // pvParameters value in the call to xTaskCreateStatic().
    configASSERT( ( uint32_t ) pvParameters == 1UL );

    for( ;; )
    {
        // Task code goes here.
    }
}

// Function that creates a task.
void vOtherFunction( void )
{
    TaskHandle_t xHandle = NULL;

    // Create the task without using any dynamic memory allocation.
    xHandle = xTaskCreateStatic(
                  vTaskCode,       // Function that implements the task.
                  "NAME",          // Text name for the task.
                  STACK_SIZE,      // Stack size in words, not bytes.
                  ( void * ) 1,    // Parameter passed into the task.
                  tskIDLE_PRIORITY,// Priority at which the task is created.
                  xStack,          // Array to use as the task's stack.
                  &xTaskBuffer );  // Variable to hold the task's data structure.

    // puxStackBuffer and pxTaskBuffer were not NULL, so the task will have
    // been created, and xHandle will be the task's handle.  Use the handle
    // to suspend the task.
    vTaskSuspend( xHandle );
}

c. 创建一个使用MPU进行限制的任务
xTaskCreateRestricted()
相关内存使用动态内存分配

删除任务可以通过

a. vTaskDelete()
删除一个用以上方法创建的任务,任务删除后不会再进入运行态,所以再也不能使用这个任务的句柄,

  1. 若该任务由动态方法创建,堆栈和控制块会在空闲任务中释放掉,所以调用 vTaskDelete()后要给空闲任务一些运行时间。
  2. 若该任务是用静态方法创建的,则需要程序员自己释放内存,否则会导致内存泄漏

I. 函数原型
void vTaskDelete( TaskHandle_t xTaskToDelete )

II. 参数
xTaskToDelete: 要删除的任务的任务句柄

III. 返回值

任务的挂起和恢复

a. vTaskSuspend()
挂起一个任务
I. 函数原型
void vTaskSuspend ( TaskHandle_t xTaskToSuspend )

II. 参数
xTaskToSuspend: 要挂起的任务的任务句柄,传入NULL的话,会将调用vTaskSuspend函数的任务挂起

III. 返回值

b. vTaskResume()
恢复一个任务的运行,无论一个任务调用了多少次vTaskSuspend,恢复任务值用调用一次vTaskResume
注: INCLUDE_vTaskSuspend must be defined as 1 for this function to be available.

I. 函数原型
void vTaskResume ( TaskHandle_t xTaskToResume )

II. 参数
xTaskToResume: 要恢复的任务的句柄

III. 返回值

c. xTaskResumeFromISR()
在中断服务函数中恢复一个任务的运行,与vTaskResume()相同,无论一个任务调用了多少次vTaskSuspend,恢复任务值用调用一次xTaskResumeFromISR()
注:INCLUDE_xTaskResumeFromISR must be defined as 1 for this function to be available
I. 函数原型
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

II. 参数
xTaskToResume: 要恢复的任务的句柄

III. 返回值
pdTRUE:恢复的任务优先级比当前运行的任务更高,所以要进行任务切换
pdFALSE:恢复的任务优先级比当前任务低,所以不用进行任务切换
例如:

extern TaskHandle_t Task2Task_Handler;

void EXTI3_IRQHandler(void)
{
	BaseType_t YieldRequired;

	delay_xms(50);						//消抖
	if(KEY0==0)
	{
		YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务2
		printf("恢复任务2的运行!\r\n");
		if(YieldRequired==pdTRUE)
		{
			/*如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
			任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
			退出中断的时候一定要进行上下文切换!*/
			portYIELD_FROM_ISR(YieldRequired);
		}
	}
	__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3);	//清除中断标志位
}

列表和列表项

列表和列表项有完整性检查的成员,原理是在结构体的起始成员和末尾成员,设置两个固定值的变量,一旦有数据越界,必定会改变头尾数据的值,每次插入前检查一次值,就可以确认成员值是否被更改,以下为代码

#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
#define listTEST_LIST_ITEM_INTEGRITY( pxItem )		configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#define listTEST_LIST_INTEGRITY( pxList )			configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )

a. 列表

/*
 * 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;

注:最后一个成员变量为 Mini列表项类型

b. 列表项

/*
 * 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. */

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;

列表的相关API函数

a. vListInitialise()
列表初始化

#define portMAX_DELAY ( TickType_t ) 0xffffffffUL

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. 确保listEnd处于最尾端*/
	
	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;  //当前列表项为0

	/* Write known values into the list if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */完整性检查
	 
	 //16_Bit_Mcu use 0x5a5a, and 32_Bit one use 0x5a5a5a5aUL
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTE  GRITY_CHECK_2_VALU E( pxList );
}

b. vListInitialiseItem()
列表项 初始化,在xTaskCreate(任务创建)的时候,函数会对其初始化

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 );
}

c. vListInsert
列表项插入,由于列表是双向的,所以结构为环状

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	const TickType_t xValueOfInsertion = pxNewListItem->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. */
	
	if( xValueOfInsertion == portMAX_DELAY )//如果插入的列表项是最大值,直接插入到列表最尾
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else//若不是最尾项,则找到要插入的位置,以下是按xItemValue 来寻找插入的位置
	{
		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; //将列表项插入到列表中

	( pxList->uxNumberOfItems )++; //列表的成员数加1
}

d. vListInsertEnd
列表项末尾插入 ,这里并不是指插入到列表尾,而是插入到pxIndex所指列表项的前面,因为pxIndex指向的是列表头,所以pxIndex所指项的前一个就是尾巴

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(). */
	//以下同样是在双向链表中插入元素的操作,只不过是在pxIndex所指项的前面插入,pxIndex所指的项是任意的,pxIndex所指的列表项就代表列表头
	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 )++; //列表的成员数加1
}

f. uxListRemove
列表项删除
返回值:删除列表项后,列表剩余的列表项的数量

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 )//如果删掉的是pxIndex所指的节点的话,则把pxIndex指向删除的节点的前一个节点
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();//用来输出调试信息
	}

	pxItemToRemove->pvContainer = NULL; //被删除的列表项要清除 所归属的列表
	( pxList->uxNumberOfItems )--; //该列表的项目数减少

	return pxList->uxNumberOfItems; //返回这个列表当前列表项数目
}

g. listGET_OWNER_OF_NEXT_ENTRY
列表的遍历
此函数用来用于从多个优先级的就绪任务中查找下一个要运行的任务
FREE_RTOS提供了一个宏定义形式的函数

#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指向了列表的END项时,则代表到了列表的末尾,到了末尾的话,就跳过END项,指向列表头的列表项																					\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;  //将指向的列表项的owner赋给TCB											\
}

任务查询和信息统计API函数

  • 1. UBaseType_t uxTaskPriorityGet ( TaskHandle_t xTask )
    功能: 获取对应任务的任务优先级
    参数: 任务的句柄
    返回值: 该任务的优先级

    #if ( INCLUDE_uxTaskPriorityGet == 1 )

     UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
     {
     TCB_t *pxTCB;
     UBaseType_t uxReturn;
    
     	taskENTER_CRITICAL();
     	{
     		/* If null is passed in here then it is the priority of the that
     		called uxTaskPriorityGet() that is being queried. */
     		pxTCB = prvGetTCBFromHandle( xTask );
     		uxReturn = pxTCB->uxPriority;
     	}
     	taskEXIT_CRITICAL();
    
     	return uxReturn;
     }
    
  • 2. void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
    功能: 将任务设定为对应的优先级
    参数:
    a. xTask:任务的句柄
    b. uxNewPriority :新的任务优先级

  • 3. UBaseType_t uxTaskGetSystemState ( TaskStatus_t * const pxTaskStatusArray,
    const UBaseType_t uxArraySize,
    uint32_t * const pulTotalRunTime )
    功能: 获取系统中所有任务的任务状态
    参数:
    a. pxTaskStatusArray:存储任务状态的数组

    typedef struct xTASK_STATUS
     {
     	TaskHandle_t xHandle;			/* The handle of the task to which the rest of the information in the structure relates. */
     	const char *pcTaskName;			/* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
     	UBaseType_t xTaskNumber;		/* A number unique to the task. */
     	eTaskState eCurrentState;		/* The state in which the task existed when the structure was populated. */
     	UBaseType_t uxCurrentPriority;	/* The priority at which the task was running (may be inherited) when the structure was populated. */
     	UBaseType_t uxBasePriority;		/* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
     	uint32_t ulRunTimeCounter;		/* 任务总共运行时间 The total run time allocated to the task so far, as defined by the run time stats clock.  See http://www.freertos.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
     	StackType_t *pxStackBase;		/* 指向任务堆栈 Points to the lowest address of the task's stack area. */
     	uint16_t usStackHighWaterMark;	/* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */栈空间历史剩余最小的容量
     } TaskStatus_t;
    

    b. uxArraySize:保存任务状态组的数组的大小
    c. pulTotalRunTime :若configGENERATE_RUN_TIME_STATS为1 时,此参数用来保存系统的总的运行时间
    返回值: 统计到的任务状态的个数

第一个创建的任务为vTaskStartScheduler()开始之前起创建的任务,在vTaskStartScheduler()中,会创建IDLE任务,以及定时器服务任务,所以任务的编号如下:
在这里插入图片描述

  • 4. UBaseType_t uxTaskGetNumberOfTasks( void )
    功能: 返回当前有多少个任务
    参数:
    返回值: 当前系统中存在的任务数量 = 挂起态 + 阻塞态 + 就绪态 + 空闲任务 + 运行态 的任务
    UBaseType_t uxTaskGetNumberOfTasks( void )
    {
    	/* A critical section is not required because the variables are of type
    	BaseType_t. */
    	return uxCurrentNumberOfTasks;
    }
  • 5. void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState )
    功能: 获取单个任务的任务状态,
    #if( configUSE_TRACE_FACILITY == 1 ) 宏定义成立才能使用
    参数:
    a. xTask: 要查找的任务的任务句柄
    b. pxTaskStatus: 存储任务状态的结构体
    c. xGetFreeStackSpace: 值为pdTRUE或者pdFALSE,为TRUE时会消耗一些时间来计算任务堆栈剩余历史最小大小,为FALSE时将跳过这个统计步骤
    d. eState : TaskStatus_t 中有成员eCurrentState是用来保存任务运行状态的,如下,此参数可以直接赋值(减少执行时间),或者将eState设为eInvalid,这样函数会去获取任务的状态。
     /* Task states returned by eTaskGetState. */
    typedef enum
    {
    	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
    	eReady,			/* The task being queried is in a read or pending ready list. */
    	eBlocked,		/* The task being queried is in the Blocked state. */
    	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
    	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
    	eInvalid			/* Used as an 'invalid state' value. */
    } eTaskState;
  • 6. TaskHookFunction_t xTaskGetApplicationTaskTag ( TaskHandle_t xTask )
    功能: 获取任务的Tag值,任务句柄中有个pxTaskTag变量来保存任务标签值的,标签的功能由用户自己决定,这个函数获取的是任务的标签值的(系统内核不会使用该标签值),如果要使用下面的宏定义要开启

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    	TaskHookFunction_t pxTaskTag;
    #endif
    

    参数: xTask获取的任务的任务句柄,若为NULL就获取正在运行的任务标签值
    返回值: 任务的标签值

  • 7. TaskHandle_t xTaskGetHandle( const char *pcNameToQuery )
    功能: 获取当前任务的任务句柄,使用的话,下述的宏定义要打开
    参数:
    返回值: 当前任务的任务句柄

     #if ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) )
    TaskHandle_t xTaskGetCurrentTaskHandle( void )
    {
    	TaskHandle_t xReturn;
    
    	/* A critical section is not required as this is not called from
    	an interrupt and the current TCB will always be the same for any
    	individual execution thread. */
    	
    	xReturn = pxCurrentTCB;
    
    	return xReturn;
    }
    
    
    
          #endif /* ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) ) */
    
    
  • 8. TaskHandle_t xTaskGetHandle( const char *pcNameToQuery )
    功能: 根据任务名称获取任务句柄
    参数: 任务名,字符串
    返回值: 返回任务句柄,若为NULL,则说明没有这个任务

  • 9. TaskHandle_t xTaskGetIdleTaskHandle ( void )
    功能: 获取空闲任务的任务句柄,使用时宏定义要打开
    参数:
    返回值: 返回空闲任务的任务句柄

        #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
        
       	TaskHandle_t xTaskGetIdleTaskHandle( void )
       	{
       		/* If xTaskGetIdleTaskHandle() is called before the scheduler has been
       		started, then xIdleTaskHandle will be NULL. */
       		configASSERT( ( xIdleTaskHandle != NULL ) );
       		return xIdleTaskHandle;
       	}
        
        #endif /* INCLUDE_xTaskGetIdleTaskHandle */
    
    
  • 10. UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    功能: 检查任务创建完成到现在栈空间历史剩余最小值,值越小溢出的可能越大,可以用于代码调试阶段,出货时不用
    参数: 要查询的任务的任务句柄,使用NULL的话,则查询调用此函数的任务本身
    返回值: 返回剩余量最小值

    #if ( INCLUDE_uxTaskGetStackHighWaterMark == 1 )
    
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    {
    	TCB_t *pxTCB;
    	uint8_t *pucEndOfStack;
    	UBaseType_t uxReturn;
    
    	pxTCB = prvGetTCBFromHandle( xTask );
    
    	#if portSTACK_GROWTH < 0
    	{
    		pucEndOfStack = ( uint8_t * ) pxTCB->pxStack;
    	}
    	#else
    	{
    		pucEndOfStack = ( uint8_t * ) pxTCB->pxEndOfStack;
    	}
    	#endif
    
    	uxReturn = ( UBaseType_t ) prvTaskCheckFreeStackSpace( pucEndOfStack );
    
    	return uxReturn;
        	
        #endif /* INCLUDE_uxTaskGetStackHighWaterMark */
    
    
  • 11. eTaskState eTaskGetState( TaskHandle_t xTask )
    功能: 查询任务的运行状态
    参数: 要查询的任务的任务句柄
    返回值: 返回为eTaskState类型,如下

     typedef enum
          {
          	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
          	eReady,			/* The task being queried is in a read or pending ready list. */
          	eBlocked,		/* The task being queried is in the Blocked state. */
          	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
          	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
          	eInvalid			/* Used as an 'invalid state' value. */
          } eTaskState;
    
  • 12. char *pcTaskGetName( TaskHandle_t xTaskToQuery )
    功能: 查询对应任务的任务名
    参数: 要查询的任务的任务句柄,NULL的话表示查询调用此函数的任务的名字
    返回值: 返回对应任务的任务名

  • 13. TickType_t xTaskGetTickCount( void )
    功能: 查询任务调度器启动到现在,计数器xTickCount的值。xTickCount是系统的时钟节拍,每次定时器中断,xTickCount就会加1,定时器一秒中断几次取决于宏定义 configTICK_RATE_HZ。
    参数:
    返回值: 时间计数器xTickCount的值

  • 14. void vTaskGetRunTimeStats( char *pcWriteBuffer )
    功能: 提供一个表格,记录了每个任务运行的时间(不是秒,是由定时器的周期决定的变量,比如一个中断周期是50us,那么5就是 5 x 50us) 和 所占总时间的比例,
    要使用的话,要打开一下几个宏定义:
    configGENERATE_RUN_TIME_STATS,configUSE_STATS_FORMATTING_FUNCTIONS,
    并且要实现一下几个宏定义:
    portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏定义要初始化一个定时器(中断频率要比系统时钟高,为系统时钟的10~20倍),比如:


void ConfigureTimeForRunTimeStats(void)
{
	//定时器3初始化,定时器时钟为108M,分频系数为108-1,所以定时器3的频率
	//为108M/108=1M,自动重装载为50-1,那么定时器周期就是50us
	FreeRTOSRunTimeTicks=0;
	TIM3_Init(50-1,108-1);	//初始化TIM3
}

以及
portGET_RUN_TIME_COUNTER_VALUE()

    #define portGET_RUN_TIME_COUNTER_VALUE()		Runtime

或者portALT_GET_RUN_TIME_COUNTER_VALUE(Time)
这两个宏定义用来获取当前时基的时间值

参数:
pcWriteBuffer:用来保存任务时间信息的buffer

返回值:

系统内核控制函数

  • 1. taskYIELD()
    任务切换
  • 2. taskENTER_CRITICAL()
    任务中的临界区进入,支持多级嵌套,注:临界区内的代码不要太长,否则屏蔽其他中断太久,其他中断得不到响应
    #define taskENTER_CRITICAL()		portENTER_CRITICAL()
    #define portENTER_CRITICAL()	    vPortEnterCritical()
    void vPortEnterCritical( void )
    {
    	portDISABLE_INTERRUPTS(); //除能5~15级的中断
    	uxCriticalNesting++;  //使用该全局变量,是因为防止有多层临界区嵌套的时候,一旦调用退出临界区,就打乱了其他临界段的保护,只有全部临界段代码退出后才打开中断
    
    	/* This is not the interrupt safe version of the enter critical function so
    	assert() if it is being called from an interrupt context.  Only API
    	functions that end in "FromISR" can be used in an interrupt.  Only assert if
    	the critical nesting count is 1 to protect against recursive calls if the
    	assert function also uses a critical section. */
    	if( uxCriticalNesting == 1 )
    	{
    		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); //断言函数
    	}
    }
  • 3. taskEXIT_CRITICAL()
    任务中的临界区退出
    void vPortExitCritical( void )
    {
    	configASSERT( uxCriticalNesting );
    	uxCriticalNesting--;
    	if( uxCriticalNesting == 0 )
    	{
    		portENABLE_INTERRUPTS(); //使能5~15级的中断
    	}
    }
  • 4. taskENTER_CRITICAL_FROM_ISR()
    中断中的任务中的临界区进入
#define taskENTER_CRITICAL_FROM_ISR()  portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		mrs ulReturn, basepri   //先读出BASEPRI的值,保存到ulReturn中
		cpsid i
		msr basepri, ulNewBASEPRI //将屏蔽的优先级数写到BASEPRI中,比如写入4的话,0~3优先级的中断会执行,而4~15级的中断将被屏蔽
		dsb
		isb
		cpsie i
	}

	return ulReturn;  //返回之前的优先级,退出时将使用到这个值
}
  • 5. taskEXIT_CRITICAL_FROM_ISR()
    中断中的任务中的临界区退出
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )	vPortSetBASEPRI( x )

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 //将进入临界区时保存的值,重新写到BASEPRI寄存器中,恢复进入之前的状态
	}
}
ISR中使用的临界区函数示例:
void TIMCallBack()
{
	uint32_t uiState;
	uiState = taskENTER_CRITICAL_FROM_ISR();
	/********要执行的函数********/
	taskEXIT_CRITICAL_FROM_ISR(uiState);
}
  • 6. taskDISABLE_INTERRUPTS()
    屏蔽5~15级的中断,见CORTEX M中断管理
#define taskDISABLE_INTERRUPTS()	portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		cpsid i
		msr basepri, ulNewBASEPRI
		dsb
		isb
		cpsie i
	}
}
  • 7. taskENABLE_INTERRUPTS()
    使能5~15级的中断,见CORTEX M中断管理
#define portENABLE_INTERRUPTS()	   vPortSetBASEPRI( 0 ) //写0即打开所有中断
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
	}
}
  • 8. taskStartScheduler()
  • 9. taskEndSchelduler()
    给x86结构使用
  • 10. vTaskSuspendAll()
    功能: 挂起所有任务,调用多少次挂起函数,就要调用多少个恢复的函数
  • 11. xTaskResumeAll()
    功能: 恢复所有任务的运行
    返回值: 有没进行任务切换,TRUE为已切换,FALSE为未切换(因为恢复的任务优先级没当前优先级高)
  • 12. vTaskStepTick()
    低功耗TickLess会用到

系统延时函数

  • 1. void vTaskDelay( const TickType_t xTicksToDelay )
    功能:从函数被调用的时刻开始,延时若干个节拍
#if ( INCLUDE_vTaskDelay == 1 )

void vTaskDelay( const TickType_t xTicksToDelay )// 延时多少个时间节拍
{
	BaseType_t xAlreadyYielded = pdFALSE;

	/* A delay time of zero just forces a reschedule. */
	if( xTicksToDelay > ( TickType_t ) 0U )
	{
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll();
		{
			traceTASK_DELAY();

			/* A task that is removed from the event list while the
			scheduler is suspended will not get placed in the ready
			list or removed from the blocked list until the scheduler
			is resumed.

			This task cannot be in an event list as it is the currently
			executing task. */
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); //此时就绪列表发生了变化
		}
		xAlreadyYielded = xTaskResumeAll();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* Force a reschedule if xTaskResumeAll has not already done so, we may
	have put ourselves to sleep. */
	if( xAlreadyYielded == pdFALSE )
	{
		portYIELD_WITHIN_API();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

#endif /* INCLUDE_vTaskDelay */

相关函数:

if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
	/* The current task must be in a ready list, so there is no need to
	check, and the port reset macro can be called directly. */
	portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );//重置任务的优先级
}

  • 2. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
    功能: 延时绝对的时间(除非中断打断)
    参数:
    a. pxPreviousWakeTime: 第一次调用时,要传入进入循环的系统计数(用getcount),以后由系统自动更新这个值
    b. xTimeIncrement: 循环执行的时间,包括功能函数 加上 延时的时间,如下图所示
    在这里插入图片描述

FREERTOS 系统节拍

SysTick为倒计数类型的计数器
在这里插入图片描述
a. 先初始化SysTick

//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
	u32 reload;
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
	fac_us=SYSCLK;						    //不论是否使用OS,fac_us都需要使用
	reload=SYSCLK;					        //每秒钟的计数次数 单位为K	   
	reload*=1000000/configTICK_RATE_HZ;		//根据delay_ostickspersec设定溢出时间,周期50ms转换为频率为20HZ(1秒20个周期),被除数为一秒钟的计数,这个值不能大于77ms
											//reload为24位寄存器,最大值:16777216,在216M下,约合77.7ms左右	
	fac_ms=1000/configTICK_RATE_HZ;			//代表OS可以延时的最少单位,OS的系统节拍	   
	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
	SysTick->LOAD=reload; 					//每1/OS_TICKS_PER_SEC秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}		

b. 注册中断服务函数

//systick中断服务函数,使用OS时用到

void xPortSysTickHandler( void )
{
	/* The SysTick runs at the lowest interrupt priority, so when this interrupt
	executes all interrupts must be unmasked.  There is therefore no need to
	save and then restore the interrupt mask value as its value is already
	known - therefore the slightly faster vPortRaiseBASEPRI() function is used
	in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
	vPortRaiseBASEPRI();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();
}


void SysTick_Handler(void)
{	
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();	
    }
    HAL_IncTick();
}

c. xTaskIncrementTick()的功能
I. 若任务调度器未被挂起
xTickCount系统节拍加1
若系统节拍计数溢出,则交换两个延时列表(overflow和正常的列表)
若系统节拍计数大于 延时列表中最近要被唤醒的任务的节拍数,判断各个任务是不是到了退出阻塞态,就绪态(State)的时候,判断任务是否要退出各个event(队列,信号量),到了就退出对应的状态,并且把这个任务从对应的列表移除出来,并把合适的任务放到readylist中。

II. 若任务调度器被挂起
++uxPendedTicks
在xTaskResumeAll中,会调用这个变量次数的xTaskIncrementTick(),这样xTickCount就会恢复,并且取消任务的阻塞

队列

用于任务和任务,任务和中断之间的传递消息,
队列长度:数据条数,创建时要指定大小和队列的长度
发送数据到队列是通过拷贝
出队阻塞: 任务从队列中读取消息可指定一个阻塞时间,阻塞时间设定为0的话,读不到数据,就马上返回执行其他程序;设定为0~portMAX_DELAY的话,任务没获取到消息就等待阻塞时间,若设置为portMAX_DELAY的话,就会一直等待,直到收到数据
入队阻塞: 当队列满时,等待有没位置,与出队阻塞类似

队列操作过程:

  1. 创建队列
  2. 向队列发送消息
  3. 从队列读取消息,数据先进先出
typedef struct QueueDefinition
{
	int8_t *pcHead;					/*< Points to the beginning of the queue storage area. */
	int8_t *pcTail;					/*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
	int8_t *pcWriteTo;				/*< Points to the free next place in the storage area. 空闲的块 */

	union							/* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
	{
		int8_t *pcReadFrom;			/*< Points to the last place that a queued item was read from when the structure is used as a queue. */
		UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
	} u;

	List_t xTasksWaitingToSend;		/*< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. 存储处于因队列满而无法发送消息进来的任务*/
	List_t xTasksWaitingToReceive;	/*< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. 存储处于因队空而无法接收到消息进来的任务*/

	volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. 目前队列中有多少消息*/
	UBaseType_t uxLength;			/*< The length of the queue defined as the number of items it will hold, not the number of bytes. 队列的长度*/
	UBaseType_t uxItemSize;			/*< The size of each items that the queue will hold. 每个消息的大小*/

	volatile int8_t cRxLock;		/*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */
	volatile int8_t cTxLock;		/*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	/*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

队列的创建:
QueueHandle_t xQueueGenericCreate ( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
功能: 创建队列的动态方法,内核自动分配内存

参数:
a. uxQueueLength: 最大的消息数量
b. uxItemSize: 每条消息的字节数

返回值: 队列的句柄,0为失败

#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
	Queue_t *pxNewQueue;
	size_t xQueueSizeInBytes;
	uint8_t *pucQueueStorage;

configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

if( uxItemSize == ( UBaseType_t ) 0 )
{
/* There is not going to be a queue storage area. */
	xQueueSizeInBytes = ( size_t ) 0;
}
else
{
/* Allocate enough space to hold the maximum number of items that
can be in the queue at any time. */
	xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}

pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

if( pxNewQueue != NULL )
{
/* Jump past the queue structure to find the location of the queue
storage area. */
	pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );

#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Queues can be created either statically or dynamically, so
note this task was created dynamically in case it is later
deleted. */
	pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */

prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
}

	return pxNewQueue;
}

#endif /* configSUPPORT_STATIC_ALLOCATION */

创建队列的静态方法
QueueHandle_t xQueueGenericCreateStatic ( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t * pucQueueStorage,
StaticQueue_t *pxStaticQueue,
const uint8_t ucQueueType )

入队函数:

  1. INCLUDE_XX
    使能或者除能函数,定义了后,某些函数才会被编译
  2. configXXX
    用来完成FREERTOS的裁剪

任务四种状态

  1. 运行态
  2. 就绪态
  3. 阻塞态
    等待事件的输入
  4. 挂起态

任务优先级

0~最大优先级数-1,
优先级0最小,优先级随数字依次递增
多个任务可以使用同一个优先级(configUSE_TIME_SLICING 为 1 时)

任务控制模块

存储任务的属性,FREERTOS把这些属性结合到一起用一个结构体表示,这个结构体就叫:TCB_t,会存储任务优先级,任务堆栈起始地址等,多数成员和功能裁剪有关,哪些功能不适用,控制块的大小也随之减小。

创建任务可以通过

a. 动态创建方法 xTaskCreate()

使用的内存是根据任务的使用情况申请的,需要传入一个内存指针
I. 函数原型
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )

II. 参数:
pxTaskCode:要执行的任务的函数
const pcName:任务的名字,是一串字符串,长度不能超过最大名字长度
usStackDepth:任务堆栈大小,申请到为参数的四倍大小,注:空闲任务的堆栈大小为configMINIMAL_STACK_SIZE
pvParameters: 要传递的参数,可以为字符串
uxPriority: 任务优先级,范围在0~最高优先级-1
pxCreatedTask: 任务句柄,其他API函数会使用到这个任务句柄
动态方法在参数中返回任务句柄,而静态方法在函数返回值中返回任务句柄(参数中无任务句柄)

III. 返回值:
pdPASS: 如果任务成功创建
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 由于堆内存不足,任务创建失败。
@return pdPASS if the task was successfully created and added to a ready
list, otherwise an error code defined in the file projdefs.h

b. 静态创建方法 xTaskCreateStatic()
要先在 FreeRTOSConfig.h中

 #define configSUPPORT_STATIC_ALLOCATION			1	

然后定义vApplicationGetIdleTaskMemory,vApplicationGetTimerTaskMemory这两个函数,因为一旦使用了静态分配模式,就要用户自行实现这两个函数
不需要传入控制块的指针,要设置任务的堆栈大小
注:堆栈大小单位为UINT类型,所以设置堆栈大小50的话,实际大小为200字节
I. 函数原型
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )

II. 参数:
pxTaskCode:要执行的任务的函数
const pcName:任务的名字,是一串字符串,长度不能超过最大名字长度
usStackDepth:任务堆栈大小,申请到为参数的四倍大小,注:空闲任务的堆栈大小为configMINIMAL_STACK_SIZE
pvParameters: 要传递的参数,可以为字符串
uxPriority: 任务优先级,范围在0~最高优先级-1
puxStackBuffer: 任务堆栈,一般为一个数组,数组类型要是StackType_t类型,当为动态创建时,这个栈空间由函数自行申请。
pxTaskBuffer: 任务控制块,必须要是StaticTask_t类型,当为动态创建时,这个任务控制块由函数自行申请。

动态方法在参数中返回任务句柄,而静态方法在函数返回值中返回任务句柄(参数中无任务句柄)

III. 返回值:
NULL:任务创建失败,puxStackBuffer 和 pxTaskBuffer 为 NULL会导致这个错误的发生
其他值:任务创建成功,返回任务的任务句柄

@return If neither pxStackBuffer or pxTaskBuffer are NULL, then the task will
be created and pdPASS is returned. If either pxStackBuffer or pxTaskBuffer
are NULL then the task will not be created and errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned.

例子:

// Structure that will hold the TCB of the task being created.
    StaticTask_t xTaskBuffer;

// Buffer that the task being created will use as its stack.  Note this is
// an array of StackType_t variables.  The size of StackType_t is dependent on
// the RTOS port.
	StackType_t xStack[ STACK_SIZE ];

// Function that implements the task being created.
void vTaskCode( void * pvParameters )
{
    // The parameter value is expected to be 1 as 1 is passed in the
    // pvParameters value in the call to xTaskCreateStatic().
    configASSERT( ( uint32_t ) pvParameters == 1UL );

    for( ;; )
    {
        // Task code goes here.
    }
}

// Function that creates a task.
void vOtherFunction( void )
{
    TaskHandle_t xHandle = NULL;

    // Create the task without using any dynamic memory allocation.
    xHandle = xTaskCreateStatic(
                  vTaskCode,       // Function that implements the task.
                  "NAME",          // Text name for the task.
                  STACK_SIZE,      // Stack size in words, not bytes.
                  ( void * ) 1,    // Parameter passed into the task.
                  tskIDLE_PRIORITY,// Priority at which the task is created.
                  xStack,          // Array to use as the task's stack.
                  &xTaskBuffer );  // Variable to hold the task's data structure.

    // puxStackBuffer and pxTaskBuffer were not NULL, so the task will have
    // been created, and xHandle will be the task's handle.  Use the handle
    // to suspend the task.
    vTaskSuspend( xHandle );
}

c. 创建一个使用MPU进行限制的任务
xTaskCreateRestricted()
相关内存使用动态内存分配

删除任务可以通过

a. vTaskDelete()
删除一个用以上方法创建的任务,任务删除后不会再进入运行态,所以再也不能使用这个任务的句柄,

  1. 若该任务由动态方法创建,堆栈和控制块会在空闲任务中释放掉,所以调用 vTaskDelete()后要给空闲任务一些运行时间。
  2. 若该任务是用静态方法创建的,则需要程序员自己释放内存,否则会导致内存泄漏

I. 函数原型
void vTaskDelete( TaskHandle_t xTaskToDelete )

II. 参数
xTaskToDelete: 要删除的任务的任务句柄

III. 返回值

任务的挂起和恢复

a. vTaskSuspend()
挂起一个任务
I. 函数原型
void vTaskSuspend ( TaskHandle_t xTaskToSuspend )

II. 参数
xTaskToSuspend: 要挂起的任务的任务句柄,传入NULL的话,会将调用vTaskSuspend函数的任务挂起

III. 返回值

b. vTaskResume()
恢复一个任务的运行,无论一个任务调用了多少次vTaskSuspend,恢复任务值用调用一次vTaskResume
注: INCLUDE_vTaskSuspend must be defined as 1 for this function to be available.

I. 函数原型
void vTaskResume ( TaskHandle_t xTaskToResume )

II. 参数
xTaskToResume: 要恢复的任务的句柄

III. 返回值

c. xTaskResumeFromISR()
在中断服务函数中恢复一个任务的运行,与vTaskResume()相同,无论一个任务调用了多少次vTaskSuspend,恢复任务值用调用一次xTaskResumeFromISR()
注:INCLUDE_xTaskResumeFromISR must be defined as 1 for this function to be available
I. 函数原型
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

II. 参数
xTaskToResume: 要恢复的任务的句柄

III. 返回值
pdTRUE:恢复的任务优先级比当前运行的任务更高,所以要进行任务切换
pdFALSE:恢复的任务优先级比当前任务低,所以不用进行任务切换
例如:

extern TaskHandle_t Task2Task_Handler;

void EXTI3_IRQHandler(void)
{
	BaseType_t YieldRequired;

	delay_xms(50);						//消抖
	if(KEY0==0)
	{
		YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务2
		printf("恢复任务2的运行!\r\n");
		if(YieldRequired==pdTRUE)
		{
			/*如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
			任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
			退出中断的时候一定要进行上下文切换!*/
			portYIELD_FROM_ISR(YieldRequired);
		}
	}
	__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_3);	//清除中断标志位
}

列表和列表项

列表和列表项有完整性检查的成员,原理是在结构体的起始成员和末尾成员,设置两个固定值的变量,一旦有数据越界,必定会改变头尾数据的值,每次插入前检查一次值,就可以确认成员值是否被更改,以下为代码

#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
#define listTEST_LIST_ITEM_INTEGRITY( pxItem )		configASSERT( ( ( pxItem )->xListItemIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxItem )->xListItemIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )
#define listTEST_LIST_INTEGRITY( pxList )			configASSERT( ( ( pxList )->xListIntegrityValue1 == pdINTEGRITY_CHECK_VALUE ) && ( ( pxList )->xListIntegrityValue2 == pdINTEGRITY_CHECK_VALUE ) )

a. 列表

/*
 * 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;

注:最后一个成员变量为 Mini列表项类型

b. 列表项

/*
 * 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. */

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;

列表的相关API函数

a. vListInitialise()
列表初始化

#define portMAX_DELAY ( TickType_t ) 0xffffffffUL

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. 确保listEnd处于最尾端*/
	
	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;  //当前列表项为0

	/* Write known values into the list if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */完整性检查
	 
	 //16_Bit_Mcu use 0x5a5a, and 32_Bit one use 0x5a5a5a5aUL
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTE  GRITY_CHECK_2_VALU E( pxList );
}

b. vListInitialiseItem()
列表项 初始化,在xTaskCreate(任务创建)的时候,函数会对其初始化

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 );
}

c. vListInsert
列表项插入,由于列表是双向的,所以结构为环状

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
	ListItem_t *pxIterator;
	const TickType_t xValueOfInsertion = pxNewListItem->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. */
	
	if( xValueOfInsertion == portMAX_DELAY )//如果插入的列表项是最大值,直接插入到列表最尾
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else//若不是最尾项,则找到要插入的位置,以下是按xItemValue 来寻找插入的位置
	{
		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; //将列表项插入到列表中

	( pxList->uxNumberOfItems )++; //列表的成员数加1
}

d. vListInsertEnd
列表项末尾插入 ,这里并不是指插入到列表尾,而是插入到pxIndex所指列表项的前面,因为pxIndex指向的是列表头,所以pxIndex所指项的前一个就是尾巴

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(). */
	//以下同样是在双向链表中插入元素的操作,只不过是在pxIndex所指项的前面插入,pxIndex所指的项是任意的,pxIndex所指的列表项就代表列表头
	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 )++; //列表的成员数加1
}

f. uxListRemove
列表项删除
返回值:删除列表项后,列表剩余的列表项的数量

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 )//如果删掉的是pxIndex所指的节点的话,则把pxIndex指向删除的节点的前一个节点
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();//用来输出调试信息
	}

	pxItemToRemove->pvContainer = NULL; //被删除的列表项要清除 所归属的列表
	( pxList->uxNumberOfItems )--; //该列表的项目数减少

	return pxList->uxNumberOfItems; //返回这个列表当前列表项数目
}

g. listGET_OWNER_OF_NEXT_ENTRY
列表的遍历
此函数用来用于从多个优先级的就绪任务中查找下一个要运行的任务
FREE_RTOS提供了一个宏定义形式的函数

#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指向了列表的END项时,则代表到了列表的末尾,到了末尾的话,就跳过END项,指向列表头的列表项																					\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;  //将指向的列表项的owner赋给TCB											\
}

任务查询和信息统计API函数

  • 1. UBaseType_t uxTaskPriorityGet ( TaskHandle_t xTask )
    功能: 获取对应任务的任务优先级
    参数: 任务的句柄
    返回值: 该任务的优先级

    #if ( INCLUDE_uxTaskPriorityGet == 1 )

     UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask )
     {
     TCB_t *pxTCB;
     UBaseType_t uxReturn;
    
     	taskENTER_CRITICAL();
     	{
     		/* If null is passed in here then it is the priority of the that
     		called uxTaskPriorityGet() that is being queried. */
     		pxTCB = prvGetTCBFromHandle( xTask );
     		uxReturn = pxTCB->uxPriority;
     	}
     	taskEXIT_CRITICAL();
    
     	return uxReturn;
     }
    
  • 2. void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
    功能: 将任务设定为对应的优先级
    参数:
    a. xTask:任务的句柄
    b. uxNewPriority :新的任务优先级

  • 3. UBaseType_t uxTaskGetSystemState ( TaskStatus_t * const pxTaskStatusArray,
    const UBaseType_t uxArraySize,
    uint32_t * const pulTotalRunTime )
    功能: 获取系统中所有任务的任务状态
    参数:
    a. pxTaskStatusArray:存储任务状态的数组

    typedef struct xTASK_STATUS
     {
     	TaskHandle_t xHandle;			/* The handle of the task to which the rest of the information in the structure relates. */
     	const char *pcTaskName;			/* A pointer to the task's name.  This value will be invalid if the task was deleted since the structure was populated! */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
     	UBaseType_t xTaskNumber;		/* A number unique to the task. */
     	eTaskState eCurrentState;		/* The state in which the task existed when the structure was populated. */
     	UBaseType_t uxCurrentPriority;	/* The priority at which the task was running (may be inherited) when the structure was populated. */
     	UBaseType_t uxBasePriority;		/* The priority to which the task will return if the task's current priority has been inherited to avoid unbounded priority inversion when obtaining a mutex.  Only valid if configUSE_MUTEXES is defined as 1 in FreeRTOSConfig.h. */
     	uint32_t ulRunTimeCounter;		/* 任务总共运行时间 The total run time allocated to the task so far, as defined by the run time stats clock.  See http://www.freertos.org/rtos-run-time-stats.html.  Only valid when configGENERATE_RUN_TIME_STATS is defined as 1 in FreeRTOSConfig.h. */
     	StackType_t *pxStackBase;		/* 指向任务堆栈 Points to the lowest address of the task's stack area. */
     	uint16_t usStackHighWaterMark;	/* The minimum amount of stack space that has remained for the task since the task was created.  The closer this value is to zero the closer the task has come to overflowing its stack. */栈空间历史剩余最小的容量
     } TaskStatus_t;
    

    b. uxArraySize:保存任务状态组的数组的大小
    c. pulTotalRunTime :若configGENERATE_RUN_TIME_STATS为1 时,此参数用来保存系统的总的运行时间
    返回值: 统计到的任务状态的个数

第一个创建的任务为vTaskStartScheduler()开始之前起创建的任务,在vTaskStartScheduler()中,会创建IDLE任务,以及定时器服务任务,所以任务的编号如下:
在这里插入图片描述

  • 4. UBaseType_t uxTaskGetNumberOfTasks( void )
    功能: 返回当前有多少个任务
    参数:
    返回值: 当前系统中存在的任务数量 = 挂起态 + 阻塞态 + 就绪态 + 空闲任务 + 运行态 的任务
    UBaseType_t uxTaskGetNumberOfTasks( void )
    {
    	/* A critical section is not required because the variables are of type
    	BaseType_t. */
    	return uxCurrentNumberOfTasks;
    }
  • 5. void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetFreeStackSpace, eTaskState eState )
    功能: 获取单个任务的任务状态,
    #if( configUSE_TRACE_FACILITY == 1 ) 宏定义成立才能使用
    参数:
    a. xTask: 要查找的任务的任务句柄
    b. pxTaskStatus: 存储任务状态的结构体
    c. xGetFreeStackSpace: 值为pdTRUE或者pdFALSE,为TRUE时会消耗一些时间来计算任务堆栈剩余历史最小大小,为FALSE时将跳过这个统计步骤
    d. eState : TaskStatus_t 中有成员eCurrentState是用来保存任务运行状态的,如下,此参数可以直接赋值(减少执行时间),或者将eState设为eInvalid,这样函数会去获取任务的状态。
     /* Task states returned by eTaskGetState. */
    typedef enum
    {
    	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
    	eReady,			/* The task being queried is in a read or pending ready list. */
    	eBlocked,		/* The task being queried is in the Blocked state. */
    	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
    	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
    	eInvalid			/* Used as an 'invalid state' value. */
    } eTaskState;
  • 6. TaskHookFunction_t xTaskGetApplicationTaskTag ( TaskHandle_t xTask )
    功能: 获取任务的Tag值,任务句柄中有个pxTaskTag变量来保存任务标签值的,标签的功能由用户自己决定,这个函数获取的是任务的标签值的(系统内核不会使用该标签值),如果要使用下面的宏定义要开启

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    	TaskHookFunction_t pxTaskTag;
    #endif
    

    参数: xTask获取的任务的任务句柄,若为NULL就获取正在运行的任务标签值
    返回值: 任务的标签值

  • 7. TaskHandle_t xTaskGetHandle( const char *pcNameToQuery )
    功能: 获取当前任务的任务句柄,使用的话,下述的宏定义要打开
    参数:
    返回值: 当前任务的任务句柄

     #if ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) )
    TaskHandle_t xTaskGetCurrentTaskHandle( void )
    {
    	TaskHandle_t xReturn;
    
    	/* A critical section is not required as this is not called from
    	an interrupt and the current TCB will always be the same for any
    	individual execution thread. */
    	
    	xReturn = pxCurrentTCB;
    
    	return xReturn;
    }
    
    
    
          #endif /* ( ( INCLUDE_xTaskGetCurrentTaskHandle == 1 ) || ( configUSE_MUTEXES == 1 ) ) */
    
    
  • 8. TaskHandle_t xTaskGetHandle( const char *pcNameToQuery )
    功能: 根据任务名称获取任务句柄
    参数: 任务名,字符串
    返回值: 返回任务句柄,若为NULL,则说明没有这个任务

  • 9. TaskHandle_t xTaskGetIdleTaskHandle ( void )
    功能: 获取空闲任务的任务句柄,使用时宏定义要打开
    参数:
    返回值: 返回空闲任务的任务句柄

        #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
        
       	TaskHandle_t xTaskGetIdleTaskHandle( void )
       	{
       		/* If xTaskGetIdleTaskHandle() is called before the scheduler has been
       		started, then xIdleTaskHandle will be NULL. */
       		configASSERT( ( xIdleTaskHandle != NULL ) );
       		return xIdleTaskHandle;
       	}
        
        #endif /* INCLUDE_xTaskGetIdleTaskHandle */
    
    
  • 10. UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    功能: 检查任务创建完成到现在栈空间历史剩余最小值,值越小溢出的可能越大,可以用于代码调试阶段,出货时不用
    参数: 要查询的任务的任务句柄,使用NULL的话,则查询调用此函数的任务本身
    返回值: 返回剩余量最小值

    #if ( INCLUDE_uxTaskGetStackHighWaterMark == 1 )
    
    UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
    {
    	TCB_t *pxTCB;
    	uint8_t *pucEndOfStack;
    	UBaseType_t uxReturn;
    
    	pxTCB = prvGetTCBFromHandle( xTask );
    
    	#if portSTACK_GROWTH < 0
    	{
    		pucEndOfStack = ( uint8_t * ) pxTCB->pxStack;
    	}
    	#else
    	{
    		pucEndOfStack = ( uint8_t * ) pxTCB->pxEndOfStack;
    	}
    	#endif
    
    	uxReturn = ( UBaseType_t ) prvTaskCheckFreeStackSpace( pucEndOfStack );
    
    	return uxReturn;
        	
        #endif /* INCLUDE_uxTaskGetStackHighWaterMark */
    
    
  • 11. eTaskState eTaskGetState( TaskHandle_t xTask )
    功能: 查询任务的运行状态
    参数: 要查询的任务的任务句柄
    返回值: 返回为eTaskState类型,如下

     typedef enum
          {
          	eRunning = 0,	/* A task is querying the state of itself, so must be running. */
          	eReady,			/* The task being queried is in a read or pending ready list. */
          	eBlocked,		/* The task being queried is in the Blocked state. */
          	eSuspended,		/* The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
          	eDeleted,		/* The task being queried has been deleted, but its TCB has not yet been freed. */
          	eInvalid			/* Used as an 'invalid state' value. */
          } eTaskState;
    
  • 12. char *pcTaskGetName( TaskHandle_t xTaskToQuery )
    功能: 查询对应任务的任务名
    参数: 要查询的任务的任务句柄,NULL的话表示查询调用此函数的任务的名字
    返回值: 返回对应任务的任务名

  • 13. TickType_t xTaskGetTickCount( void )
    功能: 查询任务调度器启动到现在,计数器xTickCount的值。xTickCount是系统的时钟节拍,每次定时器中断,xTickCount就会加1,定时器一秒中断几次取决于宏定义 configTICK_RATE_HZ。
    参数:
    返回值: 时间计数器xTickCount的值

  • 14. void vTaskGetRunTimeStats( char *pcWriteBuffer )
    功能: 提供一个表格,记录了每个任务运行的时间(不是秒,是由定时器的周期决定的变量,比如一个中断周期是50us,那么5就是 5 x 50us) 和 所占总时间的比例,
    要使用的话,要打开一下几个宏定义:
    configGENERATE_RUN_TIME_STATS,configUSE_STATS_FORMATTING_FUNCTIONS,
    并且要实现一下几个宏定义:
    portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),此宏定义要初始化一个定时器(中断频率要比系统时钟高,为系统时钟的10~20倍),比如:


void ConfigureTimeForRunTimeStats(void)
{
	//定时器3初始化,定时器时钟为108M,分频系数为108-1,所以定时器3的频率
	//为108M/108=1M,自动重装载为50-1,那么定时器周期就是50us
	FreeRTOSRunTimeTicks=0;
	TIM3_Init(50-1,108-1);	//初始化TIM3
}

以及
portGET_RUN_TIME_COUNTER_VALUE()

    #define portGET_RUN_TIME_COUNTER_VALUE()		Runtime

或者portALT_GET_RUN_TIME_COUNTER_VALUE(Time)
这两个宏定义用来获取当前时基的时间值

参数:
pcWriteBuffer:用来保存任务时间信息的buffer

返回值:

系统内核控制函数

  • 1. taskYIELD()
    任务切换
  • 2. taskENTER_CRITICAL()
    任务中的临界区进入,支持多级嵌套,注:临界区内的代码不要太长,否则屏蔽其他中断太久,其他中断得不到响应
    #define taskENTER_CRITICAL()		portENTER_CRITICAL()
    #define portENTER_CRITICAL()	    vPortEnterCritical()
    void vPortEnterCritical( void )
    {
    	portDISABLE_INTERRUPTS(); //除能5~15级的中断
    	uxCriticalNesting++;  //使用该全局变量,是因为防止有多层临界区嵌套的时候,一旦调用退出临界区,就打乱了其他临界段的保护,只有全部临界段代码退出后才打开中断
    
    	/* This is not the interrupt safe version of the enter critical function so
    	assert() if it is being called from an interrupt context.  Only API
    	functions that end in "FromISR" can be used in an interrupt.  Only assert if
    	the critical nesting count is 1 to protect against recursive calls if the
    	assert function also uses a critical section. */
    	if( uxCriticalNesting == 1 )
    	{
    		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); //断言函数
    	}
    }
  • 3. taskEXIT_CRITICAL()
    任务中的临界区退出
    void vPortExitCritical( void )
    {
    	configASSERT( uxCriticalNesting );
    	uxCriticalNesting--;
    	if( uxCriticalNesting == 0 )
    	{
    		portENABLE_INTERRUPTS(); //使能5~15级的中断
    	}
    }
  • 4. taskENTER_CRITICAL_FROM_ISR()
    中断中的任务中的临界区进入
#define taskENTER_CRITICAL_FROM_ISR()  portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		mrs ulReturn, basepri   //先读出BASEPRI的值,保存到ulReturn中
		cpsid i
		msr basepri, ulNewBASEPRI //将屏蔽的优先级数写到BASEPRI中,比如写入4的话,0~3优先级的中断会执行,而4~15级的中断将被屏蔽
		dsb
		isb
		cpsie i
	}

	return ulReturn;  //返回之前的优先级,退出时将使用到这个值
}
  • 5. taskEXIT_CRITICAL_FROM_ISR()
    中断中的任务中的临界区退出
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )	vPortSetBASEPRI( x )

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 //将进入临界区时保存的值,重新写到BASEPRI寄存器中,恢复进入之前的状态
	}
}
ISR中使用的临界区函数示例:
void TIMCallBack()
{
	uint32_t uiState;
	uiState = taskENTER_CRITICAL_FROM_ISR();
	/********要执行的函数********/
	taskEXIT_CRITICAL_FROM_ISR(uiState);
}
  • 6. taskDISABLE_INTERRUPTS()
    屏蔽5~15级的中断,见CORTEX M中断管理
#define taskDISABLE_INTERRUPTS()	portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		cpsid i
		msr basepri, ulNewBASEPRI
		dsb
		isb
		cpsie i
	}
}
  • 7. taskENABLE_INTERRUPTS()
    使能5~15级的中断,见CORTEX M中断管理
#define portENABLE_INTERRUPTS()	   vPortSetBASEPRI( 0 ) //写0即打开所有中断
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
	}
}
  • 8. taskStartScheduler()
  • 9. taskEndSchelduler()
    给x86结构使用
  • 10. vTaskSuspendAll()
    功能: 挂起所有任务,调用多少次挂起函数,就要调用多少个恢复的函数
  • 11. xTaskResumeAll()
    功能: 恢复所有任务的运行
    返回值: 有没进行任务切换,TRUE为已切换,FALSE为未切换(因为恢复的任务优先级没当前优先级高)
  • 12. vTaskStepTick()
    低功耗TickLess会用到

系统延时函数

  • 1. void vTaskDelay( const TickType_t xTicksToDelay )
    功能:从函数被调用的时刻开始,延时若干个节拍
#if ( INCLUDE_vTaskDelay == 1 )

void vTaskDelay( const TickType_t xTicksToDelay )// 延时多少个时间节拍
{
	BaseType_t xAlreadyYielded = pdFALSE;

	/* A delay time of zero just forces a reschedule. */
	if( xTicksToDelay > ( TickType_t ) 0U )
	{
		configASSERT( uxSchedulerSuspended == 0 );
		vTaskSuspendAll();
		{
			traceTASK_DELAY();

			/* A task that is removed from the event list while the
			scheduler is suspended will not get placed in the ready
			list or removed from the blocked list until the scheduler
			is resumed.

			This task cannot be in an event list as it is the currently
			executing task. */
			prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE ); //此时就绪列表发生了变化
		}
		xAlreadyYielded = xTaskResumeAll();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	/* Force a reschedule if xTaskResumeAll has not already done so, we may
	have put ourselves to sleep. */
	if( xAlreadyYielded == pdFALSE )
	{
		portYIELD_WITHIN_API();
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}
}

#endif /* INCLUDE_vTaskDelay */

相关函数:

if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
	/* The current task must be in a ready list, so there is no need to
	check, and the port reset macro can be called directly. */
	portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );//重置任务的优先级
}

  • 2. void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
    功能: 延时绝对的时间(除非中断打断)
    参数:
    a. pxPreviousWakeTime: 第一次调用时,要传入进入循环的系统计数(用getcount),以后由系统自动更新这个值
    b. xTimeIncrement: 循环执行的时间,包括功能函数 加上 延时的时间,如下图所示
    在这里插入图片描述

FREERTOS 系统节拍

SysTick为倒计数类型的计数器
在这里插入图片描述
a. 先初始化SysTick

//初始化延迟函数
//当使用ucos的时候,此函数会初始化ucos的时钟节拍
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void delay_init(u8 SYSCLK)
{
	u32 reload;
    HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
	fac_us=SYSCLK;						    //不论是否使用OS,fac_us都需要使用
	reload=SYSCLK;					        //每秒钟的计数次数 单位为K	   
	reload*=1000000/configTICK_RATE_HZ;		//根据delay_ostickspersec设定溢出时间,周期50ms转换为频率为20HZ(1秒20个周期),被除数为一秒钟的计数,这个值不能大于77ms
											//reload为24位寄存器,最大值:16777216,在216M下,约合77.7ms左右	
	fac_ms=1000/configTICK_RATE_HZ;			//代表OS可以延时的最少单位,OS的系统节拍	   
	SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;//开启SYSTICK中断
	SysTick->LOAD=reload; 					//每1/OS_TICKS_PER_SEC秒中断一次	
	SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}		

b. 注册中断服务函数

//systick中断服务函数,使用OS时用到

void xPortSysTickHandler( void )
{
	/* The SysTick runs at the lowest interrupt priority, so when this interrupt
	executes all interrupts must be unmasked.  There is therefore no need to
	save and then restore the interrupt mask value as its value is already
	known - therefore the slightly faster vPortRaiseBASEPRI() function is used
	in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
	vPortRaiseBASEPRI();
	{
		/* Increment the RTOS tick. */
		if( xTaskIncrementTick() != pdFALSE )
		{
			/* A context switch is required.  Context switching is performed in
			the PendSV interrupt.  Pend the PendSV interrupt. */
			portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
		}
	}
	vPortClearBASEPRIFromISR();
}


void SysTick_Handler(void)
{	
    if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
    {
        xPortSysTickHandler();	
    }
    HAL_IncTick();
}

c. xTaskIncrementTick()的功能
I. 若任务调度器未被挂起
xTickCount系统节拍加1
若系统节拍计数溢出,则交换两个延时列表(overflow和正常的列表)
若系统节拍计数大于 延时列表中最近要被唤醒的任务的节拍数,判断各个任务是不是到了退出阻塞态,就绪态(State)的时候,判断任务是否要退出各个event(队列,信号量),到了就退出对应的状态,并且把这个任务从对应的列表移除出来,并把合适的任务放到readylist中。

II. 若任务调度器被挂起
++uxPendedTicks
在xTaskResumeAll中,会调用这个变量次数的xTaskIncrementTick(),这样xTickCount就会恢复,并且取消任务的阻塞

队列

用于任务和任务,任务和中断之间的传递消息,
队列长度:数据条数,创建时要指定大小和队列的长度
发送数据到队列是通过拷贝
出队阻塞: 任务从队列中读取消息可指定一个阻塞时间,阻塞时间设定为0的话,读不到数据,就马上返回执行其他程序;设定为0~portMAX_DELAY的话,任务没获取到消息就等待阻塞时间,若设置为portMAX_DELAY的话,就会一直等待,直到收到数据
入队阻塞: 当队列满时,等待有没位置,与出队阻塞类似

队列操作过程:

  1. 创建队列
  2. 向队列发送消息
  3. 从队列读取消息,数据先进先出
typedef struct QueueDefinition
{
	int8_t *pcHead;					/*< Points to the beginning of the queue storage area. */
	int8_t *pcTail;					/*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
	int8_t *pcWriteTo;				/*< Points to the free next place in the storage area. 空闲的块 */

	union							/* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
	{
		int8_t *pcReadFrom;			/*< Points to the last place that a queued item was read from when the structure is used as a queue. 做出队列项的地址,作为队列时,这个成员是下个读取得地址*/
		UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. 作为递归互斥信号量时,用作统计调用的次数*/
	} u;

	List_t xTasksWaitingToSend;		/*< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. 存储处于因队列满而无法发送消息进来的任务*/
	List_t xTasksWaitingToReceive;	/*< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. 存储处于因队空而无法接收到消息进来的任务*/

	volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. 目前队列中有多少消息*/
	UBaseType_t uxLength;			/*< The length of the queue defined as the number of items it will hold, not the number of bytes. 队列的长度,能存多少消息*/
	UBaseType_t uxItemSize;			/*< The size of each items that the queue will hold. 每个消息的大小,单位字节*/

	volatile int8_t cRxLock;		/*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */
	volatile int8_t cTxLock;		/*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	/*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

I. 队列的创建:
(1)xQueueCreate()
在这里插入图片描述

(2)创建队列的静态方法
QueueHandle_t xQueueGenericCreateStatic ( const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize,
uint8_t * pucQueueStorage,
StaticQueue_t *pxStaticQueue,
const uint8_t ucQueueType )
在这里插入图片描述

QueueHandle_t xQueueGenericCreate ( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
功能: 创建队列的动态方法,内核自动分配内存
参数:
a. uxQueueLength: 最大的消息数量,队列的项目数
b. uxItemSize: 每条消息的字节数
c . ucQueueType 表明创建队列的用途
返回值: 队列的句柄,0为失败

#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )
#endif

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
	Queue_t *pxNewQueue;
	size_t xQueueSizeInBytes;
	uint8_t *pucQueueStorage;

configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

if( uxItemSize == ( UBaseType_t ) 0 ) //用作信号量时,因为只关心有多少条消息
{
/* There is not going to be a queue storage area. */
	xQueueSizeInBytes = ( size_t ) 0;
}
else//对应队列
{
/* Allocate enough space to hold the maximum number of items that
can be in the queue at any time. */
	xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. 判断需要多大的内存*/
}

pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );//申请存储消息的内存

if( pxNewQueue != NULL )//判断是否申请成功
{
/* Jump past the queue structure to find the location of the queue
storage area. */
	pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );//得出数据存储区的地址

#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
/* Queues can be created either statically or dynamically, so
note this task was created dynamically in case it is later
deleted. */
	pxNewQueue->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */

prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );//初始化新队列
}

	return pxNewQueue;
}

#endif /* configSUPPORT_STATIC_ALLOCATION */

队列创建相关函数:
prvInitialiseNewQueue()
在这里插入图片描述
函数功能:

  1. 初始化队列结构体相关变量
  2. 调用xQueueGenericReset()
    xQueueGenericReset()
    在这里插入图片描述
    函数功能:
  3. 初始化队列结构体其他变量
  4. 判断复位的队列是否为新创建的队列,如果不是的话,要移除掉队列中所有因为写不进来而阻塞的任务(xTaskWaitingToSend)
  5. 如果是新创建的队列的的话,就初始化两个列表xTaskWaitingToSend 和 xTaskWaitingToReceive
    创建成功后队列的结构
    在这里插入图片描述

II. 入队函数:
在这里插入图片描述

(1)xQueueSend( xQueue, pvItemToQueue, xTicksToWait )

#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

(2)xQueueSendToBack ( xQueue, pvItemToQueue, xTicksToWait )

#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

(3)xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait )

#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )

在这里插入图片描述
在这里插入图片描述

(4)xQueueOverwrite()

#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

(5)最终调用的xQueueGenericSend()
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
在这里插入图片描述
函数功能:

  1. 判断当前成员是否满了 或者 使用的是覆写功能
  2. 如果队列未满,使用prvCopyDataToQueue,拷贝消息到队列中
  3. 通过 xTaskRemoveFromEventList()来判断有没任务因为获取不到消息而进入阻塞态,如有的话就解除阻塞(从相应的状态和事件列表中移除,放到就绪列表中 前提是 任务调度器没挂起)
  4. 如果任务调度器被挂起,不会放到就绪态列表中,而是插入到xPendingReadyList中,等到xTaskResumeAll()恢复任务调度器时,添加到xPendingReadyList就会被处理
  5. 假如解除阻塞后,任务比当前任务优先级高,则进行任务切换
  6. 若队列满了,且阻塞时间为0,就直接返回队列满
  7. 若队列满了,但阻塞时间不为0,再次判断当前是否还是属于阻塞时间,仍为的话,将任务插入事件列表中(xTaskWaitingToSend) 和 延时列表中

当给出互斥型信号量时,优先级的继承处理
1.
中断入队函数:
(1)xQueueSendFromISR()

#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

(2)xQueueSendToBackFromISR()

#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

(3)xQueueSendToFrontFromISR()

#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )

在这里插入图片描述
在这里插入图片描述
例如:

xQueueSendFromISR(QueueKey,USART_RX_BUF,&xHigherPriorityTaskWoken);//向队列中发送数据		
USART_RX_STA=0;	
memset(USART_RX_BUF,0,USART_REC_LEN);//清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换

(4)xQueueOverwriteFromISR()

#define xQueueOverwriteFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueOVERWRITE )

在这里插入图片描述
在这里插入图片描述

(5)xQueueGenericSendFromISR()

在这里插入图片描述

函数功能:

  1. 判断队列是否已满
  2. 把队列发送锁记录下
  3. 队列未满的话,拷贝数据
  4. 如果TxLock未上锁,检查一下是否有任务因等待获取而进入阻塞态,若有的话,记录下优先级,参数会用来判断是否要进行任务切换
  5. 若TxLock上锁,则cTxLock加1,表示上锁时,入队消息的数量
  6. 若队列已满,则直接返回队列已满(因为中断服务函数非任务不允许阻塞)
    出队函数:
    在这里插入图片描述

(1)xQueueReceive()
收到的数据是一个字节(大小看队列的元素类型,long型的为两个字节)
在这里插入图片描述

(2)xQueuePeek()
在这里插入图片描述
在这里插入图片描述

(3)xQueueGenericReceive()
在这里插入图片描述
函数功能:

  1. 判断队列有无数据
  2. 若非空,则使用prvCopyDataFromQueue()拷贝数据,成员数量减1
  3. 检查一下是否有任务因等待发送消息入队而进入阻塞态,若有的话,解除阻塞态
  4. 如果队列为空,且阻塞时间为0时,直接返回空
  5. 若队列不为空,且阻塞时间不为0时,将任务添加到事件列表中

(4)xQueueReceiveFromISR()
在这里插入图片描述
函数功能:

  1. 判断队列是否为空
  2. 若不为空,则拷贝数据出来,成员数量减1
  3. 如果队列的RxLock未上锁,则解除因为等待入队(队列满)的任务的阻塞状态
  4. 若队列上锁,则cRxLock加1 ,表示上锁时,队列出队的消息数量
  5. 若队列为空,直接返回获取失败,无阻塞
    (5)xQueuePeekFromISR()
    在这里插入图片描述

队列上锁和解锁

上锁:
cRxLock: 获取Take消息(出队)上锁
cTxLock: 给出Give消息(入队)上锁

#define prvLockQueue( pxQueue )								\
	taskENTER_CRITICAL();									\
	{														\
		if( ( pxQueue )->cRxLock == queueUNLOCKED )			\
		{													\
			( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED;	\
		}													\
		if( ( pxQueue )->cTxLock == queueUNLOCKED )			\
		{													\
			( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED;	\
		}													\
	}														\
	taskEXIT_CRITICAL()

解锁:
prvUnlockQueue()

  1. 先处理cTxLock,判断有无任务因为任务锁定,导致获取消息失败(锁定后,消息无法入队,所以其他任务获取消息会失败),没失败一次cTxLock +1
  2. 如果有的话在队列解锁的时候要从xTaskWaitingToReceive中移除那些任务
  3. 假如从列表xTaskWaitingToReceive中移除阻塞态的任务比当前的任务优先级大,则标记为要进行任务切换
  4. 通过调用vTaskMissedYield()将xYieldPending置为TRUE,最终将在系统心跳中触发PendSV中断
  5. 处理完所有获取不到队列的任务后,将cTxLock置为未上锁
  6. 对于cRxLock,因为队列的出队(Take)上锁,所以想要Give的任务因为队列满而入不了队,处理同上

信号量与二值信号量

1. 二值信号量:
Give没有阻塞时间
Take可以设定阻塞时间
相当于一个只有一个队列的队列项
任务与任务的同步
中断与任务的同步
a. 二值信号量创建函数
(1)xSemaphoreCreateBinary()

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif

在这里插入图片描述
在这里插入图片描述
新版本函数功能:

  1. 调用xQueueGenericCreate()创建一个队列,队列长度是1,队列项的长度为0,队列类型是semSEMAPHORE_QUEUE_ITEM_LENGTH
  2. 与队列的区别在于:二值信号量不需要存储数据,只关心成员个数

旧版本函数功能:

  1. 创建过程和新版本相同,不过旧版本的二值信号量创建成功后,就为1,新版本的为0
    (2)xSemaphoreCreateBinaryStatic()
    在这里插入图片描述

b.释放二值信号量
在这里插入图片描述

(1)xSemaphoreGive()
在这里插入图片描述
在这里插入图片描述
函数功能:

  1. 调用xQueueGenericSend()释放信号量,本质上将队列的uxMessageWaiting加1,(信号量值加1)
  2. xQueueGenericSend()用于信号量的话,释放信号量阻塞时间为0(队列假如加入成员不成功的话,会有阻塞时间)

当给出互斥型信号量时,优先级的继承处理

  1. 调用prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );来处理优先级继承的问题
  2. 调用xTaskPriorityDisinherit来还原任务原本的优先级,当任务释放最后一个互斥信号量时,才会将任务恢复为基优先级
  3. 将pxQueue->pxMutexHolder = NULL;表示没有任务拥有此信号量

(2)xSemaphoreGiveFromISR()
在这里插入图片描述
函数功能:

c. 获取信号量
在这里插入图片描述
(1)xSemaphoreTake()
在这里插入图片描述
在这里插入图片描述

函数功能:

  1. 调用xQueueGenericReceive()获取信号量,本质是将队列的uxMessagesWaiting减1(即信号量值减1)

当获取互斥型信号量时,优先级的继承处理

  1. 优先级继承在xQueueGenericReceive()中完成
  2. 如果互斥信号量是有效的,标记pxQueue->pxMutexHolder是当前的任务,并将 pxCurrentTCB->uxMutexesHeld 加1,表示当前任务获取到了一个互斥信号量
  3. 如果信号量无效,调用函数vTaskPriorityInherit来处理优先级继承问题
  4. 如果拥有信号量的任务比当前优先级低,将拥有信号量的任务优先级提升到当前优先级,如果拥有信号量的任务在就绪态中,要将其从就绪列表中移除,重新赋予新的优先级,然后再放入就绪列表中,若该任务没有运行,则只是继承优先级
    (2)xSemaphoreTakeFromISR()
    在这里插入图片描述
    2. 计数型
    也称作数值信号量(相当于长度大于1的队列),不关心存储的数据,只关心是否为空
    常用于:
    (1)事件计数
    (2)资源管理
    a. 创建信号量
    在这里插入图片描述
    (1)xQueueCreateCountingSemaphore()
    在这里插入图片描述
    函数功能:
  5. 调用xQueueCreateCountingSemaphore(),此函数同样调用xQueueGenericCreate()创建一个队列,队列长度由uxMaxCount定义,队列项长度为0
  6. 如果创建成功后,信号量的uxMessageWaiting由用户定义(uxInitialCount)

(2)xSemaphoreCreateCountingStatic()
在这里插入图片描述

uxSemaphoreGetCount( xSemaphore )

优先级翻转现象
在这里插入图片描述

3. 互斥型
互斥型信号量有优先级继承的特性,当一个互斥信号量被低优先级的任务使用,这时有个高优先级的任务也要获取这个信号量,然而会进入阻塞态,不过,这个高优先级的任务会将低优先级的任务提到和自己一样的优先级
互斥量不能用于中断服务函数中,因为互斥信号量的优先级继承机制,只在任务中继承,中断无法继承;并且中断服务函数不能因为等待互斥信号量而设置阻塞时间(中断不能进入阻塞态)
当低优先级的任务(GIVE)交出信号量后,其优先级会恢复到原来的优先级,并且不能在中断服务函数中使用 互斥 和 递归互斥信号量
a. 创建信号量
(1)xSemaphoreCreateMutex()
信号量创建成功后,默认成员为1,所以不用像二值信号量一样,重新赋初值
在这里插入图片描述
函数功能:

  1. 调用xQueueCreateMutex,其实是调用xQueueGenericCreate创建一个队列,队列长度是1,队列项长度是0
  2. 互斥信号量创建成功后,调用prvInitialiseMutex()初始化信号量,并调用xQueueGenericSend()使其默认为信号量为1
    (1)xSemaphoreCreateMutexStatic()
    在这里插入图片描述

4. 递归互斥信号量
要使用的话,宏定义要打开,递归互斥信号量的给出和获取都有自己的函数,与互斥、二值和计数信号量获取和发送的时候调用的不同
a. 创建信号量
在这里插入图片描述
(1)xSemaphoreCreateRecursiveMutex()
在这里插入图片描述
在这里插入图片描述
函数功能:

#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )

(2)xSemaphoreCreateRecursiveMutexStatic()
在这里插入图片描述

(3)xSemaphoreGiveRecursive()
给出递归信号量,调用给出函数的第一次,会将信号量入队,以后再调用只是增加一个变量的值
在这里插入图片描述
函数功能:

  1. xQueueGenericSend
  2. 判断当前任务是否获取到递归互斥信号量,如果获取到了的话,pxMutex->u.uxRecursiveCallCount–
  3. 如果pxMutex->u.uxRecursiveCallCount为0时,调用xQueueGenericSend()完成真正的释放
    (4)xSemaphoreTakeRecursive()
    获取信号量,之前调用了多少次xSemaphoreGiveRecursive(给出信号量),就要调用多少次获取函数,当上面的函数中的变量减为1时,才释放信号量
    在这里插入图片描述
    函数功能:
  4. 实际上调用的是xQueueTakeMutexRecursive()
  5. 判断当前任务是否已经获取到递归互斥信号量,如果已获取到信号量,则pxMutex->u.uxRecursiveCallCount++
  6. 如果没获取到的话,说明本次是第一次获取,那么调用xQueueGenericReceive(),只有第一次获取才会得到信号量,往后只是变量+1
    示例:
    在这里插入图片描述

软件定时器

定时器服务函数中,不能使用有阻塞的函数,例如vTaskDelay
要使用的话,要将宏定义设为1
任务的堆栈要根据回调函数来设置
在这里插入图片描述
在这里插入图片描述

a. 创建软件定时器
在这里插入图片描述
(1)xTimerCreate()
在这里插入图片描述
在这里插入图片描述

(2)xTimerCreateStatic()
![在这里插入图片描述](https://img-blog.csdnimg.cn/20181111130350670.png)
在这里插入图片描述

b. 启动软件定时器
在这里插入图片描述

(1)xTimerStart()
在这里插入图片描述

(2)xTimerStartFromISR()
在这里插入图片描述
在这里插入图片描述

c. 停止计时器
在这里插入图片描述

(1)xTimerStop()
在这里插入图片描述

(2)xTimerStopFromISR()
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

d. 定时器的复位
在这里插入图片描述
在这里插入图片描述
(1)xTimerReset()
在这里插入图片描述
在这里插入图片描述

(2)xTimerResetFromISR
在这里插入图片描述

FREERTOS事件标志组

事件位(事件标志)
事件标志组
EventBits_t的数据类型由以下宏定义决定
数据位为32位,一共能存储24位数据(32-8)
数据位为16位,一共能存储16位数据(16-8)

#if( configUSE_16_BIT_TICKS == 1 )
	typedef uint16_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffff
#else
	typedef uint32_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffffffffUL

	/* 32-bit tick type on a 32-bit architecture, so reads of the tick count do
	not need to be guarded with a critical section. */
	#define portTICK_TYPE_IS_ATOMIC 1
#endif
typedef TickType_t EventBits_t;

a. 事件标志组的创建
在这里插入图片描述
(1)xEventGroupCreate()
在这里插入图片描述
(2)xEventGroupCreateStatic()
在这里插入图片描述

b. 设置事件位
在这里插入图片描述
在这里插入图片描述

(1)xEventGroupClearBits()
在这里插入图片描述

(2)xEventGroupClearBitsFromISR()
在这里插入图片描述

(3)xEventGroupSetBits()
在这里插入图片描述
在这里插入图片描述

(4)xEventGroupSetBits()
在这里插入图片描述

c. 获取事件位
在这里插入图片描述
(1)xEventGroupGetBits()
在这里插入图片描述
在这里插入图片描述

(2)xEventGroupGetBitsFromISR()
在这里插入图片描述

d. 等待指定的事件位
xEventGroupWaitBits()
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

FREE_RTOS任务通知

要使用的话,要打开宏定义,任务中有个32位的ulNotifiedValue变量
队列
事件标志组
模拟计数变量(实现不了资源管理,因为是单个任务对单个任务的)
简单的单个任务对单个任务的功能
速度更快
接收任务可以进入阻塞态,发送不会进入阻塞

a. 任务通知发送函数
在这里插入图片描述

(1)xTaskNotify()
模拟队列用,如果使用不覆写通知值的形式,假如通知值还未被处理,则会返回pdFAIL
在这里插入图片描述

(2)xTaskNotifyFromISR()
模拟队列用
在这里插入图片描述
在这里插入图片描述

(3)xTaskNotifyGive()
用于模拟计数型信号量 或者 二值信号量
在这里插入图片描述

(4)vTaskNotifyGiveFromISR()
用于模拟计数型信号量 或者 二值信号量
在这里插入图片描述

(5)xTaskNotifyAndQuery()
模拟队列用,用来传数值
在这里插入图片描述
在这里插入图片描述

(6)xTaskNotifyAndQueryFromISR()
模拟队列用,用来传数值
在这里插入图片描述
(7)xTaskGenericNotify

  1. 先判断参数pulPreviousNotificationValue是否有效,若有效,保存任务通知的原来的值
  2. 保存接收任务的任务接收状态,并修改为已接收到任务通知值
  3. 对动作进行判断(覆写,不动作,设置位)
  4. 假若是不覆写的模式,要判断任务之前的状态是不是 RECEIVED,是的话说明之前的任务通知还未处理,所以返回写入失败
  5. 如果接受的任务因为通知无效而进入阻塞态的话,那么解除接受任务的阻塞态,把任务添加到就绪列表中
  6. 判断解除阻塞的任务,优先级是否高于当前任务,则进行任务切换

(8)xTaskGenericNotifyFromISR()

  1. 获取接收任务的任务控制块
  2. 先判断参数pulPreviousNotificationValue是否有效
  3. 若有效保存接收任务的任务接收状态,并修改为已接收到任务通知值
  4. 其余操作与正常的通知函数相同,区别在于,会判断任务调度器是否挂起,若挂起的话,将要解除阻塞的任务放到xPendingReadyList中去,等到调用xTaskResumeAll()恢复任务调度器的时候,会进行任务的调度。若没有挂起的话,直接将任务放到准备列表中
  5. 最后判断解除阻塞态的任务优先级是否比当前任务高,把值赋给传进来的参数,退出函数后,会根据参数来决定是否进行任务调度
    b. 获取任务通知
    在这里插入图片描述

(1)ulTaskNotifyTake()
只有在任务的通知值不为0的情况下,进入阻塞态,为非0值的话,对通知值减1或者清零
在这里插入图片描述
在这里插入图片描述
函数功能:

  1. 判断任务通知值是不是为0,如果判断到任务通知值无效的话,把任务通知值的状态改为 等待通知,如果设置了阻塞时间的话,任务进入阻塞态,且任务挂到延时列表里
  2. 如果任务通知值有效,根据xClearCountOnExit决定任务通知值清0或减1
  3. 返回任务通知值,并修改任务通知状态为等待通知

(2)xTaskNotifyWait()
在这里插入图片描述
在这里插入图片描述
函数功能:

  1. 如果还未接收到任务通知,那么根据传进来的参数,对通知值指定的bit清零,并把任务通知状态改为i等待任务通知
  2. 如果设置了等待时间的话,将任务置为阻塞态
  3. 如果任务通知有效,将通知值保存到参数中去
  4. 再确认任务通知状态是 等待通知,
  5. 若确认任务通知状态不为等待通知,根据传入的参数,清除通知值指定的bit,并将任务通知状态设为taskNOT_WAITING_NOTIFICATION

FREE_RTOS Tickless 低功耗模式

运用处理空闲任务的时候,就进入低功耗模式,需要处理应用层代码,再唤醒
一般在空闲任务的钩子函数中执行低功耗的处理,比如进入处理器的低功耗模式,关闭外设时钟,降低系统主频等,
因为操作系统的心跳节拍会频繁触发中断,所以进入低功耗的时候,也要关闭时钟,但关闭时钟对任务的时间又造成影响
低功耗定时器来补偿关闭的时候,对任务的影响

  1. 获取下一个任务的运行时刻,由此值减去当前的时刻,得出周期
  2. 初始化定时器(LPTIM,SYSTICK,TIMER),设定值周期
  3. 降低功耗
  4. 通过中断退出低功耗
  5. 恢复主频,电源
  6. 补偿系统计数器,避免对任务的时间造成影响
  7. 应用层任务
    在这里插入图片描述
    用户主要对进入低功耗模式前和后进行处理

FREE_RTOS空闲任务

空闲任务的优先级最低(为1),当任务要删除自己时,就需要在空闲任务中释放任务的资源
空闲任务中不能调用任何阻塞的函数,
可以使用通用的低功耗模式

  1. 释放任务删除列表中的任务内存
  2. 检查是否使用抢占式内核,如果没用,则调用taskYIELD进行任务切换
  3. 如果使用了抢占式内核,而且configIDLE_SHOULD_YIELD等于1,那么空闲任务会把CPU使用权给同优先级的其他任务
  4. 是否使能了空闲任务钩子函数,使能的话就调用
  5. 是否使能了Tickless低功耗模式,使能的话就进入

FREE_RTOS内存管理

使用pvPortMalloc() 和 vPortFree()

  1. heap1.c
    只有申请,没有释放
  2. heap2.c
    有申请和释放,但是释放后没有合并空闲内存
  3. heap3.c
    有线程保护的申请和释放
  4. heap4.c
    会合并碎片
  5. heap5.c
    允许跨越SDRAM, SRAM , 内部RAM
    在创建任务前等分配内存的任务前,要先调用初始化函数

FREE_RTOS任务切换

  1. 执行系统调用,如vTaskDelay中调用的portYIELD()
  2. SysTick中断

任务调度器

vTaskStartScheduler

  1. 创建空闲任务
  2. 如果使能了软件定时器,就创建软件定时器任务
  3. 关中断,SVC会打开中断
  4. 初始化一些静态全局变量
  5. 初始化提供时基的定时器,如果开启了任务运行时间统计的话,要使用额外的定时器给系统提供时基
  6. 进入xPortStartScheduler,并设置SysTick和PendSV的优先级,为最低(15)
    在这里插入图片描述
  7. 初始化滴答定时器,中断周期(根据config.h),定时器的使能
  8. 假如有FPU的话,使能FPU(浮点单元),并开启惰性压栈
    在这里插入图片描述
  9. prvStartFirstTask();
__asm void prvStartFirstTask( void )
{
	PRESERVE8

	/* Use the NVIC offset register to locate the stack. */获取MSP指针
	ldr r0, =0xE000ED08	:VTOR向量表偏移寄存器的地址,这个寄存器现在指向0x80000000(向量表的地址)
	ldr r0, [r0]	:读取完后,r0 = 0x80000000
	ldr r0, [r0]	:获取0x8000000(MSP)的地址的数据,即主栈指针MSP的初始值
	/* Set the msp back to the start of the stack. */
	msr msp, r0	:将r0赋值给MSP
	/* Globally enable interrupts. */
	cpsie i		:开启中断,写0是除能(即打开中断,写1为 屏蔽 不包括fault和NMI的中断)
	cpsie f		:开启异常,写0是开启异常,写1位屏蔽比-1级数更低的中断优先级(硬FAULT也被屏蔽)
	dsb    	:数据同步隔离
	isb		:指令同步屏障
	/* Call SVC to start the first task. */
	svc 0	:运行SVC产生SVC中断,调用该代号的服务函数,写SVC 0就调用 0号系统服务,
			  只用第一次运行调用SVC,以后都使用PendSv,运行这个指令后,启动第一个任务
	nop
	nop
} 

中断向量表的首内容是栈顶指针
在这里插入图片描述

在这里插入图片描述

以下为SVC 0指令调用的函数

__asm void vPortSVCHandler( void )
{
	PRESERVE8

	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB    :获取第一个任务的栈,pxCurrentTCB的第一个成员为栈顶指针
	ldr r1, [r3]	:这样R1就为任务控制块的地址
	ldr r0, [r1]	:将任务控制块第一个字段pxTopOfStack所指位置的数据(现场的值)
	/* Pop the core registers. */
	ldmia r0!, {r4-r11, r14}	:为多加载指令,从r0的地址读取多个字,每读取一个字后,地址增加一个字,
								这个指令将R0地址的数据赋给了R4到R11以及R14寄存器,
								加载完后(数据出栈的过程),此时栈顶指针r0指向新的栈顶
							   (R0~R3等自动恢复),这些恢复的内容,应该是从堆内存中模拟的栈存储结构
	msr psp, r0	:将新的栈顶指针赋值给PSP(进程栈指针)
	isb			:指令同步屏障
	mov r0, #0	:设置寄存器R0为0
	msr	basepri, r0	:打开中断
	bx r14			:自动恢复寄存器R0~R3,R12,LR,PC和xPSR的值,进程栈使用PSP,执行PC中保存的任务函数,然后FREERTOS的任务开始运行
}

在这里插入图片描述

R14设为0xfffffffd的原因,返回线程模式,并使用线程堆栈 SP = PSP
在这里插入图片描述
R14 为 EXC_RETURN = 0xfffffffd,意为出栈后使用PSP(MSP为中断服务函数使用,当STM32没有使用操作系统时,只有使用MSP)

任务切换详解

  1. 任务切换过程是在PendSV中发生的
  2. 将ICSR寄存器()的bit28位置1,就会触发PendSV中断

上下文切换的场合

  1. 执行系统调用,如vTaskYIELD(),vTaskDelay()
  2. SysTick中断 xPortSysTickHandler();

xPortPendSVHandler( void )

  1. 获取进程栈栈顶指针
  2. 获取任务控制块的地址
  3. 判断有无使用vfp单元(浮点单元),如果使用将S16-S31入栈,当异常情况中(pendSV中断),EX_RETURN的bit4会被CONTROL的FPCA位替代,根据bit4是否为0,1则为需要保存浮点寄存器的值,0则不需要
  4. 将R4-R11,R14入栈(任务申请 的栈空间)
  5. 关闭中断
  6. 调用vTaskSwitchContext,获取下一个要运行的任务
  7. 打开中断
  8. 获取切换到的任务的栈顶指针
  9. 将新的任务保存的R4-R11,R14出栈
  10. 同样判断是否要将FPU的数据S16-S31出栈

vTaskSwitchContext()

  1. 判断任务调度器是否挂起,若挂起的话,xYieldPending = pdTRUE;
  2. 若任务调度器开始时,则查找下个任务
  3. 更新当前任务运行的时间pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );
  4. 调用taskSELECT_HIGHEST_PRIORITY_TASK()获取下个要运行的任务(硬件方法或者通用方法)

硬件方法:

  1. 用portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );取得uxTopPriority最高优先级是第几级,clz为硬件计算前导0个数,比如现在最高优先级的任务是29级,但是获取前导0个数为2,所以需要31-2得出真正的优先级

    #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

  2. 确认对应优先级的就绪列表中有任务(每个优先级都有一个就绪列表)

  3. 使用listGET_OWNER_OF_NEXT_ENTRY()获取该列表的下个成员(下个运行的任务),取得下个任务的TCB,将其保存到pxCurrentTCB中

通用方法:

  1. 获取最高优先级的任务,直接读取uxTopReadyPriority(每次任务调度都会更新这个变量)

  2. 使用listGET_OWNER_OF_NEXT_ENTRY()获取该列表的下个成员(下个运行的任务),取得下个任务的TCB,将其保存到pxCurrentTCB中

    #define taskSELECT_HIGHEST_PRIORITY_TASK()
    {
    UBaseType_t uxTopPriority = uxTopReadyPriority;

    /* Find the highest priority queue that contains ready tasks. /
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )
    {
    configASSERT( uxTopPriority );
    –uxTopPriority;
    }

    /
    listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of
    the same priority get an equal share of the processor time. /
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
    uxTopReadyPriority = uxTopPriority;
    } /
    taskSELECT_HIGHEST_PRIORITY_TASK */
    在这里插入图片描述
    在这里插入图片描述

内存管理

heap1.c
内存申请后,就不再释放,所以没有实现pvFree()
a. 内存申请
pvMalloc():

  1. 对申请的内存做字节对齐(8byte)
  2. 内存堆ucHeap的可用字节内存对齐
  3. 申请内存,从ucHeap中取出一块
  4. 返回申请到的内存的指针

heap2.c
若任务每次分配和释放的内存不是一定的,不要使用这个
a. 内存初始化
prvHeapInit()

  1. 先对pucAlignedHeap进行字节对齐
  2. 将头指针xStart指向内存块,且头指针的成员xBlockSize是0
  3. 初始化尾指针,尾指针指向NULL,xBlockSize是configADJUSTED_HEAP_SIZE
  4. 初始化pxFirstFreeBlock第一个内存块,第一个内存块为pucAlignedHeap,xBlockSize大小为configADJUSTED_HEAP_SIZE,并指向尾指针。这样就组成了 头指针->第一块->尾指针

b. 内存申请
pvMalloc():

  1. 第一次调用的时候会使用prvHeapInit()初始化内存堆
  2. 检查要申请的内存大小大于0否,实际申请的内存要加上结构体的大小,加上后的总大小再做一次字节对齐
  3. 从列表头xStart找满足申请大小的内存块
  4. 获取返回给应用的内存首地址(跳过结构体的大小)
  5. 判断申请到的内存块是否过大(根据宏定义决定,即申请到的内存比所需的大小超过阈值heapMININUM_BLOCK_SIZE),若过大,则把内存块分成两块,一块返回给用户使用,一块重新添加到空闲列表中
  6. 更新剩余的内存xFreeBytesRemaining大小
 static void prvHeapInit( void )
 {
 BlockLink_t *pxFirstFreeBlock;
 uint8_t *pucAlignedHeap;
 
 	/* Ensure the heap starts on a correctly aligned boundary. */
 	pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
 
 	/* xStart is used to hold a pointer to the first item in the list of free
 	blocks.  The void cast is used to prevent compiler warnings. */
 	xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
 	xStart.xBlockSize = ( size_t ) 0;
 
 	/* xEnd is used to mark the end of the list of free blocks. */
 	xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
 	xEnd.pxNextFreeBlock = NULL;
 
 	/* To start with there is a single free block that is sized to take up the
 	entire heap space. */
 	pxFirstFreeBlock = ( void * ) pucAlignedHeap;
 	pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
 	pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
 } 

在这里插入图片描述

c. 空闲内存块回收函数:
prvInsertBlockIntoFreeList()

  1. 查找内存块插入点
  2. 使用链表的操作,将内存块插入到插入点中

d. 内存释放:
vPortFree()

  1. 减去结构体的大小,让指针重新指向内存块的开始地址
  2. 调用上面的prvInsertBlockIntoFreeList(),将这个块插入可用内存链表中
  3. 把这个块的大小加上,更新xFreeBytesRemaining的大小

heap3.c
要根据 .s 文件中的HEAP_Size设定堆的大小
a. 内存申请
pvPortMalloc()

  1. 关闭任务调度器
  2. 使用malloc
  3. 恢复任务调度器

b. 内存释放
pvPortFree()

  1. 同样使用Free

heap4.c
多了处理内存碎片的功能

a. 内存初始化
prvHeapInit()

  1. 先对pucAlignedHeap进行字节对齐
  2. 初始化xStart
  3. 再初始化xEnd,它包含在内存块中
  4. 再初始化pxFirstFreeBlock
  5. 初始化xMininumEverFreeBytesRemaining 和 xFreeBytesRemaining
  6. 初始化xBlockAllocatedBit为 0x8000 0000
    在这里插入图片描述

b. 空闲内存块插入函数:
prvInsertBlockIntoFreeList()

  1. 查找空闲内存的插入点
  2. 检查插入的内存块能否和前一个内存块合并
  3. 若不能和前一个内存块合并,那么检查能否和后一个内存块合并,若可以则和后面的合并

c. 内存申请函数:
pvPortMalloc()

  1. 判断是否为第一次调用,若是第一次,初始化内存堆
  2. 判断所需内存大小是否满足最高位不为1(不超过0x7fffffff),因为最高位用来判断这块是否被使用
  3. 从列表头xStart找满足申请大小的内存块
  4. 获取返回给应用的内存首地址(跳过结构体的大小)
  5. 判断申请到的内存块是否过大(根据宏定义决定,即申请到的内存比所需的大小超过阈值heapMININUM_BLOCK_SIZE),若过大,则把内存块分成两块,一块返回给用户使用,一块重新添加到空闲列表中
  6. 更新剩余的内存xFreeBytesRemaining大小
  7. 将申请到的内存块的成员xBlockSize最高位设为1,表示此内存块已被使用

d. 内存释放函数:
pvPortFree()

  1. 判断要释放的内存是否正在被使用
  2. 将xBlockSize的最高位清零
  3. 调用prvInsertBlockIntoFreeList,将空闲内存插入空闲列表中
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值