1、内存管理
一般提供两种方法:
(1)动态创建:自动地从 FreeRTOS 管理的内存堆中申请创建对象所需的内存,并且在对象删除后,可将这块内存释放回FreeRTOS管理的内存堆
(2)静态创建:需用户提供各种内存空间,并且使用静态方式占用的内存空间一般固定下来了,即使任务、队列等被删除后,这些被占用的内存空间一般没有其他用途
除了 FreeRTOS 提供的动态内存管理方法,标准的 C 库也提供了函数 malloc()和函数 free()来实现动态地申请和释放内存 。FreeRTOS 提供了多种动态内存管理的算法,可针对不同的嵌入式系统!
1.1、内存管理算法:
heap_4 内存管理算法使用了首次适应算法,也支持内存的申请与释放,并且能够将空闲且相邻的内存进行合并,从而减少内存碎片的现象。
首次适应算法:
假设heap有3块空闲内存(按内存块地址由低到高排序):5字节、50字节、25字节;
现在新创建一个任务需要申请20字节的内存;
第一步:找出第一个能满足pvPortMalloc的内存:50字节;
第二步:把它划分为20字节、30字节;返回这20字节的地址,剩下30字节仍然是空闲状态,留给后续的pvPortMalloc使用;
1.2、内存管理API函数:
void* pvPortMalloc(size_t xWantedSize);//申请内存
//xWantedSize:申请的内存大小,以字节为单位
//返回值:返回一个指针 ,指向已分配大小的内存。如果申请内存失败,则返回 NULL。
void vPortFree(void* pv);//释放内存
//* pv:指针指向一个要释放内存的内存块;
size_t xPortGetFreeHeapSize(void);//获取当前空闲内存的大小
//返回值:返回当前剩余的空闲内存大小
1.3、内存管理实验
设计两个任务:start_task、task1
start_task:用来创建task1任务;
task1:用于按键扫描,当KEY0按下则申请内存,当KEY1按下则释放内存,并打印剩余内存信息。
void task1(void * pvParameters)
{
uint8_t key = 0, t = 0;
uint8_t * buf = NULL;
while(1)
{
key = key_scan(0);
if(key==KEY0_PRES)//申请内存
{
buf = pvPortMalloc(30);//返回的地址赋给buf
if(buf != NULL)
{
printf("申请内存成功!\r\n");
}else printf("申请内存失败\r\n");
}else if(key = KEY1_PRES)//释放内存
{
if(buf != NULL)//申请成功了才能释放
{
vPortFree(buf);
printf("释放内存!\r\n");
}
}
if(t++ > 50)
{
t=0;
printf("剩余的空闲内存大小为:%d\r\n", xPortGetFreeHeapSize());
}
vTaskDelay(10);
}
}
为什么申请很多次,只能释放一次,再释放就会报错呢?
每按一次KEY0,就会申请30字节,返回首地址给buf。再按一次会接着申请,buf保留的是最新的地址,当释放的时候,释放的是最新申请的地址,后面再释放也是这个buf指向的地址。
如果申请一次,就用一个buf记录下来地址,那么申请几次就释放几次地址。
2、任务状态查询
2.1、FreeRTOS任务相关函数
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);//获取指定任务的任务优先级,使用该函数需将宏INCLUDE_uxTaskPriorityGet置1
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);//改变某个任务的任务优先级,使用该函数需将宏INCLUDE_vTaskPrioritySet 为1
UBaseType_t uxTaskGetNumberOfTasks(void);//获取系统中任务的任务数量
UBaseType_t uxTaskGetSystemState(TaskStatus_t * const pxTaskStatusArray,const UBaseType_t uxArraySize,configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime);//获取系统中所有任务的任务状态信息,使用该函数需将宏 configUSE_TRACE_FACILITY 置 1
2.1、实验
start_task:用来创建task1和task2
task1:LED0每500ms闪烁一次,提示程序正在运行;
task2:用于展示任务状态信息查询相关API函数的使用;
获取某个任务的状态函数:
void vTaskGetInfo(TaskHandle_t xTask,//任务句柄
TaskStatus_t* pxTaskStatus,接收任务信息的变量,就是存储任务信息,需要定义一个内存
BaseType_t xGetFreeStackSpace,
eTaskState eState)//
实现过程:
找到函数原型,任务信息存到哪里呢?要定义一个存储变量TaskStatus_t * status_array2=0;
status_array2 = mymalloc(SRAMIN,sizeof(TaskStatus_t));
VTaskGetInfo(task1_handler,status_array2,pdTRUE,eInvalid);
printf("任务名:%s\r\n",status_array2->pcTaskName);
printf("优先级:%s\r\n",status_array2->uxCurrentPriority);
printf("任务编号:%s\r\n",status_array2->xTaskNumber);
printf("任状态:%s\r\n",status_array2->eCurrentState);
3、任务时间统计
void vTaskGetRunTimeStats(char* pcWriteBuffer)
这个功能主要用于调试的时候,统计各个任务时间占比,调试完了就删除。
系统定时器是1ms/次,时基定时器为0.1ms/0.05ms。
实现过程:先定义一个按键变量,按键扫描,按键按下,执行任务运行时间函数
char task_buff[500];//任务时间统计全部放到这个变量中
void task2(void * pvParameter)
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if(key == KEY_PRES)
{
vTaskGetRunTimeStats(task_buff);
printf("%s\r\n",task_buff);
}
vTaskDelay(10);
}
}
编译报错:因为没有实现这两个函数:
实现时基定时器的初始化:用定时器6来实现。
uint32_t FreeRTOSRunTimeTicks;
//时基定时器的初始化
void ConfigureTimeForRunTimeStats(void)
{
btim_timx_int_init(uint16_t arr, uint16_t psc);
FreeRTOSRunTimeTicks = 0;//初始化的时候清零
}
10us,定时器6的时钟是90MHz,429主频是180M,90M/90=1M。1M是1us,所以(10-1, 90-1)//100倍的系统时钟节拍
在中断函数中,每进来一次FreeRTOSRunTimeTicks 就加1,
显示的时间一定要乘以10us。
4、系统延时函数
启动了2种延时函数,一种是相对延时,一种是绝对延时。
相对延时:vTaskDelay();每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束;
绝对延时:xTaskDelayUntil();将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务。
5、队列
5.1、简介
假如使用全局变量,在任务1中,当执行到第二步时,目前r0的数据还没写入a中,被更高优先级的任务2打断,任务2执行了全部过程,此时a=1,再切换为执行任务一,然后r0=1赋给a,这样会造成数据的浪费。
在任务A写队列的时候,关中断了,首先中断函数不能打断,其他任务也没办法执行,没办法进行任务调度,因为任务调度是由PendSV引起的,PendSV中断优先级是最低的,在这里中断关掉了,PendSV不可能触发,所以任务A在写的时候,任务B是没办法打断的,
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。
(1)队列长度:n个
(2)队列项目大小:m字节
创建队列时,就要指定队列项目的大小。
5.2、队列相关API函数
流程:创建队列、写队列、读队列。
xQueueCreate();//动态创建队列
xQueueCreateStatic();//静态方式创建队列
区别:队列所需要的内存空间由FreeRTOS从内存堆自动分配。
5.3、队列操作实验
在入口函数内创建队列:
void freertos_demo(void)
{
xQueueCreate();
xTaskCreate((TaskFunction_t)start_task);
vTaskStartScheduler();
}
创建队列的时候有一个返回值,NULL,创建失败,其他值,创建失败,返回队列句柄。找到xQueueCreate()返回值类型,QueueHandle_t,把句柄定义一个全局的,句柄的名字就是队列名字。
QueueHandle_t key_queue;
QueueHandle_t big_data_queue;
添加头文件:
#include "queue.h"
把xQueueCreate()的返回值赋给key_queue。
因为我们有2个按键,因此队列的长度就2,队列项目的大小,也就是按键键值的大小,sizeof(uint8_t);
key_queue = xQueueCreate(2, sizeof(uint8_t));
if(key_queue != NULL)
{
printf("key_queue 创建成功\r\n");
}
else
{
printf("key_queue 创建失败\r\n");
}
创建big_data_queue,第一个参数是1,一个大数据,我们定义一个大数组就可以了,
char buff[100]={"我是一个大数组"};
第2个参数:队列项的大小有多大,其实存放的就是一个地址,地址其实就是一个四字节sizeof(char *)。
任务1的实现:
void task1(void * pvParamters)
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES || key == KEY1_PRES)
{
err = xQueueSend(key_queue, &key, portMAX_DELAY);//队列,写的值,死等,直到有空间可以写入
}else if(key == WKUP_PRES)
{
}
vTaskDelay(10);
}
}
只要按键按下,键值就发送到队列。队列发送函数有返回值,来判断是否发送成功,定义一个返回值类型:BaseType_t err = 0;
if(err != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
定义一个指针,char * buf;把大数组的首地址赋给这个指针;
buf = buff;//buf = &buff[0];
err = xQueueSend(big_fate_queue, &buf, portMAX_DELAY);
if(err != pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
任务二:读取队列中的数据
void task2(void * pvParameters)
{
while(1)
{
err = xQueueReceive(key_queue, &key, portMAX_DELAY);
}
}
这个函数不加延时,task2优先级比task1高,即使没有调用vTaskDelay(10),也会进入阻塞态。
实验结果:按下复位键,队列创建成功,空数据,task2和task3读取不到数据,进入阻塞态,此时task1是可以运行的。
创建队列API函数、入队函数解析、出队函数解析这三节 .
6、信号量
信号是指状态,有没有发送完,量呢是指数量。如果量是0和1,叫二值信号量,如果有很多,叫计数型信号量。
同步:我做完一个事情告诉你,你才可以做。