Problem with I2S irq on second thread - RP2040

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
User avatar
EliAaron
Posts: 6
Joined: Thu Feb 24, 2022 1:35 pm
Location: Jerusalem, Israel
Contact:

Problem with I2S irq on second thread - RP2040

Post by EliAaron » Tue Jul 05, 2022 12:34 pm

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

User avatar
Mike Teachman
Posts: 155
Joined: Mon Jun 13, 2016 3:19 pm
Location: Victoria, BC, Canada

Re: Problem with I2S irq on second thread - RP2040

Post by Mike Teachman » 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?

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.

User avatar
EliAaron
Posts: 6
Joined: Thu Feb 24, 2022 1:35 pm
Location: Jerusalem, Israel
Contact:

Re: Problem with I2S irq on second thread - RP2040

Post by EliAaron » Wed Jul 06, 2022 10:18 am

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.

Post Reply