一板网电子技术论坛

风筝
发表于: 2020-4-4 09:31:02 | 显示全部楼层

嵌入式设备中使用的操作系统称为RTOS(实时操作系统)。在嵌入式设备中,实时任务至关重要,而定时在其中起着非常重要的作用。实时任务是时间确定性的,这意味着对任何事件的响应时间始终是恒定的,因此可以确保任何特定事件将在固定时间发生。 RTOS旨在以非常精确的时序和高度的可靠性运行应用程序。 RTOS还可以通过单个内核帮助执行多任务。


在本篇文章中,我们将开始介绍FreeRTOS。 FreeRTOS是一类用于嵌入式设备的RTOS,其规模很小,可以在8/16位微控制器上运行,它的使用范围不仅限于这些微控制器。这是一个完全开源的代码,可在github上找到。如果我们了解RTOS的一些基本概念,那么使用FreeRTOS就会非常容易,因为它具有详细记录的API,可以直接在代码中使用,而无需了解代码的后端部分。完整的FreeRTOS文档可以在这里找到。


由于FreeRTOS可以在8位MCU上运行,因此它也可以在Arduino Uno开发板上运行。我们只需要下载FreeRTOS库,然后开始使用API​​来实现代码。

Arduino-FreeRTOS.jpg


RTOS是如何运作的?

在开始运行RTOS之前,让我们看看什么是任务。任务是一段可调度在CPU上执行的代码。因此,如果要执行某些任务,则应使用内核延迟或中断来安排任务。这项工作由内核中存在的Scheduler完成。在单核处理器中,调度程序可帮助任务在特定时间段内执行,但看起来不同的任务正在同时执行。每个任务都根据赋予它的优先级运行。


现在,让我们看看如果要创建一个以一秒钟为间隔的LED闪烁任务并将该任务置于最高优先级的情况,RTOS内核中会发生什么。

RTOS-Working.png


除了LED任务外,内核还会创建另一个任务,称为空闲任务。当没有任务可执行时,将创建空闲任务。此任务始终以最低优先级(即0优先级)运行。如果我们分析上面给出的时序图,可以看出执行从一个LED任务开始,并运行了指定的时间,然后在剩余时间里,空闲任务一直运行到滴答中断发生为止。然后,内核根据任务的优先级和LED任务的总经过时间来决定必须执行的任务。 1秒完成后,内核会再次选择执行LED任务,因为它比空闲任务具有更高的优先级,我们也可以说LED任务抢占了空闲任务。如果有两个以上具有相同优先级的任务,则它们将以循环方式运行指定的时间。


在状态图下方,它显示了非运行任务切换到运行状态。

FreeRTOS-Blinking-LED.png


每个新创建的任务都进入“就绪”状态(部分处于非运行状态)。如果创建的任务(Task1)的优先级高于其他任务,则它将进入运行状态。如果此正在运行的任务被其他任务抢占,则它将再次回到就绪状态。否则,如果使用阻塞API阻塞了task1,则CPU将不会参与该任务,直到用户定义的超时时间为止。


如果使用Suspend API将Task1挂起处于运行状态,则Task1将进入“挂起”状态,并且调度程序无法再次使用它。如果在挂起状态下恢复Task1,则它将返回到就绪状态,如框图所示。


以上就是任务如何运行和更改其状态的基本思想。在本文中,我们将使用FreeRTOS API在Arduino Uno中实现两个任务。


RTOS中的常用术语

1.  任务(Task):这是一段可在CPU上调度执行的代码。


2.  调度器(Scheduler):它负责从就绪状态列表到运行状态中选择任务。经常实施调度程序,以便它们使所有计算机资源处于繁忙状态(例如在负载平衡中)。


3.  抢占(Preemption):这是暂时中断已经执行的任务的动作,目的是在没有协作的情况下将其从运行状态中删除。


4.  上下文切换(Context Switching):在基于优先级的抢占中,调度程序将每个systick中断上正在运行的任务的优先级与就绪任务列表的优先级进行比较。如果列表中有任何任务的优先级高于正在运行的任务,则会发生上下文切换。基本上,在此过程中,不同任务的内容将保存在各自的堆栈存储器中。


5.  调度策略的类型:

●    抢先式调度(Preemptive Scheduling):在这种类型的调度中,任务以相等的时间片运行,而不考虑优先级。

●    基于优先级的抢占式(Priority-based Preemptive):高优先级任务将首先运行。

●    协作调度(Co-operative Scheduling):上下文切换仅在运行任务的协作下发生。任务将连续运行,直到调用任务产量(task yield)为止。


6.  内核对象(Kernel Objects):为了发信号通知任务执行某些工作,使用了同步过程。要执行此过程,将使用内核对象。一些内核对象包括事件、信号量、队列、互斥量、邮箱等。我们将在后续教程中了解如何使用这些对象。


通过以上讨论,我们对RTOS概念有了一些基本的想法,现在我们可以在Arduino中实现FreeRTOS项目。因此,让我们首先在Arduino IDE中安装FreeRTOS库。


安装Arduino FreeRTOS库

1.  打开Arduino IDE,然后转到Sketch-> Include Library-> Manage Libraries。搜索FreeRTOS并安装库,如下所示。

Arduino-FreeRTOS.png


您也可以从github下载该库,下载完成后,在Sketch-> Include Library-> Add .zip文件中添加.zip文件。


现在,重新启动Arduino IDE。该库提供了一些示例代码,也可以在File -> Examples -> FreeRTOS中找到,如下所示。

FreeRTOS-Arduino-IDE.png


在本文中,我们将从头开始编写代码以了解其工作原理,稍后您可以检查示例代码并使用它们。


电路原理图

以下是在Arduino上使用FreeRTOS创建闪烁LED任务的电路图:

Arduino-FreeRTOS-Circuit-Diagram.png


Arduino FreeRTOS示例-在Arduino IDE中创建FreeRTOS任务

让我们看一下编写FreeRTOS项目的基本结构。


1.  首先,包含Arduino FreeRTOS头文件

  1. #include <Arduino_FreeRTOS.h>
复制代码

2.  给出要编写的所有要执行的函数的函数原型,写为

  1. void Task1( void *pvParameters );
  2. void Task2( void *pvParameters );
复制代码

3.  现在,在void setup()函数中,创建任务并启动任务调度器。


为了创建任务,在setup函数中使用某些参数调用API函数xTaskCreate()。

  1. xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask );
复制代码

创建任何任务时应传递6个参数:

●    pvTaskCode:它只是实现任务的函数的指针。

●    pcName:任务的描述性名称。 FreeRTOS不使用此功能。纯粹是出于调试目的将其包括在内。

●    usStackDepth:每个任务都有自己的唯一堆栈,内核在创建任务时将其分配给任务。该值指定堆栈可以容纳的字数,而不是字节数。例如,如果堆栈为32位宽,并且usStackDepth作为100传入,那么将在RAM中分配400字节的堆栈空间(100 * 4字节)。

●    pvParameters:任务输入参数(可以为NULL)。

●    uxPriority:任务的优先级(0是最低优先级)。

●    pxCreatedTask:可用于向正在创建的任务传递句柄。然后,可以使用此句柄在API调用中引用任务,例如,更改任务优先级或删除任务(可以为NULL)。


4.  创建任务后,在setup函数中使用vTaskStartScheduler();中启动调度程序。


5.  loop()函数将保持为空,因为我们不想手动无限地运行任何任务。因为任务执行现在由调度程序处理。


6.  现在,我们必须实现任务函数,并在这些函数内编写要执行的逻辑。函数名称应与xTaskCreate()的第一个参数相同。

  1. void task1(void *pvParameters)  
  2. {
  3. while(1) {
  4. ..
  5. ..//your logic
  6. }
  7. }
复制代码

7.  大多数代码都需要延迟函数来停止正在运行的任务,但是在RTOS中,不建议使用Delay()函数,因为它会停止CPU,因此RTOS也将停止工作。因此,FreeRTOS具有内核API,可以在特定时间内阻止任务。

  1. vTaskDelay( const TickType_t xTicksToDelay );
复制代码

该API可用于延迟目的。此API将任务延迟给定的滴答数。任务保持阻塞的实际时间取决于滴答率。常量portTICK_PERIOD_MS可用于根据滴答率计算实时性。


这意味着如果您想要200ms的延迟,只需编写此行

  1. vTaskDelay( 200 / portTICK_PERIOD_MS );
复制代码

因此,对于本教程,我们将使用这些FreeRTOS API来实现三个任务。这三个任务分别为:

●    在数字引脚8上的LED以200ms的频率闪烁

●    在数字引脚7上的LED以300ms的频率闪烁

●    在串口监视器中以500ms的频率打印数字。


Arduino IDE中的FreeRTOS任务实现

1.  从上面的基本结构说明中,包括Arduino FreeRTOS头文件。然后制作函数原型。由于我们要完成三个任务,因此要实现三个函数以及它的原型。

  1. #include <Arduino_FreeRTOS.h>
  2. void TaskBlink1( void *pvParameters );
  3. void TaskBlink2( void *pvParameters );
  4. void Taskprint( void *pvParameters );
复制代码

2.  在void setup()函数中,以9600位/秒的速度初始化串行通信,并使用xTaskCreate()API创建所有三个任务。首先,将所有任务的优先级设置为“ 1”,然后启动调度程序。

  1. void setup() {
  2. Serial.begin(9600);
  3. xTaskCreate(TaskBlink1,"Task1",128,NULL,1,NULL);
  4. xTaskCreate(TaskBlink2,"Task2 ",128,NULL,1,NULL);
  5. xTaskCreate(Taskprint,"Task3",128,NULL,1,NULL);   
  6. vTaskStartScheduler();
  7. }
复制代码

3.  现在,实现所有三个函数,以下是为任务1的LED闪烁实现:

  1. void TaskBlink1(void *pvParameters)
  2. {
  3. pinMode(8, OUTPUT);
  4. while(1)
  5. {
  6. digitalWrite(8, HIGH);   
  7. vTaskDelay( 200 / portTICK_PERIOD_MS );
  8. digitalWrite(8, LOW);   
  9. vTaskDelay( 200 / portTICK_PERIOD_MS );
  10. }
  11. }
复制代码

TaskBlink2函数的实现类似。 Task3函数写为

  1. void Taskprint(void *pvParameters)  
  2. {
  3. int counter = 0;
  4. while(1)
  5. {
  6. counter++;
  7. Serial.println(counter);
  8. vTaskDelay( 500 / portTICK_PERIOD_MS );
  9. }
  10. }
复制代码

最后,在数字引脚7和8上连接两个LED,并将代码上传到Arduino开发板上,然后打开串口监视器。您将看到计数器以任务名称每500ms运行一次,如下所示。

Arduino-IDE.png


另外,观察LED,它们以不同的时间间隔闪烁。尝试在xTaskCreate函数中使用priority参数。更改编号并观察串口监视器和LED的状态。

FreeRTOS-Blink-LED-in-Arduino.jpg


现在,您可以了解创建模拟读取和数字读取任务的前两个示例代码。这样,您可以仅使用Arduino Uno和FreeRTOS API进行更高级的项目。

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

本版积分规则

主题 557 | 回复: 1077



手机版|

GMT+8, 2021-4-15 07:59 , Processed in 0.077151 second(s), 31 queries . Powered by Discuz! X3.4

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

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