FreeRTOS 数据传输方法(环形buffer,队列的本质)队列实验—多设备玩游戏

发布于:2024-11-03 ⋅ 阅读:(43) ⋅ 点赞:(0)

 数据传输方法

环形buffer

环形buffer的本质就是一个循环队列,但是有一些不同 

  1. :当头指针和尾指针相等时,表示缓冲区为空。
  2. :当尾指针的下一个位置等于头指针时,表示缓冲区已满(在环形结构中,尾指针要循环到数组的开始位置)。

队列

队列的本质

在嵌入式中队列运行就像一个流水线工作,其中有两个工人

A的作用

(1)写队列

(2)如果队列已满则可以进行对任务A处理(具体是把任务A放入SenderList链表以及阻塞Block链表

(3)任务B如何被唤醒,要么由任务A唤醒任务B,要么结束阻塞,通过Tick中断唤醒B,把任务B放入ReadyList

B的作用

(1)查看流水线有无任务(产品),没有的话将进行阻塞(眯一会)(相当于把任务放入Receiverlist链表以及阻塞Block链表

(2)读队列

(3)任务B读队列,唤醒任务A,通过Tick中断唤醒A,把任务A放入ReadyList

队列的阻塞访问

只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个
任务读写队列。
任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。口语化地
说,就是可以定个闹钟:如果能读写了就马上进入就绪态,否则就阻塞直到超时。
某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞
的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间
到之后它也会进入就绪态。
既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入
阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪
态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态
跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还
可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有
空间,则时间到之后它也会进入就绪态。
既然写队列的任务个数没有限制,那么当多个任务写 " 满队列 " 时,这些任务都会进入阻
塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?
优先级最高的任务
如果大家的优先级相同,那等待时间最久的任务会进入就绪态

队列函数

创建

队列的创建有两种方法:动态分配内存、静态分配内存

动态分配内存
动态分配内存: xQueueCreate ,队列的内存在函数内部动态分配
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

参数说明:

  1. uxQueueLength:队列中可以存放的最大项数(队列长度)。
  2. uxItemSize:每个队列项的大小(以字节为单位)。如果要存储的项是结构体或者其他数据类型,确保传入正确的大小。

返回值:

  • 返回一个队列句柄(QueueHandle_t),如果创建成功,句柄有效;如果失败,返回 NULL
静态分配内存
静态分配内存: xQueueCreateStatic ,队列的内存要事先分配好
QueueHandle_t xQueueCreateStatic(
 UBaseType_t uxQueueLength,
 UBaseType_t uxItemSize,
 uint8_t *pucQueueStorageBuffer,
 StaticQueue_t *pxQueueBuffer
 );

参数说明:

  1. uxQueueLength:队列可以存放的最大项数(队列长度)。
  2. uxItemSize:每个队列项的大小(以字节为单位)。
  3. pucQueueStorageBuffer:指向用于存储队列项的缓冲区的指针。这个缓冲区的大小应该为 uxQueueLength * uxItemSize
  4. pxQueueBuffer:指向一个 StaticQueue_t 结构体的指针,用于存储队列的控制结构。此结构体在 FreeRTOS 中被用于管理队列的状态。

返回值:

  • 返回一个队列句柄(QueueHandle_t),如果创建成功,句柄有效;如果失败,返回 NULL
写队列
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
 QueueHandle_t xQueue,
 const void *pvItemToQueue,
 TickType_t xTicksToWait
 );
/* 
* 往队列尾部写入数据,如果没有空间,阻塞时间为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
 );

 

参数说明:

  1. xQueue:要发送项的队列句柄。
  2. pvItemToQueue:指向要发送到队列的项的指针。此项的数据类型应与队列在创建时指定的数据类型匹配。
  3. xTicksToWait:如果队列已满,调用任务将等待的时间(以滴答数为单位)。可以使用 portMAX_DELAY 表示无限等待,或使用 0 表示不等待。

返回值:

  • 返回值为 BaseType_t,表示发送操作的结果。可能的返回值包括:
    • pdPASS:成功将项发送到队列。
    • errQUEUE_FULL:队列已满,未能发送项(且没有设置等待时间或等待时间已到)。
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];
	
	if (datas[2] == 0xe0)
		idata.val = UPT_MOVE_LEFT;
	else if (datas[2] == 0x90)
		idata.val = UPT_MOVE_RIGHT;
	else
		idata.val = UPT_MOVE_NONE;
	g_last_val = idata.val;
	xQueueSendFromISR(g_xQueuePlatform, &idata, NULL);
	
    return 0;
}

读队列
BaseType_t xQueueReceive( QueueHandle_t xQueue,
 void * const pvBuffer,
 TickType_t xTicksToWait );
BaseType_t xQueueReceiveFromISR(
 QueueHandle_t xQueue,
 void *pvBuffer,
 BaseType_t *pxTaskWoken
 );
1. xQueueReceive

参数说明

  • xQueue:要从中接收数据的队列句柄。
  • pvBuffer:指向接收数据存储位置的指针。接收到的数据将存储在这里。
  • xTicksToWait:如果队列为空,任务等待的最大时间(以滴答为单位)。可以使用 portMAX_DELAY 表示无限等待。

返回值

  • 如果成功接收数据,返回 pdPASS;如果超时或发生错误,返回 errQUEUE_EMPTY
2. xQueueReceiveFromISR

参数说明

  • xQueue:要从中接收数据的队列句柄。
  • pvBuffer:指向接收数据存储位置的指针。
  • pxTaskWoken:指向 BaseType_t 类型的指针,如果接收数据后需要唤醒一个任务,设置为 pdTRUE,否则设置为 pdFALSE

返回值

  • 与 xQueueReceive 相似,成功时返回 pdPASS,失败时返回 errQUEUE_EMPTY
/* 挡球板任务 */
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;
            
		// 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;            		
    }
}

注意

写队列就是在中断中写取数据,在进行复杂的操作时,连接一个中间的任务处理数据然后再提交

/* 挡球板任务 */
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;
            
		// 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;            		
    }
}

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;
		}
		
		//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 (i = 0; i < cnt; i++)
		{
			xQueueSend(g_xQueuePlatform, &idata, 0);
		}
	}
}