风筝
发表于: 2020-5-22 21:16:46 | 显示全部楼层

前一篇文章中,我们介绍了如何在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中,如下所示。

FreeRTOS-Configuration-File.jpg


现在,我们使用记事本编辑器打开此文件,并搜索#define INCLUDE_vTaskDelete并确保其值为“ 1”(1表示启用,0表示禁用)。

FreeRTOS-Configuration.jpg


在接下来的教程中,我们将经常使用此配置文件来设置参数。


现在,让我们看看如何删除任务。


在FreeRTOS Arduino代码中删除任务

要删除任务,我们必须使用API函数vTaskDelete()。它仅需一个参数。

  1. vTaskDelete( TaskHandle_t pxTaskToDelete );
复制代码

pxTaskToDelete:要删除的任务的句柄。它与xTaskCreate()函数的第六个参数相同。在前一篇文章中,此参数设置为NULL,但是您可以使用任何名称来传递任务内容的地址。假设您要为Task2设置任务句柄,可以如下声明:

  1. TaskHandle_t any_name;
  2. Example: TaskHandle_t xTask2Handle;
复制代码

现在,在vTaskCreate()中将第6个参数设置为

  1. 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);。该函数如下所示:

  1. void TaskBlink2(void *pvParameters)
  2. {
  3. Serial.println(“Task2 is running and about to delete”);
  4. vTaskDelete(NULL);
  5.   pinMode(7, OUTPUT);
  6. while(1)
  7.   {
  8.     digitalWrite(7, HIGH);   
  9.     vTaskDelay( 300 / portTICK_PERIOD_MS );
  10.     digitalWrite(7, LOW);    ​
  11.     vTaskDelay( 300 / portTICK_PERIOD_MS );
  12.   }
  13. }
复制代码

现在,上传代码并观察LED和串口监视器。您将看到第二个LED现在没有闪烁,并且在执行删除API后删除了task2。

FreeRTOS-Serial-Monitor.png


因此,该API可用于停止执行特定任务。


现在,让我们开始介绍队列。


FreeRTOS中的队列是什么?

队列(Queue)是可以容纳有限数量的固定大小元素的数据结构,它以FIFO方案(先进先出)操作。队列提供了任务到任务、任务到中断以及中断到任务的通信机制。


队列可以容纳的最大元素数称为“长度”。创建队列时,将设置每个元素的长度和大小。


FreeRTOS文档中很好地说明了如何使用队列进行数据传输的示例,可在此处找到。您可以轻松理解给定的示例。

FreeRTOS-Queues.png


了解队列后,让我们尝试了解创建队列的过程,并尝试在我们的FreeRTOS代码中实现它。


在FreeRTOS中创建一个队列

首先,我们需要在LCD显示屏上打印显示LDR传感器的值。所以现在有两个任务

1.  Task1用于获取LDR的模拟值。

2.  Task2用于在LCD上打印模拟值。

因此,这里的队列起了作用,因为它将task1生成的数据发送到task2。在task1中,我们将模拟值发送到队列,在task2中,我们将从队列中接收它。


这里有三个相关的队列函数

1.  创建队列

2.  将数据发送到队列

3.  从队列接收数据


1.  创建队列

要创建队列,请使用API函数xQueueCreate()。它有两个参数。

  1. xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
复制代码

uxQueueLength:创建的队列可以一次容纳的最大条目数。

uxItemSize:可以存储在队列中的每个数据项的大小(以字节为单位)。


如果此函数返回NULL,则由于内存不足而不会创建该队列,并且如果它返回非NULL值,则表示队列已成功创建。将此返回值存储到变量中,以将其用作访问队列的句柄,如下所示。

  1. QueueHandle_t queue1;
  2. queue1 = xQueueCreate(4,sizeof(int));
复制代码

这将在堆内存中创建一个4个int大小(每个块2个字节)元素的队列,并将返回值存储到queue1处理变量。


2.  在FreeRTOS中将数据发送到队列

要将值发送到队列,FreeRTOS为此提供了2种API函数。

xQueueSendToBack():用于将数据发送到队列的后部(尾部)。

xQueueSendToFront():用于将数据发送到队列的前端(头)。

现在,xQueueSend()等效于xQueueSendToBack(),并与之完全相同。


所有这些API都有3个参数。

  1. 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也有三个参数:

  1. xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait );
复制代码

第一个和第三个参数与发送API相同。仅第二个参数不同。

const pvBuffer:指向将接收到的数据复制到的内存的指针。


希望您理解这三个API。现在,我们将在Arduino IDE中实现这些API,并尝试解决上述问题。


电路原理图

FreeRTOS-Arduino-Circuit-Diagram.png

下面是使用面包板搭建的实物图:

Arduino-FreeRTOS-Hardware-Setup.jpg


在Arduino IDE中实现FreeRTOS队列

让我们开始为应用程序编写代码。


1.  首先,打开Arduino IDE,然后包含Arduino_FreeRTOS.h头文件。现在,如果使用了任何类似队列的内核对象,则需要包括这些头文件。由于我们使用的是1602 LCD,因此也要包含它的库。

  1. #include <Arduino_FreeRTOS.h>
  2. #include <queue.h>
  3. #include <LiquidCrystal.h>
复制代码

2.  初始化队列句柄以存储队列的内容。另外,初始化LCD引脚号。

  1. QueueHandle_t queue_1;
  2. LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
复制代码

3.  在void setup()中,以9600波特率初始化LCD和串口监视器。使用相应的API创建一个队列和两个任务。在这里,我们将创建一个大小为4的整数类型的队列。创建具有相同优先级的任务。最后,启动调度程序,如下所示。

  1. void setup() {
  2. Serial.begin(9600);
  3. lcd.begin(16, 2);
  4. queue_1 = xQueueCreate(4, sizeof(int));
  5. if (queue_1 == NULL) {
  6. Serial.println("Queue can not be created");
  7. }
  8. xTaskCreate(TaskDisplay, "Display_task", 128, NULL, 1, NULL);
  9. xTaskCreate(TaskLDR, "LDR_task", 128, NULL, 1, NULL);
  10. vTaskStartScheduler();
  11. }
复制代码

4.  现在,创建两个函数TaskDisplay和TaskLDR。在TaskLDR函数中,将LDR连接到Arduino UNO的A0引脚,读取变量中的模拟引脚A0。现在,将变量中存储的值传递到xQueueSend API中来发送该值,使用vTaskDelay()在1秒后将任务变成阻塞状态,如下所示。

  1. void TaskLDR(void * pvParameters) {
  2. int current_intensity;
  3. while(1) {
  4. Serial.println("Task1");
  5. current_intensity = analogRead(A0);
  6. Serial.println(current_intensity);
  7. xQueueSend(queue_1, ¤t_intensity, portMAX_DELAY);
  8. vTaskDelay( 1000 / portTICK_PERIOD_MS );
  9. }
  10. }
复制代码

5.  同样,为TaskDisplay创建一个函数,并在传递给xQueueReceive函数的变量中接收值。另外,如果可以从队列成功接收数据,则xQueueReceive()返回pdPASS;如果队列为空,则返回errQUEUE_EMPTY。


现在,使用lcd.print()函数将值显示在LCD上。

  1. void TaskDisplay(void * pvParameters) {
  2. int intensity = 0;
  3. while(1) {
  4. Serial.println("Task2");
  5. if (xQueueReceive(queue_1, &intensity, portMAX_DELAY) == pdPASS) {
  6. lcd.clear();
  7. lcd.setCursor(0, 0);
  8. lcd.print("Intensity:");
  9. lcd.setCursor(11, 0);
  10. lcd.print(intensity);
  11. }
  12. }
  13. }
复制代码

以上就是我们实现队列的代码部分。现在,根据电路图将LCD和LDR与Arduino UNO连接起来,上传代码。打开串口监视器并观察任务。您将看到任务在切换,LDR值根据光强度而变化。

Serial-Monitor-of-FreeRTOS.png

Arduino-FreeRTOS-Working.jpg


注意:FreeRTOS内核不支持为不同传感器编写的大多数库,因为这些库中有延迟功能。延迟使CPU完全停止,因此,FreeRTOS内核也停止工作,并且代码将无法进一步执行,并且开始出现异常。因此,我们必须使这些无延迟的库与FreeRTOS一起使用。


代码

以下是本文使用的完整代码: main.rar (617 Bytes, 下载次数: 21)

跳转到指定楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 700 | 回复: 1480



手机版|

GMT+8, 2024-4-25 13:58 , Processed in 0.042480 second(s), 6 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表