FreeRTOS(二)

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,叫二值信号量,如果有很多,叫计数型信号量。

同步:我做完一个事情告诉你,你才可以做。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值