nxp-mcuxpresso / mcux-sdk

MCUXpresso SDK
BSD 3-Clause "New" or "Revised" License
301 stars 136 forks source link

[BUG] I2S / DMA FIFO is spilling data in the buffer causing channel offsetting #197

Open carlocaione opened 1 month ago

carlocaione commented 1 month ago

The setup

Using the EVK MIMXRT685 as I2S SLAVE configured as:

The following is a sample trace:

320804746-f938ab01-3ef3-4bc8-a466-723e47db15c4

The problem

I'm interested in the RX path, that is using the EVK to record data received on the I2S interface sent for example by a PC. For the sake of simplicity let's assume that from the PC I'm streaming data only of the first channel out of 16.

We can do that by doing for example on a Linux machine:

speaker-test -r 48000 -c 16 -f 1000 -F S32_LE -D hw:APE,1 -t sine -s 1

With this command we are sending through the I2S interface a stream of data at 48kHz / 16 channels / S32_LE with a 1kHz sine wave only on the first channel, all the other channels are silent.

We try to send this data through several times, but instead of waiting for the stream to finish, we interrupt the streaming several times by pressing CTRL+C and giving the command again. This is possibly interrupting the stream of data mid-frame and restarting it from scratch.

What do we expect to see on the EVK side

We would expect to see in the I2S RX buffer something like this when the command is running (each 0 and X is 1 byte):

CH1  CH2  CH3  CH4  CH5  CH6  CH7  CH8  CH9  CH10 CH11 CH12 CH13 CH14 CH15 CH16 CH1  CH2  CH3...
XXXX 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 XXXX 0000 0000...

That is we expect to see data only on the Channel 1 and zeros on all the other channels when the command is streaming out data.

What we actually see on the EVK side

What actually happens is that the data is found on a different channel than 1. An offset is introduced in the recorded data.

For example:

CH1  CH2  CH3  CH4  CH5  CH6  CH7  CH8  CH9  CH10 CH11 CH12 CH13 CH14 CH15 CH16 CH1  CH2  CH3...
0000 0000 0000 0000 0000 0000 XXXX 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000...

In this case the data that is supposed to be on CH1, it is found on CH7 instead.

The reproducer

At https://gist.github.com/carlocaione/5c471fd25f083110e6c9f3c426874f29 the reproducer for the problem.

Since we need 16 channels we are using two FLEXCOMM interfaces with signal sharing and we use a ping-pong linked transfer onto two different buffers.

If we set a breakpoint at line 80 and we try to reproduce the issue, we can see that the execution stops at that line after a few attempts because valid data was found in channel 7 instead than channel 1.

A theory about the issue

What is possibly happening is that when we stop the streaming of data in the middle of the frame, the data in the FIFO up to that point is still flushed in memory, causing the offset when the streaming starts again at a later time.

Position in the RX buffer: CH1  CH2  CH3  CH4  CH5  CH6  CH7  CH8  CH9  CH10 CH11 CH12 CH13 CH14 CH15 CH16
Data transmitted         : XXXX 0000 0000 0000 0000 0000 XXXX 0000 0000 0000 0000 0000 0000 0000 0000 0000 
                          ^                             ^
                          RX is started                 Here we stop the streaming and start it again

Open questions

A reasonable expectation to me about the behaviour of I2S + DMA is that when the data is flushed to memory, this is done in chunks that are multiple of the size of the frame (64 bytes in this case) otherwise how can we detect when only part of the frame has been flushed to memory especially when using loop descriptors?

EvaKlejmova commented 1 month ago

Hi @carlocaione

Could you please provide more details? Specifically, which SDK demo is used, what the exact setup is, and how the I2S is generated from the PC.

Thank you!

carlocaione commented 1 month ago

which SDK demo is used

The reproducer is based on the evkmimxrt685_i2s_dma_record_playback with the proper changes, from the SDK 2.15.0.

what the exact setup is

The PC board generating the I2S signal (master) is connected to the RT685 EVK board using the FLEXCOMM5 / I2S interface (slave). The PC board is generating the TDM signal.

how the I2S is generated from the PC.

I don't think that is important but I'm using a Jetson ORIN board for that with Linux running on it. To generate the signal I'm using the speaker-test tool.

EvaKlejmova commented 1 month ago

@carlocaione Thank you for providing the details. Since the Jetson ORIN board is the master, I assume it generates the SCK clock. If that's the case, then your theory about the issue is correct. The EVK MIMXRT685 board is then clock-dependent and does not know that there has been an interruption. When the clock is interrupted, it goes into a pause state. The data remains in the buffer, and when the clock resumes, the buffer starts filling from where it left off, which can be anywhere in the frame. This could be handled in the code by detecting the interruption and then clearing the buffer.

carlocaione commented 1 month ago

This could be handled in the code by detecting the interruption and then clearing the buffer

@EvaKlejmova interesting. How can this be done? I failed to find anything related to detecting the interruption that so far

EvaKlejmova commented 1 month ago

Signal interruption handling depends on the application, and its implementation is up to the user. One approach is to use an additional pin on the Jetson ORIN board and a suitable pin on the RT685 for interrupt signaling. Another option is to use a timer (for example, within the I2S_RxCallback function in the reproducer), which can trigger the buffer to be cleared upon timeout. The implementation realy depends on the type of application.

carlocaione commented 1 month ago

@EvaKlejmova thank you for your reply. About the signal interruption I was hoping for something different like an interrupt of sorts :)

About the I2S_RxCallback timer that brings me to another weird behaviour I saw on the board and I'd like to check with you whether this is expected or not.

In one special case we have the master of the communication that is always generating the SCK signal. The difference between and active communication or not is the presence of the FSYNC / WS signal (and the related data, of course).

Using the same reproducer I get the first I2S_RxCallback call only when I start the data streaming the first time, so I guess the I2S controller waits for the first FSYNC / WS received on the wire to start gathering the data, so far so good. The weird behaviour is that when I interrupt the streaming on the master side, so only the SCK signal is still present on the wire (no FSYNC / WS), I keep receiving calls to the I2S_RxCallback (receiving all zeros).

Is this expected? My assumption was that the I2S controller should not keep getting data if the FSYNC / WS is not received, even if the SCK is still present.

This is related to the previous issue because if this behaviour is confirmed, I cannot really use the timer solution.

carlocaione commented 1 month ago

@EvaKlejmova any idea about my previous questions?

I found out that the SLVFRMERR in the STAT register goes to 1 when the WS signal is not aligned in the frame when I start the stream again after having stopped it.

This is half useful because I can poll that bit (or check that in the RX callback) and reset the controller accordingly to have the frame in sync again. This is only half useful because it detects the mis-aligned WS only when I start a new stream and not when I interrupt the stream mid-frame.