五、freeRTOS_队列的使用

目录

1. 队列的理论讲解

1.1 常规操作

 2. 队列的常规使用

3. 队列集


1. 队列的理论讲解

1.1 常规操作


队列的简化操如入下图所示,从此图可知:
队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
每个数据大小固定
创建队列时就要指定长度、数据大小
数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部

也可以强制写队列头部:覆盖头部数据

本节源码:13_freertos_example_queue,基于12_freertos_example_sync_exclusion修改。

 猜测队列中应该有:

  • 有等待数据的任务列表 list1
  • 有等待写数据的任务列表 list2
  • 指向内存的 *buf

 以下为xQueueCreate 队列源码:

  • 有头部,有尾部,是一个环形buf。
  • 有两个链表,等待发送数据,等待接收数据

创建环形队列的图解: 

 写队列

读队列

 

往头部写队列,xQueueSendToFront 这个函数写的数据,会最先被读出来,因为数据被插到了队列的头部。

假如已经在0、1位置已经写入了数据,之后再头部写入数据会在N-1③位置,在向后移动指针pcReadFrom,pcReadFrom -= ItemSize,因为pcReadFrom表示的是上一次的位置,所以向后移动之后再次读到的数据为N-1③位置。

有很多个写队列,或者有很多个读队列时,都有等待函数,那现在该唤醒谁呢?

一般会先执行优先级最高的那个,或者等待时间最长的那个,理所应当是这样的。

 2. 队列的常规使用

本节源码:13_freertos_example_queue,基于12_freertos_example_sync_exclusion修改。

队列使用了同步:

任务1、任务2使用队列实现了同步。

任务1计算完数值之后,写入队列中,任务2读取队列,当队列中有数据的时候就会打印出来,当队列中没有数据的时候就会进入到阻塞状态,任务2在等待数据的过程中,就不会参与cpu的调度。

static int sum = 0;
static volatile int flagCalcEnd = 0;
static QueueHandle_t xQueueCalcHandle;

void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 10000000; i++)
			sum++;
		//printf("1");
		//flagCalcEnd = 1;
		//vTaskDelete(NULL);
		xQueueSend(xQueueCalcHandle, &sum, portMAX_DELAY);
		sum = 1;
	}
}

void Task2Function(void * param)
{
	int val;
	
	while (1)
	{
		//if (flagCalcEnd)
		flagCalcEnd = 0;
		xQueueReceive(xQueueCalcHandle, &val, portMAX_DELAY);
		flagCalcEnd = 1;
		printf("sum = %d\r\n", val);
	}
}

/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;
		
#ifdef DEBUG
  debug();
#endif

	prvSetupHardware();

	printf("Hello, world!\r\n");

	xQueueCalcHandle = xQueueCreate(2, sizeof(int));
	if (xQueueCalcHandle == NULL)
	{
		printf("can not create queue\r\n");
	}

	InitUARTLock();

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}

运行结果,在 “四、freeRTOS_同步互斥与通信概述” 中,任务2与任务1竞争cpu的资源,导致任务1实现同步花了4s,现在使用队列同步的方法来做,任务2在等待数据的过程中,就不会参与cpu的调度,此时任务2大概花费了2s。

URAT1串口打印

队列使用了互斥:

 任务3、任务4使用队列实现了互斥。

临界资源是 TaskGenericFunction() 函数,使用初始化串口锁,上锁,下锁,来完成队列的互斥。

/*-----------------------------------------------------------*/

static int sum = 0;
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueCalcHandle;
static QueueHandle_t xQueueUARTcHandle;


int InitUARTLock(void)
{	
	int val;
	xQueueUARTcHandle = xQueueCreate(1, sizeof(int));
	if (xQueueUARTcHandle == NULL)
	{
		printf("can not create queue\r\n");
		return -1;
	}
	xQueueSend(xQueueUARTcHandle, &val, portMAX_DELAY);
	return 0;
}

void GetUARTLock(void)
{	
	int val;
	xQueueReceive(xQueueUARTcHandle, &val, portMAX_DELAY);
}

void PutUARTLock(void)
{	
	int val;
	xQueueSend(xQueueUARTcHandle, &val, portMAX_DELAY);
}


void Task1Function(void * param)
{
	volatile int i = 0;
	while (1)
	{
		for (i = 0; i < 10000000; i++)
			sum++;
		//printf("1");
		//flagCalcEnd = 1;
		//vTaskDelete(NULL);
		xQueueSend(xQueueCalcHandle, &sum, portMAX_DELAY);
		sum = 1;
	}
}

void Task2Function(void * param)
{
	int val;
	
	while (1)
	{
		//if (flagCalcEnd)
		flagCalcEnd = 0;
		xQueueReceive(xQueueCalcHandle, &val, portMAX_DELAY);
		flagCalcEnd = 1;
		printf("sum = %d\r\n", val);
	}
}

void TaskGenericFunction(void * param)
{
	while (1)
	{
		GetUARTLock();
		printf("%s\r\n", (char *)param);
		   // task 3 is waiting
		PutUARTLock(); /* task 3 ==> ready, task 4 is running  */
		vTaskDelay(1);
	}
}


/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;
		
#ifdef DEBUG
  debug();
#endif

	prvSetupHardware();

	printf("Hello, world!\r\n");

	xQueueCalcHandle = xQueueCreate(2, sizeof(int));
	if (xQueueCalcHandle == NULL)
	{
		printf("can not create queue\r\n");
	}

	InitUARTLock();

	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);

	xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
	xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}

运行结果:

任务3、任务4在互斥的交替执行。

 传输大块数据:

FreeRTOS的队列使用拷贝传输,也就是要传输uint32_t时,把4字节的数据拷贝进队列;要传输一个8字节的结构体时,把8字节的数据拷贝进队列。
如果要传输1000字节的结构体呢?写队列时拷贝1000字节,读队列时再拷贝1000字节?不建议这么做,影响效率!
这时候,我们要传输的是这个巨大结构体的地址:把它的地址写入队列,对方从队列得到这个地址,使用地址去访问那1000字节的数据。
使用地址来间接传输数据时,这些数据放在RAM里,对于这块RAM,要保证这几点:

  • RAM的所有者、操作者,必须清晰明了,这块内存,就被称为"共享内存"。要确保不能同时修改RAM。比如,在写队列之前只有由发送者修改这块RAM,在读队列之后只能由接收者访问这块RAM。
  • RAM要保持可用,这块RAM应该是全局变量,或者是动态分配的内存。对于动然分配的内存,要确保它不能提前释放:要等到接收者用完后再释放。另外,不能是局部变量。

程序会创建一个队列,然后创建1个发送任务、1个接收任务:

  • 创建的队列:长度为1,用来传输 "char *" 指针
  • 发送任务优先级为1,在字符数组中写好数据后,把它的地址写入队列
  • 接收任务优先级为2,读队列得到 "char *" 值,把它打印出来

这个程序故意设置接收任务的优先级更高,在它访问数组的过程中,接收任务无法执行、无法写这个数组。

结构体读写队列:

 字符串读写队列:

3. 队列集

本节源码:14_freertos_example_queue_set

从多个队列中得到数据,就使用队列集。

在实际工作中需要从多个队列中得到数据吗?是需要的。

如果你的鼠标既支持鼠标、键盘、触摸屏,任意一个队列有数据,都可以唤醒app去工作,这时候就需要用到队列集合。

让Task1、Task2往Queue1、Queue2中写数据,使用Task3,监测两个Queue。 

 main()函数

  1. 创建2个queue
  2. 创建queue set
  3. 把两个queue添加进queue set
  4. 创建3个任务

任务执行过程

  1. task3先运行,此时没有数据,先阻塞在这里,
  2. 然后task1、task2竞争cpu资源去运行,假设task1先运行,把 &i 先写入队列,在把对应的handle传到队列集(queue set)中,在从&i地址中读到数据,在打印出来,为0;
  3. 接着task2运行,是基于task1高风亮节,使用了该函数vTaskDelay(10);才有task2得以运行,task2也把 &i 先写入队列,在把对应的handle传到队列集(queue set)中,在从&i地址中读到数据,在打印出来,为-1;
  4. 此后打印的数据依次出来,为0 -1 1 -2 2 -3 3 ....运行结果如下图所示
static volatile int flagCalcEnd = 0;
static volatile int flagUARTused = 0;
static QueueHandle_t xQueueHandle1;
static QueueHandle_t xQueueHandle2;
static QueueSetHandle_t xQueueSet;

void Task1Function(void * param)
{
	int i = 0;
	while (1)
	{
		xQueueSend(xQueueHandle1, &i, portMAX_DELAY);
		i++;
		vTaskDelay(10);
	}
}

void Task2Function(void * param)
{
	int i = -1;
	while (1)
	{
		xQueueSend(xQueueHandle2, &i, portMAX_DELAY);
		i--;
		vTaskDelay(20);
	}
}

void Task3Function(void * param)
{
	QueueSetMemberHandle_t handle;
	int i;
	while (1)
	{
		/* 1. read queue set: which queue has data */
		handle = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);

		/* 2. read queue */
		xQueueReceive(handle, &i, 0);

		/* 3. print */
		printf("get data : %d\r\n", i);
	}
}

/*-----------------------------------------------------------*/

int main( void )
{
	TaskHandle_t xHandleTask1;
		
#ifdef DEBUG
  debug();
#endif

	prvSetupHardware();

	printf("Hello, world!\r\n");

	/* 1. 创建2个queue */

	xQueueHandle1 = xQueueCreate(2, sizeof(int));
	if (xQueueHandle1 == NULL)
	{
		printf("can not create queue\r\n");
	}

	xQueueHandle2 = xQueueCreate(2, sizeof(int));
	if (xQueueHandle2 == NULL)
	{
		printf("can not create queue\r\n");
	}

	/* 2. 创建queue set */
	xQueueSet = xQueueCreateSet(3);

	/* 3. 把2个queue添加进queue set */
	xQueueAddToSet(xQueueHandle1, xQueueSet);
	xQueueAddToSet(xQueueHandle2, xQueueSet);

	/* 4. 创建3个任务 */
	xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
	xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
	xTaskCreate(Task3Function, "Task3", 100, NULL, 1, NULL);

	/* Start the scheduler. */
	vTaskStartScheduler();

	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}

运行结果 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值