Sending large amount of data from ISR using queues in RTOS

Viewed 686

I am working on an STM32F401 MC for audio acquisition and I am trying to send the audio data(384 bytes exactly) from ISR to a task using queues. The frequency of the ISR is too high and hence I believe some data is dropped due to the queue being full. The audio recorded from running the code is noisy. Is there any easier way to send large amounts of data from an ISR to a task?

The RTOS used is FreeRTOS and the ISR is the DMA callback from the I2S mic peripheral.

3 Answers

The general approach in these cases is:

  1. Down-sample the raw data received in the ISR (e.g., save only 1 out of 4 samples)
  2. Accumulate a certain number of samples before sending them in a message to the task

You can implement a "zero copy" queue by creating a queue of pointers to memory blocks rather than copying the memory itself. Have the audio data written directly to a block (by DMA for example), then when full, enqueue a pointer to the block, and switch to the next available block in the pool. The receiving task can then operate directly on the memory block without needing to copy the data either into and out of the queue - the only thing copied is the pointer.

The receiving task when done, returns the block back to the pool. The pool should have the same number of blocks as queue length.

To create a memory pool you would start with a static array:

tAudioSample block[QUEUE_LENGTH][BLOCK_SIZE] ;

Then fill a block_pool queue with pointers to each block element - pseudocode:

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

Then to get an "available" block, you simply take a pointer from the queue, fill it, and then send to your audio stream queue, and the receiver when finished with the block posts the pointer back to the block_pool.

Some RTOS provide a fixed block allocator that does exactly what I described above with the block_pool queue. If you are using the CMSIS RTOS API rather than native FreeRTOS API, that provides a memory pool API.

However, it sounds like this may be an X-Y problem - you have presented your diagnosis, which may or may not be correct and decided on a solution which you are then asking for help with. But what if it is the wrong or nor the optimum solution? Better to include some code showing how the data is generated and consumed, and provide concrete information such as where this data is coming from, how often the ISR is generated, sample rates, the platform it is running on, the priority and scheduling of the receiving task, and what other tasks are running that might delay it.

On most platforms 384 bytes is not a large amount of data, and the interrupt rate would have to be extraordinarily high or the receiving task to be excessively delayed (i.e not real time) or doing excessive or non-deterministic work to cause this problem. It may not be the ISR frequency that is the problem, but rather the performance and schedulability of the receiving task.

It is not clear if you 384 bytes results in a single interrupt or 384 interrupts or what?

That is to say that it may be a more holistic design issue rather than simply how to pass data more efficiently - though that can't be a bad thing.

If the thread receiving the data is called at periodic intervals, the queue should be sized sufficiently to hold all data that may be received in that interval. It would probably be a good idea to make sure the queue is large enough to hold data for at least two intervals.

If the thread receiving the data is simply unable to keep up with the incoming data, then one could consider increasing its priority.

There is some overhead processing associated with each push to and pull from the queue, since FreeRTOS will check to determine whether a higher priority task should wake up in response to the action. When writing or reading multiple items to or from the queue at the same time, it may help to suspend the scheduler while the transfer is taking place.

Another solution would be to implement a circular buffer and place it into shared memory. This will basically perform the same function as a queue, but without the extra overhead. You may need to use a mutex to block simultaneous access to the buffer, depending on how the circular buffer is implemented.

Related