FreeRTOS之队列管理

FreeRTOS学习记录第三天

一、队列
1.创建三个任务一个队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*声明一个类型为xQueueHandle的变量,其用于保存队列句柄,以便三个任务都可以引用此队列*/
xQueueHandle xQueue;
static void AppTaskCreate (void)
{
/*创建的队列用于保存最多5个值,每个数据单元都有足够的空间来存储一个long型变量*/
xQueue=xQueueCreate(5,sizeof(long));
if(xQueue != NULL)
{
/*创建连个写队列任务实例,任务入口参数用于传递发送到队列的值。所以一个实例
不停的发送100,而另一个任务实例不停的往队列发送200.两个任务的优先级都设为1.*/
xTaskCreate(vSenderTask,"Sender1",1000,(void*)100,1,NULL);
xTaskCreate(vSenderTask,"Sender2",1000,(void*)200,1,NULL);
/*创建一个读队列任务实例。其优先级设为2,高于写任务优先级*/
xTaskCreate(vReceiverTask,"Receiver",1000,NULL,2,NULL);

}
}

2.发送任务(有两个实列):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static void vSenderTask(void *pvParameters)
{
long ValueToSend;
portBASE_TYPE xStatus;
/*该任务会被创建两个实例,所以写入队列的值通过任务入口参数传递;
这种方式使得每个实例使用不同的值。队列创建时指定其数据单元为long型,
所以把入口参数强制转换为数据单元要求的类型*/
ValueToSend = (long) pvParameters;
for(;;)
{
/*往队列发送数据
第一个参数是要写入的队列。队列在调度器启动之前就被创建了,所以先于此任务执行;
第二个参数是被发送数据的地址,本例中即变量ValueToSend的地址;
第三个参数是阻塞超时时间-当队列满时,任务转入阻塞状态以等待队列空间有效。本例
中没有设定超时时间,因为此独列绝不会保持超过一个数据单元的的机会(因为读数据的
任务优先级更高),所以也绝不会满,这正是我们要测试的特性*/
xStatus = xQueueReceive(xQueue,&ValueToSend,0);
if(xStatus != pdPASS)
{
/*发送操作由于队列满而无法完成-这是不可能发生的,本例中队列不可能满*/
printf("Could not send to the queue.\r\n");
}
/*允许其它发送任务执行。taskYIELD()通知调度器现在就切换到其它任务,而不必等到
本任务的时间片耗尽,这个非常有必要否则无法立即转而执行实例2*/
taskYIELD();
}
}

3.接收队列数据任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static void vReceiverTask(void *pvParameters)
{
/*声明变量,用于保存从队列中接收的值*/
long ReceivedValue;
portBASE_TYPE xStatus;
const portTickType xTicksToWait = 100/portTICK_RATE_MS;
for(;;)
{
/*此调用会发现队列一直为空,因为本任务将立即删除刚写入的数据单元(首先因为
任务优先级高,第一次会发现队列为空,因为写任务优先级低,而当写任务写入了数
据后又被该任务立即读取了,也就是删除了,所以永远为空)*/
if(uxQueueMessagesWaiting(xQueue)!=0)
{
printf("Queue should have been empty\r\n");
}
/*往队列接收数据
第一个参数是要被读取的队列。队列在调度器启动之前就被创建了,所于优先于此任务
执行;
第二个参数是保存接收到数据的缓冲区地址。此变量类型与队列数据单元类型相同,
所以有足够的大小来存储接收到的数据;
第三个参数是阻塞超时时间-当队列为空时,任务转入阻塞状态以等待队列数据有效。
本例中常量portTICK_RATE_MS用来将100ms绝对时间转换为系统心跳为单位的时间值*/
xStatus = xQueueReceive(xQueue,&ReceivedValue,xTicksToWait);
if(xStatus == pdPASS)
{
/*成功读出数据,打印出来*/
printf("Received= %ld\r\n",ReceivedValue);
}
else
{
/*等待100ms也没有收到任何数据
必然存在错误,因为发送任务在不停的往队列中写入数据*/
printf("Could not receive from the queue.\r\n");
}

}
}

4.串口打印截图:
TIM截图20180908100530.png
注:不明白为什么出现这种情况,已经在硬石论坛和原子论坛和安富莱论坛提问;
http://www.ing10bbs.com/forum.php?mod=viewthread&tid=1745&page=1&extra=#pid4199
http://www.openedv.com/thread-278757-1-1.html
http://forum.armfly.com/forum.php?mod=viewthread&tid=89359&page=1&extra=#pid144145
理论应该交叉打印100和200;


5.分析:
首先执行读取任务,读取任务进入100ms阻塞,写入任务实例1得以运行,当任务1写入数据100后,读取任务
立刻返回读取,数据被删除了,读取任务重新进入阻塞状态,程序返回到taskYIELD()立即切换到实例2,
写入数据200,立刻又被读取任务读取,数据被删除,循环往复,所以串口应该交叉打印100和200.


6.补充:
发现问题了,唉,是自己太粗心了,读取任务也用了发数据函数,太丢脸了。
现在串口打印如下:
FREERTOS3.png

FreeRTOS学习记录第四天

一、使用队列传递复合数据类型
主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*声明一个类型为xQueueHandle的变量,其用于保存队列句柄,以便三个任务都可以引用此队列*/
xQueueHandle xQueue;
/*定义队列传递的结构类型*/
typedef struct
{
unsigned int ucValue;
unsigned int ucSource;
}xData;
/*声明两个xData类型的变量,通过队列传递*/
static const xData xStructsToSend[2]=
{
{100,300},/*被发送任务的实例1使用*/
{200,400} /*被发送任务的实例2使用*/
};
static void AppTaskCreate (void)
{
/*创建队列用于保存最多3个xData类型的数据单元*/
xQueue = xQueueCreate( 3, sizeof( xData ) );
if( xQueue != NULL )/*非空说明成功创建了队列*/
{
/*为写队列任务创建2个实例。
任务的入口参数用于传递发送到队列中的数据。因为其中一个任务往队列中一直
写入xStructsToSend[0],而另一个则往队列中写入xStructsToSend[1]。这两个
任务的优先级都设为2,高于队列任务的优先级*/
xTaskCreate( vSenderTask, "Sender1", 240, ( void * ) &( xStructsToSend[ 0 ] ), 2, NULL );
xTaskCreate( vSenderTask, "Sender2", 240, ( void * ) &( xStructsToSend[ 1 ] ), 2, NULL );
/*创建读队列任务。
读队列任务优先级设为1,低于写队列任务的优先级*/
xTaskCreate( vReceiverTask, "Receiver", 240, NULL, 1, NULL );
/*启动调度器,创建的任务得到执行*/
vTaskStartScheduler();
}
else
{
/*创建队列失败,这是理论上不可能发送的*/
printf( "Queue not created.\n" );
}
}

发送任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static void vSenderTask( void *pvParameters )
{
/*队列状态标志*/
portBASE_TYPE xStatus;
/*转换为以毫秒为单位的心跳周期*/
const portTickType xTicksToWait = 100 / portTICK_RATE_MS;
for( ;; )
{
/*第一个参数是要发送的队列
第二个参数是将要发送的数据结构地址。这个地址是从任务入口参数中传入,所以直接
使用pvParameters
第三个参数是阻塞等待时间*/
xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );
/*无法发送成功*/
if( xStatus != pdPASS )
{
printf( "Could not send to the queue.\r\n" );
}
/*队列已满*/
else if( xStatus == errQUEUE_FULL )
{
printf( "Queue is full.\r\n" );
}
/*发送成功*/
else if( xStatus == pdPASS )
{
printf(" sent\r\n");
}
/*立即切出,不用等待阻塞时间耗尽,转而立即执行发送任务的实例2*/
taskYIELD();
}
}

接收任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static void vReceiverTask( void *pvParameters )
{
/*声明结构体变量以保存从队列中读出的数据单元*/
xData xReceivedStructure;
/*队列的状态标志*/
portBASE_TYPE xStatus;
for( ;; )
{
/*读队列任务的优先级最低,所以其只可能在写对垒任务阻塞时得到执行。而写队列任务
只会在队列写满时才会进入阻塞态,所以读队列任务执行时队列肯定已满。所以队列中
数据单元的个数应当等于队列深度-本例中队列深度为3*/
if( uxQueueMessagesWaiting( xQueue ) != 3 ){
printf( "Queue should have been full!\n" );/*这是不可能发生的*/
}
/*第一个参数是需要读取的队列
第二个参数是存放接收数据的缓存空间-这里简单的采用一个具有足够空间大小的变
量地址
第三个参数是阻塞超时时间-本例不需要指定超时时间,因为读队列任务只会在队列
满时才会得到执行*/
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
if( xStatus == pdPASS )
{
/*数据成功读出,打印输出数值及数据来源*/
if( xReceivedStructure.ucSource ==300)
{
printf( "From Sender 1 = %d\r\n", xReceivedStructure.ucValue );
}
else
{
printf( "From Sender 2 = %d\r\n", xReceivedStructure.ucValue );
}
}
else
{
/*没有读到任何数据。这是理论上不可能发生的,因为此任务只有在队列满时才会得
到执行*/
printf( "Could not receive from the queue.\r\n" );
}
}

}

串口打印截图:
FREEROTS3.png


分析:略,同上一篇文章类似(但是这次结构体好像很有问题)
见下面几张截图:
abc.png
def.png
ghi.png
现在还是不太明白为什么?(貌似好像是C99标准什么的问题,想问一下大家有没有什么好的解决方案)


附:
现在已经在安富莱论坛提问
地址为:http://forum.armfly.com/forum.php?mod=viewthread&tid=89371&extra=


补充说明:
如果队列存储的数据单元尺寸比较大,那最好是利用队列来传递数据的指针而不是对数据本身在队列上一字节一字节地拷贝或拷贝出。(效率更高,利用空间更好)
需注意:
1.指针指向的内存空间的所有权必须明确;
2.指针指向的内存空间必须有效;
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据就不再有效。