Page 1 of 1

Problem with I2S irq on second thread - RP2040

Posted: Tue Jul 05, 2022 12:34 pm
by EliAaron
I will try to keep it simple and explain the problem without going into too much detail.

I have a WavePlayer class that plays wave files using I2S (the builtin I2S object). The WavePlayer can play on the main thread or create a second thread and play on it (utilizing the second core). The main way I want to use the WavePlayer is on a second thread.

In the first implementation, I used a loop that reads a chunk from the wave file and sends it to the I2S in blocking mode. It was not fast enough, wave files with higher frame rates could not be played properly.

To speed up the playing loop, I used the I2S irq function. The irq raised a flag, indicating that the I2S object is ready for more data. This way, in the playing loop I could start reading a new chunk from the file before the I2S is ready, thus increasing the WavePlayer's max playing speed. I used a thread lock for the flag because the irq seems to run randomly on core0 or on core1 (regardless of where I set the irq).

The irq version worked great on the main thread, but on a second thread it would get stuck after some time, usually a few seconds. After some debugging, it seemed that the problem was that the irq stopped executing.

Note: I am using MicroPython v1.19.1 where rp2 memory corruption bug in thread was fixed.

This problem may be related to another problem I posted:
Timer gets stuck when thread is running - RP2040

Re: Problem with I2S irq on second thread - RP2040

Posted: Tue Jul 05, 2022 5:09 pm
by Mike Teachman
Here's a possible reason for this behavior - the rate of IRQ callbacks is too fast to be serviced by the Micropython scheduler

IRQ callbacks for both Timer and I2S objects don't get serviced immediately. The C code for these modules first push IRQ callback requests into a queue that is managed by the Micropython scheduler. This queue has a depth of 8. When the queue becomes full, new Timer or I2S IRQs will not be added to the queue. The fail is silent. There is no exception or error produced.

Could the Timer objects be filling this queue, and preventing the I2S object from adding IRQ requests?

Background:
Here is a doc describing how the Micropython scheduler services this IRQ callback queue:
https://docs.micropython.org/en/latest/ ... n.schedule
Note that this documentation indicates an exception will be raised when the queue is full. That only happens when a callback is added from Micropython code using micropython.schedule(func, arg). When a callback is added to the queue from C code (e.g. machine.Timer, machine.I2S) the failure is silent.

Re: Problem with I2S irq on second thread - RP2040

Posted: Wed Jul 06, 2022 10:18 am
by EliAaron
Mike Teachman wrote:
Tue Jul 05, 2022 5:09 pm
Here's a possible reason for this behavior - the rate of IRQ callbacks is too fast to be serviced by the Micropython scheduler

IRQ callbacks for both Timer and I2S objects don't get serviced immediately. The C code for these modules first push IRQ callback requests into a queue that is managed by the Micropython scheduler. This queue has a depth of 8. When the queue becomes full, new Timer or I2S IRQs will not be added to the queue. The fail is silent. There is no exception or error produced.

Could the Timer objects be filling this queue, and preventing the I2S object from adding IRQ requests?
I have two questions:
  1. If the problem only had to do with the high rate of IRQ calls, wouldn't this problem occur regardless of if there is a thread running in the background (in the timer case) or if the object triggering the IRQ is started in and used in a thread (in the I2S case)? In both cases, if I just take the thread out of the equation, everything runs smoothly, including in high IRQ rates (some IRQ calls may be dropped, but the timer/I2S IRQ keeps being triggered).
  2. If the problem was that the queue is full, wouldn't I just miss some IRQ calls but keep receiving at least some? The IRQ stops being triggered permanently. Unless the filling of the IRQ queue causes the queue to stop working, or some unhandled exception stops more IRQ requests to be triggered.