目录
1.添加FreeRTOS源码到工程文件的Middlewares目录
2.在MDK移植内核管理算法(heap_x.c)连接桥梁(port.c)
编辑 4.添加FreeRTOSConfig.h(可以裁剪出需要的功能)
添加任务到就绪列表 prvAddNewTaskToReadyList
在中断中恢复被挂起的任务 xTaskResumeFromISR()
关中断函数 portDISABLE_INTERRUPTS()
入队堵塞:队列已满 该任务挂载到等待写入事件xTaskWaittingToSend()
出队堵塞:队列已控 该任务挂载到等待读取事件xTaskWaittingToReceive()
参考资料
任务调度
任务调度器:决定当前执行哪个任务。
抢占式调度
优先级高的可以抢占优先级低的任务。(优先级数值越大,优先级越高)当前高优先级任务遇到阻塞直接切换到下一个低优先级任务
时间片调度
相同优先级在每个系统节拍切换任务(一个时间片等于SysTick滴答定时器的中断周期)当前任务遇到阻塞直接切换到下一个任务
携程式调度
当前任务一直执行,高优先级无法抢占
任务状态
运行态:正在运行
就绪态:能够执行,但还未被执行
阻塞态:因为延时或者等待外部事件发生
挂起态:
类似暂停,调用vTaskSuspend()进入挂起态,需要调用vTaskResune()才能进入就绪态
任务列表
就绪列表 pxReadyTasksLists[x]
相同优先级会在同一个就绪列表上
调度器总是从就绪列表中选出最高优先级任务进行执行
阻塞列表 pxDelayedTaskList
挂起列表 xSuspendedTaskList
FreeRTOS源码
portable文件夹
FreeRTOS 移植
1.添加FreeRTOS源码到工程文件的Middlewares目录
2.在MDK移植内核管理算法(heap_x.c)连接桥梁(port.c)
3.添加头文件路径
4.添加FreeRTOSConfig.h(可以裁剪出需要的功能)
在官方源码的demo里面有
然后添加文件路径
系统配置文件详解
config:完成FreeRTOS的功能配置和裁剪
INCLUDE:配置FreeRTOS可选的API函数
其他配置项:PendSV SVC 中断相关定义
详细解释
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* 头文件 */
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include <stdint.h>
extern uint32_t SystemCoreClock;
/* 基础配置项 */
#define configUSE_PREEMPTION 1 /* 1: 抢占式调度器, 0: 协程式调度器, 无默认需定义 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 /* 1: 使用硬件计算下一个要运行的任务, 0: 使用软件算法计算下一个要运行的任务, 默认: 0 */
#define configUSE_TICKLESS_IDLE 0 /* 1: 使能tickless低功耗模式, 默认: 0 */
#define configCPU_CLOCK_HZ SystemCoreClock /* 定义CPU主频, 单位: Hz, 无默认需定义 */
#define configSYSTICK_CLOCK_HZ (configCPU_CLOCK_HZ / 8)/* 定义SysTick时钟频率,当SysTick时钟频率与内核时钟频率不同时才可以定义, 单位: Hz, 默认: 不定义 */
#define configTICK_RATE_HZ 1000 /* 定义系统时钟节拍频率, 单位: Hz, 无默认需定义 */
#define configMAX_PRIORITIES 32 /* 定义最大优先级数, 最大优先级=configMAX_PRIORITIES-1, 无默认需定义 */
#define configMINIMAL_STACK_SIZE 128 /* 定义空闲任务的栈空间大小, 单位: Word, 无默认需定义 */
#define configMAX_TASK_NAME_LEN 16 /* 定义任务名最大字符数, 默认: 16 */
#define configUSE_16_BIT_TICKS 0 /* 1: 定义系统时钟节拍计数器的数据类型为16位无符号数, 无默认需定义 */
#define configIDLE_SHOULD_YIELD 1 /* 1: 使能在抢占式调度下,同优先级的任务能抢占空闲任务, 默认: 1 */
#define configUSE_TASK_NOTIFICATIONS 1 /* 1: 使能任务间直接的消息传递,包括信号量、事件标志组和消息邮箱, 默认: 1 */
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 /* 定义任务通知数组的大小, 默认: 1 */
#define configUSE_MUTEXES 1 /* 1: 使能互斥信号量, 默认: 0 */
#define configUSE_RECURSIVE_MUTEXES 1 /* 1: 使能递归互斥信号量, 默认: 0 */
#define configUSE_COUNTING_SEMAPHORES 1 /* 1: 使能计数信号量, 默认: 0 */
#define configUSE_ALTERNATIVE_API 0 /* 已弃用!!! */
#define configQUEUE_REGISTRY_SIZE 8 /* 定义可以注册的信号量和消息队列的个数, 默认: 0 */
#define configUSE_QUEUE_SETS 1 /* 1: 使能队列集, 默认: 0 */
#define configUSE_TIME_SLICING 1 /* 1: 使能时间片调度, 默认: 1 */
#define configUSE_NEWLIB_REENTRANT 0 /* 1: 任务创建时分配Newlib的重入结构体, 默认: 0 */
#define configENABLE_BACKWARD_COMPATIBILITY 0 /* 1: 使能兼容老版本, 默认: 1 */
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 0 /* 定义线程本地存储指针的个数, 默认: 0 */
#define configSTACK_DEPTH_TYPE uint16_t /* 定义任务堆栈深度的数据类型, 默认: uint16_t */
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t /* 定义消息缓冲区中消息长度的数据类型, 默认: size_t */
/* 内存分配相关定义 */
#define configSUPPORT_STATIC_ALLOCATION 0 /* 1: 支持静态申请内存, 默认: 0 */
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /* 1: 支持动态申请内存, 默认: 1 */
#define configTOTAL_HEAP_SIZE ((size_t)(10 * 1024)) /* FreeRTOS堆中可用的RAM总量, 单位: Byte, 无默认需定义 */
#define configAPPLICATION_ALLOCATED_HEAP 0 /* 1: 用户手动分配FreeRTOS内存堆(ucHeap), 默认: 0 */
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0 /* 1: 用户自行实现任务创建时使用的内存申请与释放函数, 默认: 0 */
/* 钩子函数相关定义 */
#define configUSE_IDLE_HOOK 0 /* 1: 使能空闲任务钩子函数, 无默认需定义 */
#define configUSE_TICK_HOOK 0 /* 1: 使能系统时钟节拍中断钩子函数, 无默认需定义 */
#define configCHECK_FOR_STACK_OVERFLOW 0 /* 1: 使能栈溢出检测方法1, 2: 使能栈溢出检测方法2, 默认: 0 */
#define configUSE_MALLOC_FAILED_HOOK 0 /* 1: 使能动态内存申请失败钩子函数, 默认: 0 */
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 /* 1: 使能定时器服务任务首次执行前的钩子函数, 默认: 0 */
/* 运行时间和任务状态统计相关定义 */
#define configGENERATE_RUN_TIME_STATS 0 /* 1: 使能任务运行时间统计功能, 默认: 0 */
#if configGENERATE_RUN_TIME_STATS
#include "./BSP/TIMER/btim.h"
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
#endif
#define configUSE_TRACE_FACILITY 1 /* 1: 使能可视化跟踪调试, 默认: 0 */
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 /* 1: configUSE_TRACE_FACILITY为1时,会编译vTaskList()和vTaskGetRunTimeStats()函数, 默认: 0 */
/* 协程相关定义 */
#define configUSE_CO_ROUTINES 0 /* 1: 启用协程, 默认: 0 */
#define configMAX_CO_ROUTINE_PRIORITIES 2 /* 定义协程的最大优先级, 最大优先级=configMAX_CO_ROUTINE_PRIORITIES-1, 无默认configUSE_CO_ROUTINES为1时需定义 */
/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 /* 1: 使能软件定时器, 默认: 0 */
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) /* 定义软件定时器任务的优先级, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_QUEUE_LENGTH 5 /* 定义软件定时器命令队列的长度, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小, 无默认configUSE_TIMERS为1时需定义 */
/* 可选函数, 1: 使能 */
#define INCLUDE_vTaskPrioritySet 1 /* 设置任务优先级 */
#define INCLUDE_uxTaskPriorityGet 1 /* 获取任务优先级 */
#define INCLUDE_vTaskDelete 1 /* 删除任务 */
#define INCLUDE_vTaskSuspend 1 /* 挂起任务 */
#define INCLUDE_xResumeFromISR 1 /* 恢复在中断中挂起的任务 */
#define INCLUDE_vTaskDelayUntil 1 /* 任务绝对延时 */
#define INCLUDE_vTaskDelay 1 /* 任务延时 */
#define INCLUDE_xTaskGetSchedulerState 1 /* 获取任务调度器状态 */
#define INCLUDE_xTaskGetCurrentTaskHandle 1 /* 获取当前任务的任务句柄 */
#define INCLUDE_uxTaskGetStackHighWaterMark 1 /* 获取任务堆栈历史剩余最小值 */
#define INCLUDE_xTaskGetIdleTaskHandle 1 /* 获取空闲任务的任务句柄 */
#define INCLUDE_eTaskGetState 1 /* 获取任务状态 */
#define INCLUDE_xEventGroupSetBitFromISR 1 /* 在中断中设置事件标志位 */
#define INCLUDE_xTimerPendFunctionCall 1 /* 将函数的执行挂到定时器服务任务 */
#define INCLUDE_xTaskAbortDelay 1 /* 中断任务延时 */
#define INCLUDE_xTaskGetHandle 1 /* 通过任务名获取任务句柄 */
#define INCLUDE_xTaskResumeFromISR 1 /* 恢复在中断中挂起的任务 */
/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
/* 断言 */
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
/* FreeRTOS MPU 特殊定义 */
//#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
//#define configTOTAL_MPU_REGIONS 8
//#define configTEX_S_C_B_FLASH 0x07UL
//#define configTEX_S_C_B_SRAM 0x07UL
//#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY 1
//#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS 1
/* ARMv8-M 安全侧端口相关定义。 */
//#define secureconfigMAX_SECURE_CONTEXTS 5
#endif /* FREERTOS_CONFIG_H */
官方API说明
任务创建和删除
动态创建
FreeRTOS从管理的堆中,自动分配任务的堆栈空间和任务块(TCB)所需内存
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask);
内部实现
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
TCB_t *pxNewTCB;
BaseType_t xReturn;
/* If the stack grows down then allocate the stack then the TCB so the stack
does not grow into the TCB. Likewise if the stack grows up then allocate
the TCB then the stack. */
#if( portSTACK_GROWTH > 0 )
{
/* Allocate space for the TCB. Where the memory comes from depends on
the implementation of the port malloc function and whether or not static
allocation is being used. */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
if( pxNewTCB != NULL )
{
/* Allocate space for the stack used by the task being created.
The base of the stack memory stored in the TCB so the task can
be deleted later if required. */
pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
if( pxNewTCB->pxStack == NULL )
{
/* Could not allocate the stack. Delete the allocated TCB. */
vPortFree( pxNewTCB );
pxNewTCB = NULL;
}
}
}
#else /* portSTACK_GROWTH */
{
StackType_t *pxStack;
/* Allocate space for the stack used by the task being created. */
pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack and this allocation is the stack. */
if( pxStack != NULL )
{
/* Allocate space for the TCB. */
pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCU's stack, and the first member of TCB_t is always a pointer to the task's stack. */
if( pxNewTCB != NULL )
{
/* Store the stack location in the TCB. */
pxNewTCB->pxStack = pxStack;
}
else
{
/* The stack cannot be used as the TCB was not created. Free
it again. */
vPortFree( pxStack );
}
}
else
{
pxNewTCB = NULL;
}
}
#endif /* portSTACK_GROWTH */
if( pxNewTCB != NULL )
{
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
{
/* Tasks can be created statically or dynamically, so note this
task was created dynamically in case it is later deleted. */
pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
}
#endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
prvAddNewTaskToReadyList( pxNewTCB );
xReturn = pdPASS;
}
else
{
xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
return xReturn;
}
任务初始化 prvInitialiseNewTask
任务栈初始化
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
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_EXC_RETURN;
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4. */
return pxTopOfStack;
}
添加任务到就绪列表 prvAddNewTaskToReadyList
静态创建
用户自己分配任务栈内存
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);
/* Dimensions of the buffer that the task being created will use as its stack.
NOTE: This is the number of words the stack will hold, not the number of
bytes. For example, if each stack item is 32-bits, and this is set to 100,
then 400 bytes (100 * 32-bits) will be allocated. */
#define STACK_SIZE 200
/* 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, /* Number of indexes in the xStack array. */
( 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 );
}
任务删除
被删除的任务将从就绪任务,阻塞任务,挂起任务和事件列表中移除
传入参数为NULL时,代表删除自身任务,此时该任务的堆栈内存由空闲任务释放
如果传入参数是其他任务,则在该函数进行释放
内部实现
临界区创建
在临界区不会进行任务的切换,可以在临界区内创建任务。
进入临界区taskENTER_CRITICAL()
退出临界区taskEXIT_CRITICAL()
注意点
任务挂起与恢复
挂起任务 vTaskSuspend()
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
必须将 INCLUDE_vTaskSuspend 定义为 1 才能使用此函数
暂停任意任务。无论任务优先级如何,任务被暂停后将永远无法获取任何微控制器处理时间。
对 vTaskSuspend 的调用不会累积次数,例如:若在同一任务上调用 vTaskSuspend () 两次,将仍然仅需调用一次 vTaskResume (),即可准备完毕暂停的任务。
参数:
xTaskToSuspend | 被挂起的任务句柄。传递空句柄将导致调用任务被暂停。 |
当参数为NULL,代表挂起当前任务
内部实现
恢复被挂起任务 vtaskResume()
void vTaskResume( TaskHandle_t xTaskToResume );
必须将 INCLUDE_vTaskSuspend 定义为 1 才能使用此函数。
恢复已挂起的任务。
由一次或多次调用 vTaskSuspend () 而挂起的任务可通过单次调用 vTaskResume () 重新运行。
参数:
xTaskToResume | 要恢复的任务句柄 |
内部实现
在中断中恢复被挂起的任务 xTaskResumeFromISR()
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );
必须将 include_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 定义为 1 才能使用此函数。
可从 ISR 内调用的恢复挂起任务的函数。
由多次调用 vTaskSuspend() 中的一次调用挂起的任务可通过单次调用 xTaskResumeFromISR() 重新运行。
xTaskResumeFromISR() 通常被视为危险函数,因为其操作未被锁定。 因此,如果中断可能在任务被挂起之前到达, 从而中断丢失, 则绝对不应使用该函数 来同步任务与中断。 可使用信号量, 或者最好是直达任务通知,来避免这种可能性。
中断优先级数值越小,优先级越高
任务优先级数值越小,优先级越低
中断服务程序中调用API函数时中断优先级不能高于FreeRTOS管理的最高优先级(5-15)
用法示例
TaskHandle_t xHandle;
void vAFunction( void )
{
// Create a task, storing the handle.
xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ... Rest of code.
}
void vTaskCode( void *pvParameters )
{
// The task being suspended and resumed.
for( ;; )
{
// ... Perform some function here.
// The task suspends itself.
vTaskSuspend( NULL );
// The task is now suspended, so will not reach here until the ISR resumes it.
}
}
void vAnExampleISR( void )
{
BaseType_t xYieldRequired;
// Resume the suspended task.
xYieldRequired = xTaskResumeFromISR( xHandle );
// We should switch context so the ISR returns to a different task.
// NOTE: How this is done depends on the port you are using. Check
// the documentation and examples for your port.
portYIELD_FROM_ISR( xYieldRequired );
}
内部实现
调度器没被挂起,就将挂起任务添加到就绪列表
调度器被挂起,就将挂起任务添加到等待就绪列表,等调度器被挂起后,会自动添加到就绪列表
中断管理
在使用在中断中恢复被挂起的任务 xTaskResumeFromISR()函数时,需要将中断分组设置为4,即全部为抢占式优先级
通过SysTickc触发PenSV中断用来实现任务切换
中断服务函数的优先级需要在FreeRTOS管理的范围内,只能调用带有“FromISR”的API函数
FreeRTOS的中断管理通过BASEPRI寄存器实现的
实现效果
关中断函数 portDISABLE_INTERRUPTS()
关中断后不能使用vTaskDelay()函数进行延时,因为该函数会打开中断
开中断函数 portENABLE_INTERRUPTS()
临界代码(临界区)保护
临界区:指不能被打断的代码段
临界段代码保护函数
进入临界段代码前需要关中断,执行完后需要开中断
保护函数可以进行成对使用(开多少关多少)和嵌套使用
任务调度器的挂起和恢复
任务调度器挂起后就不能进行任务切换,挂起时不需要关闭中断
只是防止任务之间的资源抢夺,中断可以照常响应
挂起时会把就绪列表任务放到等待就绪列表,恢复时会将等待就绪列表的任务放回就绪列表,并切换优先级最高的任务执行
挂起任务调度器 vTaskSuspendAll()
挂起调度器时,uxSchedulerSuspend+1
当uxSchedulerSuspend不为0时SysTick无法触发PendSV中断,从而无法切换任务
恢复任务调度器 vTaskResumeAll()
恢复调度器时,uxSchedulerSuspend-1 直到等于0时 允许被调度
列表和列表项
列表类似链表,是一个双项环形链表,列表项相当于节点,用来存放列表中的项目
列表
列表项
xItem Value 用于进行升序排列
pvOwner指向任务控制块
迷你列表项
仅用于标记列表的末尾和挂载其他插入列表中(方便第一个列表项的插入)的列表项,所以没有pxOwner和pxContainer两个成员变量
xItemvalue默认数值时最大为0xFFFFFFFF
函数
初始化列表 cListInitialise()
列表项初始化 vListinitalieItem()
列表项插入 vListInsert()
按照升序方式进行排序插入
void vListInsert(List* const pxList,ListItem * const pxNewListItem)
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. *//*lint !e440 The iterator moves to a different value, not xValueOfInsertion. */
{
/* 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->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}
列表项插入 vListInsertEnd()
用于将列表项插入到列表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(). */
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->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}
列表项删除 uxListRemove
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
因为列表项里面的pxContainer变量指明了所属列表,因此传入参数只需要列表项一个
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 = pxItemToRemove->pxContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Only used during decision coverage testing. */
mtCOVERAGE_TEST_DELAY();
/* Make sure the index is left pointing to a valid item. */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pxContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;
}
启动任务调度器
启动第一个任务
prvStartFirstTask() 开启第一个任务
vPortSVCHandler() SVC中断服务函数
SVC中断只在第一次启动任务时调用,以后不会调用,后面任务切换通过PenSV实现
Free使用双堆栈指针,裸机只使用MSP
启动第一个任务时回(动态创建)先初始化一个空闲任务和软件定时器(可选的,如果使能了软件定时器)(有自己的堆栈),然后软件定时器的堆栈内容会被赋值到CPU寄存器中,接着PSP指针指向任务函数,开始执行定时器任务(portTASK_FUNCTION())
软件定时器的任务优先级最高为31 空闲任务(IDLE)优先级最低为0
软件定时器作用
软件定时器相当于裸机的定时器,可以定时中断,在FreeRTOS中可以扩展出很多个定时器出来
软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务
调用xPortStartScheduler完成任务调度器
任务切换
本质就是CPU寄存器的切换
例如任务A切换到任务B 只需要将任务A的寄存器保存到自身堆栈 将任务B堆栈中寄存器的值恢复到CPU寄存器中
自动出栈压栈过程用的是PSP
任务切换过程在PendSV中断函数中完成
PenSV如何触发
1.滴答定时器中断调用(正常任务切换使用方法)
2.使用portYIELD()函数调用
本质上都是通过向中断控制及状态寄存器ICSR的bit28写入1来启动PenSV中断
时间片调度
针对同等优先级的任务
同等优先级任务轮流地享有相同的 CPU 时间(可设置),叫时间片,在FreRTOS中,一个时间片就等于sysTick 中断周期
一个时间片的大小取决于滴答定时器的中断时间
任务相关API函数
获取所有任务状态信息
使用方法
获取所有任务状态信息(列表显示)
使用方法
获取单个任务信息
使用方法
获取单个任务状态
任务时间统计
void vTaskGetRunTimeStats( char *pcWriteBuffer );
除了调用外还需要实现其他函数功能
configGENERATE_RUN_TIME_STATS、configUSE_STATS_FORMATTING_FUNCTIONS 和 configSUPPORT_DYNAMIC_ALLOCATION 必须全部定义为 1,该函数才可用。该应用程序 还必须提供 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 和 portGET_RUN_TIME_COUNTER_VALUE 的定义, 以便分别配置外设定时器/计数器和返回定时器的当前计数值。计数器的频率应该至少是滴答计数的 10 倍
相对延时与绝对延时
vTaskDelay()和vTaskDelayUntil()会将任务变成阻塞态,但是delay_ms()不会,任务依旧处于就绪态
相对延时 vTaskDelay()
会先挂载任务调度器,然后将当前任务挂载到阻塞列表,然后恢复任务调度器,再判断是否需要进行任务切换。
在滴答定时器中断里面会进行计时,获取阻塞列表的最小延时时间,到达延时时间后将相应的任务移除阻塞列表,并且移动到就绪列表中(滴答定时器中断后面还会实现时间片 抢占式调度)
绝对延时 vTaskDelayUntil()
绝对延时是指整个任务周期的延时,是将整个任务周期作为一个整体。
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );
pxPreviousWakeTime | 指向一个变量的指针,该变量 用于保存任务最后一次解除阻塞的时间。 该变量在第一次使用前 必须用当前时间进行初始化(见下方示例)。 在这之后,该变量 会在 vTaskDelayUntil() 中自动更新。 |
xTimeIncrement | 周期时间段。 该任务将在 (*pxPreviousWakeTime + xTimeIncrement)时间解除阻塞。 配合相同的 xTimeIncrement 参数值 调用 vTaskDelayUntil 将导致任务 以固定的间隔期执行。 |
//示例用法:
// Perform an action every 10 ticks.
void vTaskFunction( void * pvParameters )
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil( &xLastWakeTime, xFrequency );
// Perform action here.
}
}
队列
用于任务之间 中断之间 任务与中断之间的消息(数值)传递 相较于全局变量不会出现数据出错,因为写队列读队列是在临界区进行操作的
基于队列可以实现很多功能 如队列集 互斥信号量 技术型信号量 二值信号量 递归互斥信号量
队列创建
队列支持先进先出和后进后出,可以设置阻塞时间,在设定时间内等待写或读完成,或者死等。
入队堵塞:队列已满 该任务挂载到等待写入事件xTaskWaittingToSend()
出队堵塞:队列已控 该任务挂载到等待读取事件xTaskWaittingToReceive()
多个任务出现入队阻塞时
队列结构体
内部实现
静态创建
区别在于动态创建无需手动创建内存
QueueHandle_t xQueueCreateStatic( UBaseType_t uxQueueLength, UBaseType_t uxItemSize, uint8_t *pucQueueStorageBuffer, StaticQueue_t *pxQueueBuffer );
参数:
uxQueueLength | 队列可同时容纳的最大项目数 。 |
uxItemSize | 存储队列中的每个数据项所需的大小(以字节为单位)。 数据项按副本排队,而不是按引用排队, 因此该值为每个排队项目将被复制的字节数。队列中每个数据项 的大小必须相同。 |
pucQueueStorageBuffer | 如果 uxItemSize 非零,则 pucQueueStorageBuffer 必须 指向一个至少足够大的 uint8_t 数组, 以容纳队列中可以同时存在的最大项数, 即 (uxQueueLength * uxItemSize) 个字节。 如果 uxItemSize 为零,则 pucQueueStorageBuffer 可以为 Null。 |
pxQueueBuffer | 必须指向 StaticQueue_t 类型的变量, 该变量将用于保存队列的数据结构体。 |
返回:
如果队列创建成功,则返回所创建队列的句柄 则返回已创建队列的句柄。 如果 pxQueueBuffer 为 NULL,则返回 NULL。
动态创建
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数:
uxQueueLength | 队列可同时容纳的最大项目数 。 |
uxItemSize | 存储队列中的每个数据项所需的大小(以字节为单位)。 数据项按副本排队,而不是按引用排队, 因此该值为每个排队项目将被复制的字节数。队列中每个数据项 必须大小相同。 |
返回:
如果队列创建成功,则返回所创建队列的句柄 。 如果创建队列所需的内存无法 分配 ,则返回 NULL。
写队列 xQueueSend()
内部实现
判断队列是否已满
读队列 xQueueReceive()
xQueueReceive()读完会移除已读信息
xQueuePeek()读完不会移除已读信息
内部实现
进入临界区后需要判断队列是否为空 以及 调度器是否被挂起
信号量
解决同步问题,实现对共享资源的访问
本质就是队列长度为1的队列 队列只有空和满两种情况
信号量的计数值最大值为1时 就是二值信号量 不是1时 是计数型信号量
二值信号量
函数的详细说明可查看官方文档https://www.freertos.org/zh-cn-cmn-s/a00113.html
计数型信号量
用于事件计数和资源管理
创建信号量
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
参数:
uxMaxCount | 可以达到的最大计数值。 当信号量达到此值时,它不能再被“给定”。 |
uxInitialCount | 创建信号量时分配给信号量的计数值。 |
获取信号的信号量值
UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
参数:
xSemaphore | 正在查询的信号量的句柄。 |
返回:
如果信号量是计数信号量,则返回信号量的当前计数值 。 如果信号量是二进制信号量, 则当信号量可用时,返回 1,当信号量不可用时, 返回 0。
获取信号量(可设置获取时间)
xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
参数:
xSemaphore | 正在获取的信号量的句柄——在创建信号量时获得。 |
xTicksToWait | 等待信号量变为可用的时间(以滴答为单位)。宏 portTICK_PERIOD_MS 可用于将其转换为实时。可以用一个为零的阻塞时间来轮询信号量。 如果 INCLUDE_vTaskSuspend 设置为 “1” ,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时)。 |
返回:
如果获得信号量,则返回 pdTRUE;如果 xTicksToWait 过期,信号量不可用,则返回 pdFALSE。
优先级翻转
高优先级慢执行 低优先级先执行
由于信号量被低优先级获取并且不释放就会出现优先级翻转(高优先级获取不到信号量进入阻塞)
可以使用互斥信号量解决
互斥信号量
就是一个具有优先级继承的信号量
信号量的释放和获取与二值信号量一样 xSemaphoreGive xSemaphoreTake
优先级继承
如果低优先级获取到信号量 高优先级会阻塞 然后将低优先级任务提高到与自己同等优先级水平
这样高优先级任务的阻塞时间等于低优先级任务获得信号量的时间
不支持中断调用,因为互斥信号量有任务优先级继承 中断不是任务 没有任务优先级 而且中断服务函数不能因为等待互斥信号量而阻塞(因为互斥信号量要设置阻塞时间,而二值信号量不需要)
创建互斥信号量并且主动释放一次信号量
SemaphoreHandle_t xSemaphoreCreateMutex( void )
如果已成功创建互斥锁类型信号量,则返回创建的 互斥锁的句柄。 否则返回 NULL。
其他函数与二值信号量一致
递归互斥量
xSemaphoreCreateMutex()和 xSemaphoreCreateMutexStatic()用于创建非递归互斥锁。xSemaphoreCreateRecursiveMutex()用于创建递归互斥锁。
非递归互斥锁只能被一个任务 获取一次, 如果同一个任务想再次获取则会失败, 因为当任务第一次释放互斥锁时,互斥锁就一直 处于释放状态。
与非递归互斥锁相反,递归互斥锁可以被同一个任务获取很多次, 获取多少次就需要释放多少次, 此时才会返回递归互斥锁。
递归互斥锁采用优先级继承算法
对列集
一个队列之间传递的消息为一种类型
一个队列之间传递的消息为多种类型
往对列集添加和移除队列时,队列里面不能有消息
事件标志组
事件标志位:用一个位标志事件是否发生
事件标志组:标志位的集合 就是一个整数 变量类型为EventBits_t(要么16位要么32位数据)
任何任务和中断都可以读写这些位
与队列 信号量区别
队列信号量只会唤醒一个任务
事件标志组会唤醒所有符合的任务 相当于“广播”
函数
等待事件标志位
可以等待一位 也 可以等待多位
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
同步标志位
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );
任务通知
队列 信号量 事件标志组 也可以用来任务通知 但是占用内存大 需要另外创建结构体
任务通知就是TCB(任务控制块)的结构体成员变量ulNotifiedValue
占用内存小 效率高 但是无法给中断并且进行广播 发送受阻不会阻塞 接收可以发生阻塞
通知值更新方式
任务通知状态
函数
发送通知
可以用于任务和中断中
发送通知都是基于这个函数实现的
接收通知
只能用于任务中
接受通知都是基于这个函数实现的
软件定时器
硬件定时器:芯片自带的定时器模块 精度很高 达到定时时间后会自动触发中断 数量有限
软件定时器:具有定时功能的软件 只要内存够多 可以有无限个
软件定时器的超时回调函数用软件定时器任务调用 在回调函数中不能调用会导致任务阻塞的API
软件定时器任务作用
软件定时器优先级最大 保证定时时间一到立马执行
新创建的软件定时器处于休眠状态 未运行
单次定时器
周期定时器
软件定时器有两个定时器列表 一个是当前定时器列表 一个是溢出定时器列表
还有一个队列用来传递消息 控制软件定时器的开启 停止 复位等等
软件定时器ID用于多个软件定时器使用同一个回调函数
函数
定时器创建
当发送命令队列或者定时器超时 会唤醒软件定时器任务(刚创建时处于休眠状态)
Tickless低功耗模式
低功耗模式本质就是通过调用WFI指令进入睡眠模式
低功耗模式的实现就是让空闲任务执行期间进入低功耗模式,其他任务下退出低功耗模式
需要将滴答定时器的中断周期修改为低功耗运行时间 退出低功耗后 补上系统时钟节拍数
使用方法
配置
定义进入低功耗函数操作
通过调用configPRE_SLEEP_RPOCESSING()
定义退出低功耗函数操作
通过调用configPost_Sleep_RPOCESSING()
内存管理
C库 malloc() free()内存管理的缺点
FreeRTOS 内存管理算法
heap_1
heap_2
使用最适应算法 支持释放内存 不能将相邻空闲内存合成一个大的空闲内存 会产生内存碎片
适用于创建的任务堆栈都相同 才不会出现碎片化
heap_3
直接调用C库的malloc()和free()
heap_4
使用首次适应算法 能够实现内存申请和释放 将空闲和相邻内存合并 减少碎片化现象
适用于频繁申请 删除内存
heap_5
在 heap_4算法的基础上实现的 增加了管理多个非连续内存区域的能力
需要用户手动定义内存堆 即内存的起始地址和大小
适用于嵌入式系统中 内存地址不连续的场景
函数
申请内存的单位是字节
由于8字节对齐 以及 内存块需要占用内存 因此实际占用的内存比申请的值大
申请内存之后要记得释放 否则可能出现内存泄漏
内存块结构跟链表结构相似 一个成员变量表示当前内存大小 另一个成员变量指向下一个内存块
内存申请会生成对应的内存块 并且将内存块移除内存空闲列表
内存释放将内存块放到内存空闲列表
在一段内存没有被释放之前不要再调用pvPortMalloc()为其再分配内存 否则会出现内存泄漏