FreeRTOS

0.基础知识

RTOS :Real Time Operating System 实时操作系统

实时:任务或者说实现一个功能的线程必须在给定时间内完成

几个重要的概念
  • 时间片时间片是实现时间片轮转调度算法的核心机制 就是说一个任务的执行只有固定的时间
  • 分配时间片操作系统会给每个任务分配固定的时间片
  • 任务调度器调度器决定了什么时候执行什么任务.FreeRTOS一般采用固定优先级的抢占式调度策略,即高优先级的可以抢占低优先级
  • 任务执行当一个任务被调度器选中的时候,就给在分配给它的时间片内执行

如果当前任务在时间片还没有结束的时候就执行完成了 剩下的时间系统会切换至下一个任务直到当前时间片完成 然后操作系统再切换任务到下一个任务执行

  • 上下文切换(核心)在任务切换的时候 操作系统要保存当前任务的状态(如寄存器的值,程序计数器等) 并且加载下一个任务的状态
  • 时间片轮转调度
    • 仅当多个任务处于相同优先级时,时间片轮转调度才会生效
    • 高优先级任务会直接抢占低优先级任务(无论时间片是否用完)
    • 在 FreeRTOS 中,一个时间片(Time Slice)的具体时长取决于系统节拍(SysTick)的频率配置,即 configTICK_RATE_HZ
任务状态

​ 一个任务有四种状态: 运行态(X) 就绪态(R) 挂起态(S) 阻塞态(B)

其中 除了运行态,其他三种状态都有对应的任务状态列表:

就绪列表:通过一个数组pxReadyTasksLists[x] 实现(x表示任务的优先级) 每一个数组下面又挂着一个双向链表,类比哈希表

阻塞列表:由两个双向链表构成:pxDelayedTaskListpxOverflowDelayedTaskList

  • 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 的中断会被屏蔽,但更高优先级的中断(如实时性要求极高的硬件中断)仍可响应。

nullimg

高四位非零:定义异常处理的基础优先级。处理器不会处理任何优先级值大于或等于 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_FUNCTIONSconfigSUPPORT_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();
  }
  1. FreeRTOS需要一个定期的时基(tick)来驱动任务调度。例如,当时间片用完时,调度器会进行任务切换;延时函数也是基于tick计数实现的。
  2. FreeRTOS提供了一个名为xPortSysTickHandler的函数(在port.c文件中),这个函数就是SysTick中断的服务函数,用于处理与操作系统相关的时基更新和任务调度。
    • FreeRTOS需在SysTick中断中执行关键调度操作
      • 更新系统时钟计数器(xTickCount++
      • 检查任务阻塞超时(如vTaskDelay()
      • 触发任务切换(若时间片到期)

xPortSysTickHandler()的作用

​ 该函数是FreeRTOS移植层的核心,由官方提供(位于port.c)。它包含:

​ 时基更新与任务调度逻辑

​ 可移植的汇编代码(如PendSV触发)

​ 优先级处理等硬件相关操作

条件判断的必要性

if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
    xPortSysTickHandler();
}
  • 防止调度器启动前调用:在vTaskStartScheduler()执行前调用FreeRTOS代码会导致崩溃。
  • 安全退出机制:系统终止时(如调用vTaskEndScheduler())需停止响应Tick。

添加新评论