嵌入式编程中最常听到的建议是“保持中断时间短”。
现在我的情况是我的main()循环中有一个非常长的运行任务(将大块数据写入SD卡),有时需要100ms。因此,为了保持我的系统响应,我将所有其他内容移动到了中断处理程序。
例如,通常可以在中断中处理传入的UART数据,然后在main()循环中处理传入的命令,然后发回响应。但在我的情况下,命令的整个处理/处理也在中断中发生,因为我的main()循环可以被阻塞(相对)长时间。
最佳解决方案是切换到RTOS,但我没有RAM。我的设计是否有替代方案可以缩短中断时间?
答案 0 :(得分:10)
传统的方法是让Interrupts安排延期程序并尽快结束中断。
一旦中断完成,延期程序列表就会从最重要的程序走到最不重要的程序。
考虑你有主要(较低职业)行动和两个中断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。
答案 1 :(得分:9)
根据CPU架构(中断嵌套和优先级划分,软件中断支持等),有100种不同的方法可以对这只猫进行换肤,但让我们采用一种非常直接的方法,这种方法相对简单易懂并且不受竞争条件影响和先发制人内核的资源共享危害。
(免责声明:我的第一选择通常是抢占式实时内核,其中许多可以在极其资源受限的系统中运行...... SecurityMatt的建议很好但是如果你不习惯实现自己的可抢占内核/任务切换器特别是处理异步(中断触发)抢占的问题,你可以很快地绕过轴。所以我在下面提出的建议并不像基于抢占的内核那样响应,但它更简单,通常也足够了。
创建3个事件/工作队列:
我将UART RX FIFO读取和读取数据包的处理分开,以便在数据包处理之前始终为FIFO读取提供服务;也许你想把它们放在一起,你的选择。
为了实现这一目标,您可以将大型(~100ms)SD卡写入过程分解为一系列较小的,离散的,运行至完成步骤。
因此,例如,要写入5个块,每个块20ms,您编写第一个块,然后将“写下一个块”排队到Q1。您将在每个步骤结束时返回到您的计划程序。从Q3开始按优先级顺序扫描队列。如果Q2和Q3为空,则将下一个事件从Q1中拉出(“写下一个块”),再运行该命令20ms,然后再返回并再次扫描队列。如果20ms没有足够的响应,则将每个20ms的块写入分解为更细粒度的步骤,并在下一个工作步骤中不断向Q1发布。
现在输入UART的东西;在UART RX ISR中,您可以简单地在Q3中将“读取UART FIFO”命令排入队列,并从中断返回到被中断的20ms“写入块”步骤。一旦CPU完成写入,它就会返回并按优先级顺序扫描队列(如果在中断时块写入刚刚开始,则最坏情况响应将为20ms)。队列扫描程序(调度程序)将看到Q3现在有工作要做,它将在返回并再次扫描之前运行该命令。
在最坏的情况下,系统的响应能力将由系统中最长的完成运行步骤决定,无论优先级如何。通过在小型,离散,运行到完成步骤中完成工作,您可以使系统保持高响应性。
请注意,我必须在这里说一般性。也许你想在ISR中读取UART RX FIFO,将数据放入缓冲区,只推迟数据包处理,而不是实际读取FIFO(那时你只有2个队列)。你必须自己解决这个问题。但我希望这种方法有道理。
这种具有优先级队列的事件驱动方法正是Quantum Platform (QP) event-driven framework使用的方法。 QP实际上支持底层的非抢占式(协作式)调度程序,例如此处描述的调度程序,或者每个事件排队时运行调度程序的抢占式调度程序(类似于SecurityMatt建议的方法)。您可以在QP网站上查看QP协作调度程序的代码/实现。
答案 2 :(得分:3)
另一种解决方案如下:
FAT库可以长时间捕获处理器的任何地方,您插入一个通常非常快的新函数的调用,并在几个机器周期后返回给调用者。这种快速功能不会影响耗时操作的实时性能,例如读/写SD闪存。您可以在等待擦除闪存扇区的任何循环中插入此类调用。您还可以在写入的每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时,继续调用可能需要一些微秒或甚至毫秒才能执行的函数。您可能需要尝试更小或更大的二进制掩码,具体取决于您希望为串行端口提供服务的速度和其他操作。您甚至可以同时使用多个掩码来比其他掩码更快地为某些操作提供服务。
例如,当两次闪存擦除操作之间发生偶然延迟时,FAT库和SD卡不会出现任何问题。
这里给出的解决方案甚至可以使用只有2K字节的微控制器,就像8051的许多变体一样。尽管看起来令人难以置信,1980年到1990年的弹球机有几K的RAM,慢处理器(如10 MHz)和它们能够测试100个开关......完全去抖动,更新X / Y矩阵显示器,产生声音效果等。这些工程师开发的解决方案仍然可以用来提升大型系统的性能。即使拥有64 GB RAM和许多太字节硬盘的最佳服务器,我也认为当一些公司想要索引数十亿个WEB页面时,任何字节数都会计算在内。
答案 3 :(得分:2)
由于没有人建议从这一端开始,但我会把它扔进去:
可能会将 SD卡服务程序置于低优先级中断,如果可以,可能会抛出一些DMA,可以释放主循环和放大器。其他中断更具响应性,而不是陷入main()循环中等待很长时间才能完成。
需要注意的是,当SD卡准备好更多时,我不知道硬件是否有任何触发中断的方法,你可能不得不通过运行轮询计时器来检查&强制中断。但是,如果你有备用的硬件计时器,那我就不会超过那种东西。中断它可以用很少的开销来完成。
使用这样的东西看RTOS似乎有点矫枉过正承认我失败了......;)