FreeRTOS 与中断之间的关系是什么?
Arduino PORTENTA Pro C33 的 FreeRTOS 实现是一个中断驱动的过程,每次 tick 之间的时间为 1 毫秒。FreeRTOS 只是运行在 C33 上的众多中断服务例程 (ISR) 之一。当这些 ISR 在实时系统中竞争微控制器资源时,我们面临一个挑战。让我们以项目的形式来阐述这个问题。
这篇工程简报是之前 FreeRTOS 介绍的延续。回想一下,Arduino PORTENTA Pro C33 被编程为使用 FreeRTOS,包括多种任务,如闪烁 LED、向 Arduino 串行监视器发送数据以及通过有线以太网进行通信。在此基础上,我们将添加本文中描述的增量编码器。此外,回想一下,增量编码器的速度较高,每秒 11.2 k 计数,需要仔细集成 ISR 以防止漏计。增量编码器代码包括一个状态机,用于识别常见故障,例如从位置 00 到 11 的误计。
技术提示 :请始终记住,如果漏掉任何一个计数器转换,增量编码器系统将比无用更糟。如果微控制器太忙而无法跟踪编码器,物理机制将会失效并逐渐迷失。
图 1 展示了该问题的过于简化的表示。这里我们看到增量编码器的 A 和 B 输出。第三条线是一个脉冲,表示 ISR 已处理增量编码器事件。请注意,圆圈标记的转换被更高优先级的 FreeRTOS ISR 延迟了。还要注意,12 VDC 电机在施加 1.5 VDC 时移动得非常缓慢。如果我们加快电机速度,FreeRTOS 相关的延迟时间将比增量编码器周期更长。结果是漏掉了一个转换,机械系统迷失了。
请不要误解。我绝不是暗示 Arduino 和默认的 FreeRTOS 或中断设置有误。相反,这些设置对于大多数项目来说是完全合适的。相反,这个项目及其增量编码器的快速实时操作是一个例外,需要非默认设置。
图 1 :逻辑分析仪显示增量编码器的 A 和 B 输出,以及一个指示在相关 ISR 中花费时间的 tick。观察 FreeRTOS 具有优先级时的跳跃。
技术提示 :如果您还没有这样做,建议您回顾一下前面提到的正交编码器文章。乍一看,基于有限状态机(FSM)的代码似乎过于复杂,尤其是当我们考虑到理想的正交编码器代码可以用几行代码编写时。然而,当我们考虑到代码开发以及协调多个竞争性ISR的复杂性时,知道时间关键部分有一个内置的故障安全机制是很好的。FSM不会捕获所有错误,但它会轻松捕获遗漏的转换。这对于每秒中断超过11,000次的系统尤其重要。
FreeRTOS 如何与正交编码器等其他高速中断发生冲突?
FreeRTOS是为微控制器量身定制的轻量级操作系统(OS)。它旨在协调和控制多个任务的活动,使它们看起来无缝运行。请记住,微控制器是一种有限资源,在任何给定时间只能运行一个任务。目前,我们将忽略多核处理器和增强外设,如直接内存访问(DMA)。
技术提示 :任务是FreeRTOS的基本构建块。目标是将一个较大的微控制器程序划分为几个有意义的任务,然后使用FreeRTOS机制来协调这些任务。这些任务可以按功能/硬件划分,也可以按优先级划分。例如,一个简单的机器人可能包括控制电机的任务。然后,它可能有更高层次但速度较慢的任务来控制机器人行为。在某些方面,这反映了人类自主和反射神经系统之间的差异。
软件和硬件中的优先级
FreeRTOS的目的是选择和指导正在运行的任务,然后根据程序员为每个任务分配的优先级交换任务。此优先级由程序员在任务实例化时分配。例如,响应式比例积分微分(PID)控制任务将被分配比后台通信任务更高的优先级。在这个例子中,PID前台任务必须定期完成,以保持实时过程的稳定性。通常,与用户的后台通信不是时间关键的。
不应将FreeRTOS分配的优先级与微控制器的各种ISR的优先级混淆。在这种情况下,FreeRTOS滴答是许多事件(定时器)驱动的ISR之一,它们竞争微控制器资源。例如,FreeRTOS上下文交换机制本身主要作为由硬件定时器驱动的ISR存在。对于硬件计时器的每一次滴答,FreeRTOS ISR 都会被交换进来并执行其高级魔法,例如激活 PID 和激活通信。与所有 ISR 一样,FreeRTOS 被编写得尽可能小,以免占用微控制器资源。
在这种低级分析中,FreeRTOS ISR 与其他 ISR 竞争,例如引脚上的变化:
attachInterrupt(digitalPinToInterrupt(QUAD1_A_PIN), ISR_QUAD1, CHANGE);
attachInterrupt(digitalPinToInterrupt(QUAD1_B_PIN), ISR_QUAD1, CHANGE);
总结一下,不要将高级的 FreeRTOS 任务优先级与低级的 ISR 优先级混淆。FreeRTOS 任务在软件级别进行,而 ISR 优先级是硬件配置。
基于硬件的 ISR 优先级和 ARM NVIC
PORTENTA Pro C33 配备了一个复杂的瑞萨 Arm Cortex 处理器。该微控制器设计用于在复杂的实时环境中运行。因此,它具有一个强大的中断向量硬件机制,允许为相关的 ISR 分配优先级。更好的是,它具有一个嵌套向量中断控制器(NVIC),其中高优先级的 ISR 可以抢占低优先级的 ISR。作为一个简单的例子,我们可以为 FreeRTOS 滴答 ISR 分配一个优先级,为 Arduino attachInterrupt 分配另一个优先级。
这让我们进入文章的核心。
这些基于硬件的优先级必须正确分配,以便高速正交编码器在前台运行。事实上,它必须具有比 FreeRTOS 更高的优先级,以防止丢失脉冲。换句话说,选择正交编码器的 NVIC 优先级,以便它抢占 FreeRTOS。
我们如何更改 Arduino PORTENTA Pro ARM 处理器的 NVIC ISR 优先级?
虽然有几种方法可以更改 NVIC 的行为,但我们将专注于一个简单的单行更改解决方案。这需要我们探索 Arduino IDE 的内部工作原理,并从 renesas_portenta 文件夹中找到 IRQManager.cpp 文件:
C:\Users\your_name\AppData\Local\Arduino15\packages\arduino\hardware\renesas_portenta\1.1.0\cores\arduino\IRQManager.cpp
找到文件后,更改优先级。在此示例中,默认的 Arduino 优先级从 12 更改为 5(较小的数字具有更高的优先级)。相对而言,与正交编码器相关的外部中断现在具有最高优先级。
#define EXTERNAL_PIN_PRIORITY 5 // default is 12
更改 Arduino ARM NVIC 优先级有哪些限制?
就我个人而言,我不喜欢这个解决方案,因为我们暴露了 Arduino IDE 深处的敏感文件,使其容易破坏 Arduino。此外,IDE 的更新将需要修改新文件。不用说,这个解决方案不可移植,需要每个程序员进行底层更改。最后,也不能保证Arduino团队不会改变底层结构。
如果你擅长Arduino IDE和ARM NVIC操作,请评论或提供更好的解决方案。如果我们能够用所需的ARM NVIC优先级重载attachInterrupt()函数,那就太好了。另一种选择,也许attachInterrupt()可以返回NVIC编号。这将允许使用诸如NVIC_SetPriority()之类的函数。
当我们配置 ARM NVIC 以使正交编码器具有更高优先级时,结果如何?
在这个演示中,我们改变了中断优先级,使正交编码器具有最高优先级。这是通过将NCIV优先级从默认的12更改为5来实现的。结果显示,与图1相比有显著改进。圈出的延迟完全消失,电机现在以最大速度运行,没有丢失计数。请注意,这个示例是早期项目的延续。C33被编程为承担重负载,包括通过有线以太网连接流式传输“the quick brown fox jumps over the lazy dog”以及通过串行连接流式传输位置计数。
你可以在这里下载代码:FreeRTOSISR.zip (7.3 KB)
回想一下,正交编码器包括一个强大的基于状态的错误检测机制。在操作系统时没有检测到任何错误。
最后的思考
Arduino PORTENTA C33,凭借其ARM Cortex微控制器,通过ARM NVIC机制提供了多功能的中断控制。当与FreeRTOS结合使用时,我们必须仔细分配FreeRTOS软件和基于硬件的中断的优先级。本文中介绍的正交编码器机制为探索复杂性提供了一个很好的起点。我们鼓励你继续学习,因为我们才刚刚开始探索这个丰富的话题。
