|
在前一篇文章中,我们介绍了如何在Arduino Uno中开发板中使用FreeRTOS操纵系统,并且创建了一个LED闪烁的任务。在本篇文章中,我们将更深入地研究RTOS API的高级概念,以及了解不同任务之间的通信方法。我们将介绍使用队列(Queue)将数据从一个任务传输到另一个任务,并通过将LCD和LDR与Arduino Uno连接来演示队列API的工作原理。
在讨论队列之前,让我们看一下另外一个FreeRTOS API,它有助于完成分配的工作后删除任务。有时需要删除任务以释放分配的内存。在前一篇文章中,我们将在同一代码中使用API函数vTaskDelete()删除其中一个任务。任务可以使用API函数vTaskDelete()删除自身或其他任务。
要使用此API,您必须配置FreeRTOSConfig.h文件。该文件用于根据应用程序定制FreeRTOS。它用于更改调度算法和许多其他参数。该文件可在Arduino目录中找到,该目录通常可在PC的Documents文件夹中找到。我这里该目录位于\Documents\Arduino\libraries\FreeRTOS\src中,如下所示。
现在,我们使用记事本编辑器打开此文件,并搜索#define INCLUDE_vTaskDelete并确保其值为“ 1”(1表示启用,0表示禁用)。
在接下来的教程中,我们将经常使用此配置文件来设置参数。
现在,让我们看看如何删除任务。
在FreeRTOS Arduino代码中删除任务 要删除任务,我们必须使用API函数vTaskDelete()。它仅需一个参数。 - vTaskDelete( TaskHandle_t pxTaskToDelete );
复制代码pxTaskToDelete:要删除的任务的句柄。它与xTaskCreate()函数的第六个参数相同。在前一篇文章中,此参数设置为NULL,但是您可以使用任何名称来传递任务内容的地址。假设您要为Task2设置任务句柄,可以如下声明: - TaskHandle_t any_name;
- Example: TaskHandle_t xTask2Handle;
复制代码现在,在vTaskCreate()中将第6个参数设置为 - xTaskCreate(TaskBlink2 , "task2" , 128 , NULL, 1 , &xTask2Handle );
复制代码现在可以使用您提供的句柄访问此任务的内容。
同样,任务可以通过传递NULL代替有效任务句柄来删除自身。
如果要从任务3删除自身任务,则只需执行vTaskDelete(NULL);在Task3函数中,但是如果您要从任务2中删除任务3,请在task2函数中执行vTaskDelete(xTask3Handle);。
在以前的教程代码中,要从task2本身删除Task2,只需在void TaskBlink2(void *pvParameters)函数中添加vTaskDelete(NULL);。该函数如下所示: - void TaskBlink2(void *pvParameters)
- {
- Serial.println(“Task2 is running and about to delete”);
- vTaskDelete(NULL);
- pinMode(7, OUTPUT);
- while(1)
- {
- digitalWrite(7, HIGH);
- vTaskDelay( 300 / portTICK_PERIOD_MS );
- digitalWrite(7, LOW);
- vTaskDelay( 300 / portTICK_PERIOD_MS );
- }
- }
复制代码
现在,上传代码并观察LED和串口监视器。您将看到第二个LED现在没有闪烁,并且在执行删除API后删除了task2。
因此,该API可用于停止执行特定任务。
现在,让我们开始介绍队列。
FreeRTOS中的队列是什么? 队列(Queue)是可以容纳有限数量的固定大小元素的数据结构,它以FIFO方案(先进先出)操作。队列提供了任务到任务、任务到中断以及中断到任务的通信机制。
队列可以容纳的最大元素数称为“长度”。创建队列时,将设置每个元素的长度和大小。
FreeRTOS文档中很好地说明了如何使用队列进行数据传输的示例,可在此处找到。您可以轻松理解给定的示例。
了解队列后,让我们尝试了解创建队列的过程,并尝试在我们的FreeRTOS代码中实现它。
在FreeRTOS中创建一个队列 首先,我们需要在LCD显示屏上打印显示LDR传感器的值。所以现在有两个任务 1. Task1用于获取LDR的模拟值。 2. Task2用于在LCD上打印模拟值。 因此,这里的队列起了作用,因为它将task1生成的数据发送到task2。在task1中,我们将模拟值发送到队列,在task2中,我们将从队列中接收它。
这里有三个相关的队列函数 1. 创建队列 2. 将数据发送到队列 3. 从队列接收数据
1. 创建队列 要创建队列,请使用API函数xQueueCreate()。它有两个参数。 - xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
复制代码uxQueueLength:创建的队列可以一次容纳的最大条目数。 uxItemSize:可以存储在队列中的每个数据项的大小(以字节为单位)。
如果此函数返回NULL,则由于内存不足而不会创建该队列,并且如果它返回非NULL值,则表示队列已成功创建。将此返回值存储到变量中,以将其用作访问队列的句柄,如下所示。 - QueueHandle_t queue1;
- queue1 = xQueueCreate(4,sizeof(int));
复制代码这将在堆内存中创建一个4个int大小(每个块2个字节)元素的队列,并将返回值存储到queue1处理变量。
2. 在FreeRTOS中将数据发送到队列 要将值发送到队列,FreeRTOS为此提供了2种API函数。 xQueueSendToBack():用于将数据发送到队列的后部(尾部)。 xQueueSendToFront():用于将数据发送到队列的前端(头)。 现在,xQueueSend()等效于xQueueSendToBack(),并与之完全相同。
所有这些API都有3个参数。 - xQueueSendToBack( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
复制代码xQueue:将数据发送(写入)到的队列的句柄。此变量与用于存储xQueueCreate的返回值的变量相同。 pvItemToQueue:指向要复制到队列中的数据的指针。 xTicksToWait:任务应保持在“阻塞”状态以等待队列中的空间可用的最长时间。
如果在FreeRTOSConfig中将INCLUDE_vTaskSuspend设置为1,则将xTicksToWait设置为portMAX_DELAY将导致任务无限期等待(无超时)。否则,您可以使用宏pdMS_TO_TICKS()将以毫秒为单位的指定时间转换为以滴答为单位的时间。
3. 在FreeRTOS中从队列接收数据 要从队列中接收(读取)项目,请使用xQueueReceive()。接收到的项目会从从队列中删除。
此API也有三个参数: - xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
复制代码第一个和第三个参数与发送API相同。仅第二个参数不同。 const pvBuffer:指向将接收到的数据复制到的内存的指针。
希望您理解这三个API。现在,我们将在Arduino IDE中实现这些API,并尝试解决上述问题。
电路原理图
下面是使用面包板搭建的实物图:
在Arduino IDE中实现FreeRTOS队列 让我们开始为应用程序编写代码。
1. 首先,打开Arduino IDE,然后包含Arduino_FreeRTOS.h头文件。现在,如果使用了任何类似队列的内核对象,则需要包括这些头文件。由于我们使用的是1602 LCD,因此也要包含它的库。 - #include <Arduino_FreeRTOS.h>
- #include <queue.h>
- #include <LiquidCrystal.h>
复制代码
2. 初始化队列句柄以存储队列的内容。另外,初始化LCD引脚号。 - QueueHandle_t queue_1;
- LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
复制代码
3. 在void setup()中,以9600波特率初始化LCD和串口监视器。使用相应的API创建一个队列和两个任务。在这里,我们将创建一个大小为4的整数类型的队列。创建具有相同优先级的任务。最后,启动调度程序,如下所示。 - void setup() {
- Serial.begin(9600);
- lcd.begin(16, 2);
- queue_1 = xQueueCreate(4, sizeof(int));
- if (queue_1 == NULL) {
- Serial.println("Queue can not be created");
- }
- xTaskCreate(TaskDisplay, "Display_task", 128, NULL, 1, NULL);
- xTaskCreate(TaskLDR, "LDR_task", 128, NULL, 1, NULL);
- vTaskStartScheduler();
- }
复制代码
4. 现在,创建两个函数TaskDisplay和TaskLDR。在TaskLDR函数中,将LDR连接到Arduino UNO的A0引脚,读取变量中的模拟引脚A0。现在,将变量中存储的值传递到xQueueSend API中来发送该值,使用vTaskDelay()在1秒后将任务变成阻塞状态,如下所示。 - void TaskLDR(void * pvParameters) {
- int current_intensity;
- while(1) {
- Serial.println("Task1");
- current_intensity = analogRead(A0);
- Serial.println(current_intensity);
- xQueueSend(queue_1, ¤t_intensity, portMAX_DELAY);
- vTaskDelay( 1000 / portTICK_PERIOD_MS );
- }
- }
复制代码
5. 同样,为TaskDisplay创建一个函数,并在传递给xQueueReceive函数的变量中接收值。另外,如果可以从队列成功接收数据,则xQueueReceive()返回pdPASS;如果队列为空,则返回errQUEUE_EMPTY。
现在,使用lcd.print()函数将值显示在LCD上。 - void TaskDisplay(void * pvParameters) {
- int intensity = 0;
- while(1) {
- Serial.println("Task2");
- if (xQueueReceive(queue_1, &intensity, portMAX_DELAY) == pdPASS) {
- lcd.clear();
- lcd.setCursor(0, 0);
- lcd.print("Intensity:");
- lcd.setCursor(11, 0);
- lcd.print(intensity);
- }
- }
- }
复制代码
以上就是我们实现队列的代码部分。现在,根据电路图将LCD和LDR与Arduino UNO连接起来,上传代码。打开串口监视器并观察任务。您将看到任务在切换,LDR值根据光强度而变化。
注意:FreeRTOS内核不支持为不同传感器编写的大多数库,因为这些库中有延迟功能。延迟使CPU完全停止,因此,FreeRTOS内核也停止工作,并且代码将无法进一步执行,并且开始出现异常。因此,我们必须使这些无延迟的库与FreeRTOS一起使用。
代码 以下是本文使用的完整代码:
main.rar
(617 Bytes, 下载次数: 21)
|