04 FreeRTOS 队列(queue)

1、队列的特性

        队列可以理解为一个传送带,一个流水线。

        队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)

        每个数据大小固定

        创建队列时就要指定长度、数据大小

        数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读,把数据写在队列头部并不会覆盖原来头部的数据,因为队列中的数据使用环形缓冲区管理数据,把数据放到头部时,会先移动头部位置,并不会覆盖原来数据。

2、队列的函数

2.1 创建

//函数原型
    //队列长度,每一项的大小
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

2.2 写队列

        可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。

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

 

2.3 读队列

        使用 xQueueReceive() 函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版 本:在任务中使用、在ISR中使用。

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

2.4 删除

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

void vQueueDelete( QueueHandle_t xQueue );

2.5 查询

        可以查询队列中有多少个数据、有多少空余空间。

/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

2.6 复位

        队列刚被创建时,里面没有数据,使用过程中可以调用 xQueueReset() 把队列恢复为初始状态。

/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);

2.7 覆盖

        当队列长度为1时,可以使用 xQueueOverwrite() xQueueOverwriteFromISR() 来覆盖数据。 注意,队列长度必须为1。当队列满时,这些函数会覆盖里面的数据,这也意味着这些函数不会被阻塞。

/* 覆盖队列
* xQueue: 写哪个队列
* pvItemToQueue: 数据地址
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueueOverwrite(QueueHandle_t xQueue,
                            const void * pvItemToQueue
);
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue,
                                    const void * pvItemToQueue,
                                    BaseType_t *pxHigherPriorityTaskWoken
);

2.8 偷看

        如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那么可以使用"窥视",也就是 xQueuePeek() xQueuePeekFromISR() 。这些函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻塞;一旦队列中有数据,以后每次"偷看"都会成功。

/* 偷看队列
* xQueue: 偷看哪个队列
* pvItemToQueue: 数据地址, 用来保存复制出来的数据
* xTicksToWait: 没有数据的话阻塞一会
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueuePeek(QueueHandle_t xQueue,
                        void * const pvBuffer,
                        TickType_t xTicksToWait
);
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue,
                                void *pvBuffer,
);

3、示例代码

3.1 同步

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

void Task1Function( void * param)
{
	volatile int i = 0;	//使用volatile修饰,让系统不要去优化这个变量
	while(1){
		for(i = 0; i < 10000000; i++){
			sum++;
		}
		xQueueSend(xQueueCalcHandle, &sum, portMAX_DELAY);
		sum = 1;
	}
}

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


//main函数中
printf("hello go\r\n");
	
xQueueCalcHandle = xQueueCreate(2, sizeof(int));
if(xQueueCalcHandle == NULL){
	printf("can not create queue\r\n");
}
	
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);

        使用队列的方法实现了同步,flagCalcEnd在被拉高的时候,用时2s

3.2 互斥

static QueueHandle_t xQueueUARTHandle;

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

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

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

void TaskGenericFunction( void * param)
{
	while(1){
		GetUARTLock();
		printf("%s\r\n", (char *)param);
		//在释放锁之前,任务三在等待
		PutUARTLock();	//在释放锁时,任务三进入就绪状态,但是此时任务四是运行状态,马上又要上锁,轮不到任务三去执行
		vTaskDelay(1);	//主动让一下
		/*
			除了通过vTaskDelay让出CPU资源,还有更合理的函数:
				使用taskYIELD(),主动发起—次任务切换
				vTaskDelay会让任务阻塞、暂停若干tick,taskYIELD()更合理
				可以设置不同的优先级来实现抢占
		*/
	}
}

//main函数中
InitUARTLock();
	
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);

         先初始化一个锁,这个锁用队列来实现,创建完队列,往里面随便写一个数据,队列里面有数据就表示别人可以来读取这个数据。假设某个任务要使用串口,先用GetUARTLock获得串口的锁,去读队列,得到这个队列的数据就表示得到这个串口的使用权,用完串口之后就往队列里随便写一个数据,表示使用完串口了,把这个使用权释放掉。

3.3 队列集

        创建两个队列Queue1和Queue2,Task1和Task2分别往这两个队列里写入数据,Task3使用Queue Set来监测这两个队列。队列集的长度是包含的队列长度之和。

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

//main函数中
TaskHandle_t xHandleTask1;

/* 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);

3.4 邮箱(mailboc)

        FreeRTOS的邮箱概念跟别的RTOS不一样,这里的邮箱称为"橱窗"也许更恰当:

                它是一个队列,队列长度只有1

                写邮箱:新数据覆盖旧数据,在任务中使用 xQueueOverwrite() ,在中断中使用 xQueueOverwriteFromISR() 。 既然是覆盖,那么无论邮箱中是否有数据,这些函数总能成功写入数据。

                读邮箱:读数据时,数据不会被移除;在任务中使用 xQueuePeek() ,在中断中使用 xQueuePeekFromISR() 。 这意味着,第一次调用时会因为无数据而阻塞,一旦曾经写入数据,以后读邮箱时总能成功。

        main函数中创建了队列(队列长度为1)、创建了发送任务、接收任务:

                发送任务的优先级为2,它先执行

                接收任务的优先级为1

/* 队列句柄, 创建队列时会设置这个变量 */
QueueHandle_t xQueue;
int main( void )
{
    prvSetupHardware();
    /* 创建队列: 长度为1,数据大小为4字节(存放一个char指针) */
    xQueue = xQueueCreate( 1, sizeof(uint32_t) );
    if( xQueue != NULL )
    {
        /* 创建1个任务用于写队列
        * 任务函数会连续执行,构造buffer数据,把buffer地址写入队列
        * 优先级为2
        */
        xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
        /* 创建1个任务用于读队列
        * 优先级为1
        */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
        /* 启动调度器 */
        vTaskStartScheduler();
    }
    else
    {
        /* 无法创建队列 */
    }
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值