C++抽象化封装FreeRTOS,一种多人并行开发方案3

这一章我们来分析源码,并修改源码来修改功能

前置知识:c++:类的继承,纯虚函数,类的指针this,类的构造函数

目录

头文件

私有成员

公有成员

源文件

构造函数

任务函数

对任务函数的修改

内存膨胀


头文件

考虑到keil很多时候使用AC5,CubeMX生成freeRTOS默认使用AC5,而AC5兼容c++03和部分c++11特性,所以代码没有使用c++11特性。后面我出一期将CubeMX生成FreeRTOS适配AC6的文章。AC6引入了大量gcc编译器的特性,可以支持到c++17

//.h文件
#ifndef MY_TASK_H
#define MY_TASK_H

#ifdef __cplusplus
extern "C"{
#endif
#include "FreeRTOS.h"
#include "timers.h" 
#include "task.h"
#include "cmsis_os.h"
class TaskBase{
protected:
/* 任务属性定义 */
	const char * const pcName;
	const uint16_t Task_Size;
	UBaseType_t Priority;
	TaskHandle_t Handler;

public:
	void vdelete(); /* 删除任务 */
	void vSuspend();//挂起任务
	void vResume();//恢复任务
	eTaskState eGetState();//状态查询
	static void App(void *arg);
	virtual void task(void *arg) = 0;//纯虚任务函数
	unsigned portBASE_TYPE uxGetWater();//获取高水位值
	TaskHandle_t getHandle(){return Handler;}/* 获取句柄 */
	TaskBase (const char * const Name,const uint16_t Size,UBaseType_t Pri);
};
	

#ifdef __cplusplus
}

#endif
#endif

我们在头文件中定义基类,所有任务都是继承于基类TaskBase,例如前面两篇文章的class Main_Task:public TaskBase,继承其公共属性。

私有成员

在基类TaskBase的私有成员中设置有四种基本属性:名字,内存大小,优先级,句柄。但是,有小伙伴发现,我们填写参数时只有三个参数?

Main_Task():TaskBase("main_task",128,1){};

因为我发现,在官方提供的freeRTOS的API中,我们填写的句柄只是一个承载数据的指针,&Handler,Handler的值本身是多少并不重要,创建成功会给Handler一个值,既然不需要我们指定,我就将它隐去了。

xTaskCreate(Fun,pcName,Task_Size,NULL,Priority,&Handler);

公有成员

而在public成员中,除了API函数,构造函数外,还有两个特殊的函数App,task

	static void App(void *arg);
	virtual void task(void *arg) = 0;//纯虚任务函数

App()就是使用官方API中的任务函数Fun,而task就是我们要去实现的业务函数。基类TaskBase会通过调用App来调用task来实现任务。这么说可能有些抽象,我们通过源文件来看具体实现

xTaskCreate(Fun,pcName,Task_Size,NULL,Priority,&Handler);

源文件

//.cpp文件
#include "my_task.h"

/* 任务属性赋值 */
TaskBase::TaskBase (const char * const Name,const uint16_t Size,UBaseType_t Pri):
pcName(Name),Task_Size(Size),Priority(Pri),Handler(NULL)
{
    xTaskCreate(App,pcName,Task_Size,this,Priority,&Handler);
}

/* 任务函数成员,调用纯虚函数 */
void TaskBase::App(void *arg)
{
    TaskBase *p = static_cast<TaskBase*>(arg);
    p->task(NULL);
}

 /* 删除任务 */
void TaskBase::vdelete()
{
    if (Handler != NULL)
    {
        vTaskDelete(Handler);
        Handler = NULL;
    }
}

//获取高水位值
unsigned portBASE_TYPE TaskBase::uxGetWater()
{
	return uxTaskGetStackHighWaterMark(getHandle());
}

//挂起任务
void TaskBase::vSuspend()
{
	vTaskSuspend(getHandle());
}

//恢复任务
void TaskBase::vResume()
{
	//处于挂起态,则恢复
	if (eGetState() == eSuspended)
		vTaskResume(getHandle());
}

//状态查询
eTaskState TaskBase::eGetState()
{
	return eTaskGetState(getHandle());
}
/*------------------------------------------------------------------*/
/* end------------------------------------------------------------- */

构造函数

在构造函数中,Handler会被初始化为NULL,创建成功后,Handler也会被正常赋值。这里参数我们填“this”,基类的指针,会与App()函数联动。

/* 任务属性赋值 */
TaskBase::TaskBase (const char * const Name,const uint16_t Size,UBaseType_t Pri):
pcName(Name),Task_Size(Size),Priority(Pri),Handler(NULL)
{
    xTaskCreate(App,pcName,Task_Size,this,Priority,&Handler);
}

任务函数

在freeRTOS中,任务函数会接收到来自xTaskCreate()传入的参数作为自己的参数(这一步由freeRTOS自身实现)。于是我们就有了参数arg就是基类的指针,将其强转,调用它自己的纯虚函数task();这里我统一给了NULL,不需要参数。当然,这是可修改的。

//.cpp文件
/* 任务函数成员,调用纯虚函数 */
void TaskBase::App(void *arg)
{
    TaskBase *p = static_cast<TaskBase*>(arg);
    p->task(NULL);
}
//.h文件
virtual void task(void *arg) = 0;//纯虚任务函数
//这里的arg == NULL;

对任务函数的修改

        c++中有一个重要的特性:默认参数。比如

int x = 10;

void fun(int a = 0){
    x = a;
}

int main()
{
    fun();//调用默认值,x = 0;
    fun(30);// x = 30
    return 0;
}

利用这个特性,我们修改基类,在我们不传参数时,task()的参数arg = NULL,传参则调用它

,在私有属性中增加成员,task_arg,增加构造函数参数

//.h文件
protected:
/* 任务属性定义 */
	const char * const pcName;
	const uint16_t Task_Size;
	UBaseType_t Priority;
	TaskHandle_t Handler;
	void *task_arg;
......

public:
...
//增加 void *arg = NULL
TaskBase (const char * const Name,const uint16_t Size,
            UBaseType_t Pri, void *arg = NULL);




//.cpp文件
//加入新变量arg,初始化task_arg
TaskBase::TaskBase (const char * const Name,const uint16_t Size
                    ,UBaseType_t Pri, void *arg):
pcName(Name),Task_Size(Size),Priority(Pri),Handler(NULL),task_arg(arg)
{
    xTaskCreate(App,pcName,Task_Size,this,Priority,&Handler);
}


//将参数由NULL变为arg,这样就有了外部传参的功能
/* 任务函数成员,调用纯虚函数 */
void TaskBase::App(void *arg)
{
    TaskBase *p = static_cast<TaskBase*>(arg);
    p->task(p->task_arg);
}

做一个简单的实验,使用stm32f103c8t6点亮小灯,小灯闪烁事件就是传入参数。实验很成功,200ms闪烁一次。

int time = 200;

class Main_Task:public TaskBase{
public:
Main_Task():TaskBase("main_task",128,1,(void*)&time){};
virtual void task(void*arg)
{
    while(1)
    {
		LL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
		vTaskDelay(pdMS_TO_TICKS(*(TickType_t*)arg));
    }
}	
};
Main_Task maintask;

同样的原理,可以将构造函数设置的更简洁,默认128字空间,1优先级,就只需要填个名字就可以了。
 

Main_Task():TaskBase("main_task"){};

内存膨胀

在我们的类中使用了纯虚函数,会创建虚函数表,每个虚函数会在虚函数表中占用一个指针的位置,在这里就是4个字节。我们每创建一个任务就会多消耗一个字的空间,比如我们参数填128,也就是512byte空间,加上虚函数指针实际为516byte空间。所有改的时候·不建议写太多虚函数,比如定义个10个20个虚函数,那就恐怖了,一个任务多消耗几十个字空间。

不过都用上freeRTOS了4byte的消耗还是花的起的,花不起的也用不上freeRTOS。

public:
    ...
	virtual void task1(void *arg) = 0;//纯虚任务函数
    virtual void task2(void *arg) = 0;
    virtual void task3(void *arg) = 0;
    virtual void task4(void *arg) = 0;
    ....

API函数的话就是单纯的把官方API封装一遍而已,大家根据自己的使用习惯封装就好了。这个系列到这里就结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值