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;
		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);
		}
	}
}
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);
#endif
	g_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);
    }
}	
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值