在上一教程中,我们在Arduino Uno中介绍了FreeRTOS,并为LED闪烁创建了一个任务。现在,在本教程中,我们将更深入地研究RTOS API的高级概念,并了解不同任务之间的通信。在这里,我们还学习了将数据从一个任务传输到另一个任务的队列,并通过将16x2 LCD和LDR与Arduino Uno接口来演示队列API的工作。
在讨论队列之前,让我们看一下另外一个FreeRTOS API,它有助于完成分配的工作后删除任务。有时需要删除任务以释放分配的内存。在上一教程的继续中,我们将在同一代码中使用 vTaskDelete() API函数删除其中一个任务。任务可以使用 vTaskDelete() API函数删除自身或任何其他任务。
要使用此API,您必须配置 FreeRTOSConfig.h 文件。该文件用于根据应用程序定制FreeRTOS。它用于更改调度算法和许多其他参数。该文件可在Arduino目录中找到,该目录通常可在PC的Documents文件夹中找到。就我而言,它在 \ Documents \ Arduino \ libraries \ FreeRTOS \ src中 可用,如下所示。
现在,使用任何文本编辑器打开此文件,然后搜索 了的#define INCLUDE_vTaskDelete 并确保其值为“1”(1所表示启用,0表示禁用)。默认情况下为1,但会对其进行检查。
在接下来的教程中,我们将经常使用此配置文件来设置参数。
现在,让我们看看如何删除任务。
在FreeRTOS Arduino中删除任务
要删除任务,我们必须使用vTaskDelete()API函数。它仅需一个参数。
vTaskDelete(TaskHandle_t pxTaskToDelete);
pxTaskToDelete:这是要删除的任务的句柄。它与 xTaskCreate() API的第六个参数相同。在上一教程中,此参数设置为NULL,但是您可以使用任何名称来传递任务内容的地址。假设您要为宣告为
TaskHandle_t any_name; 示例:TaskHandle_t xTask2Handle;
现在,在vTaskCreate()API中将第6个参数设置为
xTaskCreate(TaskBlink2,“ task2”,128,NULL,1,&xTask2Handle);
现在可以使用您提供的句柄访问此任务的内容。
同样,任务可以通过传递NULL代替有效任务句柄来删除自身。
如果要从任务3本身删除任务3,则需要编写 vTaskDelete(NULL); 在Task3函数中,但是如果您要从任务2中删除任务3,请编写 vTaskDelete(xTask3Handle); 在task2函数中。
在以前的教程代码中,要从task2本身删除Task2,只需添加 vTaskDelete(NULL);。 在 void TaskBlink2(void * pvParameters) 函数中。然后上面的功能看起来像这样
void TaskBlink2(void * pvParameters) { Serial.println(“ Task2正在运行,即将删除”); vTaskDelete(NULL); pinMode(7,输出); while(1) { digitalWrite(7,HIGH); vTaskDelay(300 / portTICK_PERIOD_MS); digitalWrite(7,LOW); vTaskDelay(300 / portTICK_PERIOD_MS); } }
现在,上传代码并观察LED和串行监视器。您将看到第二个LED现在没有闪烁,并且在遇到删除API后删除了task2。
因此,可以使用该API停止执行特定任务。
现在,让我们从队列开始。
FreeRTOS中的队列是什么?
队列是可以容纳有限数量的固定大小元素的数据结构,它以FIFO方案(先进先出)操作。队列提供了任务到任务,任务到中断以及中断到任务的通信机制。
队列可以容纳的最大元素数称为“长度”。创建队列时,将设置每个元素的长度和大小。
FreeRTOS文档中很好地说明了如何使用队列进行数据传输的示例,可在此处找到。您可以轻松理解给定的示例。
了解了队列之后,让我们尝试了解创建队列的过程,并尝试在我们的FreeRTOS代码中实现它。
在FreeRTOS中创建队列
首先,描述要在FreeRTOS队列和Arduino Uno的帮助下实现的问题陈述。
我们要在16 * 2 LCD上打印LDR传感器的值。所以现在有两个任务
- Task1正在获取LDR的模拟值。
- Task2正在LCD上打印模拟值。
因此,这里的队列起了作用,因为它将task1生成的数据发送到task2。在task1中,我们将模拟值发送到队列,在task2中,我们将从队列中接收它。
有三个函数可以使用队列
- 创建队列
- 将数据发送到队列
- 从队列接收数据
要创建队列,请使用xQueueCreate()函数API。它有两个参数。
xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);
uxQueueLength:正在创建的队列可以一次容纳的最大项目数。
uxItemSize:可以存储在队列中的每个数据项的大小(以字节为单位)。
如果此函数返回NULL,则由于内存不足而不会创建该队列,并且如果它返回非NULL值,则表示队列创建成功。将此返回值存储到变量中,以将其用作访问队列的句柄,如下所示。
QueueHandle_t queue1; queue1 = xQueueCreate(4,sizeof(int));
这将在int大小(每个块2个字节)的堆内存中创建一个4元素队列,并将返回值存储到 queue1 处理变量。
2.在FreeRTOS中将数据发送到队列
要将值发送到队列,FreeRTOS为此提供了2种API变体。
- xQueueSendToBack():用于将数据发送到队列的后部(尾部)。
- xQueueSendToFront():用于将数据发送到队列的前端(头)。
现在, xQueueSend() 等效于 xQueueSendToBack() 并与之完全相同 。
所有这些API都有3个参数。
xQueueSendToBack(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t xTicksToWait);
xQueue:将数据发送(写入)到的队列的句柄。此变量与用于存储xQueueCreate API的返回值的变量相同。
pvItemToQueue:指向要复制到队列中的数据的指针。
xTicksToWait:任务应保持在“阻塞”状态以等待队列中可用空间的最长时间。
设置 xTicksToWait 到 portMAX_DELAY 将导致任务无限期地等待(没有超时),提供 INCLUDE_vTaskSuspend 被设置为1 FreeRTOSConfig.h中 否则你可以使用宏 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 头文件。现在,如果使用了任何类似队列的内核对象,则包括它的头文件。由于我们使用的是16 * 2 LCD,因此也要包含它的库。
#include #include
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)); 如果(queue_1 == NULL){ Serial.println(“无法创建队列”); } 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() API在1秒后将任务发送到块状态,如下所示。
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 strength = 0; while(1){ Serial.println(“ Task2”); 如果(xQueueReceive(queue_1,&intensity,portMAX_DELAY)== pdPASS){ lcd.clear(); lcd.setCursor(0,0); lcd.print(“ Intensity:”); lcd.setCursor(11,0); lcd.print(强度); } } }
而已。我们已经完成了Queue实现的编码部分。最后可以找到完整的代码以及有效的视频。
现在,根据电路图将LCD和LDR与Arduino UNO连接起来,上传代码。打开串行监视器并观察任务。您将看到任务在切换,LDR值根据光强度而变化。
注意: FreeRTOS内核不支持为不同传感器制造的大多数库,原因是这些库中存在延迟功能。延迟使CPU完全停止,因此,FreeRTOS内核也停止工作,并且代码将无法进一步执行,并且开始出现异常。因此,我们必须使这些库无延迟以与FreeRTOS一起使用。