FreeRTOS

FreeRTOS 摘要-快速上手

  • Fang XS.
  • [email protected]
  • 如果有错误,希望被指出,学习技术的路难免会磕磕绊绊

摘要

  • 关于轮询系统,前后台系统,实时操作系统
  • 关于FreeRTOS时钟管理
  • 关于FreeRTOS任务管理
  • 关于FreeRTOS内存管理
  • 关于FreeRTOS通信与同步
  • 关于FreeRTOS原子操作
  • 简单的main.c模板

前言

关于嵌入式常用的轮询系统,前后台系统,实时操作系统:

  1. 传统单任务循环
    • 轮询系统是在main函数里,先进行初始化,初始化完毕进入循环且不退出。
    • 轮询系统中,对于外部事件的发生,是轮询响应,轮询处理。
    • 前后台系统中,前台是中断,后台是大循环,前后台系统是在轮询系统的基础上加入中断程序。
    • 前后台系统中,对于外部事件的发生,是中断实时响应,轮询处理。
  2. 多任务RTOS
    • RTOS系统中,将程序划分成不同的任务,通过特定的调度策略调度不同的任务运行。
    • RTOS系统中,对于外部事件的发生,实时响应,实时处理。
  3. 嵌入式系统的实时性
    • 实时性是衡量系统对外部事件处理,响应的重要标准。
    • 非实时
      • 在对实时性没有硬性要求的场合,会采用非实时操作系统。
      • 例如:玩具。
    • 软实时
      • 系统超时响应,超时处理,不会产生严重后果,会拖慢系统速度影响性能。
      • 例如,网页的更新,网速慢,只是影响用户体验,并不会产生严重后果。
      • 典型的嵌入式软实时操作系统是Linux。
    • 硬实时
      • 系统必须在规定时间内响应和处理,否则会产生严重后果。
      • 例如,汽车的自动驾驶,飞机控制系统,火箭姿态调整。
      • 典型的硬实时操作系统是FreeRTOS等。
  • FreeRTOS官网。
  • FreeRTOS最小裁剪验证:只留实时内核,去掉软件定时器和IPC机制的情况下ROM约占2K,RAM依照任务及IPC通信机制等不同而不同。

FreeRTOS 时钟管理

  • 所有的RTOS都需要一个时钟,作为RTOS的心脏。
  • FreeRTOS与Cortex-M是绝配,Cortex-M内核自带一个SystemCoreClock
  • 在FreeRTOS中,配置时钟相关的宏定义:
    1. configCPU_CLOCK_HZ
      • 用于定义 CPU 的主频,单位 Hz。
    2. configTICK_RATE_HZ
      • 用于定义系统时钟节拍数,单位 Hz,一般取 1000Hz 即可。
      • 过高的的系统时钟节拍将使得 FreeRTOS 内核运行占用过多的时间,增加系统负荷。
    3. configUSE_16_BIT_TICK
      • 1 TickType_t 定义的就是 16 位无符号数,用于 8 位和 16 位架构的处理器。
      • 0 TickType_t 定义的就是 32 位无符号数,用于 32 位架构的处理器。
  • 时间管理
    • 相对延时:每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束。
    • 绝对延时:指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务,即以固定频率执行任务。
/* FreeRTOS内核自带的延时函数 */

/** vTaskDelay
 * @brief 相对延时。
 * @param xTicksToDelay 延时周期
 * @retval None.
*/
void vTaskDelay( const TickType_t xTicksToDelay );
/** vTaskDelayUntil
 * @brief 绝对延时。
 * @param pxPreviousWakeTime 上次唤醒的时间
 * @param xTimeIncrement 以这个参数的频率执行任务 单位Ticks
 * @retval None.
*/
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );

/* 内核自带宏 配合延时函数使用*/
/**
 * 传入需要延时的MS,转换为Ticks。
*/
pdMS_TO_TICKS( xTimeInMs );

/* 相对延时使用方法 */
static void LED_task( void * pvParameters )
{   
(void)pvParameters;
    for( ;; ) {
        printf("LED_task is running.");
        LED0=!LED0;
        vTaskDelay(pdMS_TO_TICKS(500)); //延时500ms
    }
} 
/* 绝对延时使用方法 */
static void LED_task( void * pvParameters )
{   
(void)pvParameters;
static portTickType xLastWakeTime;
const portTickType xFrequency = pdMS_TO_TICKS(500);

    // 使用当前时间初始化变量xLastWakeTime 
    xLastWakeTime = xTaskGetTickCount(); 
    for( ;; ) {
        printf("LED_task is running.");
        LED0=!LED0;
        vTaskDelayUntil( &xLastWakeTime,xFrequency ); // 周期阻塞500ms
    }
} 

FreeRTOS 任务管理

  • FreeRTOS中,每个任务都有自己独立的栈空间和任务函数局部变量。
  • FreeRTOS中,每个任务都有自己的堆栈大小,任务在创建时,确定的栈空间大小单位是(*4bit)。
  • FreeRTOS中,每个任务都有常用的4种状态,但是任意时刻只有一种状态。
  • FreeRTOS中,每个任务都有一个可通过API修改的优先级
  • FreeRTOS中,每个任务都有一个任务名加上结尾的\0不超过configMAX_TASK_NAME_LEN
  1. 任务状态
    • 就绪态
      • 创建成功的任务处于就绪态。
      • 就绪态的任务具备执行的资格,但是还没有被调度器调度运行,还处于任务就绪队列。
    • 运行态
      • 调度器使用一定的调度策略,选择就绪列表中的某个适合的任务,并运行任务。
      • 处于运行态的任务,就是正在使用CPU的任务。
      • 单核处理器中,在任意时刻,只有一个任务处于运行态。
    • 阻塞态
      • 当某个任务在等待某个事件,或者调用了阻塞API,这个任务就处于阻塞态。
      • 阻塞态的任务,在等待的某个事件来临之前,或者阻塞时间没到,不会运行。
    • 挂起态
      • 当某个任务调用了vTaskSuspend()API,可以挂起这个任务,或者其他任务。
      • 被挂起的任务,永远不会运行,直到别的任务调用vTaskResume()API恢复这个任务。
    • 删除
      任务被删除没有任务了。
  2. 任务优先级
    • 在FreeRTOS中,每个任务都有一个优先级。
    • FreeRTOSConfig.h中,通过配置configMAX_PRIORITIES宏可以配置FreeRTOS任务的优先级范围。
    • 任务优先级区间为[0 , configMAX_PRIORITIES-1]。
    • 在FreeRTOS中,0是最低优先级,configMAX_PRIORITIES-1是最高优先级。
    • 抢占式调度中,高优先级可以打断低优先级的任务获得运行权。
    • 任务创建完毕任务具备一个优先级,如果想修改可以调用vTaskPrioritySet()API接口。
    • FreeRTOS的最大优先级没有限制,但是优先级范围应该设置的合适,避免浪费资源。
    • 任务优先级设置
  3. 任务句柄
    • task.h中有定义typedef void * TaskHandle_t;
    • 任务创建函数,会返回这个任务的任务句柄,删除任务时可以传入需要删除的任务的任务句柄。
    • 任务句柄包含创建任务的所有状态,信息等。
    • 关于操作任务的API都会用到任务句柄。
  4. 任务调度策略
    • FreeRTOS支持的调度方式包括抢占式调度,时间片调度,合作式调度(也叫协程,已经弃用)。
    • 通过FreeRTOSConfig.h中的宏configUSE_PREEMPTION
      • 1使能抢占式调度器。
      • 0使能合作式调度器。
    • 通过FreeRTOSConfig.h中的宏configUSE_TIME_SLICING
      • 1使能时间片调度(默认使能)
    • 一般采用抢占式加时间片式调度。
    • 只使能抢占式调度
      • 对于同等优先级的任务:
        • 调度器选择一个任务运行,阻塞后运行另一个任务。
        • 调度器选择一个任务运行,任务主动释放CPU使用权,运行另一个任务。
      • 对于不同优先级任务:
        • 只要高优先级任务处于就绪态就抢占低优先级任务,高优先级任务不阻塞,就一直运行。
        • 高优先级任务阻塞,调度器选择小于高优先级任务的较高优先级任务运行。
    • 只使能时间片调度:
      • 对于同等优先级的任务:
        • 调度器选择一个任务运行一段时间,切换任务。
        • 调度器选择一个任务运行一段时间,运行过程中阻塞,切换任务运行。
        • 调度器选择一个任务运行一段时间,运行过程中主动释放CPU使用权,切换任务运行。
    • 合作式调度(也叫协程,已经弃用)
  5. 任务启动方式
    1. 在主函数中,创建所有任务,创建完毕启动调度器。
    2. 在主函数中,创建启动任务,创建完毕启动调度器。
      • 启动任务只用来创建其他任务或者信号量,队列等。
      • 启动任务中,进入临界区,创建其他任务,创建完毕,退出临界区,删除自身。
  6. 空闲任务 IDLE
    • 在任何时刻,RTOS都必须有一个任务运行,FreeRTOS使用了IDLE任务。
    • 在FreeRTOS中,IDLE是启动调度器时自动创建的。
    • 在FreeRTOS中,IDLE的作用就是查询是否有任务删除自身,有就释放该任务的TCB和栈。
    • IDLE的优先级是0,IDLE不会抢占其他任务,其他任务优先级只要比IDLE高就可以抢占IDLE。
    • FreeRTOSConfig.h中的宏configUSE_IDLE_HOOK配置为1使能空闲任务的钩子函数。
      • 钩子函数可以执行其他操作,由用户定义。钩子函数尽可能短,不调用阻塞。
    • FreeRTOSConfig.h中的宏configIDLE_SHOULD_YIELD
      • 作用:空闲任务主动让出CPU使用权
      • 需要 1.配置为抢占调度器 2.有IDLE同优先级的任务 这个宏才有效,
  7. 任务创建和删除函数
    只讨论动态创建
/** xTaskCreate
 * @brief Create a task dynamically.
 * @param pxTaskCode task code to be executed.
 * @param pcName the name of the task.
 * @param usStackDepth the stack size of the task.
 * @param uxPriority the priority of the task.
 * @param pxCreatedTask Used to pass back a handle by which the created task
 * can be referenced.
 * @retval pdPASS if the task was successfully created and added to a ready
 * list, otherwise an error code defined in the file projdefs.h
*/
BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
                        const char * const pcName,
                        const uint16_t usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask );

/** vTaskDelete
 * @brief The task being
 * deleted will be removed from all ready, blocked, suspended and event lists.
 * @param xTask The handle of the task.
 * @retval None.
*/
void vTaskDelete( TaskHandle_t xTask );
  1. 部分任务控制API
/* 不包含中断版本 */

/* 临界区API */
taskENTER_CRITICAL();
taskEXIT_CRITICAL();
/* 任务阻塞延时 */
void vTaskDelay( const TickType_t xTicksToDelay );
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );
/* 任务优先级 */
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );
/* 调度器相关 */
void vTaskSuspendAll( void );
BaseType_t xTaskResumeAll( void );

  1. 任务创建的参数确定
  • 任务函数pxTaskCode:即该任务的具体执行函数;
  • 任务名称pcName:作为Debug使用;
  • 任务堆栈usStackDepth:应大于任务的所有局部变量 + CPU寄存器 + 函数调用深度 + 预留;
  • 任务参数pvParameters:这个一般不使用;
  • 任务优先级uxPriority:参考上文《2. 任务优先级设置》;
  • 任务句柄pxCreatedTask :参考下文main.c模板中设置方式;

FreeRTOS 内存管理

FreeRTOS 内存管理方案有5个。

  1. heap_1.c
    • 只分配不回收,所需时间是确定的;
  2. heap_2.c
    • 分配且回收,不合并相邻内存,会产生内存碎片,适合申请释放相同大小内存的场合。
  3. heap_3.c
    • 封装了标准库mallocfree函数,通过暂时暂停FreeRTOS调度程序,让mallocfree函数具备线程保护,效率低。
  4. heap_4.c
    • 在方案2的基础上,合并相邻内存块,不会产生内存碎片,适合频繁申请释放场合。
  5. heap_5.c
    • 分配且释放,可以从多个独立的内存空间中分配内存。

FreeRTOS 通信与同步

FreeRTOS所有通信都是基于队列的
任务通知比较特殊,不需要创建直接使用,可以模拟其他同步或通信机制,也有局限性
1.队列

  • 使用队列需要包含queue.h添加queue.c
  • 没有RTOS的嵌入式系统,一般采用全局变量来通信。
  • 使用了RTOS全局变量的访问就涉及到资源管理的问题,任务间访问全局变量就不安全了。
  • 队列是一种FIFO的数据结构,在FreeRTOS中作为主要的通信机制。
  • 用来实现任务与任务,任务与中断,中断与任务的通信。
  • 队列中可以存储有限的、大小固定的数据项目。
  • 队列所能保存的最大数据项目数量叫做队列的长度。
  • 创建队列的时候会指定数据项目的大小和队列的长度。
  • 队列用来传递消息,所以也叫消息队列。
  • 对于较小数据可以直接发送进队列,较大数据可以传递指针进队列。
  1. 信号量
  • FreeRTOS中,信号量是由队列实现,使用信号量需要包含queue.h添加queue.c
  • FreeRTOS中,信号量分为二值信号量,计数型信号量,互斥信号量,递归互斥信号量。
  • 创建API函数是独立的,但是获取和释放API函数都是相同的;
  • 递归互斥信号量的创建、获取和释放API函数都是独立的。
  • 信号量用来实现任务间同步,实现临界资源访问,实现任务互斥。
  • 在FreeRTOS中,二值信号量和互斥信号量类型完全相同。
  • 二值型信号量用于同步而互斥型信号量用于资源保护。
  • 二值信号量
    • 只有01的信号量,只有2种状态
  • 计数型信号量
    • 计数型信号量用于多个任务共享使用某资源。
  • 互斥信号量
    • 互斥信号量是包含优先级继承机制的二进制信号量。
    • 任务间资源保护的重要手段。
    • 已经获取了互斥信号量的任务不能再次获取这个互斥信号量。
  • 递归互斥信号量
    • 递归互斥信号量只能用于任务中。
    • 递归互斥信号量是一种特殊的互斥信号量,已经获取了递归互斥信号量的任务可以再次获取这个递归互斥信号量(即可以嵌套使用),且次数不限。
    • 递归互斥信号量也有优先级继承的问题。一个任务获取了多少次递归互斥信号量就必须释放多少次。
  • 优先级翻转问题-使用二值信号量常见问题
    • 优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象。
    • 假设有3个任务,分别是A,B,C任务,优先级分别是H,M,L。
    • 当H优先级任务A,要访问资源时,该资源被L优先级任务C占用,C任务运行时,还没到释放资源的时候,优先级M的B任务抢占了L优先级的C任务,导致H优先级A任务由于等待资源被其他低优先级任务抢占而阻塞,系统的实时性的得不到保障。
    • 解决办法:
      • 优先级继承
        • 较好的解决方案是使用互斥信号量。如果高优先级任务在尝试获取当前由较低优先级任务持有的互斥信号量时阻塞, 则持有互斥信号量的任务的优先级会暂时提高到阻塞任务的优先级。 从而最大限度地减少已经发生的“优先级反转”现象。
        • 通过提升C任务优先级,让其他任务不能抢占C任务,保证了系统的实时性。
  1. 事件组
  • 事件组在FreeRTOS中应用广泛,FreeRTOS就是基于事件驱动的RTOS
  • 事件标志:用于指示事件是否发生。只能为布尔值10
  • 使用事件组需要包含"event_groups.h"
  • FreeRTOSConfig.h 中的configUSE_16_BIT_TICKS配置宏:
    • 1,则每个事件组包含8个可用事件位。
    • 0,则每个事件组包含24个可用事件位。
  • 事件组创建成功后,本身是一个对象,所有的任务都可以使用事件组。
  • 使用事件标志组可以让 RTOS 内核有效地管理任务。
  • 使用事件标志组,可以有效的防止多任务的访问冲突。
  • 使用事件标志组可以有效地解决中断服务程序和任务之间的同步问题。
  1. 任务通知
    每个任务都有一个32位的通知值,任务创建时,这个值被初始化为0。
    任务通知相当于直接向任务发送一个事件,接收到通知的任务可以解除因等待通知引起的阻塞状态。
    发送通知的同时,也可以可选的改变接收任务的通知值。
  2. API接口
    • 发送通知xTaskNotify( xTaskToNotify, ulValue, eAction )
    • 接收通知xTaskNotifyGive()
  3. 注意事项
    • FreeRTOSConfig.h中将宏configUSE_TASK_NOTIFICATIONS置为0来禁止这个功能。(默认开启)。
    • 禁止后每个任务节省8字节内存。
    • 局限性:任务通知用起来方便,速度快,但是一个任务只能给一个任务发,
    • 等待通知的任务可以因为等不到而阻塞,但是发送通知的任务不会因为发送不了阻塞。
    • 官网描述中:任务通知比信号量解除阻塞快45%。

FreeRTOS 中的原子操作

  • Embedded OS中,需要进行原子操作的场合(比如IIC通信)不能被打断的操作,可以使用以下3种方法
    1. 临界区
      • 在FreeRTOS中,临界区是关中断实现的。
      • 关中断,调度器就不能切换任务,就能保证原子操作的执行。
      • 临界区适合代码相对较短的场合。
      • 临界区可以嵌套使用。
      • FreeRTOS本身内核也有使用临界区,临界区尽可能短,尽可能少,否则将影响RTOS的实时性
    2. 挂起调度器
      • 在FreeRTOS中,挂起调度器有专用接口(x86架构)。
      • STM32中,使用void vTaskSuspendAll( void );接口挂起调度器。
      • STM32中,使用BaseType_t xTaskResumeAll( void );接口恢复调度器。
      • 与临界区相比,挂起调度器更适用于代码相对较长的场合,而且挂起调度器不关中断。
    3. 信号量互斥访问
      • 安全有效的方法。
      • 相比临界区,要创建互斥信号量,申请释放信号量,临界区2个API解决。

main.c模板

使用FreeRTOS的一般步骤:

  1. 包括头文件
  2. 创建任务句柄,任务函数
  3. 创建任务(方式有2种 参考任务管理)
  4. 启动调度器
/************************************************************************************
 * @file: main.c
 * @author: Fang XS.
 * @date: Create on 2021-05-22
 ***********************************************************************************/

/* Includes -----------------------------------------------------------------------*/
#include "freertos.h"
#include "task.h"
#include "printf.h"

#include "hardware.h"

/* variables ----------------------------------------------------------------------*/
static TaskHandle_t LEDTaskHandle_t = NULL ;
static TaskHandle_t DBGTaskHandle_t = NULL ;

/* Macros -------------------------------------------------------------------------*/
#define LEDTaskStackSize (512)
#define LEDTaskPriority  (8)

#define DBGTaskStackSize (512)
#define DBGTaskPriority  (5)

/* Exported functions -------------------------------------------------------------*/
static void LED_task(void* pvParameters);
static void DBG_task(void* pvParameters);

int main(void)
{
    /* System Clock Init */    
    SystemInit();
    /* User Hardware Init */ 
    Hardware_Init();
    /* variables Init */ 
    BaseType_t xReturn = pdPASS;
    /* Task Init */
    xReturn = xTaskCreate((TaskFunction_t )DBG_task,            /* 任务入口函数             */
                          (const char*    )"DBG_Task",          /* 任务名字                 */
                          (uint16_t       )DBGTaskStackSize,    /* 任务栈大小               */
                          (void*          )NULL,                /* 任务入口函数参数         */
                          (UBaseType_t    )DBGTaskPriority,     /* 任务的优先级             */
                          (TaskHandle_t*  )&DBGTaskHandle_t);   /* 任务控制块指针           */
    // if(pdPASS == xReturn){ }

    xReturn = xTaskCreate((TaskFunction_t )LED_task,            /* 任务入口函数             */
                          (const char*    )"LED_Task",          /* 任务名字                 */
                          (uint16_t       )LEDTaskStackSize,    /* 任务栈大小               */
                          (void*          )NULL,                /* 任务入口函数参数         */
                          (UBaseType_t    )LEDTaskPriority,     /* 任务的优先级             */
                          (TaskHandle_t*  )&LEDTaskHandle_t);   /* 任务控制块指针           */
    if(pdPASS == xReturn)
        vTaskStartScheduler();                                  /* 启动任务 开启调度        */
    else
        return -1;  

    while(1){ 
    	;/* 启动任务调度器不会运行到这里 */ 
	}
}

static void DBG_task( void * pvParameters )
{
(void)pvParameters;
    for( ;; ) { 
        printf("DBG_task is running.");
        vTaskDelay(500);
    }
}

static void LED_task( void * pvParameters )
{
(void)pvParameters;
    for( ;; ) {
        printf("LED_task is running.");
        LED0=!LED0;
        vTaskDelay(500);
    }
} 

/*********************************** END OF FILE ***********************************/

你可能感兴趣的:(FreeRTOS,c语言)