在 RTOS 中使用队列从 ISR 发送大量数据
Sending large amount of data from ISR using queues in RTOS
我正在使用 STM32F401 MC 进行音频采集,我正在尝试使用队列将音频数据(正好 384 字节)从 ISR 发送到任务。 ISR 的频率太高,因此我相信由于队列已满而丢弃了一些数据。从 运行 代码录制的音频有噪音。有没有更简单的方法可以将大量数据从 ISR 发送到任务?
使用的RTOS是FreeRTOS,ISR是来自I2S麦克风外设的DMA回调。
这些情况下的一般方法是:
- 对 ISR 中接收到的原始数据进行下采样(例如,仅保存 4 个样本中的 1 个)
- 在将样本发送到任务的消息中之前积累一定数量的样本
如果定期调用接收数据的线程,则队列的大小应足以容纳在该时间间隔内可能接收到的所有数据。确保队列足够大以至少保存两个间隔的数据可能是个好主意。
如果接收数据的线程根本跟不上传入的数据,那么可以考虑提高其优先级。
每次向队列中推送和从队列中拉取时,都会有一些开销处理,因为 FreeRTOS 将检查以确定是否应唤醒更高优先级的任务以响应该操作。同时向队列写入或读取多个项目时,在传输进行时暂停调度程序可能会有所帮助。
另一种解决方案是实现循环缓冲区并将其放入共享内存中。这将基本上执行与队列相同的功能,但没有额外的开销。您可能需要使用互斥锁来阻止同时访问缓冲区,具体取决于循环缓冲区的实现方式。
您可以通过创建指向内存块的指针队列而不是复制内存本身来实现“零复制”队列。将音频数据直接写入一个块(例如通过 DMA),然后在满时将指向该块的指针排入队列,然后切换到池中的下一个可用块。然后接收任务可以直接在内存块上操作,而不需要将数据复制到队列中和从队列中复制出来——唯一复制的是指针。
接收任务完成后,returns将块放回池中。池中的块数应与队列长度相同。
要创建内存池,您将从静态数组开始:
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 次中断还是什么?
也就是说,这可能是一个更全面的设计问题,而不仅仅是如何更有效地传递数据——尽管这不是坏事。
我正在使用 STM32F401 MC 进行音频采集,我正在尝试使用队列将音频数据(正好 384 字节)从 ISR 发送到任务。 ISR 的频率太高,因此我相信由于队列已满而丢弃了一些数据。从 运行 代码录制的音频有噪音。有没有更简单的方法可以将大量数据从 ISR 发送到任务?
使用的RTOS是FreeRTOS,ISR是来自I2S麦克风外设的DMA回调。
这些情况下的一般方法是:
- 对 ISR 中接收到的原始数据进行下采样(例如,仅保存 4 个样本中的 1 个)
- 在将样本发送到任务的消息中之前积累一定数量的样本
如果定期调用接收数据的线程,则队列的大小应足以容纳在该时间间隔内可能接收到的所有数据。确保队列足够大以至少保存两个间隔的数据可能是个好主意。
如果接收数据的线程根本跟不上传入的数据,那么可以考虑提高其优先级。
每次向队列中推送和从队列中拉取时,都会有一些开销处理,因为 FreeRTOS 将检查以确定是否应唤醒更高优先级的任务以响应该操作。同时向队列写入或读取多个项目时,在传输进行时暂停调度程序可能会有所帮助。
另一种解决方案是实现循环缓冲区并将其放入共享内存中。这将基本上执行与队列相同的功能,但没有额外的开销。您可能需要使用互斥锁来阻止同时访问缓冲区,具体取决于循环缓冲区的实现方式。
您可以通过创建指向内存块的指针队列而不是复制内存本身来实现“零复制”队列。将音频数据直接写入一个块(例如通过 DMA),然后在满时将指向该块的指针排入队列,然后切换到池中的下一个可用块。然后接收任务可以直接在内存块上操作,而不需要将数据复制到队列中和从队列中复制出来——唯一复制的是指针。
接收任务完成后,returns将块放回池中。池中的块数应与队列长度相同。
要创建内存池,您将从静态数组开始:
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 次中断还是什么?
也就是说,这可能是一个更全面的设计问题,而不仅仅是如何更有效地传递数据——尽管这不是坏事。