FreeRTOS——队列(基于百问网DshanMCU-F103实现挡球板游戏改造)

一、队列的本质

队列中,数据的读写本质就是环形缓冲区,在这个基础上增加了互斥措施、阻塞-唤醒机制。

如果这个队列不传输数据,只调整"数据个数",它就是信号量(semaphore)。

如果信号量中,限定"数据个数"最大值为1,它就是互斥量(mutex)。

二、队列的实现

使用队列的流程:创建队列、写队列、读队列、删除队列。

2.1 创建队列

①动态分配内存

函数原型:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize ); 
参数说明
uxQueueLength队列长度,最多能存放多少个数据(item)
uxItemSize每个数据(item)的大小:以字节为单位
返回值非 0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为内存不足

示例

QueueHandle_t g_xQueuePlatform;/* 挡球板队列 */

struct input_data
{
	uint32_t dev;
	uint32_t val; /* UPT_MOVE_NONE,UPT_MOVE_RIGHT,UPT_MOVE_LEFT  */
};
/* 动态方式创建队列,长度为10,大小为input_data的大小 */
g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); 
②静态分配内存

函数原型:

QueueHandle_t xQueueCreateStatic(      
					UBaseType_t uxQueueLength, 
					UBaseType_t uxItemSize, 
					uint8_t *pucQueueStorageBuffer, 
					staticQueue_t *pxQueueBuffer);											
参数说明
uxQueueLength队列长度,最多能存放多少个数据(item)
uxItemSize每个数据(item)的大小:以字节为单位
pucQueueStorageBuffer如果 uxItemSize 非 0,pucQueueStorageBuffer 必须指向一个 uint8_t 数组,此数组大小至少为"uxQueueLength * uxItemSize"
pxQueueBuffer必须执行一个 StaticQueue_t 结构体,用来保存队列的数据结构
返回值非 0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为 pxQueueBuffer 为 NULL

示例

QueueHandle_t g_xQueueRotary;/* 旋转编码器队列 */

/* 静态方式创建队列 */
struct rotary_data
{
	int32_t cnt;
	int32_t speed;
};
/* 静态缓冲区,大小为10个struct rotary_data结构体的大小 */
static uint8_t g_ucQueueRotaryBuf[10*sizeof(struct rotary_data)];

/* 静态队列结构体,用于存储旋转编码器队列的数据结构 */
static StaticQueue_t g_xQueueRotaryStaticStruct;

/* 动态方式创建队列,长度为10,同时使用定义的缓冲区和结构体 */
g_xQueueRotary  = xQueueCreateStatic(10,sizeof(struct rotary_data),g_ucQueueRotaryBuf,&g_xQueueRotaryStaticStruct);

2.2 写队列

函数原型:

/* 等同于xQueueSendToBack 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait 
 */ 
BaseType_t xQueueSend( 
 QueueHandle_t xQueue, 
 const void *pvItemToQueue, 
 TickType_t xTicksToWait 
 ); 
 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/fe651ec482a54412a9ea35960d7bce95.png#pic_center)

/* 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait 
 */ 
BaseType_t xQueueSendToBack( 
 QueueHandle_t xQueue, 
 const void *pvItemToQueue, 
 TickType_t xTicksToWait 
 ); 
 
/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞 
 */ 
BaseType_t xQueueSendToBackFromISR( 
 QueueHandle_t xQueue, 
 const void *pvItemToQueue, 
 BaseType_t *pxHigherPriorityTaskWoken 
 ); 
 
/* 
 * 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait 
 */ 
BaseType_t xQueueSendToFront( 
 QueueHandle_t xQueue, 
 const void *pvItemToQueue, 
 TickType_t xTicksToWait 
 ); 
 
/* 
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞 
 */ 
BaseType_t xQueueSendToFrontFromISR( 
 QueueHandle_t xQueue, 
 const void *pvItemToQueue, 
 BaseType_t *pxHigherPriorityTaskWoken 
 ); 

参数说明
xQueue队列句柄,要写哪个队列
pvItemToQueue数据指针,这个数据的值会被复制进队列,复制多大的数据?在创建队列时已经指定了数据大小
xTicksToWait如果队列满则无法写入新数据,可以让任务进入阻塞状态,xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为 0,无法写入数据时函数会立刻返回;如果被设为 portMAX_DELAY,则会一直阻塞直到有空间可写
返回值pdPASS:数据成功写入了队列;errQUEUE_FULL:写入失败,因为队列满了

示例:

struct input_data idata;

/* 在IRReceiver_IRQTimes_Parse回调函数中,将数据写入队列中 */
	idata.dev = datas[0];
	idata.val = datas[2];
	xQueueSendToBackFromISR(g_xQueuePlatform,&idata,NULL);
	
/* 在IRReceiver_IRQ_Callback回调函数中,将数据写入队列中 */	
	idata.dev=0;
	idata.val=0;
	xQueueSendToBackFromISR(g_xQueuePlatform,&idata,NULL);

2.3 读队列

函数原型:

BaseType_t xQueueReceive( QueueHandle_t xQueue, 
 void * const pvBuffer, 
 TickType_t xTicksToWait ); 
 
BaseType_t xQueueReceiveFromISR( 
 QueueHandle_t xQueue, 
 void *pvBuffer, 
 BaseType_t *pxTaskWoken 
 ); 
参数说明
xQueue队列句柄,要读哪个队列
pvBufferbufer 指针,队列的数据会被复制到这个 buffer;复制多大的数据?在创建队列时已经指定了数据大小
xTicksToWait如果队列空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为 0,无法读出数据时函数会立刻返回;如果被设为 portMAX_DELAY,则会一直阻塞直到有数据可写
返回值pdPASS:从队列读出数据入;errQUEUE_EMPTY:读取失败,因为队列空了

示例:

/* 通过xQueueReceive读取挡球板队列的数据 */
if(pdPASS == xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY))
		{
			data = idata.val;
            if (data == 0x00)
            {
                data = last_data;
            }
            
            if (data == 0xe0) /* Left */
            {
                btnLeft();
            }

            if (data == 0x90)  /* Right */
            {
                btnRight();
            }
            last_data = data;

2.4 删除队列

删除队列的函数为 vQueueDelete(),只能删除使用动态方法创建的队列,它会释放内存。原型如下:

void vQueueDelete( QueueHandle_t xQueue ); 

三、功能实现

3.1 红外接收管实现

关键代码

 /************************************
 * 函数名称: IRReceiver_IRQ_Callback
 * 功能描述: 红外接收器的中断回调函数,记录中断时刻
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 ************************************/
void IRReceiver_IRQ_Callback(void)
{
    uint64_t time;
    static uint64_t pre_time = 0;
	struct input_data idata;	
        
	/* 1. 记录中断发生的时刻 */	
	time = system_get_ns();
    
    /* 一次按键的最长数据 = 引导码 + 32个数据"1" = 9+4.5+2.25*32 = 85.5ms
     * 如果当前中断的时刻, 举例上次中断的时刻超过这个时间, 以前的数据就抛弃
     */
    if (time - pre_time > 100000000) 
    {
        g_IRReceiverIRQ_Cnt = 0;
    }
    pre_time = time;
    
	g_IRReceiverIRQ_Timers[g_IRReceiverIRQ_Cnt] = time;

	/* 2. 累计中断次数 */
	g_IRReceiverIRQ_Cnt++;

	/* 3. 次数达标后, 解析数据, 放入buffer */
	if (g_IRReceiverIRQ_Cnt == 4)
	{
		/* 是否重复码 */
		if (isRepeatedKey())
		{
			/* device: 0, val: 0, 表示重复码 */
//			PutKeyToBuf(0);
//			PutKeyToBuf(0);
			/* 写队列 */
			idata.dev=0;
			idata.val=0;
			xQueueSendToBackFromISR(g_xQueuePlatform,&idata,NULL);
			g_IRReceiverIRQ_Cnt = 0;
		}
	}
	if (g_IRReceiverIRQ_Cnt == 68)
	{
		IRReceiver_IRQTimes_Parse();
		g_IRReceiverIRQ_Cnt = 0;
	}
}

/**************************************
 * 函数名称: IRReceiver_IRQTimes_Parse
 * 功能描述: 解析中断回调函数里记录的时间序列,得到的device和key放入环形缓冲区
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 0 - 成功, (-1) - 失败
 ***************************************/
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 input_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;
	}

//	PutKeyToBuf(datas[0]);
//	PutKeyToBuf(datas[2]);
	
	idata.dev = datas[0];
	idata.val = datas[2];
	xQueueSendToBackFromISR(g_xQueuePlatform,&idata,NULL);
    return 0;
}

game1.c

QueueHandle_t g_xQueuePlatform;/* 挡球板队列 */

/* 挡球板任务 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;
	
	struct input_data idata;

    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        /* 读取红外遥控器 */
		//if (0 == IRReceiver_Read(&dev, &data))
		
		if(pdPASS == xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY))
		{
			data = idata.val;
            if (data == 0x00)
            {
                data = last_data;
            }
            
            if (data == 0xe0) /* Left */
            {
                btnLeft();
            }

            if (data == 0x90)  /* Right */
            {
                btnRight();
            }
            last_data = data;

            
            // Hide platform
            draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
            draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
            
            // Move platform
            if(uptMove == UPT_MOVE_RIGHT)
                platformXtmp += 3;
            else if(uptMove == UPT_MOVE_LEFT)
                platformXtmp -= 3;
            uptMove = UPT_MOVE_NONE;
            
            // Make sure platform stays on screen
            if(platformXtmp > 250)
                platformXtmp = 0;
            else if(platformXtmp > g_xres - PLATFORM_WIDTH)
                platformXtmp = g_xres - PLATFORM_WIDTH;
            
            // Draw platform
            draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
            draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
            
            platformX = platformXtmp;
            
		}
    }
}

void game1_task(void *params)
{		
    uint8_t dev, data, last_data;
    
    g_framebuffer = LCD_GetFrameBuffer(&g_xres, &g_yres, &g_bpp);
    draw_init();
    draw_end();
    
	/* 创建队列:平台任务从里面读到红外数据 */
	g_xQueuePlatform = xQueueCreate(10, sizeof(struct input_data)); 
	
	
	uptMove = UPT_MOVE_NONE;

	ball.x = g_xres / 2;
	ball.y = g_yres - 10;
        
	ball.velX = -0.5;
	ball.velY = -0.6;
//	ball.velX = -1;
//	ball.velY = -1.1;

	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();
        //draw_end();
        vTaskDelay(50);
    }
}

freertos.c

  xTaskCreate(game1_task, "GameTask", 128, NULL, osPriorityNormal, NULL);

上机实验:

可以使用遥控器左键和右键控制挡球板的移动。

3.2 旋转编码器实现

关键代码:

game1.c

QueueHandle_t g_xQueueRotary;/* 旋转编码器队列 */
static uint8_t g_ucQueueRotaryBuf[10*sizeof(struct rotary_data)];
static StaticQueue_t g_xQueueRotaryStaticStruct;

static void RotaryEncoderTask(void *params)
{
	struct rotary_data rdata;
	struct input_data idata;
	int left;
	int cnt;
	
	while(1)
	{
		/* 读旋转编码器队列 */
		xQueueReceive(g_xQueueRotary,&rdata,portMAX_DELAY);
		
		/* 处理数据 */
		/* 判断速度:负数表示向左转动,正数表示向右 */
		if(rdata.speed < 0)
		{
			left = 1;
			rdata.speed  = 0 - rdata.speed;
		}else{
			left = 0; 
		}
//		cnt = rdata.speed/10;
//		if(!cnt)
//			cnt=1; 
		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(int i = 0; i < cnt; i++)
		{
			xQueueSend(g_xQueuePlatform,&idata,0);
		}	
	}
}
/*创建旋转编码器队列*/
g_xQueueRotary  = xQueueCreateStatic(10,sizeof(struct rotary_data),g_ucQueueRotaryBuf,&g_xQueueRotaryStaticStruct);

driver_rotary_encoder.c

static int32_t g_count = 0;
static int32_t g_speed = 0; /* 速度(正数表示顺时针旋转,负数表示逆时针旋转,单位:每秒转动次数) */

extern QueueHandle_t g_xQueueRotary;/* 旋转编码器队列 */

/***************************************
 * 函数名称: RotaryEncoder_IRQ_Callback
 * 功能描述: 旋转编码器的中断回调函数
 * 输入参数: 无
 * 输出参数: 无
 * 返 回 值: 无
 ****************************************/
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);
	/* 当前中断的时间 跟 上次中断的时间 < 2ms的话,认为是抖动 */
	if(time - pre_time < 2000000)
	{
		pre_time = time;
		return;
	}
	
    if (!RotaryEncoder_Get_S1())
        return;

    /* S1上升沿触发中断
     * S2为0表示逆时针转, 为1表示顺时针转
     */
    g_speed = (uint64_t)1000000000/(time - pre_time);
	
	/*time - pre_time 可能很小 趋于0*/
	if(g_speed == 0)
		g_speed = 1;
	
    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;
	xQueueSendToBackFromISR(g_xQueueRotary,&rdata,NULL);
}

上机实验:

在红外接收管实现的项目基础上实现旋转编码器的功能,能够通过遥控器左键和右键控制挡球板的移动和通过旋转旋钮实现挡球板的移动。

四、队列集

假设有2个输入设备:红外遥控器、旋转编码器,它们的驱动程序应该专注于“产生硬件数据”,不应该跟“业务有任何联系”。比如:红外遥控器驱动程序里,它只应该把键值记录下来、写入某个队列,它不应该把键值转换为游戏的控制键。在红外遥控器的驱动程序里,不应该有游戏相关的代码,这样,切换使用场景时,这个驱动程序还可以继续使用。

把红外遥控器的按键转换为游戏的控制键,应该在游戏的任务里实现。

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

InputTask 如何及时读取到多个队列的数据?要使用队列集。

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

a. 创建队列A,它的长度是n1

b. 创建队列B,它的长度是n2

c. 创建队列集S,它的长度是“n1+n2”

d. 把队列A、B加入队列集S

e. 这样,写队列A的时候,会顺便把队列A的句柄写入队列集S

f. 这样,写队列B的时候,会顺便把队列B的句柄写入队列集S

g. InputTask 先读取队列集 S,它的返回值是一个队列句柄,这样就可以知道哪个队列有数据了;然后InputTask再读取这个队列句柄得到数据。

创建队列集框图如下:

在这里插入图片描述

4.1 创建队列集

函数原型

QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength);
参数说明
uxQueueLength队列集长度,最多能存放多少个数据(队列句柄)
返回值非0:成功,返回句柄,以后使用句柄来操作队列;NULL:失败,因为内存不足

代码示例,传入的参数是各个队列的长度。

g_xQueueSetInput  = xQueueCreateSet(IR_QUEUE_LEN + ROTARY_QUEUE_LEN + MPU6050_QUEUE_LEN);

4.2 加入队列集

函数原型:

BaseType_t xQueueAddToSet( 
			QueueSetMemberHandle_t xQueueOrSemaphore, 
 			QueueSetHandle_t xQueueSet ); 
参数说明
xQueueOrSemaphore队列句柄,这个队列要加入队列集
xQueueSet队列集句柄
返回值pdTRUE:成功;pdFALSE:失败

示例(第一个参数是,创建的队列句柄,第二个参数是队列集的句柄):

/* 将创建的队列放入队列集 */
	xQueueAddToSet(g_xQueueIR,g_xQueueSetInput);
	xQueueAddToSet(g_xQueueRotary,g_xQueueSetInput);
	xQueueAddToSet(g_xQueueMPU6050,g_xQueueSetInput);

4.3 读队列集

QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet, 
 TickType_t const xTicksToWait ); 
参数说明
xQueueSet队列集句柄
xTicksToWait如果队列集空则无法读出数据,可以让任务进入阻塞状态,xTicksToWait 表示阻塞的最大时间(Tick Count)。如果被设为0,无法读出数据时函数会立刻返回;如果被设为portMAX_DELAY,则会一直阻塞直到有数据可写
返回值NULL:失败;队列句柄:成功

代码示例:

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();
			}
			
		}
	}
}

五、 功能完善

基于红外接收管与旋转编码器的项目,增加硬件设备MPU6050的功能,代码实现如下:

关键代码:

driver_mpu6050.c

/*在初始化代码段中添加创建MPU6050队列的代码*/
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_task
 * 功能描述: MPU6050任务,它循环读取MPU6050并把数值写入队列
 * 输入参数: params - 未使用
 * 输出参数: 无
 * 返 回 值: 0 - 成功, 其他值 - 失败

 *************************************************/
void MPU6050_Task(void *params)
{	
    int16_t AccX;
	struct mpu6050_data result;
	int ret;
	extern volatile int bInUsed;

    
    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.c

QueueHandle_t g_xQueuePlatform;/* 挡球板队列 */

static QueueHandle_t g_xQueueIR;
static QueueHandle_t g_xQueueRotary;
static QueueHandle_t g_xQueueMPU6050;

static QueueHandle_t g_xQueueSetInput;/* 输入设备的队列集 */

/* 挡球板任务 */
static void platform_task(void *params)
{
    byte platformXtmp = platformX;    
    uint8_t dev, data, last_data;
	
	struct input_data idata;

    // Draw platform
    draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
    draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
    
    while (1)
    {
        /* 读取红外遥控器 */
		//if (0 == IRReceiver_Read(&dev, &data))
		xQueueReceive(g_xQueuePlatform,&idata,portMAX_DELAY);

		uptMove = idata.val;
		if (data == 0x00)
		{
			data = last_data;
		}
		
		if (data == 0xe0) /* Left */
		{
			btnLeft();
		}

		if (data == 0x90)  /* Right */
		{
			btnRight();
		}
		last_data = data;
		
		// Hide platform
		draw_bitmap(platformXtmp, g_yres - 8, clearImg, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		// Move platform
		if(uptMove == UPT_MOVE_RIGHT)
			platformXtmp += 3;
		else if(uptMove == UPT_MOVE_LEFT)
			platformXtmp -= 3;
		uptMove = UPT_MOVE_NONE;
		
		// Make sure platform stays on screen
		if(platformXtmp > 250)
			platformXtmp = 0;
		else if(platformXtmp > g_xres - PLATFORM_WIDTH)
			platformXtmp = g_xres - PLATFORM_WIDTH;
		
		// Draw platform
		draw_bitmap(platformXtmp, g_yres - 8, platform, 12, 8, NOINVERT, 0);
		draw_flushArea(platformXtmp, g_yres - 8, 12, 8);
		
		platformX = platformXtmp;
		
		}
    }
/* 当读取队列集的句柄为g_xQueueIR时,处理红外接收管数据的代码 */
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);
}

/* 当读取队列集的句柄为g_xQueueRotary时,处理旋转编码器数据的代码 */
static void ProcessRotaryData()
{
	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(int i = 0; i < cnt; i++)
	{
		xQueueSend(g_xQueuePlatform,&idata,0);
	}	
}
/* 当读取队列集的句柄为g_xQueueMPU6050时,处理MPU6050数据的代码 */
static void ProcessMPU6050Data(void)
{
	struct mpu6050_data mdata;
	struct input_data idata;
	int left;
	
	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;
	}
	
	/* 写挡球板队列 */
	idata.dev = 2;
	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)
{		
    uint8_t dev, data, last_data;
    
	/* 创建队列,队列集,创建输入任务InputTask */
	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(InputTask, "InputTask", 128, NULL, osPriorityNormal, NULL);
	xTaskCreate(MPU6050_Task, "InputTask", 128, NULL, osPriorityNormal, NULL);
}

上机实验:

在红外接收管实现的项目基础上实现旋转编码器的功能,能够通过遥控器左键和右键控制挡球板的移动、通过旋转旋钮实现挡球板的移动和摇晃瑞士军刀开发板能够实现对挡球板的控制。

  • 40
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值