PIC微控制器上的多任务处理

电器工程 微控制器 图片 rtos 线
2022-01-29 08:40:35

如今,多任务处理很重要。我想知道我们如何在微控制器和嵌入式编程中实现它。我正在设计一个基于 PIC 微控制器的系统。我在 MplabX IDE 中使用 C 设计了它的固件,然后在 Visual Studio 中使用 C# 为它设计了一个应用程序。

由于我已经习惯在桌面上使用 C# 编程中的线程来实现并行任务,有没有办法在我的微控制器代码中做同样的事情?MplabX IDE 提供pthreads.h但它只是一个没有实现的存根。我知道有 FreeRTOS 支持,但使用它会使您的代码更加复杂。一些论坛说中断也可以用作多任务,但我不认为中断等同于线程。

我正在设计一个将一些数据发送到 UART 的系统,同时它需要通过(有线)以太网将数据发送到网站。用户可以通过网站控制输出,但输出会延迟 2-3 秒打开/关闭。所以这就是我面临的问题。微控制器中的多任务处理有什么解决方案吗?

4个回答

有两种主要类型的多任务操作系统,抢占式和协作式。两者都允许在系统中定义多个任务,不同之处在于任务切换的工作方式。当然,对于单个核心处理器,一次实际上只运行一个任务。

两种类型的多任务操作系统都需要为每个任务使用单独的堆栈。所以这意味着两件事:首先,处理器允许将堆栈放置在 RAM 中的任何位置,因此具有移动堆栈指针 (SP) 的指令——即没有像低端那样的专用硬件堆栈PIC的。这不包括 PIC10、12 和 16 系列。

您几乎可以完全用 C 编写操作系统,但 SP 移动的任务切换器必须在汇编中。我曾多次为 PIC24、PIC32、8051 和 80x86 编写任务切换器。根据处理器的体系结构,胆量完全不同。

第二个要求是有足够的 RAM 来提供多个堆栈。通常,一个堆栈至少需要几百个字节;但是即使每个任务只有 128 字节,八个堆栈也需要 1K 字节的 RAM——尽管您不必为每个任务分配相同大小的堆栈。请记住,您需要足够的堆栈来处理当前任务以及对其嵌套子例程的任何调用,还需要堆栈空间来进行中断调用,因为您永远不知道何时会发生中断调用。

有相当简单的方法可以确定您为每个任务使用了多少堆栈;例如,您可以将所有堆栈初始化为特定值,例如 0x55,然后运行系统一段时间,然后停止并检查内存。

你没有说你想使用什么样的PIC。大多数 PIC24 和 PIC32 将有足够的空间来运行多任务操作系统;PIC18(唯一在 RAM 中有堆栈的 8 位 PIC)的最大 RAM 大小为 4K。所以这很不确定。

通过协作多任务处理(两者中较简单的一种),任务切换仅在任务“放弃”其控制权返回给操作系统时完成。每当任务需要调用操作系统例程来执行它将等待的某些功能时,就会发生这种情况,例如 I/O 请求或计时器调用。这使得 OS 更容易切换堆栈,因为不需要保存所有的寄存器和状态信息,SP 可以切换到另一个任务(如果没有其他任务准备运行,则空闲堆栈是给予控制)。如果当前任务不需要进行操作系统调用但已经运行了一段时间,它需要主动放弃控制以保持系统响应。

协作多任务处理的问题是,如果任务从不放弃控制,它会占用系统。只有它和任何碰巧获得控制的中断例程才能运行,因此操作系统似乎会锁定。这是这些系统的“合作”方面。如果实现了仅在执行任务切换时重置的看门狗定时器,则可以捕获这些错误任务。

Windows 3.1 和更早的版本是协作操作系统,这也是它们性能不那么出色的部分原因。

抢先式多任务处理更难实现。在这里,任务不需要手动放弃控制,而是可以给每个任务一个最大的运行时间(比如 10 毫秒),然后执行任务切换到下一个可运行任务(如果有的话)。这需要任意停止一个任务,保存所有状态信息,然后将 SP 切换到另一个任务并启动它。这使得任务切换器更加复杂,需要更多的堆栈,并且稍微降低了系统速度。

对于协作式和抢占式多任务处理,任何时候都可能发生中断,这将暂时抢占正在运行的任务。

正如 supercat 在评论中指出的那样,协作式多任务处理的一个优势是更容易共享资源(例如,多通道 ADC 等硬件或修改链表等软件)。有时两个任务想要同时访问同一个资源。通过抢占式调度,操作系统可以使用资源在一个任务的中间切换任务。所以是必要的,以防止另一个任务进入并访问相同的资源。对于协作式多任务处理,这不是必需的,因为任务控制何时将其自身释放回操作系统。

线程由操作系统提供。在嵌入式世界中,我们通常没有操作系统(“裸机”)。所以这留下了以下选项:

  • 经典的主轮询循环。您的主要功能有一个 while(1) 执行任务 1 然后执行任务 2 ...
  • 主循环 + ISR 标志:您有一个执行时间关键功能的 ISR,然后通过标志变量提醒主循环该任务需要服务。也许 ISR 将一个新字符放入循环缓冲区中,然后告诉主循环在准备好处理数据时处理数据。
  • 所有 ISR:这里的大部分逻辑都是从 ISR 执行的。在具有多个优先级的现代控制器上,例如 ARM。这可以提供强大的“类似线程”的​​方案,但也可能会混淆调试,因此它应该只保留用于关键时序约束。
  • RTOS:RTOS 内核(由定时器 ISR 促进)可以允许在多个执行线程之间切换。您提到了 FreeRTOS。

我建议您使用适用于您的应用程序的上述最简单的方案。根据您的描述,我会让主循环生成数据包并将它们放入循环缓冲区。然后有一个基于 UART ISR 的驱动程序,只要前一个字节完成发送就会触发,直到发送缓冲区,然后等待更多缓冲区内容。以太网的类似方法。

与任何单核处理器一样,不可能进行真正的软件多任务处理。因此,您必须注意以一种方式在多个任务之间切换。不同的 RTOS 正在处理这个问题。他们有一个调度程序,并根据系统滴答声,他们将在不同的任务之间切换,为您提供多任务处理能力。

这样做所涉及的概念(上下文保存和恢复)非常复杂,因此手动执行此操作可能会很困难,并且会使您的代码更加复杂,并且因为您以前从未这样做过,所以其中会出现错误。我的建议是使用经过测试的 RTOS,就像 FreeRTOS 一样。

您提到中断提供了一定程度的多任务处理。这是真的。中断将在任何时候中断您当前的程序并在那里执行代码,它类似于一个两个任务系统,其中一个任务具有低优先级,另一个任务具有高优先级,在调度程序的一个时间片内完成。

因此,您可以为循环定时器编写一个中断处理程序,它将通过 UART 发送一些数据包,然后让程序的其余部分执行几毫秒并发送接下来的几个字节。这样你就可以获得有限的多任务处理能力。但是你也会有一个相当长的中断,这可能是一件坏事。

在单核 MCU 上同时执行多项任务的唯一真正方法是使用 DMA 和外围设备,因为它们独立于内核工作(DMA 和 MCU 共享相同的总线,因此在两者都处于活动状态)。因此,当 DMA 将字节改组到 UART 时,您的内核可以自由地将内容发送到以太网。

其他答案已经描述了最常用的选项(主循环、ISR、RTOS)。这是另一种折衷方案:Protothreads它基本上是一个非常轻量级的线程库,它使用主循环和一些 C 宏来“模拟”一个 RTOS。当然,它不是完整的操作系统,但对于“简单”线程来说,它可能很有用。