11

嵌入式编程中听到最多的建议是“保持中断短”。

现在我的情况是,我的 main() 循环中有一个运行时间很长的任务(将大块数据写入 SD 卡),有时可能需要 100 毫秒。所以为了让我的系统保持响应,我把所有其他的东西都移到了中断处理程序中。

例如,通常会在中断中处理传入的 UART 数据,然后在 main() 循环中处理传入的命令,然后发回响应。但就我而言,命令的整个处理/处理也发生在中断中,因为我的 main() 循环可以(相对)长时间阻塞。

最佳解决方案是切换到 RTOS,但我没有 RAM。我的设计是否有中断可以很短的替代方案?

4

4 回答 4

10

传统的方法是让中断安排一个延迟的过程并尽快结束中断。

一旦中断完成,延迟过程的列表就会从最重要到最不重要。

考虑这样一种情况,您有主要(较低优先级)操作,以及两个中断 I1 和 I2,其中 I2 比 main 更重要,但不如 I1 重要。

在这种情况下,假设您正在运行 main 并且 I1 触发。I1 安排一个延迟的过程并向硬件发出 I1 已完成的信号。I1 的 DPC 现在开始运行。突然,I2 来自硬件。I2 的中断从 I1 的 DPC 接管并调度 I2 的 DPC 并向硬件发出它已完成的信号。

然后调度器返回到 I1 的 DPC(因为它更重要),当 I1 的 DPC 完成时,I2 的 DPC 开始(因为它比 main 更重要),然后最终返回执行到 main。

这种设计允许您安排不同中断的重要性,鼓励您保持较小的中断,并允许您以有序和按优先级的方式完成 DPC。

于 2013-02-02T19:20:28.273 回答
10

根据 CPU 架构(中断嵌套和优先级、软件中断支持等),有 100 种不同的方法可以给这只猫换皮,但让我们采用一种相对简单易懂且不受竞争条件和资源共享的直接方法抢占式内核的危害。

(免责声明:我的首选通常是抢占式实时内核,其中许多可以在资源极其受限的系统中运行……SecurityMatt 的建议很好,但如果您不习惯实现自己的抢占式内核/任务切换器,尤其是一个处理异步(中断触发)抢占,你可以很快地绕在轴上。所以我在下面提出的不如基于抢占的内核响应迅速,但它更简单且通常足够)。

创建 3 个事件/工作队列:

  • Q1 是最低优先级,处理缓慢的后台 SD 卡写入
  • Q2 持有处理传入 UART 数据包的请求
  • Q3(最高优先级)保存 UART RX FIFO 读取请求。

我将 UART RX FIFO 读取和读取数据包的处理分开,以便始终在数据包处理之前为 FIFO 读取提供服务;也许你想让它们在一起,你的选择。

为此,您将大型(约 100 毫秒)SD 卡写入过程分解为一组较小的、离散的、运行到完成的步骤

例如,要写入 5 个块,每个 20 毫秒,您写入第一个块,然后将“写入下一个块”排入队列到 Q1。您在每个步骤结束时返回调度程序并按优先级顺序扫描队列,从 Q3 开始。如果 Q2 和 Q3 为空,则将下一个事件从 Q1 中拉出(“写入下一个块”),然后再运行该命令 20 毫秒,然后再返回并再次扫描队列。如果 20 毫秒的响应不够快,您可以将每个 20 毫秒的块写入分解为一组更细粒度的步骤,不断将下一个工作步骤发布到 Q1。

现在对于传入的 UART 的东西;在 UART RX ISR 中,您只需在 Q3 中将“读取 UART FIFO”命令排入队列,然后从中断返回到被中断的 20 毫秒“写入块”步骤。一旦 CPU 完成写入,它就会返回并按优先级顺序扫描队列(如果块写入在中断时刚刚开始,最坏情况下的响应将是 20 毫秒)。队列扫描器(调度器)将看到 Q3 现在有工作要做,它会在返回并再次扫描之前运行该命令。

在最坏的情况下,系统中的响应能力将取决于系统中最长的运行到完成步骤,而不管优先级如何。通过在小的、离散的、运行到完成的步骤中进行工作,您可以保持系统的响应速度非常快。

请注意,我必须在这里泛泛而谈。也许您想读取 ISR 中的 UART RX FIFO,将数据放入缓冲区,并且只延迟数据包处理,而不是实际读取 FIFO(那么您只有 2 个队列)。你必须自己解决这个问题。但我希望这种方法是有意义的。

这种具有优先队列的事件驱动方法正是Quantum 平台 (QP) 事件驱动框架使用的方法。QP 实际上支持底层的非抢占式(协作式)调度程序,例如这里描述的,或者在每个事件排队时运行调度程序的抢占式调度程序(类似于 SecurityMatt 建议的方法)。您可以在 QP 网站上查看 QP 协作调度程序的代码/实现。

于 2013-02-02T20:18:19.227 回答
3

另一种解决方案如下:

在 FAT 库可以长时间捕获处理器的任何地方,您插入一个对新函数的调用,该函数通常非常快,并在几个机器周期后返回给调用者。如此快速的功能不会影响您耗时操作的实时性能,例如读取/写入 SD Flash。您将在等待擦除闪存扇区的任何循环中插入此类调用。您还可以在每写入 512 个字节或读取 512 个字节之间插入对此类函数的调用。

该函数的目标是执行您通常在嵌入式设备的典型“main()”中的“while(1)”循环中执行的大部分任务。它首先会增加一个整数并对新值执行快速模运算,然后如果模不等于任意常数则返回。代码如下:

void premption_check(void)
{
    static int fast_modulo = 0;
    //divide the number of call
    fast_modulo++;
    if( (fast_modulo & 0x003F) != 3)
    {
        return;
    }
    //the processor would continue here only once every 64 calls to "premption_check"

接下来,您调用从串行端口中断中提取 RS232 字符/字符串的函数,如果收到完整的字符串则处理任何命令等

上面使用的二进制掩码 0x3F 意味着我们只查看计数器的 6 个最低有效位。当这 6 位恰好等于任意值 5 时,继续调用可能需要一些微秒甚至毫秒才能执行的函数。您可能需要尝试更小或更大的二进制掩码,具体取决于您希望为串行端口和其他操作提供服务的速度。您甚至可以同时使用多个面罩来比其他面罩更快地为某些操作提供服务。

例如,当两次 Flash 擦除操作之间发生一些零星延迟时,FAT 库和 SD 卡应该不会遇到任何问题。

这里给出的解决方案甚至适用于只有 2K 字节的微控制器,就像 8051 的许多变体一样。尽管看起来令人难以置信,1980 到 1990 年的弹球机只有几 K 的 RAM,处理器速度很慢(比如 10 MHz)他们可以测试一百个开关……完全去抖,更新 X/Y 矩阵显示,产生声音效果等这些工程师开发的解决方案仍然可以用来提高大型系统的性能。即使拥有 64 Gig RAM 和许多 TB 硬盘的最好的服务器,我认为当某些公司想要索引数十亿个 WEB 页面时,任何字节都算数。

于 2013-02-04T13:56:04.210 回答
2

由于没有人建议从这一端开始,但我会把它扔在帽子里:

SD 卡服务例程置于低优先级中断中,如果可以的话,可能会抛出一些 DMA,这可能会释放主循环和其他中断以提高响应速度,而不是卡在 main() 循环等待中很长时间来完成某件事。

对此的警告是,我不知道当 SD 卡准备好更多时硬件是否有任何方式触发中断,您可能必须通过运行轮询计时器来检查并强制中断来作弊。不过,我不在这种事情之上,如果你有备用的硬件定时器和中断,它可以用很少的开销来完成。

为这样的事情诉诸 RTOS 似乎有点矫枉过正,而且我承认失败了……;)

于 2013-02-04T10:46:17.757 回答