我在AVR上有许多中断服务程序。这些包括用于USART串行通信,计时器和SPI通信的中断。

对于所有这些,我都使用循环队列(使用开始和结束指针,不进行边界检查)。

如果AVR开始过载,则开始出现一些问题。循环队列将丢失数据块。为了解决这个问题,我可以减轻AVR的负担(例如,跳过计时器中的某些周期)。但是,这是一个手动过程,如果AVR似乎有问题,可以减少此过程。部分原因是因为我确实想要相对一致的计时器周期。

但是,即使在平均处理器负载为70%的情况下,队列也可能会随机填充。

有时会出现虚假重载,如何使它更具适应性,以避免队列溢出?

评论

我想知道这个问题是否比Robotics更适合StackOverflow网站?

带有嵌入标记的StackOverflow问题。

如果我们要尝试将其分流到另一个站点,那么对于程序员来说,Stack Exchange比Stack Overflow更合适,因为这是一个概念问题。实际上,由于在非机器人环境下针对机器人技术的答案与该问题有所不同,因此我认为这很好。

什么型号的AVR?您是否考虑过运行FreeRTOS?它提供了内置的队列管理功能,并能够根据负载动态地对任务进行优先级排序。

各位,请不要再说机器人编程问题与此无关。因为他们这样做。信不信由你,如果你在stackoverflow上问这个问题,你将不会得到任何答案。导致程序员在那儿几乎不了解中断或AVR编程。.如果您不知道答案,请直接回答另一个问题,而不是投下反对票或投票关闭。谢谢。让社区生活下去!!

#1 楼

听起来您正在观察“字节丢失”的症状。有几种不同的情况可能会导致AVR丢失字节。

听起来您正在猜测,如果在缓冲区已满时传入新字节,它就会丢失字节。尽管有多种补救措施可以解决或至少改善该特定问题,但是如果“丢失字节”的真正原因还有其他原因,这些方法将无用且适得其反。

我首先要要做的是设置某种“状态调试”端口,该端口为我提供了一个线索,为什么丢失了确切的字节-至少是一个状态LED。一旦知道了字节丢失的原因,就可以采取适当的补救措施。

大多数现代协议在每个数据包的末尾都有某种校验值。
状态调试很不错系统可以报告数据包吞吐率和数据包错误率-至少使每个经过验证的数据包闪烁绿色LED并针对每个失败的数据包检查闪烁红色LED。

此后(尤其是与无线电连接)偶然损坏的字节几乎是不可避免的,大多数现代协议都经过精心设计,因此,如果任何字节损坏或丢失,系统最终将丢弃该数据包,并最终重新同步并正确处理将来的数据包。

由于中断处理程序从未将字节放入缓冲区而丢失了字节

经常丢失的字节是因为中断处理程序从未将它们放入缓冲区。有多种原因,每种都有不同的补救措施:外部问题,以及内部中断关闭时间过长。

外部问题:


线路噪声导致错误
物理有线连接意外被临时拔下
无线电连接上的信号丢失

通常我们将示波器夹在输入引脚上,如果幸运的话,我们可以看到问题并尝试各种技术以查看是否可以清除信号。

即使输入引脚上的信号看起来很完美,我们仍然会有数据丢失的问题。

在字节的最后一位进入USART或SPI端口后,立即
通常会触发该端口的中断处理程序,并且该中断处理程序会拉该字节并将其粘贴到循环缓冲区中。但是,如果中断关闭的时间太长,则进入该端口的下一个字节将不可避免地覆盖并丢失第一个字节-该端口的中断处理程序永远不会看到该第一个字节。在“什么可能导致UART接收中断异常长的延迟的原因?”中列出了四种“中断处理程序可以关闭得太久的方式”。

要解决此问题,您可以需要获得最长的中断处理程序关闭时间少于传输一个字符的时间。因此,您必须


减少中断被关闭的时间;或
降低通信比特率以增加传输一个字符的时间;
或两者兼而有之。

编写中断例程非常诱人,以便在放置中断后立即在循环缓冲区中的一个字节中,相同的中断例程然后检查它是否是完整的数据包,如果是,则完全解析并处理它。 ,解析通常要花费很长时间,以至于丢失了进入同一端口或任何其他端口的任何其他字节。

我们通常通过减少每个中断处理程序来解决此问题(因此在处理此中断时禁用了中断的时间)处理程序),以最小的方式获取一个字节并将其保留在循环缓冲区中并从中断返回。
所有包分析和包处理的东西都在启用了中断的情况下执行。

最简单的方法是让主循环(又称为“后台任务”)定期
调用一个函数,该函数检查循环缓冲区中是否有完整的数据包,
并进行解析和处理。其他更复杂的方法包括第二级中断处理程序,嵌套中断等。

但是,即使中断处理程序完美地接收了每个字节并将其正确放入缓冲区后,有时系统仍然会丢失字节缓冲区溢出导致的字节丢失:

缓冲区溢出导致的字节丢失

很多人写的数据包处理程序在处理程序看到缓冲区中的完整数据包之前不会做任何事情,然后处理程序将整个数据包作为一个整体进行处理。这种方法会使缓冲区溢出,并且如果任何传入数据包大于您的预期,太大而无法容纳在缓冲区中,则数据会丢失-单数据包溢出。

即使您的缓冲区足够大为了容纳最大可能的数据包,有时在您处理该数据包的过程中,下一个数据包太大,以至于在您的数据包处理程序到来之前从队列中删除第一个数据包以为将来腾出空间之前,循环队列就溢出了数据包-两数据包溢出。

状态调试系统是否可以通过某种方式检测并发出信号,以指示是否有字节进入,并且由于队列中没有更多空间而不得不丢弃该字节?
解决这两个问题的简单方法是增加缓冲区的大小以容纳至少两个最大大小的数据包,或者以某种方式将发送数据包的内容更改为发送较小的数据包-因此两个数据包将适合您现在拥有的空间。

有时,传入数据填充缓冲区的速度比数据包处理程序将数据拉出缓冲区的速度快。增加缓冲区的大小只会暂时延迟问题,而发送较小的数据包(但更多)将只会使情况变得更糟。实时系统必须至少处理传入数据的速度。否则,处理器过载只会使处理器越来越落后。您的状态调试系统是否可以通过某种方式检测到这种信号并发出信号?

如果这种溢出很少发生,也许最简单的“解决方案”(可以说只是一个hack)将以更多方式处理它。 -或以与处理无线电连接上的(非常少见的)电源故障或信号丢失相同的方式:检测到溢出时,让AVR擦除整个缓冲区并假装它从未接收到那些字节。大多数现代协议都是经过精心设计的,因此,如果丢失任何数据包,系统最终将重新同步并正确处理将来的数据包。

要真正解决此问题,需要以某种方式花费“时间来处理数据包”。数据包”小于“从一个数据包的末尾到下一个数据包的末尾的时间”。
因此您必须


降低比特率。
修改发送方以给AVR更多的时间来处理数据包-可能无条件地在数据包前导中发送50个额外的“虚拟字节”-或需要很多时间来给AVR足够的时间来完全处理最后一个数据包并为下一个数据包做好准备。
减少处理数据包的时间
或某种组合。

处理数据包的挂钟时间既涉及AVR花费的时间,实际处理数据包时,以及AVR花在“其他事情”上的时间,例如处理所有其他I / O端口和中断处理程序。

S减少实际处理数据包时间的一些方法是:


有时,将数据包从队列中复制到其他缓冲区以进行进一步处理的速度更快,将其从循环队列中删除。如果数据包从另一个缓冲区的开始处开始,它将使数据包处理程序更简单,因此,数据包的关键部分是从该缓冲区的开始处开始的固定常数偏移量。 (这具有使仅写入循环队列的串行中断处理程序无法在将该数据包复制到其他缓冲区后意外破坏该数据包的优势。)(这种方法使您可以使用经过测试和“优化”的已知有效的函数,它们以连续顺序处理以十六进制数字或十进制数字的ASCII字符串表示的数字,在线性缓冲区上运行的速度可能比“等效”函数要快,后者也必须处理循环缓冲区的环绕式拆分)。这就要求队列和另一个缓冲区的大小都必须至少等于最大可能数据包的大小。
有时,在解析数据包时将其留在队列中并仅在数据包处理程序之后才将其从队列中删除会更快。
有时一对“乒乓”缓冲区比循环队列要快。
许多系统仅使用一个缓冲区来容纳最大的有效数据包,并完全禁用从该端口进行中断,直到中断处理程序处理完最后一个数据包并为下一个数据包做好准备。
实际上,每个数据包的工作量减少了。

处理以下情况的更通用方法“其他东西”吃了很多时间,以致没有足够的时间来处理缓冲区中的数据包(并且还可能有助于减少实际处理数据包的时间):


如果您很幸运,您可以找到一些算法上的调整,可以在更少的周期内有效地完成相同的工作。 br />减负:少做一些不太重要的事情;或在重负载时根本不做它们。 (如Apollo 11 AGC中所实现)。
yield()更多:如果您的主循环在“如果我们有来自端口1的完整数据包,则处理它”与“如果我们有一个完整的来自端口2的数据包”处理,并且其中一个数据包解析器花费的时间太长,以至于另一个端口的缓冲区溢出,这可能有助于将数据包解析器分解为更短的片段,并且每次仅进行少量处理通过主循环,使另一个数据包解析器有机会在其缓冲区溢出之前处理数据包。甚至甚至可以考虑切换到抢占式任务调度程序或完整的RTOS。减少处理中断处理程序所花费的时间。 (防止中断过载)。一条中断线上的高频脉冲可以无限期地暂停主循环任务。可能需要对每个低优先级中断处理程序进行编程,以识别高负载情况并完全禁用其自身的特定中断,然后再根据其从中断返回指令重新启用全局中断,并使主循环识别低负载情况并重新启用这些中断。
提高处理器的时钟频率。
切换到功能更强大的处理器。 “功能更强大”的处理器与“时钟速度更快的处理器”不同。您也许能够找到比特定的8位AVR以更少的周期执行相同工作的处理器-也许其他一些具有专用外设的AVR可以在您当前在软件中运行的硬件中运行,从而释放软件周期为了其他事情;或者是32位处理器,它可以比任何8位处理器以更少的周期完成软件中的某些任务。
有时在机器人技术中,“一个大的快速处理器”比许多小的处理器要好:将消息从一个任务复制到同一处理器上的另一个任务的代码量和通信等待时间总是比从同一处理器复制同一消息要少得多。通常在机器人技术中,添加更多处理器可以使事情变得更加简单和“证明正确”,这可能是每条腿增加一个处理器,甚至每个伺服器增加一个处理器。如果每个处理器只有一个任务,那么上面的许多潜在问题将永远不会发生。


评论


$ \ begingroup $
很好的答案大卫,我更笼统的回答是羞耻。 * 8')我特别喜欢中间的建议,建议不要尝试在中断服务例程中解析数据包,这是一开始就很容易犯的错误。
$ \ endgroup $
– Mark Booth♦
2012年11月6日20:50

#2 楼

我无法针对AVR专门回答,但我可以提供一些更一般的建议。

水平较低

我认为您必须优先考虑。查看当系统开始超载时您可以负担得起的东西以及可以承受的东西。另外,分析您的使用情况以查看何时有数据输入。

例如,如果串行通信上有更高级别的重试协议,那么当您过载时,您可能会丢弃传入的串行数据字节,将其留给重试机制以在CPU有时间时进行追赶。

如果可以这样做,那么您将想尽早失败。当串行缓冲区已满时,没有必要从uart读取数据,将其写入缓冲区,覆盖现有数据-只是浪费了可以在其他地方使用的周期(您将不得不丢弃部分串行流无论如何)。当遇到串行缓冲区超限时,只需清除uart而不读取它,将缓冲区保留为旧状态并返回,让CPU继续处理可以成功完成的事情。

完全禁用低优先级事件的中断。

对使用情况进行性能分析也非常有价值。假设您有四个16字节缓冲区,其中一个不断进入溢出状态,因为您正在向其发送36字节的数据包,而其他三个缓冲区永远不会一次超过5或6个字节。在这种情况下,值得重写缓冲区代码以允许使用不同大小的缓冲区。这样,您可以为三个流分别分配8个字节,为最后一个流分配40个字节。

高级

您需要分析为什么缓冲区随机填充的原因。您是否想一口气处理数据?您能否将处理拆分开来,并执行几个较短的过程,而不是一个较长的过程。

例如,如果您的中断通常占用您70%的CPU,而另一个例程平均占用20%,但在某些时候仅需要10%的时间,而在其他时间则用尽CPU,然后将该处理分成更可预测的处理,即使总体上需要更多的处理能力,可重复的块也可能是值得的。

此外,如果您没有更高级别的重试机制,请考虑实施一种,这将启用低优先级,这可能否则将无法实现。

最后,学习如何针对硬件进行优化。再次,分析可以在这里有所帮助。如果ISR占用了大量CPU时间,则节省的每个周期就是您可以在其他地方使用的周期。不要盲目地“优化”各处,选择自己的战斗方式。

概要分析将告诉您代码中最关键的位置,因此您可以将精力放在最重要的位置(请参阅在进行编码时进行微优化是否重要?)。

#3 楼

首先,所有处理器的性能都受到限制。因此,结果可能是性能不足以应付您要做的工作。

平均负载对于诊断丢失的中断非常无趣。有趣的是,在高峰情况下,有多少个中断,因此中断服务程序有多少负载。如果中断导致太多的计算需要在固定的时间内完成,则您需要更快的CPU。

由于ISR中的计算量很少且缓冲区很大,因此这些峰值可以平坦化出来,但这会带来延迟。如果您可以减少写入缓冲区的数据量,则ISR中的更多处理可能会有所收获。

如果您的解决方案是跳过主例程中的不重要(?)处理,则您应该对ISR进行更多处理。这样,CPU负载将流到ISR,而主例程将减少。这里的问题是,如果正在进行一个中断的处理,则下一个中断将被延迟,因为其中断服务程序只能在完成另一个中断服务程序之后才能运行。如果此延迟太长,则中断将丢失。

,因此请选择最适合您的解决方案的方式。但是请注意:“ CPU电源的唯一替代品是CPU电源。”

评论


$ \ begingroup $
我曾经在一个带有第四个微控制器的机器人上工作,将它换成另一个在经济上是不可行的,我们已经从中挤出了最后的一点性能,但是对于某些我们需要的新功能来说,它仍然不够快。尽管我们发现80%的芯片会超频20%,这为我们提供了所需的性能,所以我们购买了所需数量的两倍,并排除了故障。由于CPU故障,我们从未获得过一次保修退货,这为我们节省了一大笔费用或进行了重新开发。 * 8')
$ \ endgroup $
– Mark Booth♦
2012年11月9日在11:30



$ \ begingroup $
谢谢您的回答。我当前的解决方案只是限制数据收集的速度(它正在执行模拟DAQ,但正在执行三角函数,同时实现平滑滤波器,并区分-Savitzky-Golay滤波器)。可能会有一段时间我不再需要这样做了-我们正计划购买不会产生噪声的编码器,而这种噪声使平滑变得必要。
$ \ endgroup $
– ronalchn
2012年11月12日下午3:26