本帖最后由 dfrobot 于 2019-5-24 22:17 编辑
简介 本篇ESP32 Arduino教程的目的是介绍基于ESP32和Arduino内核使用FreeRTOS队列的方法。 在任务间通信时,队列非常有用,可以从一个任务向其他任务以并发安全的方式发送消息[1]。队列通常采取FIFO(先入先出)[1]模式,也就是说从队列尾部插入新数据并从队列首部取出数据。 FreeRTOS有非常丰富的队列API,提供了多样的功能。您可以在此处(https://www.freertos.org/a00018.html)了解队列API的相关内容。
有一点需要特别注意,队列中所插入的数据是复制过去的,并非仅仅是对数据储存地址的简单引用[1]。这就意味着,如果我们向队列发送一个整型数,那么这个数的值就会被复制到队列中,即使我们改变了原始数值,也不会有任何问题。 话虽如此,我们仍然可以将数据指针作为元素插入到队列中,在所交换消息数据量特别大的时候,这一点非常有用。在这种情况下,指针(而不是消息本身)将被复制到队列中,因此我们需要确保它的内容不会发生改变。但是,这是更高级的应用场景,本文暂不讨论。 还需要注意的是,如果出现向一个已满队列插入数据或者从一个空队列取出数据的操作,那么相关调用会被阻止一段特定的时间[1](时间长短取决于API的参数设置)。 尽管前文介绍队列通常用于任务间通信,但是为了把重点放到需要调用的基本API上,我们在这个入门级的示例中将在Arduino主循环函数中插入和消耗队列中的元素。
代码 本教程不需要任何额外的库。代码中首先声明了一个QueueHandle_t类型的全局变量,这是引用FreeRTOS队列要用到的数据类型。 接下来在setup函数中,首先打开一个串行连接,该连接将用于输出程序结果。
[mw_shl_code=applescript,true]QueueHandle_t queue;[/mw_shl_code]
然后,通过调用xQueueCreate函数创建队列。该函数接收两个参数,第一个参数是队列在特定时间所能容纳的最大元素个数,第二个参数是每个元素的大小(单位为字节)[2]。请注意,队列的每个元素应该具有相同的大小[2]。
我们将新建一个队列,这个队列最多容纳10个元素,每个元素都是整型数,所以可以通过调用sizeof函数得到整型数的字节数。 若执行成功, xQueueCreate函数会返回QueueHandle_t 类型的队列句柄[2],这个类型和我们所定义的全局变量类型相同。如果在分配队列时出现问题,函数就会返回NULL[2]。因此,我们还需要在setup函数中进行NULL检查,并在发现问题时向用户发出警告。
[mw_shl_code=applescript,true]void setup() {
Serial.begin(115200);
queue = xQueueCreate( 10, sizeof( int ) );
if(queue == NULL){
Serial.println("Error creating the queue");
}
}[/mw_shl_code]
再来看主函数,首先向队列中插入数值,以备稍后使用。只需调用xQueueSend 函数,即可向队列末尾插入一个新元素[3]。 该函数的第一个输入参数是队列句柄[3],该句柄就是我们先前声明的全局变量,之后将其赋值为队列创建函数调用的结果。第二个输入参数是指向所插入数据项的指针(请记住,尽管我们传给函数的是一个指针,但是数据项本身是被复制到队列中的)。最后一个输入参数指的是当队列已满情况下,插入操作被阻止时的最长等待时间[3]。 最后一个参数的单位是时钟计数值[3],我们在代码中使用的是portMAX_DELAY,意思是如果队列已满,代码将无限期等待。尽管如此,因为程序是我们自己写的,所以我们绝不会在队列已满时尝试插入新数据,所以也无需担心主函数会被阻止。 队列最多能容纳10个元素,所以我们将使用一个简单的loop循环,每次迭代时插入当前数值。
[mw_shl_code=applescript,true]for(int i = 0; i<10; i++){
xQueueSend(queue, &i, portMAX_DELAY);
}[/mw_shl_code]
需要说明的是,尽管我们传递的是指向同一个变量的指针,但是因为其实际数值会被复制到队列中,所以在每次迭代时将变量更改为一个新的数值是完全没有问题的,我们在运行代码时也会对此再次进行确认。 同样,我们也将使用loop循环来消耗队列中的数据。只需调用xQueueReceive函数即可消耗队列中的数据项。函数的第一个输入参数是队列句柄,第二个输入参数是缓冲区指针(缓冲区表示我们要把接受到的数据项复制到什么位置),最后一个参数是时钟计数值(表示队列为空时的等待时间)。 跟前面一样,我们使用全局队列句柄变量作为第一个参数,最后一个参数依然使用portMAX_DELAY。此外,还需要声明一个整型变量缓冲区,用于储存接收到的数据项。 请注意,所消耗的数据项将被从队列中移除。如果不想在取出数据时将其从队列中移除,可以使用xQueuePeek 函数。 在使用loop循环消耗队列元素的同时,我们还会把这些元素打印到串口,如下图所示。
[mw_shl_code=applescript,true]int element;
for(int i = 0; i<10; i++){
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}[/mw_shl_code]
完整的源代码如下所示。在Arduino主循环的开始部分还进行了零值检验,以避免对尚未分配的队列进行操作。在主循环的每次迭代之间还添加了一个延时。
[mw_shl_code=applescript,true]QueueHandle_t queue;
void setup() {
Serial.begin(115200);
queue = xQueueCreate( 10, sizeof( int ) );
if(queue == NULL){
Serial.println("Error creating the queue");
}
}
void loop() {
if(queue == NULL)return;
for(int i = 0; i<10; i++){
xQueueSend(queue, &i, portMAX_DELAY);
}
int element;
for(int i = 0; i<10; i++){
xQueueReceive(queue, &element, portMAX_DELAY);
Serial.print(element);
Serial.print("|");
}
Serial.println();
delay(1000);
}[/mw_shl_code]
测试代码 使用Arduino IDE编译并上传代码,即可对其进行测试。然后,打开serial monitor,将其波特率设为begin函数中指定的数值(115200)。您将看到类似图1的输出结果,主循环每次迭代都会将插入到队列中的数据项打印出来。
图1 - 队列例程输出
注:本文作者是Nuno Santos,他是一位和蔼可亲的电子和计算机工程师,住在葡萄牙里斯本 (Lisbon)。 他写了很多有关ESP32、ESP8266的有用的教程和项目。
查看更多ESP32/ESP8266教程和项目,请点击 : ESP32教程汇总贴
|