声明:项目源码参考韦东山老师百问网嵌入式专家-韦东山嵌入式专注于嵌入式课程及硬件研发 (100ask.net)
在本项目中,打砖块游戏的核心逻辑在一个单独的任务中实现,同时系统还需要处理来自红外遥控、旋转编码器和MPU6050传感器的数据输入。为此,使用FreeRTOS的队列机制,将各个硬件模块的输入数据通过队列发送给游戏逻辑任务,以便做出相应的处理。
队列(Queue)
在FreeRTOS中,队列(Queue) 是一种常用的数据结构,用于在不同任务之间传递数据。队列提供了一种线程安全的方式,可以在线程之间传递消息、事件或数据块。队列可以用来缓冲数据,使得生产者任务和消费者任务之间的解耦。
队列的基本概念
- 队列长度:队列可以保存的最大数据项数目。例如,一个长度为10的队列可以存储10个数据项。
- 队列项大小:每个数据项的大小。例如,每个数据项可能是一个整数、结构体或者指针。
- 发送(写入)数据到队列:通过调用
xQueueSend()
或xQueueSendToBack()
函数将数据项添加到队列的末尾。还有xQueueSendToFront()
函数可以将数据添加到队列的头部。 - 接收(读取)数据从队列:通过调用
xQueueReceive()
函数从队列的头部取出数据项。
队列的本质
队列中,数据的读写本质就是环形缓中区,在这个基础上增加了互斥措施、阻塞-唤醒机制
如果这个队列不传输数据,只调整“数据个数”,它就是信号量(信号量)。
如果信号量中,限定“数据个数”最大值为1,它就是互斥量(mutex)。
应用场景
在嵌入式系统中,队列常用于以下场景:
- 任务间通信:当多个任务需要协作完成某一功能时,可以使用队列传递状态信息或数据。
- 事件处理:当一个任务检测到一个事件时,可以将事件信息发送到队列,另一个任务从队列读取事件并处理。
- 数据缓冲:队列可以用作缓冲区,当数据生产者和消费者速率不匹配时,可以避免数据丢失。
队列集(Queue Set)
队列集(Queue Set)是FreeRTOS中一个重要的功能,它允许多个队列或信号量组合在一起,方便任务等待其中任何一个队列或信号量变为可用。队列集为复杂的任务间通信和同步提供了一种更加灵活的方式。
队列集的主要特点
-
多源触发:队列集可以包含多个队列和信号量,任务可以通过队列集来等待其中任意一个队列或信号量变为可用。例如,任务A可能需要等待来自传感器数据队列、命令队列或信号量中的任何一个可用数据。通过队列集,任务A可以只在一个点等待,而不是在多个队列上循环等待。
-
单一等待点:队列集提供了一个统一的等待点,即使多个队列或信号量发生变化,任务也可以通过队列集来捕获这些变化,而不必单独等待每个队列或信号量。这样可以减少系统复杂性和CPU资源的浪费。
-
灵活性:队列集可以动态地添加或删除队列和信号量,这使得任务能够灵活地应对不同的通信需求。
使用队列集的步骤
-
创建队列集:首先需要创建一个队列集,这个队列集将容纳多个队列或信号量。
QueueSetHandle_t xQueueSet = xQueueCreateSet( /* Maximum number of items in the set */ );
-
创建队列或信号量:接下来,创建要添加到队列集中的队列或信号量。
QueueHandle_t xQueue1 = xQueueCreate( /* Queue length */, /* Item size */ ); SemaphoreHandle_t xSemaphore = xSemaphoreCreateBinary();
-
将队列或信号量添加到队列集:
xQueueAddToSet( xQueue1, xQueueSet ); xQueueAddToSet( xSemaphore, xQueueSet );
-
等待队列集:任务可以通过
xQueueSelectFromSet()
函数等待队列集中的任意队列或信号量变为可用。QueueSetMemberHandle_t xActivatedMember; xActivatedMember = xQueueSelectFromSet( xQueueSet, portMAX_DELAY );
-
处理激活的队列或信号量:一旦队列集中的某个队列或信号量变为可用,
xQueueSelectFromSet()
会返回对应的队列或信号量的句柄,任务可以根据这个句柄进行相应处理。
应用场景
队列集在以下场景中特别有用:
-
多事件源等待:当一个任务需要从多个来源接收消息或事件时,队列集可以将这些队列或信号量组合在一起,使任务能够高效地等待其中任意一个变为可用。
-
复杂同步机制:在更复杂的实时系统中,任务可能需要同步来自多个不同的任务或中断的信号。队列集允许这些信号的组合和统一管理。
项目框架
源码分析
主要用户任务
核心用户任务:
1、小球运动:void game1_task(void *params)
2、挡球板运动 :static void platform_task(void *params)
在game1_task()任务中死循环不断移动小球,判断是否碰撞、出界。并创建platform_task
在platform_task()中读取挡球板队列,控制挡球板运动,即隐藏挡球板、移动挡球板、显示挡球板。
挡球板队列由底层控制模块写入
红外接收器
红外接收器的初始化函数、创建队列
/* 动态创建队列 */
g_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
RegisterQueueHandle(g_xQueueIR);
}
解析红外接收器数据,并写入红外队列
/* 3. 次数达标后, 解析数据, 放入buffer */
if (g_IRReceiverIRQ_Cnt == 4)
{
/* 是否重复码 */
if (isRepeatedKey())
{
/* device: 0, val: 0, 表示重复码 */
//PutKeyToBuf(0);
//PutKeyToBuf(0);
/* 写队列 */
data.dev = 0;
data.val = 0;
DispatchKey(&data);
g_IRReceiverIRQ_Cnt = 0;
}
}
if (g_IRReceiverIRQ_Cnt == 68)
{
IRReceiver_IRQTimes_Parse();
g_IRReceiverIRQ_Cnt = 0;
}
IRReceiver_IRQ_Callback:主要负责记录中断发生的时间,并决定何时调用解析函数
IRReceiver_IRQTimes_Parse
:负责解析记录下来的时间序列,将其转换为具体的按键信息,并通过队列传递给其他部分程序
DispatchKey:将解析出来的按键数据写入所有已注册的队列中,用于任务间通信。
旋转编码器
旋转编码器的初始化函数、创建队列
/* 硬件驱动初始化中创建队列,解耦 */
g_xQueueRotary = xQueueCreateStatic(ROTARY_QUEUE_LEN, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct);
旋转编码器的中断回调函数、记录数据写入队列
/* 写队列 /*
* 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
rdata.cnt = g_count;
rdata.speed = g_speed;
xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}
MPU6050
MPU6050通过创建一个新任务,读取I2C、写队列,旋转编码器和红外在中断中获得数据写队列
创建MPU6050队列、创建事件组,中断回调通过事件组唤醒 MPU6050任务,读I2C、写队列
/*创建MPU6050队列*/
g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));
/*创建事件组*/
g_xEventMPU6050 = xEventGroupCreate();
中断回调函数设置事件组
void MPU6050_Callback(void)
{
/* 设置事件组: bit0 */
xEventGroupSetBitsFromISR(g_xEventMPU6050, (1<<0), NULL);
}
MPU6050任务,等待事件组,然后循环读取MPU6050并把数值写入队列
while(1)
{
/*等待事件*/
xEventGroupWaitBits(g_xEventMPU6050,(1<<0),pdTRUE,pdFALSE,portMAX_DELAY);
/* 读数据 */
// while (bInUsed);
// bInUsed = 1;
GetI2C();
ret = MPU6050_ReadData(&AccX, NULL, NULL, NULL, NULL, NULL);
PutI2C();
// bInUsed = 0;
if (0 == ret)
{
/* 解析数据 */
MPU6050_ParseData(AccX, 0, 0, 0, 0, 0, &result);
/* 写队列 */
xQueueSend(g_xQueueMPU6050, &result, 0);
}
/* delay */
vTaskDelay(50);
}
队列集
创建队列集,通过队列集管理多个队列
g_xQueueSetInput = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);
将三个队列添加进队列集
/*将队列或信号量添加到一个队列集合中*/
xQueueAddToSet(g_xQueueIR, g_xQueueSetInput);
xQueueAddToSet(g_xQueueRotary, g_xQueueSetInput);
xQueueAddToSet(g_xQueueMPU6050, g_xQueueSetInput);
输入任务
读队列集,检测输入队列,并调用对应的数据处理函数
static void InputTask(void *params)
{
/*QueueSetMemberHandle_t 用于标识和操作队列集合中的单个队列或信号量成员。*/
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();
}
}
}
}
数据处理函数
static void ProcessIRData(void)
static void ProcessRotaryData(void)
static void ProcessMPU6050Data(void)
读取各自队列中数据,并处理数据将数据转换成游戏按键值,即控制挡球板移动的值,然后将处理后数据写入挡球板队列
标签:编码器,队列,void,MPU6050,信号量,任务,砖块,数据 From: https://blog.csdn.net/dcq715/article/details/141421343