韦东山dshanmcu-爱代码爱编程
队列
参考《FreeRTOS入门与工程实践(基于DshanMCU-103)》里《第11章 队列(queue)》
资料来自韦东山freertos 教程,写博客的目的是为了做一个记录,不对请喷。
FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)_哔哩哔哩_bilibili
1. 数据传输的方法
1.1 任务之间如何传输数据
多种方法比较:
数据个数 | 互斥措施 | 阻塞-唤醒 | 使用场景 | |
全局变量 | 1 | 无 | 无 | 一读一写 |
环形缓冲区 | 多个 | 无 | 无 | 一读一写 |
队列 | 多个 | 有 | 有 | 多读多写 |
1.2 队列的本质
队列中,数据的读写本质就是环形缓冲区,在这个基础上增加了互斥措施、阻塞-唤醒机制。
如果这个队列不传输数据,只调整"数据个数",它就是信号量(semaphore)。
如果信号量中,限定"数据个数"最大值为1,它就是互斥量(mutex)。
画图演示队列的"阻塞-唤醒"机制。
使用队列的方法可以,做到互斥和同步的效果,提高程序的执行效率,避免浪费CPU资源。
2. 队列实验_多设备玩游戏(挡球板游戏)
实验目的:使用红外遥控器、旋转编码器玩游戏。
实现方案:
- 游戏任务:读取队列A获得控制信息,用来控制游戏
- 红外遥控器驱动:在中断函数里解析出按键后,写队列A
- 旋转编码器:
-
- 它的中断函数里解析出旋转编码器的状态,写队列B
- 它的任务函数里,读取队列B,构造好数据后写队列A
3. 队列集实验(挡球板游戏)
3.1 改进程序框架
上面程序框架,导致硬件驱动程序和应用业务处理逻辑没有分离。如果需要增加新的设备对挡球板的控制,我们还需要添加新的任务,这会导致系统资源紧张。
我们需要做到像于Rotary 旋转编码器数据处理一样的程序框架,应用处理和驱动分离,需要有一个任务来统一处理传感器的数据,然后上传给App使用。我们可以利用队列集来完成这个框架。
4. 队列实验_分发数据给多个任务(赛车游戏)
红外遥控器的中断函数解析出按键值后,写入3个队列:3个赛车任务读取其中一个队列得到按键数据。
代码思路创建三个队列,赛车任务不断地去读取队列执行业务逻辑。
第一版代码,最傻实现(学生时期的我就是这样写代码的)
思路过程:
赛车任务里面需要不断地去查询自己的队列,通过任务的名字去判断我们需要读取的队列。这样做的坏处是如果需要加赛车很麻烦。
任务函数
static void CarTask(void *params)
{
char *task_1 = "car1";
char *task_2 = "car2";
char *task_3 = "car3";
char *task_name;
uint8_t re1;
uint8_t re2;
uint8_t re3;
struct car *pcar = params;
ir_data_t idata;
#if 0
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(ir_data_t));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
#endif
/* 显示汽车 */
ShowCar(pcar);
task_name = pcTaskGetName(NULL);
re1 = strcmp(task_name, task_1);
re2 = strcmp(task_name, task_2);
re3 = strcmp(task_name, task_3);
while (1)
{
#if 0
/* 读取按键值:读队列 */
xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
#endif
if (re1 == 0)
{
// printf("%s\r\n",task_name);
/* 读取按键值:读队列 */
xQueueReceive(g_queue_car1_handler, &idata, portMAX_DELAY);
}
if (re2 == 0)
{
/* 读取按键值:读队列 */
xQueueReceive(g_queue_car2_handler, &idata, portMAX_DELAY);
// printf("%s\r\n",task_name);
}
if (re3 == 0)
{
// printf("%s\r\n",task_name);
/* 读取按键值:读队列 */
xQueueReceive(g_queue_car2_handler, &idata, portMAX_DELAY);
}
/* 控制汽车往右移动 */
if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x += 20;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
}
}
}
}
中断函数
static DispatchKey(ir_data_t *idata)
{
extern QueueHandle_t g_queue_car1_handler;
extern QueueHandle_t g_queue_car2_handler;
extern QueueHandle_t g_queue_car3_handler;
xQueueSendFromISR(g_queue_car1_handler, idata, NULL);
xQueueSendFromISR(g_queue_car2_handler, idata, NULL);
xQueueSendFromISR(g_queue_car3_handler, idata, NULL);
}
第二版代码
加入一个注册中心的概念(实际产品项目中也经常用到这种思想),创建一个内存池里面保存队列的句柄,把想要使用的队列放入这个内存池中。
中断函数
static QueueHandle_t g_xQueues[10]; //队列内存池
static int g_queue_cnt = 0;
void RegisterQueueHandle(QueueHandle_t queueHandler){
if(g_queue_cnt != 10){
g_xQueues[g_queue_cnt++] = queueHandler;
}
}
static DispatchKey(ir_data_t *idata)
{
#if 0
extern QueueHandle_t g_queue_car1_handler;
extern QueueHandle_t g_queue_car2_handler;
extern QueueHandle_t g_queue_car3_handler;
xQueueSendFromISR(g_queue_car1_handler, idata, NULL);
xQueueSendFromISR(g_queue_car2_handler, idata, NULL);
xQueueSendFromISR(g_queue_car3_handler, idata, NULL);
#endif
for (uint8_t i = 0; i < g_queue_cnt; i++)
{
if(g_queue_cnt == 10){
break;
}else{
xQueueSendFromISR(g_xQueues[i],idata,NULL);
}
}
}
任务函数
static void CarTask(void *params)
{
#if 0
char *task_1 = "car1";
char *task_2 = "car2";
char *task_3 = "car3";
char *task_name;
uint8_t re1;
uint8_t re2;
uint8_t re3;
#endif
struct car *pcar = params;
ir_data_t idata;
/* 创建自己的队列 */
QueueHandle_t xQueueIR = xQueueCreate(10, sizeof(ir_data_t));
/* 注册队列 */
RegisterQueueHandle(xQueueIR);
/* 显示汽车 */
ShowCar(pcar);
#if 0
task_name = pcTaskGetName(NULL);
re1 = strcmp(task_name, task_1);
re2 = strcmp(task_name, task_2);
re3 = strcmp(task_name, task_3);
#endif
while (1)
{
/* 读取按键值:读队列 */
xQueueReceive(xQueueIR, &idata, portMAX_DELAY);
#if 0
if (re1 == 0)
{
// printf("%s\r\n",task_name);
/* 读取按键值:读队列 */
xQueueReceive(g_queue_car1_handler, &idata, portMAX_DELAY);
}
if (re2 == 0)
{
/* 读取按键值:读队列 */
xQueueReceive(g_queue_car2_handler, &idata, portMAX_DELAY);
// printf("%s\r\n",task_name);
}
if (re3 == 0)
{
// printf("%s\r\n",task_name);
/* 读取按键值:读队列 */
xQueueReceive(g_queue_car2_handler, &idata, portMAX_DELAY);
}
#endif
/* 控制汽车往右移动 */
if (idata.val == pcar->control_key)
{
if (pcar->x < g_xres - CAR_LENGTH)
{
/* 隐藏汽车 */
HideCar(pcar);
/* 调整位置 */
pcar->x += 20;
if (pcar->x > g_xres - CAR_LENGTH)
{
pcar->x = g_xres - CAR_LENGTH;
}
/* 重新显示汽车 */
ShowCar(pcar);
}
}
}
}