【问题标题】:How to keep interrupts short?如何保持中断时间短?
【发布时间】:2013-01-17 21:25:33
【问题描述】:

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

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

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

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

【问题讨论】:

  • 你有多少内存? 16K 对 RTOS 来说很好,如果你不会因为太多的线程/堆栈而发疯,也许是 8K。在我的系统上,最简单的“Flash LED”线程只需要 12 个字的堆栈(两个睡眠调用)。
  • 8k 并且您使用的是 RTOS?我不想开始争论,但我还没有确信需要任何 RTOS,到值得拥有一个操作系统的时候,有一个可以在硬件上运行的 linux 版本。
  • @JohnU 他说 最佳 解决方案是 RTOS,但他没有内存

标签: embedded arm interrupt firmware interrupt-handling


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 你说得比我好,所以我将删除我的答案。
  • 我不知道如何实现这样的“延迟程序”,但我认为它非常接近我现在使用的 (ARM) PendSV 中断。它可以从更高优先级的中断中设置为挂起,并在中断完成后立即执行。从那里我做我的主要工作。
  • 每个过程都有一个标志,主循环中有一个调度程序。在每次迭代中,调度程序按优先级顺序检查标志并运行第一个设置标志的过程。
【解决方案2】:

根据 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 Platform (QP) event-driven framework 使用的方法。 QP 实际上支持底层的非抢占式(协作式)调度器,例如这里所描述的,或者在每个事件排队时运行调度器的抢占式调度器(类似于 SecurityMatt 建议的方法)。您可以在 QP 网站上查看 QP 的协作调度程序的代码/实现。

【讨论】:

  • 非常好的和全面的答案!但是我不能以小步骤分解 microsd 写入的原因是因为即使我一次只写入一个字节,在每个集群边界上,即使是最小的写入也可能需要很长时间(比我想要的响应时间长得多)是)。除此之外,事实证明多扇区写入要快得多,因此我尝试尽可能多地进行缓冲,然后一口气(长时间)完成所有操作。
  • 有道理。我看到您正在使用 ARM Cortex 和 PendSV,这正是 PendSV 的设计目的(嗯,通常它用于执行上下文切换,但它的工作是以低于任何其他中断的优先级运行,但在优先级高于任何后台活动)。
【解决方案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 页面时,任何字节都算数。

【讨论】:

    【解决方案4】:

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

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

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

    为了这样的事情求助于 RTOS 似乎有点矫枉过正,而且我承认失败了...... ;)

    【讨论】:

    • 我不直接与卡对话,我与公开简单 POSIX 函数的 FAT 库对话,例如 f_read() 和 f_write()。因此,即使 SPI 传输是非阻塞的,对 f_write() 的调用也将始终阻塞,因为在整个写入完成之前它无法返回。如果我使用低优先级中断来调用 f_write(),我的 main() 循环仍将被阻塞(就像现在一样),因为它的优先级甚至低于中断。所以我看不出将 sd 卡处理转移到中断有什么好处?
    • 我假设由于您没有使用 RTOS,因此您可以更直接地访问该过程或自己编写了该东西。你有图书馆的来源,所以你可以做出改变吗?那将是我的下一个停靠港。
    • 是的,它是非常流行的 FatFS 库 (elm-chan.org/fsw/ff/00index_e.html)。但是将它重写为异步的东西会花费很多精力(我怀疑它甚至可能)。
    • 一切皆有可能。我没有深入研究 SD 卡 IO,但我不明白为什么你不能修改驱动程序代码以在忙碌时返回一个新的、不同的状态,而不仅仅是阻塞。
    • 总是约束的问题;在不了解您的项目的情况下,这个问题可能是值得的,也可能是微不足道的。也许你的老板不会付钱让你花半年时间移植到 RTOS 并调试它,再加上许可费……也许你的老板会付钱让你在 SD 卡和你的主微之间放一个便宜的小微瓶颈,或者由于硬件限制,这可能是不可能的......有很多选择。也许你的老板会付钱给写这个库的人,让它对你来说没有阻塞。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-20
    • 2019-12-22
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多