学习记录:
队列=环形缓存区+各类同步互斥操作(包括:发送者链表、接收者链表、各种状态切换操作)
发送者A(taskA):发送数据,如果流水线(环形buffer)没空间了,则等待(tickdelay,进入delay链表),如果有空间,则写入值并返回。
A发送完成后,会查看接收者链表,如果非空,则将第一个任务从delay链表拖出来,使其进入就绪状态,用于接收数据。
A和B(发送和接收任务)的工作状态相反:
发送者B(taskB):接收数据,如果流水线(环形buffer)没数据了,则等待(tickdelay,进入delay链表),如果有数据,则接收值并返回。
B接收完成后,会查看发送者链表,如果非空,则将第一个任务从delay链表拖出来,使其进入就绪状态,用于发送数据。
注:
只有当环形buffer满了,才会将发送任务放到发送者链表,放进去的意义表示该任务进入堵塞状态
只有当环形buffer空了,才会将接收任务放到接收者链表,放进去的意义表示该任务进入堵塞状态
这说明:刚创建好队列的时候,队列为空,此时接收任务处于堵塞状态,使其堵塞是同步操作。
这时接收任务只能等待发送任务将其从delay链表拖出,或者等待超时。(此时任务出错,这也是为什么不用暂停链表的原因,暂停链表等于小黑屋,自己无法主动脱身)
所以,接收任务B永远得等待发送任务A准备好数据,才能处理该数据,这是互斥操作。
故:数据传输靠环形Buffer,同步互斥靠链表、链表数组的不断调控
2025.9.20更新:
队列集:就是队列集合,其本质也是一个队列,只不过其数据是其他子队列的句柄。
发送者在发送的时候,除了上面说的工作,还会在发送成功后检测该队列是否在某个队列集里面,如果在,则顺便将该队列的句柄放到对应的队列集里面,如下图:每写一个数据,队列集就会增加一个句柄(是有序增加,后续读取也是有序读取)。

这样的话,我们只要创建一个input_Task任务,按序处理这个队列集数据,就能处理好下面所有硬件的数据,而不需要为每一个硬件创建一个单独的数据处理任务,减少内存调度开销。而input_Task可以向功能应用层的函数发送(也是队列)处理好的数据,如角度、计数值什么的

底层不同函数两两之间还是用单个的队列传输,底层到上面的应用函数层,则通过数据集传输,而input_Task可根据任务需求定制。
2025.9.21更新:
队列集设置步骤(hal):
①在freertosconfig.h里面增加#define ConfigUSE_QUEUE_SETS 1
②新建所有的队列\队列集
③把所有队列放到队列集里。,
一定要先把队列放进去再创建任务,因为队列集的时候要保证队列里面没有数据,有数据就会放放置失败,后续input_task就不会处理该队列的数据,表象上就是任务不更新了。
④之后再创建任务、开启调度器等其他初始化任务
顺序:
①初始化函数(内部会创建对应外设的队列)
②应用任务创建,主要是顶层应用输出
③在应用任务内部,创建各类队列\队列集,创建好后立刻加入队列集,之后再创建底层驱动任务(如果是中断驱动,则不用创建),最后再创建input_task任务。
如下代码:
void MX_FREERTOS_Init(void) {
.....
xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);
//game1_task是顶层应用函数
.....
}
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);
/创建底层驱动函数
xTaskCreate(MPU6050_Task, "MPU6050Task", 128, NULL, osPriorityNormal, NULL);
xTaskCreate(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
...
}
小技巧:
①static让函数只局限在该文件使用,可以通过函数输出:
ir.c文件:
static QueueHandle_t g_xQueueIR;
QueueHandle_t GetQueueIR(void)
{
return g_xQueueIR;
}
game.c文件:
g_xQueueIR = GetQueueIR();
②任务不一定放在freertos.c里面,可以附属在其他文件,通过extern可以引用到。
③中断函数在外面可以随意放。
1699

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



