0.基础知识
RTOS :Real Time Operating System 实时操作系统
实时:任务或者说实现一个功能的线程必须在给定时间内完成
几个重要的概念
- 时间片时间片是实现时间片轮转调度算法的核心机制 就是说一个任务的执行只有固定的时间
- 分配时间片操作系统会给每个任务分配固定的时间片
- 任务调度器调度器决定了什么时候执行什么任务.FreeRTOS一般采用固定优先级的抢占式调度策略,即高优先级的可以抢占低优先级
- 任务执行当一个任务被调度器选中的时候,就给在分配给它的时间片内执行
如果当前任务在时间片还没有结束的时候就执行完成了 剩下的时间系统会切换至下一个任务直到当前时间片完成 然后操作系统再切换任务到下一个任务执行
- 上下文切换(核心)在任务切换的时候 操作系统要保存当前任务的状态(如寄存器的值,程序计数器等) 并且加载下一个任务的状态
- 时间片轮转调度
- 仅当多个任务处于相同优先级时,时间片轮转调度才会生效
- 高优先级任务会直接抢占低优先级任务(无论时间片是否用完)
- 在 FreeRTOS 中,一个时间片(Time Slice)的具体时长取决于系统节拍(SysTick)的频率配置,即
configTICK_RATE_HZ
任务状态
一个任务有四种状态: 运行态(X) 就绪态(R) 挂起态(S) 阻塞态(B)
其中 除了运行态,其他三种状态都有对应的任务状态列表:
就绪列表:通过一个数组pxReadyTasksLists[x] 实现(x表示任务的优先级) 每一个数组下面又挂着一个双向链表,类比哈希表
阻塞列表:由两个双向链表构成:pxDelayedTaskList和pxOverflowDelayedTaskList
pxDelayedTaskList:用于存储当前周期内需要等待的任务pxOverflowDelayedTaskList:用于处理时间溢出的情况,确保长时间等待的任务能够正确处理
挂起列表:挂起列表是一个双向链表,称为xSuspendedTaskList,调用vTaskSuspend()函数可以将任务添加到这个列表中,而调用vTaskResume()函数则可以将其从列表中移除并转换成就绪态
什么时候进行上下文切换?
在需要切换任务的时候进行上下文切换,真正执行上下文切换是在PendSV(可挂起系统服务)的ISR中处理的。使用PendSV是因为其可以手动触发,并且可以在其他更高中断优先级的ISR(中断服务程序)中来进行设置,比较灵活。
为什么要使用PendSV中断切换呢?
- 手动触发:通过将中断控制和状态寄存器(ICSR)的bit28位置1来手动触发。
- 最低优先级:FreeRTOS通常将其设置为最低优先级,确保只有在没有其他更高优先级的中断处理时才会执行任务切换,避免影响正常ISR的执行。
既然PendSV优先级最低,那会不会在PendSV的ISR中被其他中断打断呢?
在PendSV的中断服务程序(ISR)开头,FreeRTOS会先用汇编指令 CPSID I(关闭全局中断),确保切换过程不会被其他中断干扰。在切换结束、准备返回新任务前,才会用 CPSIE I 重新打开中断
PendSV的触发条件是什么?
- RTOS滴答中断
- 任务结束
- 强制切换:某些情况下,任务可能需要主动调用任务切换函数(如
vTaskSwitchContext()),这通常发生在任务释放资源、信号量等之后,确保系统能够及时响应新的就绪态任务。
FreeRTOS的滴答
在每个滴答中断(RTOS滴答中断)期间,内核需要进行以下操作:
- 更新全局滴答计数
- 检查任务状态
- 休眠任务有没有到时间
- 阻塞任务有没有超出最大等待时间
- 优先级检查与上下文切换
空闲任务
在任务调度器启动的时候,系统会自动创建一个空闲任务
- 确保系统始终有任务可以执行 避免CPU进行空转或进入未知状态
- 以最低优先级创建 可以随时被打断
- 可以释放被删除任务的内存
1.动态创建任务xTaskCreate
创建一项新任务并将其添加到准备运行的任务列表中。configSUPPORT_DYNAMIC_ALLOCATION必须在 FreeRTOSConfig.h 中设置为 1,或处于未定义状态(默认为 1), 才可使用此 RTOS API 函数。
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE uxStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
);
// 参数:
pxTaskCode: 任务函数
pcName: 任务名字
usStackDepth: 堆栈大小
pvParameters: 任务参数
uxPriority: 任务优先级
pxCreatedTask: 任务句柄
// 返回:
如果任务创建成功,则返回 pdPASS,
否则返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY。要分配用作任务堆栈的字数(不是字节数!)。例如,如果 堆栈宽度为 16 位,uxStackDepth 为 100,则将分配 200 字节用作任务 堆栈。再举一例,如果堆栈宽度为 32 位,uxStackDepth 为 400, 则将分配 1600 字节用作任务堆栈。
2.静态创建任务xTaskCreateStatic
创建一项新任务并将其添加到准备运行的任务列表中。configSUPPORT_STATIC_ALLOCATION 必须在
FreeRTOSConfig.h中设置为 1,才可使用此 RTOS API 函数。
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 );
// 参数:
pxTaskCode:任务函数
pcName:任务名字
ulStackDepth:任务栈的大小
pvParameters:任务参数
uxPriority:任务优先级
puxStackBuffer:必须指向至少包含ulStackDepth个
索引的StackType_t数组
pxTaskBuffer:必须指向StaticTask_t类型的变量。
该变量将用于保存新任务的数据 结构体 (TCB),
因此必须持久存在(不能在函数的堆栈上声明)。
// 返回值:
xTaskCreateStatic返回值为任务句柄FreeRTOS要求静态分配时为空闲任务和软件定时器任务指定资源,是因为它们是内核自动创建的核心任务,且在静态模式下系统无法动态分配内存。通过用户显式提供内存,既能保证关键任务的可靠性,又能满足对内存布局的严格管控需求
// 5. 静态创建任务方式,需要手动指定2个特殊任务的资源
// 5.1 空闲任务的配置
#define IDLE_TASK_STACK 128
StackType_t idle_task_stack[IDLE_TASK_STACK];
StaticTask_t idle_task_tcb;
// 5.2 软件定时器任务的配置
StackType_t timer_task_stack[configTIMER_TASK_STACK_DEPTH];
StaticTask_t timer_task_tcb;
// 5.3 分配空闲任务的资源
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
StackType_t **ppxIdleTaskStackBuffer,
uint32_t *pulIdleTaskStackSize)
{
*ppxIdleTaskTCBBuffer = &idle_task_tcb;
*ppxIdleTaskStackBuffer = idle_task_stack;
*pulIdleTaskStackSize = IDLE_TASK_STACK;
}
// 5.4 分配软件定时器任务的资源
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
StackType_t **ppxTimerTaskStackBuffer,
uint32_t *pulTimerTaskStackSize)
{
*ppxTimerTaskStackBuffer = timer_task_stack;
*ppxTimerTaskTCBBuffer = &timer_task_tcb;
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}3.进入临界区
通过调用 taskENTER_CRITICAL() 进入临界区,随后 通过调用 taskEXIT_CRITICAL() 退出临界区。
临界区(Critical Section)是一段不可被中断的代码,用于保护共享资源(如全局变量、硬件寄存器等)在多任务或中断环境下的原子性操作。
task. h
void taskENTER_CRITICAL( void );
void taskEXIT_CRITICAL( void );
/*
作用:
通常用于启动任务
由于启动任务优先级最低 所以在启动任务创建其他任务的时候
创建的任务的优先级都大于启动任务 所以可能会导致启动任务被抢占 从而导致异常
这时候需要先进入临界区 该函数会内部会阻止其他任务抢占启动任务
原理:
内部禁用了中断 这样PendSV就不会执行 就不会造成当前启动任务被抢占
该函数内部有一个全局变量 uxCriticalNesting 每次调用都会++
退出该函数时会以uxCriticalNesting是否为0判断
因此该函数应该成对出现 调用几次就要退出几次
*/如果所使用的 FreeRTOS 移植使用了 configMAX_SYSCALL_INTERRUPT_PRIORITY 内核配置常量,则调用 taskENTER_CRITICAL() 会禁用优先级等于或低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置的优先级的中断, 并启用所有高于此优先级的中断。
抢占式上下文切换仅在中断内发生, 在中断被禁用时不会发生。 因此,调用 taskENTER_CRITICAL() 的任务一定会保持在运行状态, 直到退出临界区,除非任务明确试图阻塞或让出 (任务不应在临界区内部进行该操作)。
taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 的调用采用嵌套结构。因此,只有在每次调用 taskENTER_CRITICAL() 后执行相应的 taskEXIT_CRITICAL() 调用时, 才会退出临界区。
临界区必须尽量简短,否则会对中断响应时间产生不利影响。 每次调用 taskENTER_CRITICAL() 时,都必须有对应的 taskEXIT_CRITICAL() 调用。
不得从临界区调用 FreeRTOS API 函数。
不得从中断服务程序 (ISR) 调用 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL()
4.临界区底层原理
在部分架构(如 ARM Cortex-M)中,taskENTER_CRITICAL() 会通过设置 CPU 的中断屏蔽寄存器BASEPRI来屏蔽特定优先级以下的中断,而不是完全关闭所有中断。
例如,若配置 configMAX_SYSCALL_INTERRUPT_PRIORITY 为 5,则优先级低于或等于 5 的中断会被屏蔽,但更高优先级的中断(如实时性要求极高的硬件中断)仍可响应。

高四位非零:定义异常处理的基础优先级。处理器不会处理任何优先级值大于或等于 BASEPRI 的异常。
5.任务的挂起与恢复
vTaskSuspend()挂起任务, 类似暂停,可恢复
vTaskResume()恢复被挂起的任务
xTaskResumeFromISR()在中断中恢复被挂起的任务
/*
INCLUDE_vTaskSuspend 必须定义为 1,才可使用此函数。
挂起任意任务。无论任务优先级如何,任务被挂起后将永远无法获取任何微控制器处理时间。
对vTaskSuspend的调用不会累积次数,例如:若在同一任务上调用 vTaskSuspend()两次将仍然仅需调用一 次vTaskResume()即可准备完毕挂起的任务。
参数: xTaskToSuspend
被挂起的任务句柄。传递空句柄将导致调用任务被挂起。
*/
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
/*
INCLUDE_vTaskSuspend必须定义为 1,才可使用此函数。
恢复已挂起的任务。
因一次或多次调用vTaskSuspend()而挂起的任务可通过 单次调用vTaskResume()恢复运行。
参数: xTaskToResume
待恢复任务的句柄。
*/
void vTaskSuspend( TaskHandle_t xTaskToSuspend );6.调度器的挂起与恢复
挂起调度器。挂起调度器会阻止上下文切换, 但会让中断处于启用状态。如果调度器被挂起时,中断请求切换上下文, 那么请求将会被挂起。而且只有在调度器恢复(取消挂起)时才会执行。
void vTaskSuspendAll( void );
/*
对 vTaskSuspendAll()的调用可以嵌套。调用 xTaskResumeAll()的次数 必须与先前调用 vTaskSuspendAll()的次数相同, 然后调度器将取消挂起状态并重新进入活动状态。
xTaskResumeAll()只能在正在执行的任务中调用, 因此不能在调度器处于初始化状态时(启动调度器之前)调用。
不得在调度器挂起时调用其他 FreeRTOS API 函数。
调度器挂起时,禁止调用可能切换上下文的 API 函数
*/恢复通过调用 vTaskSuspendAll() 挂起的调度器。
xTaskResumeAll() 仅恢复调度器,不会恢复 之前通过调用 vTaskSuspend() 而挂起的任务。
返回:如果恢复调度器导致了上下文切换,则返回 pdTRUE,否则返回 pdFALSE。
BaseType_t xTaskResumeAll( void );7.中断管理
FreeRTOS利用BASEPRI寄存器实现中断管理,屏蔽优先级低于某一个阈值的中断。
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
// 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. */
/* *INDENT-OFF* */
msr basepri, ulNewBASEPRI
dsb
isb
/* *INDENT-ON* */
}
}该函数先把configMAX_SYSCALL_INTERRUPT_PRIORITY赋值给ulNewBASEPRI,然后通过msr指令将ulNewBASEPRI的值赋给BASEPRI寄存器。
// vPortSetBASEPRI也是一个内联汇编函数,如下
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. */
/* *INDENT-OFF* */
msr basepri, ulBASEPRI
/* *INDENT-ON* */
}
}该函数将ulBASEPRI赋值给BASEPRI寄存器。因此,portENABLE_INTERRUPTS()宏的逻辑是将0赋给BASERPI,不再屏蔽任何中断。
8.任务相关API函数
| 函数 | 描述 |
|---|---|
| uxTaskPriorityGet() | 获取任务优先级 |
| vTaskPrioritySet() | 设置任务优先级 |
| uxTaskGetNumberOfTasks() | 获取系统中任务的数量 |
| uxTaskGetSystemState() | 获取所有任务状态信息 |
| vTaskGetInfo() | 获取指定单个的任务信息 |
| xTaskGetCurrentTaskHandle() | 获取当前任务的任务句柄 |
| xTaskGetHandle() | 根据任务名获取该任务的任务句柄 |
| uxTaskGetStackHighWaterMark() | 获取任务的任务栈历史剩余最小值 |
| eTaskGetState() | 获取任务状态 |
| vTaskList() | 以“表格”形式获取所有任务的信息 |
| vTaskGetRunTimeStats() | 获取任务的运行时间 |
9.获取任务的运行时间
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 倍
/* 运行时间和任务状态统计相关定义 */
#define configGENERATE_RUN_TIME_STATS 1 /* 1: 使能任务运行时间统计功能, 默认: 0 */
#if configGENERATE_RUN_TIME_STATS
extern volatile unsigned long ulHighFrequencyTimerTicks;
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL )
#define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
#endif
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 portCONFIGURE_TIMER_FOR_RUNTIME_STATE():用于初始化用于配置任务运行时间统计的时基定时器。它的时间精度需要比 tick 中断具有更高的精度,建议10到100倍。portGET_RUN_TIME_COUNTER_VALUE():返回该定时器的计数值,即当前已运行的时间。
10.相对延迟和绝对延迟
// 相对延迟
void vTaskDelay( const TickType_t xTicksToDelay );
// 绝对延迟
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_t xTimeIncrement );
/*
参数:
pxPreviousWakeTime:指向一个变量的指针,该变量用于保存任务最后一次解除阻塞的 时间。该变量在首次使用前 必须用当前时间初始化
xTimeIncrement: 周期时间段。
该任务将在 (*pxPreviousWakeTime + xTimeIncrement)时间解除 阻塞。使用相同的xTimeIncrement参数值调用 vTaskDelayUntil将 导致任务以固定的间隔期执行
*/
// 相对延迟:执行到该函数处再延迟额外的时间
// 绝对延迟:整个任务一共就那么多时间11.消息队列
创建新队列并返回一个可以引用该队列的句柄
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );uxQueueLength:队列一次可存储的最大项目数。
uxItemSize:存储队列中每个项目所需的大小(以字节为单位)。
12.信号量
二值信号量
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
| xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
| xSemaphoreGive() | 释放信号量 |
| xSemaphoreGiveFromISR() | 在中断中释放信号量 |
| xSemaphoreTake() | 获取信号量 |
| xSemaphoreTakeFromISR() | 在中断中获取信号量 |
计数型信号量
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
| xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
| uxSemaphoreGetCount() | 获取信号量的计数值 |
优先级翻转
典型的优先级翻转场景如下:
- 任务A(高优先级):拥有高优先级,需要访问共享资源,比如一个关键数据结构。
- 任务B(低优先级):拥有低优先级,目前正在访问该共享资源。
- 任务C(中优先级):位于任务A和任务B之间,具有介于两者之间的优先级。
互斥信号量
| 函数 | 描述 |
|---|---|
| xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
| xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
13.队列集
| 函数 | 描述 |
|---|---|
| xQueueCreateSet() | 创建队列集 |
| xQueueAddToSet() | 队列添加到队列集中 |
| xQueueRemoveFromSet() | 从队列集中移除队列 |
| xQueueSelectFromSet() | 获取队列集中有有效消息的队列 |
| xQueueSelectFromSetFromISR() | 在中断中获取队列集中有有效消息的队列 |
14.事件标志组
| 函数 | 描述 |
|---|---|
| xEventGroupCreate() | 使用动态方式创建事件标志组 |
| xEventGroupCreateStatic() | 使用静态方式创建事件标志组 |
| xEventGroupClearBits() | 清零事件标志位 |
| xEventGroupClearBitsFromISR() | 在中断中清零事件标志位 |
| xEventGroupSetBits() | 设置事件标志位 |
| xEventGroupSetBitsFromISR() | 在中断中设置事件标志位 |
| xEventGroupWaitBits() | 等待事件标志位 |
| xEventGroupSync() | 设置事件标志位,并等待事件标志位 |
EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
/*
参数:
句柄
等待哪些比特位(掩码)
满足等待的条件后,是否要将对应的bit清0
所有bit位都满足还是其中有一位置1即可
阻塞等待
*/15.任务通知
任务通知是 FreeRTOS 中一种用于任务间通信的机制,它允许一个任务向其他任务发送简单的通知或信号,任务通知通常用于替代二值信号量或事件标志组,提供了更轻量级的任务间通信方式。
任务通知无需使用中间对象,可直接通信
每个 RTOS 任务都有一个任务通知组,每条通知均独立运行,都有“挂起”或“非挂起”的通知状态,以及一个 32 位通知值。常量 configTASK_NOTIFICATION_ARRAY_ENTRIES 可设置任务通知组中的索引数量。
| 函数 | 描述 |
|---|---|
| xTaskNotify() | 发送通知,带有通知值 |
| xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
| xTaskNotifyGive() | 发送通知,不带通知值 |
| xTaskNotifyFromISR() | 在中断中发送任务通知 |
| xTaskNotifyAndQueryFromISR() | |
| vTaskNotifyGiveFromISR() | |
| ulTaskNotifyTake() | 获取任务通知,可选退出函数时对通知置清零或减1 |
| xTaskNotifyWait() | 获取任务通知,可获取通知值和清除通知值的指定位 |
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify );
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction );
BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction );
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn,
BaseType_t xClearCountOnExit,
TickType_t xTicksToWait );
/*
参数:
xClearCountOnExit: pdTRUE:接收完是清0 pdFALSE:减1
xTicksToWait: 阻塞时间
返回值: 被递减或清除之前的任务通知值的值
*/
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
/*
xTaskNotifyWait() 和 xTaskNotifyWaitIndexed() 是等效宏,唯一区别在于 xTaskNotifyWaitIndexed() 可以操作数组中的任何任务通知, 而 xTaskNotifyWait() 总是操作数组中索引为 0 的任务通知。
*/16.软件定时器
FreeRTOS 中的软件定时器是一种轻量级的时间管理工具,用于在任务中创建和管理定时器。软件定时器是基于 FreeRTOS 内核提供的时间管理功能实现的,允许开发者创建、启动、停止、删除和管理定时器,从而实现在任务中对时间的灵活控制。
软件定时器与硬件定时器的主要区别如下:
| 软件定时器 | 硬件定时器 |
|---|---|
| FreeRTOS提供的功能来模拟定时器,依赖系统的任务调度器来进行计时和任务调度 | 由芯片或微控制器提供,独立于 CPU,可以在后台运行,不受任务调度器的影响 |
| 精度和分辨率可能受到任务调度的影响 | 具有更高的精度和分辨率 |
| 不需要额外的硬件资源,但可能会增加系统的负载 | 占用硬件资源,不会增加 CPU 的负载 |
软件定时器相关函数如下:
| 函数 | 描述 |
|---|---|
| xTimerCreate() | 动态方式创建软件定时器 |
| xTimerCreateStatic() | 静态方式创建软件定时器 |
| xTimerStart() | 开启软件定时器定时 |
| xTimerStartFromISR() | 在中断中开启软件定时器定时 |
| xTimerStop() | 停止软件定时器定时 |
| xTimerStopFromISR() | 在中断中停止软件定时器定时 |
| xTimerReset() | 复位软件定时器定时 |
| xTimerResetFromISR() | 在中断中复位软件定时器定时 |
| xTimerChangePeriod() | 更改软件定时器的定时超时时间 |
| xTimerChangePeriodFromISR() | 在中断中更改定时超时时间 |
FreeRTOS 中的软件定时器有三种状态,分别是:
- 未创建(Uncreated):软件定时器被创建之前的状态。在这个状态下,定时器的数据结构已经被定义,但尚未通过 xTimerCreate() 函数创建。
- 已创建(Created):软件定时器已被成功创建,但尚未启动。在这个状态下,可以对定时器进行配置,如设置定时器的周期、回调函数等,但定时器并未开始计时。
- 已运行(Running):软件定时器已经被启动,正在运行中。在这个状态下,定时器会按照预定的周期定时触发超时事件,执行注册的回调函数。
在 FreeRTOS 中,软件定时器主要有两种类型:一次性定时器和周期性定时器。
- 一次性定时器(One-shot Timer): 这种定时器在触发一次超时后就会停止,不再执行。适用于只需在特定时间执行一次任务或动作的场景。
- 周期性定时器(Periodic Timer): 这种定时器会在每个超时周期都触发一次,循环执行。适用于需要在固定的时间间隔内重复执行任务或动作的场景。
17.Tickless低功耗模式
| 配置项 | 说明 |
|---|---|
| configUSE_TICKLESS_IDLE | 使能低功耗 Tickless 模式,默认0 |
| configEXPECTED_IDLE_TIME_BEFORE_SLEEP | 系统进入相应低功耗模式的最短时长,默认2 |
| configPRE_SLEEP_PROCESSING(x) | 在系统进入低功耗模式前执行的事务,比如关闭外设时钟 |
| configPOST_SLEEP_PROCESSING(x) | 系统退出低功耗模式后执行的事务,比如开启之前关闭的外设时钟 |
#define configUSE_TICKLESS_IDLE 1
#include "FreeRTOS_demo.h"
#define configPRE_SLEEP_PROCESSING( x ) PRE_SLEEP_PROCESSING()
#define configPOST_SLEEP_PROCESSING( x ) POST_SLEEP_PROCESSING()18.内存管理
内存管理相关函数如下:
| 函数 | 描述 |
|---|---|
| void * pvPortMalloc( size_t xWantedSize ); | 申请内存 |
| void vPortFree( void * pv ); | 释放内存 |
| size_t xPortGetFreeHeapSize( void ); | 获取当前空闲内存的大小 |
问题
// 将FreeRTOS移植到stm32上的时候,为什么要在systick中断里面加上下面这段代码
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}- FreeRTOS需要一个定期的时基(tick)来驱动任务调度。例如,当时间片用完时,调度器会进行任务切换;延时函数也是基于tick计数实现的。
- FreeRTOS提供了一个名为
xPortSysTickHandler的函数(在port.c文件中),这个函数就是SysTick中断的服务函数,用于处理与操作系统相关的时基更新和任务调度。
- FreeRTOS需在SysTick中断中执行关键调度操作:
- 更新系统时钟计数器(
xTickCount++) - 检查任务阻塞超时(如
vTaskDelay()) - 触发任务切换(若时间片到期)
- 更新系统时钟计数器(
xPortSysTickHandler()的作用
该函数是FreeRTOS移植层的核心,由官方提供(位于port.c)。它包含:
时基更新与任务调度逻辑
可移植的汇编代码(如PendSV触发)
优先级处理等硬件相关操作
条件判断的必要性
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
xPortSysTickHandler();
}- 防止调度器启动前调用:在
vTaskStartScheduler()执行前调用FreeRTOS代码会导致崩溃。 - 安全退出机制:系统终止时(如调用
vTaskEndScheduler())需停止响应Tick。