FreeRTOS 已集成到 Arduino IDE 中,并已准备好用于 PORTENTA Pro 设备
Arduino最近推出了其专业系列的微控制器,包括如图1所示的PORTENTA C33。该设备配备了瑞萨Arm Cortex M33微控制器。Arduino产品线的这一令人兴奋的补充具有物联网外设和相关模块,如蓝牙和以太网连接。
硬件能力的提升伴随着软件复杂性的增加。一个解决方案是使用FreeRTOS。你可能会惊喜地发现FreeRTOS已内置在IDE中。事实上,你可以在IDE的文件选项卡中找到FreeRTOS模板,与其他熟悉的Arduino示例一起。
本文的目的是什么?
本工程简报通过Arduino的视角介绍FreeRTOS。它首先介绍了FreeRTOS,然后提供了一个代码模板,该模板通过标签清晰地分离了各种操作系统任务。你会发现Arduino的setup()和loop()结构为理解和应用FreeRTOS环境提供了一个自然的框架。
图 1 : Arduino PORTENTA C33是一款新的 Arduino 产品,提供开箱即用的 FreeRTOS功能。 C33 显示在 PORTENTA扩展板上。
什么是 Arduino Pro PORTENTA 产品系列?
简而言之,PORTENTA系列是Arduino模块化微控制器产品线的自然延伸,为工程师和技术人员的先进需求提供解决方案。这些即用型产品配备了高端的32位微控制器。所有设备都能够运行操作系统,如FreeRTOS,而最强大的多核PORTENTA X8设备可以运行Linux(Yocto)。
为了更好地理解Arduino Pro系列,回顾Arduino的历史及其对教育的影响是有帮助的。正如本文所述,Arduino社区比母公司Arduino要大得多。新的Pro系列默认承认,通过保持Arduino的感觉,同时为具有额外处理能力的微控制器编程,可以利用这一庞大的用户群。
通过一个简单的思维实验,可以揭示Pro系列的功率和成本节约。问问自己,设计一个具有现代球栅阵列(BGA)微控制器、相关电源以及蓝牙收发器的定制印刷电路板(PCB)需要多长时间。这是一项非平凡的任务,高昂的一次性开发成本必须通过高终端产品成本或大规模生产来分摊。因此,像Arduino Pro这样的设备非常适合开发和生产,最多可能达到1000个终端单元。
什么是基于微控制器的操作系统?
基于微控制器的操作系统是用于调度和协调多个软件资源的高级软件。一个相关的原语是Arduino中断服务例程(ISR)。回想一下,ISR涉及两段代码,包括主循环和ISR本身。一个例子是attachInterrupt()函数,它在引脚变化事件时被调用。在这种情况下,主循环代码将被中断以服务更高优先级的中断。完整的描述超出了本文的范围,然而,对于单核机器,我们注意到在任何给定时间只有一个进程是活动的。还有一个上下文保存的概念,允许微控制器返回到它离开的地方。
一个功能齐全的微控制器操作系统(OS)要复杂得多。我们现在在更大的微控制器项目中有多个代码部分,而不是两个半协调的异步软件部分。所有微控制器操作系统都设计为优先考虑并选择在任何给定时间哪个任务将处于活动状态。随着操作系统激活和停用各种任务,上下文的概念变得非常重要。
许多微控制器操作系统使用基于硬件的ISR。例如,操作系统可能每毫秒“滴答”一次。在每次滴答时,操作系统将根据程序员为每个任务选择的优先级来评估并交换任务。结果是一个看起来同时在执行所有任务的微控制器。
为什么要使用微控制器操作系统?
从程序员的角度来看,操作系统可以简化代码,使其更易于阅读、调试和修改。也许最大的好处是用于协调任务之间活动的强制结构。
让我们首先认识到,基于微控制器的操作系统并不是绝对必要的。只要有足够的时间和精力,程序员可以编写高度复杂的微控制器行为。这当然包括基于ISR的操作,因为ISR是微控制器的基本构建块和最大优势。同样,我们认识到复杂的手工编码微控制器程序开始类似于基于操作系统的结构。当我们进行时间共享并同步各种程序任务时,这一点尤其正确。在许多情况下,程序员通过使用像FreeRTOS这样的操作系统提供的广为人知的结构,可能会在时间和金钱上领先。您还可以节省时间,因为FreeRTOS已经存在了20年,足够长的时间来发现和修复许多错误——这些错误您需要在一次性微控制器代码中修复。
使用微控制器操作系统有哪些缺点?
我们还要认识到,操作系统在软件、硬件和时间方面增加了项目的开销。操作系统是运行在微控制器上的一段软件。像任何软件一样,操作系统在选择和协调任务操作时会消耗宝贵的微控制器资源。在基础层面上,操作系统总是加载到内存中。每次“滴答”时,它都会消耗CPU周期。
是否使用操作系统并不是一个简单的问题。除了软件、硬件和时间的考虑外,我们还必须考虑能源。在某些方面,操作系统本质上是寄生的。一个实现不佳的操作系统肯定会消耗能源,导致便携产品的电池寿命缩短。对于优化的手工编码程序,也有值得一说的地方,它大部分时间都让微控制器处于睡眠模式。
技术提示 :一个常见的问题涉及选择理想的微控制器。对于初学者来说尤其如此,他们面对的是一个硬件资源差异巨大的微控制器列表。一个务实的答案表明,没有完美的微控制器。我们所能做的就是将微控制器优化到特定项目。因此,微控制器总是存在巨大的差异。
选择操作系统是微控制器选择过程的另一层。通常,操作系统适用于需要32位微控制器的复杂性和性能要求的项目。然而,不要排除8位设备。自8051推出30多年前以来,轻量级操作系统就已经可用。
什么是 FreeRTOS ?
FreeRTOS是专门为资源受限的微控制器设计的操作系统。它是用C语言编写的,设计为与程序的其余部分一起编译。在许多方面,它就像任何其他Arduino库一样,成为编译程序的一个组成部分。
为了更好地理解操作系统,让我们通过Arduino示例进行探索。回想一下,每个Arduino程序都有一个setup()和一个loop()函数。你的程序也可能包含一个或多个ISR。在所有情况下,任何时候只能有一个程序段处于活动状态。例如,我们知道setup()在Arduino通电时运行一次。我们知道loop()会永远运行。我们还知道ISR会在其关联事件发生时运行。
这是一个注意力的问题,微控制器只能将其警觉的眼睛(程序计数器)集中在代码的单个部分上。为了在过渡时更清晰,让我们将这些代码段称为任务。因此,我们可以说微控制器可以专注于运行单个任务。程序员需要协调任务并在任务之间安全地共享数据。有关共享数据的更多信息,请参阅这篇专注于原子数据传输的文章。文章介绍了一种标志和邮箱同步方案,这是一种原始技术,暗示了在更强大的操作系统中发现的优雅选项。
技术提示 :Arduino的loop()函数不应与传统C程序中的main()混淆。回想一下,main()是一个锚点,描述了C程序的起点。你的Arduino程序有一个main()函数,但它被Arduino抽象层隐藏了。实际上,setup()和loop()是从一个较低且隐藏的main()中调用的。每个有经验的Arduino程序员都知道这是真的,因为必须使用static关键字来在loop()调用之间保留变量。
FreeRTOS 扩展了 Arduino 工作空间,包括许多 setup 和 loop 函数。
为了更好地理解FreeRTOS的强大功能,让我们扩展对Arduino的视角。想象一下,我们可以自由地建立许多这些setup/loop结构,而不是单一的setup()和loop()函数。为了方便起见,让我们再次改变我们对Arduino FreeRTOS“任务”的定义,以识别任何具有自己的setup和loop的代码段。我们可以编程任务来闪烁LED、发送串行数据、控制电机等。在每种情况下,任务彼此独立运行。
这种独立性是操作系统的关键属性。为了更好地理解,考虑一个简单的例子。假设我们希望让一个LED闪烁。回顾你最初的Arduino程序,你可能会记得这样的代码:
void loop( ) {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
后来你了解到,由于delay()函数的阻塞作用,这种代码应不惜一切代价避免。你了解到微控制器什么也不做;在阻塞函数完成之前,微控制器是盲目的。因此,我们喜欢delay()函数,因为它易于阅读。我们避免使用它,因为它会阻塞其他微控制器任务。
现在不再如此。
使用FreeRTOS,闪烁任务是一个独立的实体。为了清晰起见,我们可以使用vTaskDelay()函数而不影响任何其他独立任务。例如,这里是一个代表性的FreeRTOS任务来闪烁LED:
#include "TaskBlink.h"
void blink_thread_func(void *pvParameters) {
/* setup( ) */
pinMode(LEDR, OUTPUT);
/* loop( ) */
for (;;) {
digitalWrite(LEDR, LOW); // Note that the PORTENTA C33 LED is active low
xSemaphoreGive(xSemaphoreLEDOn);
vTaskDelay(100);
digitalWrite(LEDR, HIGH);
xSemaphoreGive(xSemaphoreLEDOff);
vTaskDelay(900);
}
}
FreeRTOS 任务的组成部分是什么?
之前的代码清单封装了一个简单的FreeRTOS任务。它是一个自包含的代码片段,包含了相当于setup和loop的功能。pinMode()函数在任务的第一次迭代中执行,有效地执行了setup。然后任务进入一个无限循环,各种语句包含在永无止境的for( ; ; )中。通常,这样的无限循环是不可取的,因为微控制器被阻塞,无法在ISR之外执行其他操作。在这种情况下并非如此。相反,操作系统是控制任务的提偶大师。
早些时候,我们描述了一个以毫秒速率滴答的操作系统。这个滴答类比代表了操作系统的结构化决策时刻。它可以保持活动任务,或者根据程序员给定的优先级将任务交换为更高优先级的任务。虽然单个任务可能在for( ; ; )循环中看起来连续运行,但它只有在操作系统允许时才会运行。在这个例子中,当需要执行我们的闪烁代码时,操作系统将上下文加载到微控制器的硬件中。在其他时候,会加载其他更高优先级任务的上下文。
请注意,Arduino 的 delay( ) 已被 FreeRTOS 的 vTaskDelay( ) 取代。默认情况下,如 #include <Arduino_FreeRTOS.h> 中所定义,delay( ) 和 vTaskDelay( ) 都以毫秒级别运行。这两个函数都适用于我们简单的 LED 闪烁延迟。FreeRTOS 版本是首选,因为它为操作系统提供了下一个激活周期何时需要的提示。由于操作系统知道自己的计时,它会立即挂起当前任务,并交换优先级列表中下一个任务的上下文。taskYIELD( ) 函数是一个类似的 CPU 释放(上下文交换)命令。
技术提示 :上下文是操作系统执行的基本操作,允许多任务处理。每个任务都需要特定的硬件来完成其工作,包括内存(局部和堆栈)、程序计数器和状态寄存器。这些硬件位置共同体现了任务的上下文。操作系统负责在其指导下保存和恢复每个任务的上下文。例如,当操作系统抢占低优先级任务以支持高优先级任务时,这是必要的。请注意,操作系统切换上下文需要时间。在某些方面,这就像一个人试图多任务处理。需要时间记住你之前的位置,然后才能恢复旧的活动。
任务如何与操作系统链接?
任务通过 xTaskCreate( ) 函数锚定到更大的 FreeRTOS 操作系统中。这段关键代码作为传统 Arduino setup( ) 的一部分被调用,允许它在 Arduino 上电或重置时运行一次且仅一次。
请注意,xTaskCreate( ) 函数实例化了一个任务。因此,任何给定任务与 xTaskCreate( ) 函数之间存在一对一的关系。我们还为任务保留了工作空间以及非常重要的优先级。在这个例子中,代码需求是最小的,闪烁 LED 的优先级很低。
xTaskCreate(
blinkTask, /* Function that implements the task. */
"Blink Task", /* Text name for the task. */
32, /* Stack size in words, not bytes. */
NULL, /* Parameter passed into the task. */
2, /* Priority at which the task is created. */
&xHandle /* Used to pass out the created task's handle. */
);
if( xReturned == pdPASS ){
vTaskDelete( xHandle );
}
技术提示 :堆栈是每个任务存储其变量的专用位置。请注意,错误的堆栈分配或优先级倒置会导致操作系统崩溃。请参阅FreeRTOS文档以了解检测实际使用情况的技术。
要从崩溃中恢复,请务必双击PORTENTA的重置按钮。您应该会看到一个绿色LED缓慢循环亮度。
Arduino FreeRTOS 模板
要开始使用FreeRTOS和Arduino,请下载并查看示例代码,同时我们继续讨论。
在此处下载代码:FreeRTOSExample.zip (4.5 KB)
请理解此代码仅用于演示目的。它未经过优化,且FreeRTOS配置(如堆栈分配)尚未验证。请“按原样”接受此代码,因为其不提供“任何形式的保证”。将其视为探索FreeRTOS在Arduino上运行可能性的起点。此外,请注意该代码源自Arduino IDE中包含的几个示例,包括PORTENTA C33 Arduino_FreeRTOS示例和Arduino IDE v2.3.2中包含的EthernetC33 DhspChatServer。
该模板是一个多标签的Arduino草图,每个任务分配给它自己的标签(.cpp和.h对)。主要的Arduino .ino文件用作各种任务的启动点。它还用于保存必要的全局变量,包括每个任务的实例化句柄和信号操作系统设备(如信号量)。这种模块化方法应有助于故障排除和添加其他模块。它将共享项目保存在单一位置。它还有助于集中注意力,因为每个单独任务的操作很容易看到。
代码模板执行哪些操作?
选择该软件是为了探索Arduino / FreeRTOS组合的实时能力。具体来说,它探索了系统在毫秒级别的响应。这是一个实验,用于确定未来的PID应用是否需要基于定时器的ISR。
所选的FreeRTOS任务(.cpp和.h对)旨在:
-
闪烁红色LED并发出指示LED状态的信号量。这是一个基本指示器,用于显示微控制器处于活动状态而非冻结状态。
-
获取信号量,然后将LED状态发送到Arduino串行监视器。
-
将D6输出脉冲置高1毫秒,然后置低9毫秒。此任务具有最高优先级,将用于评估C33 / FreeRTOS解决方案的实时适用性。目的是使用此心跳任务来支持未来的PID控制系统。
-
通过有线以太网接口向PuTTY等终端程序发送信息。
实时性能
图2展示了PORTENTA C33在FreeRTOS控制下的关键实时性能测量结果。这张来自Digilent Analog Discovery的屏幕截图显示了此简单任务的结果。重要的是要知道,此任务被赋予了最高优先级,并且我们已加载C33以其他繁重任务,试图干扰正常流程。
for (; ;) {
digitalWrite(HEARTBEAT_PIN, HIGH);
vTaskDelay(1);
digitalWrite(HEARTBEAT_PIN, LOW);
vTaskDelay(9);
}
图2中测量的抖动表明10毫秒信号的抖动为+/-40微秒。这表明C33 / FreeRTOS组合可以保持一致的时序,这对于PID等控制算法至关重要。+/-40微秒的抖动完全符合我们未来实现PID控制器的要求,该控制器与时间常数在500毫秒范围内的伺服电机耦合。这是个好消息,因为它表明我们可以使用高级Arduino实现,而无需深入研究C33复杂的Renesas Arm Cortex微控制器的裸机ISR编程。
图 2 : Arduino PORTENTA C33的实时性能,显示在通过以太网发送“quick brown fox” pangram时,从一个10毫秒上升沿到另一个上升沿的+/-40微秒抖动。
学习 FreeRTOS 的下一步是什么
在使用FreeRTOS方面,我们只是触及了表面。例如,演示程序展示了FreeRTOS信号量,其中闪烁任务告诉串行任务LED已改变状态。这种任务间通信是FreeRTOS的基本方面之一,因为它提供了一种在异步任务之间安全通信的方式。
你当然可以在他们的主页上了解更多关于FreeRTOS的信息。
结论
入门的一部分是知道什么是可能的。我相信这个简短的介绍已经让你尝到了Arduino和FreeRTOS的可能性,尤其是与Arduino Pro系列微控制器的高性能相结合时。
请点击此链接获取相关的Arduino教育内容。