【FreeRTOS】第七课(3):任务间的通信——使用队列集优化程序架构
目录
一、前言
二、使用队列集的程序框架
三、队列集介绍
1.简介
2.用到的函数原型
四、使用实例
1.顶层部分
1)游戏函数(创建队列集)
2)中间数据转换函数(使用队列集)
2.红外遥控器部分
1)创建原始数据接收队列
2)存储原始数据
3.)创建句柄返回函数
旋转编码器部分
1) 创建原始数据接收队列
2)存储原始数据
3)创建句柄返回函数
3.MPU6050部分
1)创建原始数据接收队列
2)将原始数据写入设备队列
3)创建句柄返回函数
五、程序小bug(处理方式见后文)
一、前言
根据上一节课写出的程序可以看出:旋转编码器的解耦性更好
推荐程序架构:代码之间耦合性低
二、使用队列集的程序框架
三、队列集介绍
1.简介
队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:
- 创建队列A,它的长度是n1,存放的是原始数据
- 创建队列B,它的长度是n2,存放的是原始数据
- 创建队列集S,它的长度是“n1+n2”,存放的是队列的句柄
- 把队列A、B加入队列集S
- 队列A每被写入一个数据,就会把自己的句柄写入队列集S
- 队列B每被写入一个数据,就会把自己的句柄写入队列集S
- 读取队列集S,返回值是一个队列句柄,通过句柄可以知道是哪个队列的数据,再通过句柄调用对应的队列数据处理函数,将原始数据处理为可直接使用的数据
2.用到的函数原型
创建队列集函数原型如下:
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )
参数 | 说明 |
---|---|
uxQueueLength | 队列集长度,最多能存放多少个数据(队列句柄) |
返回值 | 非0:成功,返回句柄,以后使用句柄来操作队列NULL:失败,因为内存不足 |
将队列加入队列集函数原型如下:
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,QueueSetHandle_t xQueueSet );
参数 | 说明 |
---|---|
xQueueOrSemaphore | 队列句柄,这个队列要加入队列集 |
xQueueSet | 队列集句柄 |
返回值 | pdTRUE:成功pdFALSE:失败 |
读队列集函数原型如下:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,TickType_t const xTicksToWait );
参数 | 说明 |
---|---|
xQueueSet | 队列集句柄 |
xTicksToWait | 如果队列集空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait表示阻塞的最大时间(Tick Count)。如果被设为0,无法读出数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写 |
返回值 | NULL:失败,队列句柄:成功 |
四、使用实例
1.顶层部分
1)游戏函数(创建队列集)
static QueueHandle_t g_xQueuePlatform; /* 接收创建挡球板队列函数返回值的变量(也可称为句柄) */
static QueueSetHandle_t g_xQueueSetInput; /* 接收创建队列集函数返回值的变量(也可称为句柄) */void game1_task(void *params)
{ /* 创建挡球板数据队列 */g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data));/* 创建队列集,长度为写入队列集的所有队列的长度之和 */g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);/* 得到各设备原始数据队列的句柄 */ g_xQueueIR = GetQueueIR();g_xQueueRotary = GetQueueRotary();g_xQueueMPU6050 = GetQueueMPU6050();/* 将原始数据队列添加进队列集 */xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);/* 创建MPU6050任务 */xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);/* 创建数据处理任务InputTask */xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);/* 其他代码 */...
}
2)中间数据转换函数(使用队列集)
/* 红外遥控器数据转换函数 */
static void ProcessIRData(void)
{/* 将红外遥控器原始数据转换成挡球板控制数据 */.../* 写挡球板队列 */xQueueSend(g_xQueuePlatform, &input, 0);
}/* 旋转编码器数据转换函数 */
static void ProcessRotaryData(void)
{/* 将旋转编码器原始数据转换成挡球板控制数据 */.../* 写挡球板队列 */xQueueSend(g_xQueuePlatform, &input, 0);
}/* MPU6050数据转换函数 */
static void ProcessRotaryData(void)
{/* 将MPU6050原始数据转换成挡球板控制数据 */.../* 写挡球板队列 */xQueueSend(g_xQueuePlatform, &input, 0);
}/********************输入任务,检测多个输入设备并调用对应处理函数********************/
static void InputTask(void *params)
{/* 接收不同句柄,选择不同处理方式 */QueueSetMemberHandle_t xQueueHandle;while (1){/* 读队列集, 得到有数据的队列句柄 */xQueueHandle = xQueueSelectFromSet(g_xQueueSetInput, portMAX_DELAY);if (xQueueHandle){/* 读队列句柄得到数据,处理数据 */if (xQueueHandle == g_xQueueIR){ProcessIRData();}else if (xQueueHandle == g_xQueueRotary){ProcessRotaryData();} else if (xQueueHandle == g_xQueueMPU6050){ProcessMPU6050Data();} }}
}
2.红外遥控器部分
1)创建原始数据接收队列
/************************************** 一些参数 **************************************/
#define IR_QUEUE_LEN 10static QueueHandle_t g_xQueueIR; /* 接收创建红外遥控器队列函数返回值的变量(也可称为句柄) */struct ir_data { /* 红外遥控器数据结构体 */uint32_t dev;uint32_t val;
};/*************************** 初始化,顺便创建设备原始数据队列 ***************************/
void IRReceiver_Init(void)
{/* 其他代码 */.../* 创建红外遥控器数据队列 */g_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
}
2)存储原始数据
/*************************** 处理原始数据并存入自己的队列 ***************************/
static int IRReceiver_IRQTimes_Parse(void)
{/* 前置代码 */.../* 写红外遥控原始数据队列 */struct ir_data data;data.dev = dev;data.val = val;xQueueSendFromISR(g_xQueueIR, &idata, NULL);
}/*************************** 采集原始数据,回调数据处理函数 ***************************/
void IRReceiver_IRQ_Callback(void)
{/* 前置代码 */...IRReceiver_IRQTimes_Parse();
}
3.)创建句柄返回函数
/***************************句柄返回函数,用于队列集使用***************************/
QueueHandle_t GetQueueIR(void)
{return g_xQueueIR;
}
旋转编码器部分
1) 创建原始数据接收队列
/************************************** 一些参数 **************************************/
#define ROTARY_QUEUE_LEN 10struct rotary_data { /* 旋转编码器数据结构体 */int32_t cnt;int32_t speed;
};static QueueHandle_t g_xQueueRotary; /* 接收创建旋转编码器队列函数返回值的变量(也可称为句柄) */
static StaticQueue_t g_xQueueRotaryStaticStruct; /* 实例化FreeRTOS给出的结构体 */
static uint8_t g_ucQueueRotaryBuf[ROTARY_QUEUE_LEN*sizeof(struct rotary_data)]; /* 静态创建开辟的空间 */ /*************************** 初始化,顺便创建设备原始数据队列 ***************************/
void RotaryEncoder_Init(void)
{/* 前置代码 */.../* 静态创建旋转编码器数据队列 */g_xQueueRotary = xQueueCreateStatic(ROTARY_QUEUE_LEN, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct);
}
2)存储原始数据
/*************************** 采集原始数据存入自己的队列 ***************************/
void RotaryEncoder_IRQ_Callback(void)
{/* 其他代码 */...struct rotary_data rdata;rdata.cnt = cnt;rdata.speed = speed;xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}
3)创建句柄返回函数
/*************************** 句柄返回函数,用于队列集使用 ***************************/
QueueHandle_t GetQueueRotary(void)
{return g_xQueueRotary;
}
3.MPU6050部分
1)创建原始数据接收队列
/************************************** 一些参数 **************************************/
#define MPU6050_QUEUE_LEN 10static QueueHandle_t g_xQueueMPU6050; /* 接收创建MPU6050原始数据队列函数返回值的变量(也可称为句柄) */struct mpu6050_data { /* MPU6050数据结构体 */int32_t angle_x;
};/*************************** 初始化,顺便创建设备原始数据队列 ***************************/
void IRReceiver_Init(void)
{/* 其他代码 */.../* 创建MPU6050数据队列 */g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
}
2)将原始数据写入设备队列
void MPU6050_Task(void *params)
{ int16_t AccX;struct mpu6050_data result;int ret;extern volatile int bInUsed;while (1){ /* 读数据 */while (bInUsed);bInUsed = 1;ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);bInUsed = 0;if (0 == ret){/* 解析数据 */MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);/* 写队列 */xQueueSend(g_xQueueMPU6050, &result, 0);}/* delay */vTaskDelay(50);}
}
3)创建句柄返回函数
/***************************句柄返回函数,用于队列集使用***************************/
QueueHandle_t GetQueueMPU6050(void)
{return g_xQueueMPU6050;
}
五、程序小bug(处理方式见后文)
程序 bug ,屏幕和MPU6050使用了同一个 IIC ,长时间使用会导致屏幕显示出现问题
解决方法:
使用全局变量作为能否使用IIC的标志位
增加互斥操作让两个外设不能同时使用,保证通信时序的正常(或者使用两个IIC)