本文章介绍一种在FreeRTOS项目中任务间共享数据的管理框架,思路比较简单
任务在更新和获取共享数据时利用互斥量进行上锁保护数据,操作完之后进行解锁,并且当共享数据使用setting()更新时调用对应的回调函数,后续可以根据回调函数判断数据是否发生变化来进行另外的程序操作。
框架实现的程序思路:
一、回调函数接口
typedef void (*Callback)(EventType *ev); //回调函数指针
//事件类型枚举
typedef enum
{
EV_NONE = 0x00,
EV_UPDATED = 0x01, //数据更新
} EventType;
//回调相关结构体
typedef struct __attribute__((packed))
{
EventType event; //回调事件
Callback cb; //回调函数
} CallbackType;
创建回调函数要用到的队列,其他接口用到的信号量
static xQueueHandle mQueue;
static xSemaphoreHandle mMutex;
uint8_t CallbackInitialize(void)
{
mMutex = xSemaphoreCreateRecursiveMutex();
if(mMutex == NULL)
{
return 1;
}
// Create event queue
mQueue = xQueueCreate(20, sizeof(CallbackType));
xQueueReset(mQueue);
}
注册回调函数接口,将对用的回调函数绑定回调handle
uint32_t CallbackRegister(CallbackType *handle, EventType event, Callback cb)
{
handle->cb = cb;
handle->event = event;
return 0;
}
队列发送回handle接口
uint32_t CallbackQueue(CallbackType *handle)
{
CallbackType callbackInfo;
memcpy(&callbackInfo, handle, sizeof(CallbackType));
uint32_t result = xQueueSend(mQueue, &callbackInfo, 0);
return result;
}
创建一个CallbackTask任务,用于轮询 队列接收回调handle并调用对应回调函数,避免回调函数中需要阻塞或者代码量过大影响到更新数据的任务
void CallbackTask(void * pvParameters)
{
CallbackType evInfo;
int limit = SEND_QUEUELENGTH;
while(1)
{
if(xQueueReceive(mQueue, &evInfo, 0) == pdTRUE)
{
if(evInfo.cb != 0)
{
evInfo.cb(&evInfo.event);
}
}
}
}
二、Setting接口
互斥量上锁,将要更新的数据覆盖共享数据地址,调用CallbackQueue(),通过CallbackTask处理回调函数,最后互斥量解锁
uint32_t GlobalDataSet(CallbackType *handle, uint8_t *srcdata,const void *dataIn, uint32_t offset,uint32_t size)
{
// Lock
xSemaphoreTakeRecursive(mMutex, portMAX_DELAY);
//更新数据
memcpy(srcdata + offset, dataIn, size);
handle->event = EV_UPDATED;
//触发回调函数
CallbackQueue(handle);
// unLock
xSemaphoreGiveRecursive(mMutex);
return 0;
}
三、Getting接口
互斥量上锁,取出共享数据,最后互斥量解锁
uint32_t GlobalDataGet(CallbackType *handle, const uint8_t *srcdata, void *datout, uint32_t offset,uint32_t size)
{
// Lock
xSemaphoreTakeRecursive(mMutex, portMAX_DELAY);
//取出目标地址数据到放进来的地址
memcpy(datout, srcdata + offset, size);
// unLock
xSemaphoreGiveRecursive(mMutex);
return 0;
}
四、使用例程
将共享数据封装成结构体类型,方便数据管理
typedef struct __attribute__ ((__packed__))
{
uint8_t data;
} Data1;//共享数据1
typedef struct __attribute__ ((__packed__))
{
uint8_t data[2];
} Data2;//共享数据2
typedef struct __attribute__ ((__packed__))
{
Data1 data1;
Data2 data2;
} GloblaDataType;//共享数据结构体类型
static GloblaDataType global_data; //声明一个全局共享数据,用于提供接口的共享数据源地址
static CallbackType handle; //声明一个全局数据的回调handle,用于回调函数
再次封装一次setting,getting接口,只需要定义一个·Data1,Data2类型的变量,将地址传进接口。传入&handle用于回调函数,使用offsetof()获取全局数据结构成员相对于结构开头的字节偏移量。
void GlobalData1Setting(Data1 *NewData)//更新data1数据
{
GlobalDataSet(&handle,(uint8_t *)&global_data,(void *)NewData, offsetof(GloblaDataType, data1), sizeof(Data1));
}
void GlobalData1Getting(Data1 *NewData)//获取data1数据
{
GlobalDataGet(&handle,(uint8_t *)&global_data,(void *)NewData, offsetof(GloblaDataType, data1), sizeof(Data1));
}
void GlobalData2Setting(Data2 *NewData)//更新Data2数据
{
GlobalDataSet(&handle,(uint8_t *)&global_data,(void *)NewData, offsetof(GloblaDataType, data2), sizeof(Data2));
}
void GlobalData2Getting(Data2 *NewData)//获取data2数据
{
GlobalDataGet(&handle,(uint8_t *)&global_data,(void *)NewData, offsetof(GloblaDataType, data2), sizeof(Data2));
}
注册回调函数GlobalDataUpdateCb(),在AppTask1和AppTask2中更新data1和data2,回调函数中打印data1和data2的值。
void GlobalDataUpdateCb(EventType *ev)
{
Data1 data1;
Data2 data2;
GlobalData1Getting(&data1);
GlobalData1Getting(&data2);
PRINTF("GlobalData1Getting data1%d",data1);
PRINTF("GlobalData2Getting data2%d,%d",data2[0],data2[1]);
}
void AppTask1(void * pvParameters)
{
CallbackRegister(&GlobalDataUpdateCb);
Data1 data1 = 1;
while(1)
{
GlobalData1Setting(&data1);
}
}
void AppTask2(void * pvParameters)
{
Data2 data2 = {1,1};
while(1)
{
GlobalData2Setting(&data2);
}
}
五、总结
本文章只是简单的介绍这个任务间更新和获取数据的框架,可能在某些应用中才比较使用,在接口上可能对数据地址的偏移没有做保护,用于出现地址越界等情况,后续还会在应用中进行完善。
备注:本文章的编写思路来源于一个开源项目,如有侵权请联系删除文章。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)