当前位置: 首页 > news >正文

FreeRTOS_同步互斥与通信_队列集_学习笔记

FreeRTOS_同步互斥与通信_环形buffer、队列_学习笔记

5.5 队列集

要支持多个输入设备时,我们需要实现一个“InputTask”,它读取各个设备的队列,得到数据后再分别转换为游戏的控制键。
InputTask如何及时读取到多个队列的数据?要使用队列集。

队列集的本质也是队列,只不过里面存放的是“队列句柄”。使用过程如下:

创建队列A,它的长度是n1
创建队列B,它的长度是n2
创建队列集S,它的长度是“n1+n2”
把队列A、B加入队列集S
这样,写队列A的时候,会顺便把队列A的句柄写入队列集S
这样,写队列B的时候,会顺便把队列B的句柄写入队列集S
InputTask先读取队列集S,它的返回值是一个队列句柄,这样就可以知道哪个队列有有数据了;然后InputTask再读取这个队列句柄得到数据。
创建队列集:

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

加入队列到队列集:

BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet );

读取队列集:

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, TickType_t const xTicksToWait);

5.5.1 多控制器实验

在这里插入图片描述

对上一篇笔记的代码进行修改,实现红外遥控和旋转编码器对游戏的共同控制。
在上一篇代码中,直接在中断响应函数IRReceiver_IRQ_Callback中写队列A,
在本例程中,先通过RotaryEncoder_IRQ_Callback(void)写队列B,再通过任务RotaryEncoder_Task读队列B,经过处理后再写队列A

在game1.c中做以下修改:

QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */static uint8_t g_ucQueueRotaryBuf[10*sizeof(struct rotary_data)]; //定义全局变量,保存队列buffer
static StaticQueue_t g_xQueueRotaryStaticStruct; //定义队列/* 挡球板任务 */
static void platform_task(void *params)
{...while (1){//if (0 == IRReceiver_Read(&dev, &data))xQueueReceive(g_xQueuePlatform, &idata, portMAX_DELAY);...}...
}
static void RotaryEncoderTask(void *params)//旋转编码器的读写队列和解析
{struct rotary_data rdata;struct input_data idata;int left;int i, cnt;while (1){/* 读旋转编码器队列 */xQueueReceive(g_xQueueRotary, &rdata, portMAX_DELAY);				/* 处理数据 *//* 判断速度: 负数表示向左转动, 正数表示向右转动 */if (rdata.speed < 0){left = 1;rdata.speed = 0 - rdata.speed;}else{left = 0;}if (rdata.speed > 100)cnt = 4;else if (rdata.speed > 50)cnt = 2;elsecnt = 1;/* 写挡球板队列 */idata.dev = 1;idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;for (i = 0; i < cnt; i++){xQueueSend(g_xQueuePlatform, &idata, 0);}}
}
void game1_task(void *params)
{		.../* 创建队列 */g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); //动态创建队列g_xQueueRotary   = xQueueCreateStatic(10, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct); //静态创建队列// rotary_data{cnt,speed}和input_data{dev,val}这两个数据结构体在另一个文件中被定义//g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct在全局变量区域被定义了/*创建旋转编码器的任务,读队列B,解析数据,再写队列A*/xTaskCreate(RotaryEncoderTask, "RotaryEncoderTask", 128, NULL, osPriorityNormal, NULL);...
}

在旋转编码器的驱动程序中也需要进行读写队列的修改,driver_rotary_encoder.c如下:

extern QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */
void RotaryEncoder_IRQ_Callback(void)
{uint64_t time;static uint64_t pre_time = 0;struct rotary_data rdata;/* 1. 记录中断发生的时刻 */	time = system_get_ns();/* 上升沿触发: 必定是高电平 * 防抖*/mdelay(2);if (!RotaryEncoder_Get_S1())return;/* S1上升沿触发中断* S2为0表示逆时针转, 为1表示顺时针转*/g_speed = (uint64_t)1000000000/(time - pre_time);if (RotaryEncoder_Get_S2()){g_count++;}else{g_count--;g_speed = 0 - g_speed;}pre_time = time;/* 写队列 */rdata.cnt   = g_count;rdata.speed = g_speed;xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}

5.5.2 队列集改良实验

上一个实验中,红外写队列直接作用于任务队列,一般不推荐这么做,因为不方便代码移植。较好的做法是旋转编码器那边的做法,硬件部分管硬件,任务部分管任务。即,旋转编码器中断响应写队列(RotaryEncoder_IRQ_Callback)和旋转编码器任务读写队列(RotaryEncoderTask)分开,这样当旋转编码器要控制别的任务时,只需要修改后者即可,前者不需要修改。

现在将控制器增加为3个,每个控制器都有一个队列并各有一个任务写队列,并用一个Input_Task读取这三个队列并写挡球板队列。Input_Task读取三个队列的方法有两种:轮询和队列集。
如果采用轮询,依次进行读队列操作,那么将不可以设定超时时间(因为超时后才能读下一个队列),这种没有阻塞的轮询会极度浪费CPU资源。
因此使用队列集是合适的,队列集的创建与读写在本文开头说明,在此不再赘述。(队列集s是一个存放队列句柄的队列,写入队列a的时候会顺便把队列句柄写入其所在的队列集s)

因此本实验的改进思路就清晰了:三个控制器分别写三个队列abc,并把abc放到一个队列集s中。Iuput_Task任务读队列集s得到队列句柄,再读队列句柄得到队列abc,再处理数据写入挡球板队列。
在这里插入图片描述
红外接收器驱动程序中做如下修改:

static QueueHandle_t g_xQueueIR; /* 红外队列 */
/*返回红外接收器驱动程序里的队列句柄*/
QueueHandle_t GetQueueIR(void)
{return g_xQueueIR;
}
/*在初始化函数中添加创建队列的语句*/
void IRReceiver_Init(void)
{
#if 0/*Configure GPIO pin : PB10 */GPIO_InitStruct.Pin = GPIO_PIN_10;GPIO_InitStruct.Mode = GPIO_MODE_EVT_RISING_FALLING;GPIO_InitStruct.Pull = GPIO_PULLUP;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
#endifg_xQueueIR = xQueueCreate(IR_QUEUE_LEN, sizeof(struct ir_data));
}
/* 在终端回调函数中写队列 */
static int IRReceiver_IRQTimes_Parse(void)
{uint64_t time;int i;int m, n;unsigned char datas[4];unsigned char data = 0;int bits = 0;int byte = 0;struct ir_data idata;/* 1. 判断前导码 : 9ms的低脉冲, 4.5ms高脉冲  */time = g_IRReceiverIRQ_Timers[1] - g_IRReceiverIRQ_Timers[0];if (time < 8000000 || time > 10000000){return -1;}time = g_IRReceiverIRQ_Timers[2] - g_IRReceiverIRQ_Timers[1];if (time < 3500000 || time > 55000000){return -1;}/* 2. 解析数据 */for (i = 0; i < 32; i++){m = 3 + i*2;n = m+1;time = g_IRReceiverIRQ_Timers[n] - g_IRReceiverIRQ_Timers[m];data <<= 1;bits++;if (time > 1000000){/* 得到了数据1 */data |= 1;}if (bits == 8){datas[byte] = data;byte++;data = 0;bits = 0;}}/* 判断数据正误 */datas[1] = ~datas[1];datas[3] = ~datas[3];	if ((datas[0] != datas[1]) || (datas[2] != datas[3])){g_IRReceiverIRQ_Cnt = 0;return -1;}/* 写队列 */idata.dev = datas[0];idata.val = datas[2];	xQueueSendFromISR(g_xQueueIR, &idata, NULL);	return 0;
}

旋转编码器驱动程序中做如下修改:

static QueueHandle_t g_xQueueRotary;   /* 旋转编码器队列 */
static uint8_t g_ucQueueRotaryBuf[ROTARY_QUEUE_LEN*sizeof(struct rotary_data)];//用来静态分配的参数
static StaticQueue_t g_xQueueRotaryStaticStruct;
/*返回旋转编码器驱动程序里的队列句柄*/
QueueHandle_t GetQueueRotary(void)
{return g_xQueueRotary;
}
/*在初始化函数中创建队列*/
void RotaryEncoder_Init(void)
{g_xQueueRotary   = xQueueCreateStatic(ROTARY_QUEUE_LEN, sizeof(struct rotary_data), g_ucQueueRotaryBuf, &g_xQueueRotaryStaticStruct);
}
/*在中断回调函数中写队列*/
void RotaryEncoder_IRQ_Callback(void)
{uint64_t time;static uint64_t pre_time = 0;struct rotary_data rdata;   /* 1. 记录中断发生的时刻 */	time = system_get_ns();/* 上升沿触发: 必定是高电平 * 防抖*/mdelay(2);if (!RotaryEncoder_Get_S1())return;/* S1上升沿触发中断* S2为0表示逆时针转, 为1表示顺时针转*/g_speed = (uint64_t)1000000000/(time - pre_time);if (RotaryEncoder_Get_S2()){g_count++;}else{g_count--;g_speed = 0 - g_speed;}pre_time = time;/* 写队列 */rdata.cnt   = g_count;rdata.speed = g_speed;xQueueSendFromISR(g_xQueueRotary, &rdata, NULL);
}

MPU6050驱动程序中做如下修改:

#define MPU6050_QUEUE_LEN 10 /*定义队列长度*/
struct mpu6050_data{int32_t angle_x;};  /*定义陀螺仪数据*/
static QueueHandle_t g_xQueueMPU6050; /*陀螺仪队列*/
/*返回MPU6050驱动程序里的队列句柄*/
QueueHandle_t GetQueueMPU6050(void)
{return g_xQueueMPU6050;
}
/*在初始化函数中添加创建队列的语句*/
int MPU6050_Init(void)
{MPU6050_WriteRegister(MPU6050_PWR_MGMT_1, 0x00);	//解除休眠状态MPU6050_WriteRegister(MPU6050_PWR_MGMT_2, 0x00);MPU6050_WriteRegister(MPU6050_SMPLRT_DIV, 0x09);MPU6050_WriteRegister(MPU6050_CONFIG, 0x06);MPU6050_WriteRegister(MPU6050_GYRO_CONFIG, 0x18);MPU6050_WriteRegister(MPU6050_ACCEL_CONFIG, 0x18);g_xQueueMPU6050 = xQueueCreate(MPU6050_QUEUE_LEN, sizeof(struct mpu6050_data));return 0;
}
/*解析MPU6050数据,通过原生输入(加速度)得到x方向的角度*/
void MPU6050_ParseData(int16_t AccX, int16_t AccY, int16_t AccZ, int16_t GyroX, int16_t GyroY, int16_t GyroZ, struct mpu6050_data *result)
{if (result){result->angle_x = (int32_t)(acos((double)((double)(AccX + MPU6050_X_ACCEL_OFFSET) / 16384.0)) * 57.29577);}
}
/*陀螺仪任务,将当前的角度处理后写队列*/
void MPU6050_Task(void *params)
{//MPU6050_Init(); 把陀螺仪初始化函数放到freertos的入口执行int16_t AccX; // 这里只关注x方向加速度struct mpu6050_data result;int ret;extern volatile int bInUsed;// 这是判断LCD是否占用的全局变量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);}
}

在game1程序中做如下修改:

static QueueSetHandle_t g_xQueueSetInput; /* 输入设备的队列集 */
static QueueHandle_t g_xQueuePlatform; /* 挡球板队列 */
static QueueHandle_t g_xQueueIR; /*红外接收器队列*/
static QueueHandle_t g_xQueueRotary; /*旋转编码器队列*/
static QueueHandle_t g_xQueueMPU6050; /*陀螺仪队列*/
/*挡球板任务*/
static void platform_task(void *params){...}
/*读取红外遥控器键值并转换为游戏控制键,写入挡球板队列*/
static void ProcessIRData(void)
{struct ir_data idata; //创建红外接收数据static struct input_data input; // 创建挡球板数据xQueueReceive(g_xQueueIR, &idata, 0); // 读红外接收器队列/*根据红外接收器数据修改挡球板数据*/if (idata.val == IR_KEY_LEFT){input.dev = idata.dev;input.val = UPT_MOVE_LEFT;}else if (idata.val == IR_KEY_RIGHT){input.dev = idata.dev;input.val = UPT_MOVE_RIGHT;}else if (idata.val == IR_KEY_REPEAT){/* 保持不变 */;}else{input.dev = idata.dev;input.val = UPT_MOVE_NONE;}	xQueueSend(g_xQueuePlatform, &input, 0); //写挡球板队列
}
/*读取旋转编码器数据并转换为游戏控制键,写入挡球板队列*/
static void ProcessRotaryData(void)
{struct rotary_data rdata; // 创建旋转编码器数据struct input_data idata; // 创建挡球板数据int left;int i, cnt;xQueueReceive(g_xQueueRotary, &rdata, 0); // 读旋转编码器队列			/*根据旋转编码器数据修改挡球板数据*//* 判断速度: 负数表示向左转动, 正数表示向右转动 */if (rdata.speed < 0){left = 1;rdata.speed = 0 - rdata.speed;}else{left = 0;}	if (rdata.speed > 100){cnt = 4;}	else if (rdata.speed > 50){cnt = 2;}else{cnt = 1;}idata.dev = 1;idata.val = left ? UPT_MOVE_LEFT : UPT_MOVE_RIGHT;for (i = 0; i < cnt; i++){xQueueSend(g_xQueuePlatform, &idata, 0); // 写挡球板队列}
}/*读取MPU6050D的角度值并转换为游戏控制键,写入挡球板队列*/
static void ProcessMPU6050Data(void)
{struct mpu6050_data mdata; // 创建陀螺仪数据struct input_data idata; // 创建挡球板数据xQueueReceive(g_xQueueMPU6050, &mdata, 0); // 读陀螺仪队列/*根据陀螺仪数据修改挡球板数据*//* 判断角度, 大于90度表示往左移动挡球板, 小于90度表示往右移动挡球板 */if (mdata.angle_x > 90){idata.val = UPT_MOVE_LEFT;}else if(mdata.angle_x < 90){idata.val = UPT_MOVE_RIGHT;}else{idata.val = UPT_MOVE_NONE;}xQueueSend(g_xQueuePlatform, &idata, 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();}			}}
}
void game1_task(void *params)
{		    g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);draw_init();draw_end();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);uptMove = UPT_MOVE_NONE;ball.x = g_xres / 2;ball.y = g_yres - 10;       ball.velX = -0.5;ball.velY = -0.6;blocks = pvPortMalloc(BLOCK_COUNT);memset(blocks, 0, BLOCK_COUNT);lives = lives_origin = 3;score = 0;platformX = (g_xres / 2) - (PLATFORM_WIDTH / 2);xTaskCreate(platform_task, "platform_task", 128, NULL, osPriorityNormal, NULL);while (1){game1_draw();vTaskDelay(50);}
}	

相关文章:

  • MySQL实战——主从异步复制搭建(一主一从)
  • 解决ModuleNotFoundError: No module named ‘open_clip‘问题
  • 三维空间坐标系变换(旋转平移)
  • python实现520表白图案
  • LLama3 | 一. 本地 Web Demo 部署
  • 手写tomcat(Ⅱ)——Socket通信+tomcat静态资源的获取
  • python手写数字识别(PaddlePaddle框架、MNIST数据集)
  • 嵌入式科普(18)Ubuntu在移动硬盘的安装和启动
  • JavaWeb基础(HTML,CSS,JS)
  • 大数据运维面临的常见问题及其解决方案和步骤
  • 力扣HOT100 - 136. 只出现一次的数字
  • 继续分析开发人员容易被骗的原因和防范措施
  • 3D工业视觉
  • 功耗相关总结
  • Vue3实战笔记(33)—组件传值props终章
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • CSS 三角实现
  • ECS应用管理最佳实践
  • miaov-React 最佳入门
  • OSS Web直传 (文件图片)
  • SpiderData 2019年2月13日 DApp数据排行榜
  • v-if和v-for连用出现的问题
  • 技术发展面试
  • 前嗅ForeSpider中数据浏览界面介绍
  • 区块链共识机制优缺点对比都是什么
  • 项目管理碎碎念系列之一:干系人管理
  • 学习Vue.js的五个小例子
  • 主流的CSS水平和垂直居中技术大全
  • 函数计算新功能-----支持C#函数
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #Z0458. 树的中心2
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (1)STL算法之遍历容器
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (done) 两个矩阵 “相似” 是什么意思?
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (分享)自己整理的一些简单awk实用语句
  • (三)mysql_MYSQL(三)
  • (十)T检验-第一部分
  • (一)kafka实战——kafka源码编译启动
  • (一)基于IDEA的JAVA基础12
  • (转)IOS中获取各种文件的目录路径的方法
  • (转)负载均衡,回话保持,cookie
  • .gitignore文件---让git自动忽略指定文件
  • .NET Framework与.NET Framework SDK有什么不同?
  • .net MySql
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET 发展历程
  • .Net面试题4
  • .net生成的类,跨工程调用显示注释
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .NET中使用Protobuffer 实现序列化和反序列化