这一章我们来分析源码,并修改源码来修改功能
前置知识: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封装一遍而已,大家根据自己的使用习惯封装就好了。这个系列到这里就结束了。
1510

被折叠的 条评论
为什么被折叠?



