上个月,我花了大量时间让UART(用于MIDI)与使用中断的STM(STM32F103C8T6)一起使用,但收效甚微。
但是,今天晚上使用DMA的速度非常快。
由于据我所知,DMA速度更快并且可以减轻CPU负担,为什么不总是使用DMA来支持中断呢?特别是由于在STM32上似乎存在很多问题。
我正在使用STM32CubeMx / HAL。
上个月,我花了大量时间让UART(用于MIDI)与使用中断的STM(STM32F103C8T6)一起使用,但收效甚微。
但是,今天晚上使用DMA的速度非常快。
由于据我所知,DMA速度更快并且可以减轻CPU负担,为什么不总是使用DMA来支持中断呢?特别是由于在STM32上似乎存在很多问题。
我正在使用STM32CubeMx / HAL。
Answers:
尽管DMA减轻了CPU的负担,因此可以减少在同一内核上运行的其他中断驱动的应用程序的延迟,但与此相关的成本是:
仅有有限数量的DMA通道,并且这些通道如何与不同的外设交互也受到限制。同一通道上的另一个外设可能更适合DMA使用。
例如,如果您每5毫秒进行一次批量I2C传输,则与偶尔到达UART2的调试命令相比,这似乎更适合DMA。
设置和维护DMA本身就是一项成本。(通常,由于内存管理,所涉及的更多外设,使用中断本身的DMA以及可能需要解析DMA之外的前几个字符的原因,设置DMA比设置常规的按字符中断驱动的传输更为复杂。无论如何,请参见下文。)
DMA可能会使用额外的功率,因为它是内核的另一个域,需要时钟控制。另一方面,如果内核支持,则可以在进行DMA传输时挂起CPU。
DMA需要使用内存缓冲区(除非您正在进行外设到外设DMA),因此存在一些与之相关的内存成本。
(使用逐个字符的中断时,内存开销可能也会存在,但是如果立即在中断内部解释消息,则内存开销可能会更小或消失。)
DMA会产生延迟,因为仅在传输完成/一半完成时才通知CPU(请参阅其他答案)。
除了将数据流传输到环形缓冲区中或从环形缓冲区中传输数据之外,您需要事先知道将要接收/发送多少数据。
这可能意味着需要使用每个字符的中断来处理消息的前几个字符:例如,与XBee接口时,您首先要读取数据包的类型和大小,然后触发DMA传输到分配的缓冲区中。
对于其他协议,如果它们仅使用消息结尾定界符,则根本不可能实现:例如,'\n'
用作定界符的基于文本的协议。(除非DMA外设支持字符匹配。)
如您所见,这里有很多折衷考虑。一些与硬件限制有关(通道数,与其他外设冲突,字符匹配),一些与所使用的协议有关(分隔符,已知长度,内存缓冲区)。
为了增加一些轶事证据,我在一个业余项目中面对了所有这些折衷,该项目使用了许多不同的外围设备,并且协议非常不同。需要做出一些权衡,主要是基于“我要传输多少数据,我要多久执行一次?”这一问题。本质上,这使您可以粗略估计简单的中断驱动的传输对CPU的影响。因此,相对于每隔几秒钟使用相同DMA通道的UART传输,我每5ms优先进行上述I2C传输。另一个UART传输发生的频率更高,而具有更多数据,则优先于另一个I2C传输,这种传输很少发生。这都是权衡。
当然,使用DMA也有优势,但这不是您所要求的。
使用DMA通常意味着您不再需要对每个字符进行中断,而只是在接收(或发送)字符的“缓冲区已满”之后才中断。这会增加处理这些字符的等待时间-在接收到缓冲区中的最后一个字符之后才处理第一个字符。
这种延迟可能是一件坏事,尤其是在诸如MIDI之类的对延迟敏感的应用程序中,此处几毫秒可能会增加现场表演的严重可播放性问题。
DMA不能替代中断-它们通常一起使用!例如,如果您使用DMA通过UART发送数据,则仍然需要一个中断来告诉您何时完成发送。
使用DMA会带来一些有趣的问题和挑战,而不仅仅是UART外围设备使用的所有其他考虑因素。我举几个例子:假设您的uC与其他设备一起坐在RS485(或任何其他)总线上。总线上有很多消息,有些是给您的uC用的,有些则不是。另外,假设这些总线邻居都使用不同的数据协议,这意味着消息长度是不同的。
仅在使用DMA时出现的一些问题是:
无论如何,只是值得深思。
在接收端(我记得),DMA终止于字符匹配或终端计数。一些协议和许多交互式应用程序不容易融入此模型,您确实需要逐个字符地处理事情。如果通信链路不可靠,则DMA技术也可能会很脆弱,丢失流中的单个字符很容易使DMA状态机混乱。
我现在已经在几个项目上使用了STM32CubeMx / HAL,发现它生成的UART处理软件在接收端有一定的缺点。
传输时,通常需要发送一个数据块或一行文本。在这种情况下,您可以预先知道数据传输的时间,因此使用DMA是显而易见的解决方案。传输完成后,您将收到一个中断,并且可以使用UART TX complete回调函数向您的主代码指示传输已完成,您可以发送另一个数据块。
当涉及到数据接收时,意法半导体提供的所有功能都假定您知道发送设备在开始发送之前将给您多少字符。通常这是未知的。中断功能将接收到的数据放入缓冲区,并且仅在接收到预定义数量的字符时才指示有可用数据。如果您尝试通过设置顺序的单个字符传输来使用DMA或中断功能来接收数据,则每次传输的建立时间都将意味着您将以最慢的数据速率(波特率会降低)以外的任何速率丢失字符。开始丢失数据将取决于您的处理器时钟速度),并且将过度加载处理器,从而没有任何其他处理的指令周期
为了解决这个问题,我编写了自己的中断处理程序函数,该函数将数据存储在一个小的本地循环缓冲区中,并设置一个由主代码读取的计数(RTOS计数信号量),以指示已准备好接收数据。然后,主代码可以在空闲时从该缓冲区收集数据,如果本地缓冲区在收集数据之前不溢出,则收集数据是否有延迟并不重要。