Hi,
I'm looking for example code that implements *reading* I2S from the CODEC on the EVK using the `xcore_iot` build system, in the `bare_metal` implementation.
Why?:
As I understand it, the `xcore_iot` system is the recommended build system (correct me if I'm wrong?), and the `bare_metal/explorer_board` example code implements a lot of useful peripheral code and core/tile communication code that we've used to build components of the application we're working on.
This example however does not include an implementation of reading I2S from the CODEC or specifically in our case, from the input jack on the CODEC via the I2S programming interface.
While the I2S API is fairly simple, and the documentation is fairly clear, I have been unable to implement a working input I2S stream.
I *believe* this is because there is a bug: when you try to read an input frame from the CODEC, a silent error occurs that locks up the CODEC (or the I2S implementation code on the xmos) entirely. I have reported this bug on the `xcore_iot` repo here: https://github.com/xmos/xcore_iot/issues/634
However I am open to the idea that I have simply implemented my code incorrectly, or that the documentation is incomplete. If this is the case, could someone point me at an example implementation?
Many thanks.
N
xcore_iot build system: Example 'baremetal' code for reading I2S from CODEC on the EVK
-
- Member
- Posts: 12
- Joined: Fri Aug 17, 2018 4:42 pm
-
Verified
- Member++
- Posts: 22
- Joined: Wed May 22, 2024 2:36 pm
Hey, I have just looked at your example. Do I understand that the I2S appears to work _until_ you add the following to the callback?
In which case it seems like that the issue is not related to the I2S driver, but rather this channel interaction. s_chan_out_buf_word will block if there is not another thread that is reading from the channel. In your system have you added another thread which reads from the channel?
These callbacks are executed on the same thread that is reading/writing to the IO ports, so if your callback hangs the the I2S will stop.
Hope this helped
Code: Select all
s_chan_out_buf_word(*input_c, (uint32_t*)i2s_sample_buf, appconfFRAMES_IN_ALL_CHANS);
These callbacks are executed on the same thread that is reading/writing to the IO ports, so if your callback hangs the the I2S will stop.
Hope this helped
XMOS Software Engineer
-
- Member
- Posts: 12
- Joined: Fri Aug 17, 2018 4:42 pm
Hi, thanks for your response.
Yes I've added another thread that is reading from that channel. The thread is on another core in the same tile.
In the bug rep I mention that I modified the `audio_pipeline.c` code in the example to achieve this. In practice I simply 'rewired' one of the existing `ap_stage_x` functions to accept the opposite chanend from that callback, and to trigger when that buffer is written to (on firing of this callback).
Do you need me to clarify the scenario any further?
In practice if you have a working example, or know where to look, this might shortcut this debugging.
Yes I've added another thread that is reading from that channel. The thread is on another core in the same tile.
In the bug rep I mention that I modified the `audio_pipeline.c` code in the example to achieve this. In practice I simply 'rewired' one of the existing `ap_stage_x` functions to accept the opposite chanend from that callback, and to trigger when that buffer is written to (on firing of this callback).
Do you need me to clarify the scenario any further?
In practice if you have a working example, or know where to look, this might shortcut this debugging.
-
- Member
- Posts: 12
- Joined: Fri Aug 17, 2018 4:42 pm
With this hint on the hang though I think I can investigate further stages of the audio pipeline to make sure something isn't subsequently blocking the tx callback. I assume if this callback blocks then the same thing will happen: all I2S will stop?
I'll investigate further and report back.
Would still appreciate any pointers to examples using this build ssystem.
-
Verified
- Member++
- Posts: 22
- Joined: Wed May 22, 2024 2:36 pm
Hey, I can't think of publicly available examples showing the I2S master receiving in a bare metal application. Although we have multiple internal applications which do this using the same API that you are using. The changes you have made see sensible to get it working.
If one of the threads in your pipeline blocks for longer than a single sample period then this will cause the I2S to block, and the output clocks to stop. Dealing with this is one of the greatest challenges when designing an audio system.
One more observation, it the code you posted in the github issue you use the macro `appconfFRAMES_IN_ALL_CHANS` as the number of I2S chans, is this correct? The number of I2S chans should be 2 for a single I2S input. I found that in the example it is not set to 2:
https://github.com/xmos/xcore_iot/blob/ ... conf.h#L10
If one of the threads in your pipeline blocks for longer than a single sample period then this will cause the I2S to block, and the output clocks to stop. Dealing with this is one of the greatest challenges when designing an audio system.
One more observation, it the code you posted in the github issue you use the macro `appconfFRAMES_IN_ALL_CHANS` as the number of I2S chans, is this correct? The number of I2S chans should be 2 for a single I2S input. I found that in the example it is not set to 2:
https://github.com/xmos/xcore_iot/blob/ ... conf.h#L10
XMOS Software Engineer
-
- New User
- Posts: 2
- Joined: Mon Aug 12, 2024 3:02 pm
Hi xhuw, thank you very much for your hints. This has been enough information for me to work out how to get a basic demo functioning.
There is still one underlying bit of 'unexpected' behaviour that may cause issues for our application:
When the app is initialised, the `i2s_send` callback fires immediately (but the `i2s_receive` does not). Naturally this callback blocks until an i2s frame is sent to it. The problem with this is that while it is blocking, the i2s clocks don't appear to run. This appears to prevent the `i2s_receive` callback from ever firing. Since our intended signal path is `codec -> i2s_receive -> dsp_block -> i2s_send -> codec`, this puts us in a catch22: we can't receive data until we send data, and we cant send data until we receive data.
For the 'demo' that I've just put together, I've bypassed this by having completely independent signal paths, i.e. we stream dummy data to `i2s_send -> codec` to get the clocks running, and after that I'm consuming data from the `i2s_receive` callback to keep that from blocking.
In order to 'hack' together our desired signal path from this setup, I'd have to be able to switch one `chanend` from the function producing 'dummy data' to the output of the DSP after the `i2s_receive` callback has started firing, and that sounds like a bunch of hacky work that I'd rather avoid. Is the development team aware of this issue with the `i2s_receive` callback not firing until after the `i2s_send` callback has received at least one frame? Is this an issue worth raising on the repo?
There is still one underlying bit of 'unexpected' behaviour that may cause issues for our application:
When the app is initialised, the `i2s_send` callback fires immediately (but the `i2s_receive` does not). Naturally this callback blocks until an i2s frame is sent to it. The problem with this is that while it is blocking, the i2s clocks don't appear to run. This appears to prevent the `i2s_receive` callback from ever firing. Since our intended signal path is `codec -> i2s_receive -> dsp_block -> i2s_send -> codec`, this puts us in a catch22: we can't receive data until we send data, and we cant send data until we receive data.
For the 'demo' that I've just put together, I've bypassed this by having completely independent signal paths, i.e. we stream dummy data to `i2s_send -> codec` to get the clocks running, and after that I'm consuming data from the `i2s_receive` callback to keep that from blocking.
In order to 'hack' together our desired signal path from this setup, I'd have to be able to switch one `chanend` from the function producing 'dummy data' to the output of the DSP after the `i2s_receive` callback has started firing, and that sounds like a bunch of hacky work that I'd rather avoid. Is the development team aware of this issue with the `i2s_receive` callback not firing until after the `i2s_send` callback has received at least one frame? Is this an issue worth raising on the repo?
-
Verified
- Member++
- Posts: 22
- Joined: Wed May 22, 2024 2:36 pm
It's not a bug, it's a feature ;) unfortunately, this is the nature of how i2s has been implemented and your application will have to work around it.
The solution here will be to write a new thread which wraps the I2S and synchronises the comms. Then replace the call to i2s_master in the main function with this.
This will give you the flexibility to add any custom inter-thread communication before setting up the i2s master
The solution here will be to write a new thread which wraps the I2S and synchronises the comms. Then replace the call to i2s_master in the main function with this.
Code: Select all
DECLARE_JOB(i2s_wrapper, ())
void i2s_wrapper(...) {
(void)send_dummy_data_to_other_thread();
i2s_master(...);
}
XMOS Software Engineer
-
- New User
- Posts: 2
- Joined: Mon Aug 12, 2024 3:02 pm
Thanks. I'm not 100% sure about your example here though. Without passing e.g. a streaming `chanend` to `send_dummy_data_to_other_thread()` then how can we get any inter-thread communication?xhuw wrote: ↑Tue Aug 13, 2024 10:56 amCode: Select all
DECLARE_JOB(i2s_wrapper, ()) void i2s_wrapper(...) { (void)send_dummy_data_to_other_thread(); i2s_master(...); }
And if we *do* pass a streaming `chanend` to that function (routed to the send callback) then do we have to do something special to make sure that that route through the network is 'torn down' before the new one is established between the receive callback (or DSP block) and the send callback?
The fundamental problem here seems to be that the input end of the channel going to the send callback can only be in one place at a time (I might be misunderstanding how streaming channels work here though). In my mind then, the solution is to have a single thread handle both the initialisation and the data streaming (send a dummy frame, then pipe the DSP data through), but since the implementation of `TRIGGERABLE_WAIT_EVENT` is not well documented I have so far struggled to work out how to implement that. In any case I might work up an example of this approach and start a new thread in these forums to get advice on that, since it's a separate issue.
Many thanks again for your help.
-
Verified
- Member++
- Posts: 22
- Joined: Wed May 22, 2024 2:36 pm
Yes, the example code I posted was intended to be pseudo-code. You could pass the channel in there and basically do whatever is needed to trigger your DSP pipeline.
As this is a streaming channel, the network is never torn down. This provides a small performance improvement over regular channels. Channels are full duplex, so there is no input end; it is possible to input on both ends as long as the protocol that is used is understood by both sides.
Are you using separate channels for the I2S -> DSP and DSP -> I2S threads? I recommend that you do this to be sure that different threads don't try to access the same channel at once.
As this is a streaming channel, the network is never torn down. This provides a small performance improvement over regular channels. Channels are full duplex, so there is no input end; it is possible to input on both ends as long as the protocol that is used is understood by both sides.
Are you using separate channels for the I2S -> DSP and DSP -> I2S threads? I recommend that you do this to be sure that different threads don't try to access the same channel at once.
XMOS Software Engineer
-
- New User
- Posts: 2
- Joined: Mon Aug 12, 2024 3:02 pm
Thanks xhuw, I've managed to get a working demo now. There were a couple of tricks:
1) as you mentioned early on, making sure that the frame length and number of channels was properly defined. Channels is 2, and frame length is 1 (unlike the original PDM mic frame length from the demo which is 256).
2) creating an initialisation stage that basically just spammed several frames of junk data to the i2s output, in order to 'kickstart' the full i2s process. Sending only 1 frame of junk didn't work, but sending 10 words at once (effectively 5 frames) did. I'm not sure what's going on there really, but for reference here's the line to demonstrate what I mean:
which is running during the setup of ap_stage_c (before the while loop starts). It should be noted that here isn't even properly defined (it's an array of length 2), so this is *literally* undefined junk data I'm sending.
1) as you mentioned early on, making sure that the frame length and number of channels was properly defined. Channels is 2, and frame length is 1 (unlike the original PDM mic frame length from the demo which is 256).
2) creating an initialisation stage that basically just spammed several frames of junk data to the i2s output, in order to 'kickstart' the full i2s process. Sending only 1 frame of junk didn't work, but sending 10 words at once (effectively 5 frames) did. I'm not sure what's going on there really, but for reference here's the line to demonstrate what I mean:
Code: Select all
s_chan_out_buf_word(c_output, (uint32_t*) output, 10);
Code: Select all
output
I did do this in the end, and haven't gone back to testing with just a single channel to see if it still works.
Last edited by nrs23 on Fri Aug 16, 2024 11:50 am, edited 1 time in total.