【问题标题】:Sending large amount of data from ISR using queues in RTOS使用 RTOS 中的队列从 ISR 发送大量数据
【发布时间】:2022-01-17 18:21:46
【问题描述】:

我正在使用 STM32F401 MC 进行音频采集,我正在尝试使用队列将音频数据(确切地说是 384 字节)从 ISR 发送到任务。 ISR 的频率太高,因此我相信由于队列已满而丢弃了一些数据。运行代码录制的音频很嘈杂。有没有更简单的方法可以将大量数据从 ISR 发送到任务?

使用的 RTOS 是 FreeRTOS,ISR 是来自 I2S 麦克风外设的 DMA 回调。

【问题讨论】:

  • FreeRTOS xQueueSendFromISR() "queues by copy",这意味着它会复制数据,这需要一些时间。您应该重新设计,以便 ISR 不会花时间复制数据。也许通过引用发送。
  • @kkrambo 使用内存池并仅对引用进行排队。

标签: c queue stm32 freertos rtos


【解决方案1】:

在这些情况下的一般方法是:

  1. 对 ISR 中收到的原始数据进行下采样(例如,仅保存 4 个样本中的 1 个)
  2. 累积一定数量的样本,然后将它们以消息的形式发送给任务

【讨论】:

  • 这是一个很好的建议,尽管它取决于接收线程正在做什么。例如,如果接收线程正在执行某种批量处理,例如 FFT 或某种特殊类型的过滤,则可能无法实现。
  • @JonathonS:根据我的经验,任何类型的 FS 或磁盘活动(在本例中为记录)都必须在单独的线程中进行。这是因为这种类型的活动通常会由于 FS 数据不时重新排列而出现零星的滞后。例如,保存数据帧通常需要几毫秒,但每隔一段时间,它“突然”需要半秒。因此,简而言之,您可能希望将该线程拆分为两个线程 - 一个用于处理,一个用于记录。
  • 可能是正确的。如果目标是在接收任务中处理数据之前对数据进行下采样,我肯定会使用您建议的方法。
【解决方案2】:

如果定期调用接收数据的线程,则队列的大小应足以容纳在该时间间隔内可能接收到的所有数据。确保队列足够大以容纳至少两个时间间隔的数据可能是个好主意。

如果接收数据的线程根本无法跟上传入的数据,那么可以考虑提高其优先级。

每次从队列中推入和拉出都会产生一些开销处理,因为 FreeRTOS 将检查以确定是否应唤醒更高优先级的任务以响应该操作。当同时向队列写入或读取多个项目时,在传输过程中暂停调度程序可能会有所帮助。

另一种解决方案是实现一个循环缓冲区并将其放入共享内存中。这将基本上执行与队列相同的功能,但没有额外的开销。您可能需要使用互斥锁来阻止对缓冲区的同时访问,具体取决于循环缓冲区的实现方式。

【讨论】:

  • 我还考虑使用(更精简/更快)FreeRTOS 流缓冲区(或消息缓冲区)机制,如上所述,为后处理任务提供合理的高优先级以避免/最大限度地减少积压。
【解决方案3】:

您可以通过创建指向内存块的指针队列而不是复制内存本身来实现“零复制”队列。将音频数据直接写入一个块(例如通过 DMA),然后在写满时,将指向该块的指针排入队列,并切换到池中的下一个可用块。然后接收任务可以直接对内存块进行操作,而无需将数据复制到队列中或从队列中复制出来——唯一复制的是指针。

接收任务完成后,将块返回池中。池应具有与队列长度相同的块数。

要创建内存池,您将从静态数组开始:

tAudioSample block[QUEUE_LENGTH][BLOCK_SIZE] ;

然后用指向每个块元素的指针填充block_pool 队列 - 伪代码:

for( int i = 0; i < QUEUE_LENGTH; i++ )
{
    queue_send( block_pool, block[i] ) ;
}

然后要获得一个“可用”块,您只需从队列中获取一个指针,填充它,然后发送到您的音频流队列,接收器在完成块后将指针发送回block_pool .

一些 RTOS 提供了一个固定的块分配器,它完全按照我在上面描述的 block_pool 队列。如果您使用的是 CMSIS RTOS API 而不是本机 FreeRTOS API,则会提供memory pool API

但是,这听起来可能是一个 X-Y 问题 - 您已经提出了您的诊断,该诊断可能正确也可能不正确,并决定了一个解决方案,然后您正在寻求帮助。但是,如果它是错误的或不是最佳解决方案怎么办?最好包含一些代码来显示数据是如何生成和使用的,并提供具体信息,例如数据来自哪里、生成 ISR 的频率、采样率、运行平台、优先级和调度接收任务,以及正在运行的其他哪些任务可能会延迟它。

在大多数平台上 384 字节并不是很大的数据量,并且中断率必须非常高或接收任务被过度延迟(即非实时)或执行过多或不确定的工作导致这个问题。问题可能不是 ISR 频率,而是接收任务的性能和可调度性。

不清楚你是384字节导致单个中断还是384中断还是什么?

也就是说,这可能是一个更全面的设计问题,而不是简单地如何更有效地传递数据——尽管这并不是一件坏事。

【讨论】:

  • 我在一个中断中接收到 384 个字节。音频采集的问题在于,由于此中断,其他中断(例如 I2C)会显着减慢。
  • @Thilak :如果这个中断抢占了另一个并导致它错过了最后期限,那么要么你的中断优先级错误,要么中断做的“太多”——或者两者兼而有之。应该应用 RMS 的原则,并且中断应该做最少的工作。在我的建议中 - 来自固定块内存池和指针队列的 DMA 缓冲区会将 ISR 中的工作减少到非常少。无论哪种方式,这听起来都像是一个 X-Y 问题——你有调度问题,你认为你有一个解决方案,并且你正在询问如何实施它——而是询问实际问题。
  • RMS btw - 速率单调调度 - 运行时间最短的处理程序获得最高抢占优先级。如果这导致错过最后期限,您必须优化处理程序,使其运行时间更短,从而获得更高的优先级。
  • 我再次阅读了这个问题,发现您已经在使用 DMA,所以您已经完成了一半。您只需在每次中断时将 DMA 缓冲区设置为池中的一个新内存块,并在队列上传递指针 - 避免 384 字节的 memcpy。即使这样,如果您正确设置优先级(并使用抢占优先级),您也可能会摆脱 memcpy。
  • 您还没有指定时间。例如,我使用 72MHz STM32F107 处理的一个项目每 833 微秒从三个 ADC DMA 缓冲区传输 240 个字节,同时处理多个 UART、USB I2C 和 SPI 流。在这种情况下,不需要队列,DMA 半/全传输双缓冲就足够了。来自三个通道的 ADC 样本不是“零复制”,而是“去交错”到共享内存缓冲区中。因此,您可以看到我为什么对您的设计持怀疑态度,以及为什么时序规范对于理解您的问题至关重要。
猜你喜欢
  • 2012-05-06
  • 2022-09-22
  • 2017-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多