笔记:FreeRTOS 要点总结

一、基本配置

1.1 数据类型

FreeRTOS 使用的数据类型主要分为 stdint.h 文件中定义的和自己定义的两种
FreeRTOS 主要自定义了以下四种数据类型:
TickType_t----32 位无符号数( 32位MCU,配置configUSE_16_BIT_TICKS = 0)
BaseType_t----32 位有符号数(32位MCU)
UBaseType_t---32 位无符号数(BaseType_t类型无符号版本)
StackType_t----32 位变量(栈变量数据类型定义,32位MCU)

1.2 代码风格

变量

uint32_t 定义的变量都加上前缀 ul。u 代表 unsigned 无符号,l 代表 long 长整型。
uint16_t 定义的变量都加上前缀 us。u 代表 unsigned 无符号,s 代表 short 短整型。
uint8_t 定义的变量都加上前缀 uc。u 代表 unsigned 无符号,c 代表 char 字符型。
stdint.h 文件中未定义的变量类型,在定义变量时需要加上前缀 x,比如 BaseType_t 和
TickType_t 定义的变量。
stdint.h 文件中未定义的无符号变量类型,在定义变量时要加上前缀 u,比如 UBaseType_t 定义
的变量要加上前缀 ux。
size_t 定义的变量也要加上前缀 ux。
枚举变量会加上前缀 e。
指针变量会加上前缀 p,比如 uint16_t 定义的指针变量会加上前缀 pus。
根据 MISRA 代码规则,char 定义的变量只能用于 ASCII 字符,前缀使用 c。
根据 MISRA 代码规则,char *定义的指针变量只能用于 ASCII 字符串,前缀使用 pc。

函数

加上了 static 声明的函数,定义时要加上前缀 prv,这个是单词 private 的缩写。
带有返回值的函数,根据返回值的数据类型,加上相应的前缀,如果没有返回值,即 void 类型
,函数的前缀加上字母 v。
根据文件名,文件中相应的函数定义时也将文件名加到函数命名中,比如 tasks.c 文件中函数
vTaskDelete,函数中的 task 就是文件名中的 task。

宏定义

根据宏定义所在的文件,文件中的宏定义声明时也将文件名加到宏定义中,比如宏定义
configUSE_PREEMPTION 是定义在文件 FreeRTOSConfig.h 里面。宏定义中的 config 就是文
件名中的 config。另外注意,前缀要小写。
除了前缀,其余部分全部大写,同时用下划线分开。

排版和注释

缩进
Tab 制表符用于缩进,Tab 一次缩进 4 个字符空间。
注释
FreeRTOS 中注释不会超过 80 个字符宽度,除非对函数的参数进行注释时。源码中主要是采用/* */
的形式进行注释,不采用 C++中的双斜杠风格来注释。

1.3 关键配置

configCPU_CLOCK_HZ---配置MCU主频,单位Hz
configTICK_RATE_HZ---配置系统时钟节拍数,单位 Hz
configMAX_PRIORITIES---配置用户可用最大优先级数,0~configMAX_PRIORITIES-1
configTOTAL_HEAP_SIZE---配置堆大小
configUSE_TASK_NOTIFICATIONS---配置任务通知
configUSE_TIME_SLICING---配置时间片轮转调度
configLIBRARY_LOWEST_INTERRUPT_PRIORITY---受RTOS管理的最小中断优先级
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY---受RTOS管理的最大中断优先级

二、任务管理

2.1 任务栈

#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )

1,局部变量,函数调用时的现场保护和返回地址,函数的形参,进入中断函数前和中断嵌套等都需要栈空间,栈空间定义小了会造成系统崩溃
2,实际分配的栈大小可以在最小栈需求的基础上乘以一个安全系数,一般取 1.5-2
3,栈生长方向从高地址向低地址生长(M4 和 M3 是这种方式)

2.2 系统栈

任务栈不使用系统栈控件,中断函数和中断嵌套使用
Cortex-M3 和 M4 内核具有双堆栈指针,MSP 主堆栈指针和 PSP 进程堆栈指针
在 FreeRTOS 操作系统中,主堆栈指针 MSP 是给系统栈空间使用的,进
程堆栈指针 PSP 是给任务栈使用的
对于 Cortex-M3 内核和未使用 FPU(浮点运算单元)功能的 Cortex-M4 内核,需要64字节
对于具有 FPU(浮点运算单元)功能的 Cortex-M4 内核,需要200字节

2.3 任务优先级

FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 进行配置的,用户实际可以使用的优先级范围是 0 到 configMAX_PRIORITIES – 1
用户配置任务的优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是 0
建议用户配置宏定义 configMAX_PRIORITIES 的最大值不要超过 32
FreeRTOS 中处于运行状态的任务永远是当前能够运行的最高优先级任务

2.3.1 任务优先级分配

IRQ 任务:IRQ 任务是指通过中断服务程序进行触发的任务,此类任务应该设置为所有任务里面优先级最高的
高优先级后台任务:比如按键检测,触摸检测,USB 消息处理,串口消息处理等,都可以归为这一类任务 低优先级的时间片调度任务:比如 emWin 的界面显示,LED 数码管的显示等不需要实时执行的都可以归为这一类任务
实际应用中用户不必拘泥于将这些任务都设置为优先级 1 的同优先级任务,可以设置多个优先级,只需注意这类任务不需要高实时性
空闲任务:空闲任务是系统任务
特别注意:IRQ 任务和高优先级任务必须设置为阻塞式(调用消息等待或者延迟等函数即可), 只有这样,高优先级任务才会释放 CPU 的使用权,,从而低优先级任务才有机会得到执行

2.3.2 中断优先级与任务优先级

中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序
对于 STM32F103,F407 和 F429 来说,中断优先级的数值越小,优先级越高。而 FreeRTOS的任务优先级是,任务优先级数值越小,任务优先级越低

2.3.2 任务调度

2.2.3 相关API

vTaskStartScheduler-任务启动

空闲任务和可选的定时器任务是在调用这个函数后自动创建
正常情况下这个函数是不会返回的,运行到这里极有可能是用于定时器任务或者空闲任务的 heap 空 间不足造成创建失败,此时需要加大 FreeRTOSConfig.h 文件中定义的 heap 大小
如果在此类任务函数里面填的任务 ID 是 NULL,即数值 0 的话,那么执行生效的就是当前正在执行的任务

系统框架写法

int main(void)
{
    /* 初始化外设 */

    xTaskCreate();          /* 创建开始任务 */  

    vTaskStartScheduler();  /* 开启任务调度 */
}

void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           /*  */进入临界区

    xTaskCreate();  /* 创建任务  1 */
    xTaskCreate();  /* 创建任务  2 */
    xTaskCreate();  /* 创建任务  3 */

    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
    taskEXIT_CRITICAL();            /* 退出临界区 */
}

任务创建、挂起、删除

/* 相关配置 */
#define configSUPPORT_DYNAMIC_ALLOCATION        1                       /* 支持动态内存申请  */
//#define configSUPPORT_STATIC_ALLOCATION           1                       /* 支持静态内存申请 */
#define configTOTAL_HEAP_SIZE                   ((size_t)(20*1024))     /* 系统所有总的堆大小,heap_x.h需要,动态申请 */

/* 任务创建 */
/* 任务创建宏定义,便于修改 */

#define START_TASK_PRIO         1       /* 任务优先级 */
#define START_STK_SIZE          256     /* 任务堆栈大小 */

TaskHandle_t StartTask_Handler;         /* 任务句柄 */
void start_task(void *pvParameters);    /* 任务函数 */

/* 任务创建函数 */

xTaskCreate((TaskFunction_t )start_task,            /* 任务函数 */
            (const char*    )"start_task",          /* 任务名称 */
            (uint16_t       )START_STK_SIZE,        /* 任务堆栈大小 */
            (void*          )NULL,                  /* 传递给任务函数的参数 */
            (UBaseType_t    )START_TASK_PRIO,       /* 任务优先级 */
            (TaskHandle_t*  )&StartTask_Handler);   /* 任务句柄 */

/* 任务挂起 */
vTaskSuspend(Task1Task_Handler);

/* 任务内解挂 */
vTaskResume(Task1Task_Handler);

/* 中断内解挂 */
BaseType_t YieldRequired;
YieldRequired=xTaskResumeFromISR(Task1Task_Handler);
portYIELD_FROM_ISR(YieldRequired);    /* 判断是否需要调度到恢复的任务 */

/* 任务删除 */
vTaskDelete(Task1Task_Handler);

任务调度

嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法

抢占式务调度

每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的 API 函数,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任
根据抢占式调度器,当前的任务要么被高优先级任务抢占,
要么通过调用阻塞式 API 来释放 CPU 使用权让低优先级任务执行,没有用户任务执行时就执行空闲任务

时间片调度

实现 Round-robin 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,
并为每个任务分配一个时间片
每个任务都有相同的优先级,任务会运行固定的时间片个数或者遇到阻塞式的 API 函数
在 FreeRTOS 操作系统中只有同优先级任务才会使用时间片调度
默认情况下,此宏定义已经在 FreeRTOS.h 文件里面使能
FreeRTOS的时间片仅支持一个时钟周期,比如你的系统时钟节拍是1000Hz,那么时间片的大小就是1ms,时间片调度仅存在于同优先级任务
在 RTOS 中,最小的时间单位为一个 tick,即 SysTick 的中断周期,RT-Thread 和 μC/OS可以指定时间片的大小为多个 tick,但是 FreeRTOS不一样,时间片只能是一个 tick。与其说 FreeRTOS 支持时间片,倒不如说它的时间片就是正常的任务调度

/* 相关配置,时间片长度即1/Tick中断频率 */
#define configUSE_PREEMPTION                    1                       /* 1使用抢占式内核,0使用协程 */
#define configUSE_TIME_SLICING                  1                       /* 1使能时间片调度(默认式使能的) */
#define configTICK_RATE_HZ                      (20)                    /* 时钟节拍频率,20HZ = 50ms */

/* 两个任务优先级相等 */
#define TASK1_TASK_PRIO     2
#define TASK1_STK_SIZE      128 
TaskHandle_t Task1Task_Handler;
void task1_task(void *pvParameters);

#define TASK2_TASK_PRIO     2
#define TASK2_STK_SIZE      128 
TaskHandle_t Task2Task_Handler;
void task2_task(void *pvParameters);

三、系统延时

const TickType_t xDelayTime = pdMS_TO_TICKS(300);/* 将延时ms转换为系统节拍 */
const TickType_t xDelayTime = 300 / portTICK_RATE_MS;/* 将延时ms转换为系统节拍 */
/* TickType_t 等价于 portTickType */
/* 系统延时函数,单位:系统节拍,阻塞延时   portMAX_DELAY-最大延时等待 */

/* 相对延时 */
const TickType_t xDelayTime = pdMS_TO_TICKS(300);
vTaskDelay(xDelayTime);

/* 绝对延时 */
static portTickType xLastWakeTime;
const portTickType xFrequency = pdMS_TO_TICKS(500); 
vTaskDelayUntil(&xLastWakeTime,xFrequency);

如果想精确周期性执行某个任务,可以调用系统节拍钩子函数vApplicationTickHook(),它在系统节拍中断服务函数中被调用,因此这个函数中的代码必须简洁

四、软件定时器

/* 定时器结构体、函数声明 */
TimerHandle_t   MyTimer_Handle;    /* 定时器句柄 */
void ReloadCallback(TimerHandle_t xTimer);      /* 定时器回调函数 */

/* 创建软件定时器 */
MyTimer_Handle=xTimerCreate((const char*        )"ReloadTimer",         /* 定时器名称 */
                            (TickType_t         )1000,                  /* 周期1s(1000个时钟节拍) */
                            (UBaseType_t        )pdTRUE,                /* 周期模式 */
                            (void*              )1,                     /* 定时器ID */
                            (TimerCallbackFunction_t)ReloadCallback);   /* 定时器回调函数 */
/* 定时器回调函数 */
void ReloadCallback(TimerHandle_t xTimer)
{
    //do something
}

xTimerStart(MyTimer_Handle,0);  /* 开启定时器 */
xTimerStop(MyTimer_Handle,0);   /* 定时器停止 */
xTimerReset(MyTimer_Handle, 0); /* 定时器复位 */

五、消息队列

/* 消息队列宏定义 */
#define MESSAGE_Q_NUM   4           /* 发送数据的消息队列的数量 */
#define MESSAGE_Q_ITEM_NUM  200     /* 每个消息的空间大小 */
QueueHandle_t Message_Queue;        /* 信息队列句柄 */

/* 创建消息队列 */
Message_Queue=xQueueCreate(MESSAGE_Q_NUM,MESSAGE_Q_ITEM_NUM);

/* 任务内消息发送 */
u8 sendData[MESSAGE_Q_ITEM_NUM];
BaseType_t err;
err=xQueueSend(Message_Queue,&senddata,10);    /* 10为发送等待时间,有可能队列已满,err = errQUEUE_FULL 或 err = pdPASS */

/* 中断内消息发送 */
8 sendData[MESSAGE_Q_ITEM_NUM];
BaseType_t xHigherPriorityTaskWoken;
xQueueSendFromISR(Message_Queue,sendData,&xHigherPriorityTaskWoken);    /* 向队列中发送数据,返回值,依然是 满了或Pass,第三个参数是判断高优先级 */接受到队列后,退出中断,是否需要调度
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);/*如果需要的话进行一次任务切换  */

/* 任务内消息接收 */
u8 *receiveData;
xQueueReceive(Message_Queue,receiveData,portMAX_DELAY)    /* 返回值为pdPASS 或 errQUEUE_EMPTY,这里等待时间用portMAX_DELAY阻塞,所以不用再判断 */

/* 中断内消息接收 */
u8 *receiveData;
err=xQueueReceiveFromISR(Message_Queue,receiveData,&xTaskWokenByReceive); /* 向队列中接受数据,返回值, FAIL或Pass,第三个参数是判断高优先级接受到队列后,退出中断,是否需要调度 */
portYIELD_FROM_ISR(xTaskWokenByReceive);/* 如果需要的话进行一次任务切换 */

u8 remain_size;     /* 消息队列剩余大小 */
remain_size=uxQueueSpacesAvailable(Message_Queue);  /* 得到队列剩余大小 */
u8 used_size;       /* 消息队列使用大小 */
used_size=uxQueueMessagesWaiting(Message_Queue);    /* 得到队列使用大小 */

六、信号量

FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量
信号量API函数实际上都是宏,它使用现有的队列机制。这些宏定义在semphr.h文件中。如果使用信号量或者互斥量,需要包含semphr.h头文件。

二进制信号量、计数信号量和互斥量信号量的创建API函数是独立的,但是获取和释放API函数都是相同的;递归互斥信号量的创建、获取和释放API函数都是独立的

6.1 二值信号量

/* 二值信号量创建 */
SemaphoreHandle_t BinarySemaphore;  /* 二值信号量句柄 */
BinarySemaphore=xSemaphoreCreateBinary();   /* 创建二值信号量 */

/* 二值信号量等待 */
BaseType_t err;
err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY);    /* 获取信号量 */

/* 二值信号量发送 */
BaseType_t err;
err = xSemaphoreGive(BinarySemaphore);  /* 释放二值信号量 */

6.2 计数信号量

/* 计数信号量创建 */
SemaphoreHandle_t CountSemaphore;/* 计数型信号量句柄 */
CountSemaphore=xSemaphoreCreateCounting(255,0);     /* 创建计数型信号量,最大计数和初始化计数,参数没改动的话,为long,所以最大值可以设计为不止255 */

/* 计数信号量等待 */
UBaseType_t semavalue;
xSemaphoreTake(CountSemaphore,portMAX_DELAY);   /* 等待数值信号量,阻塞 */
semavalue=uxSemaphoreGetCount(CountSemaphore);  /* 获取数值信号量值 */

/* 计数信号量发送 */
BaseType_t err;
err=xSemaphoreGive(CountSemaphore);/* 释放计数型信号量 */

6.3 互斥信号量

/* 创建互斥信号量 */
SemaphoreHandle_t MutexSemaphore; /* 互斥信号量句柄 */
MutexSemaphore=xSemaphoreCreateMutex();     /* 创建互斥信号量 */

/* 互斥信号量等待 */
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);   /* 获取互斥信号量,因为是阻塞,也就不需要查看什么返回值 */

/* 互斥信号量发送 */    
xSemaphoreGive(MutexSemaphore);                 /* 释放互斥信号量 */

6.4 递归信号量

SemaphoreHandle_t RecursiveMutex;/* 递归互斥信号量句柄 */
RecursiveMutex = xSemaphoreCreateRecursiveMutex();        /* 创建递归互斥信号量 */

/* 递归互斥信号量等待 */
xSemaphoreTakeRecursive(RecursiveMutex,10);        /* 10为等待节拍 */

/* 递归互斥信号量发送 */
xSemaphoreGiveRecursive(RecursiveMutex);        /* 发送递归互斥信号量 */

七、事件标志组

/* 例子,3个事件 */
#define EVENTBIT_0  (1<<0)               
#define EVENTBIT_1  (1<<1)
#define EVENTBIT_2  (1<<2)
#define EVENTBIT_ALL    (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)

EventGroupHandle_t EventGroupHandler;    /* 事件标志组句柄 */
EventGroupHandler=xEventGroupCreate();   /* 创建事件标志组 */

/* 事件标志组置位 */
xEventGroupSetBits(EventGroupHandler,EVENTBIT_1);        /* 事件1置位 */

/* 事件标志组清除 */
xEventGroupClearBits(EventGroupHandler,EVENTBIT_1);       /* 事件1清除 */

/* 事件标志组获取 */
EventBits_t NewValue;
NewValue = xEventGroupGetBits(EventGroupHandler);   

/* 事件标志组等待 */
EventValue=xEventGroupWaitBits((EventGroupHandle_t  )EventGroupHandler,     /* 句柄 */   
                               (EventBits_t         )EVENTBIT_ALL,          /* 标志位 */
                               (BaseType_t          )pdTRUE,                /* 获取成功后 清除  */
                               (BaseType_t          )pdTRUE,                /* 等待所有标志位 置位 */
                               (TickType_t          )portMAX_DELAY);        /* 阻塞 */

八、任务通知

任务通知可完成消息队列、信号量、事件标志组的功能,不过任务通知是只能实现一对一的,也就是一个任务对一个任务

/* 通知值发送,设置通知值,可发送一个数据 */
u8 data;
BaseType_t err;
err=xTaskNotify((TaskHandle_t   )Task_Handler,      /* 接收任务通知的任务句柄 */
                (uint32_t       )data,                      /* 任务通知值 */
                (eNotifyAction  )eSetValueWithOverwrite);   /* 覆写的方式发送任务通知 */


/* 通知值发送,设置通知值,可做标记位组 */
#define EVENTBIT_1  (1<<1)
xTaskNotify((TaskHandle_t   )Task_Handler,      /* 接收任务通知的任务句柄 */
            (uint32_t       )EVENTBIT_1,        /* 要更新的bit */
            (eNotifyAction  )eSetBits);         /* 更新指定的bit */

/* 通知值获取,获取通知值,并判断是否需要清零 */
BaseType_t err;
uint32_t NotifyValue;
err=xTaskNotifyWait((uint32_t   )0x00,              /* 进入函数,没有接受到通知,不清除任何bit */
                    (uint32_t   )ULONG_MAX,         /* 退出函数,接受到通知,清除所有(0xffffffffUL)位的bit, */
                    (uint32_t*  )&NotifyValue,      /* 保存任务通知值 */
                    (TickType_t )portMAX_DELAY);    /* 阻塞时间 */

九、临界段

/* 任务内临界段处理 */
taskEXIT_CRITICAL();
/* 任务处理 */
taskEXIT_CRITICAL();


/* 中断内临界段处理 */
taskENTER_CRITICAL_FROM_ISR();
/* 中断内处理 */
taskEXIT_CRITICAL_FROM_ISR();

十、内存管理

/* 内存申请 */
u8 *buffer;
buffer=pvPortMalloc(30);            /* 申请内存,30个字节 */

/* 释放内存 */
vPortFree(buffer); 

/* 获取内存剩余空间 */
u32 freeSize;
freeSize = xPortGetFreeHeapSize();  

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注