Integrating I2S/TDM library with USB Audio Module Topic is solved

Sub forums for various specialist XMOS applications. e.g. USB audio, motor control and robotics.
Post Reply
rgilio
Active Member
Posts: 36
Joined: Wed Jul 03, 2019 1:01 am

Integrating I2S/TDM library with USB Audio Module

Post by rgilio »

Hi, I've got a few questions regarding using the TDM library.

HARDWARE: XMOS Mic Array 2V0 , custom microphone array with TDM output.

The current goal is to take the TDM output from the custom mic array and transfer it over USB. The general plan is connecting the custom mic array to the expansion header slots to provide both the LRCLK/FSYNC and BCLK/SCLK clock signals to the array and also connect SDA IN line from the custom board back to the xCore-200.

I'm currently using the "app_usb_aud_mic_array" as a base as it supports USB audio by default which means we just need to start the clocks and point them to the correct expansion header ports and then receive the SDA and process using TDM library and output via USB. I've currently got the I2C and I2S library included and the project builds just fine, however there are a couple of gray areas with the initialization of the tdm interface.

For TDM, I'm referencing the AN00239 example as it uses the TDM aspect of the library and also the 'pcm_pdm_mic.xc' file in the 'module_usb_audio/pdm_mics/' folder for having the mic data interface the USB.

The first question is how the clocks are driven. The 'tdm_master()' function/task takes the 'bclk' clock block as one of the parameters. I'm unsure if this task starts the clock automatically or if I need to tell it to start using "start_clock(bclk);" It also takes in the fsync port in which I'm unsure if it automatically handles the clock configuration of that as well. If it does, then there's a minor issue, one of the quirks regarding the custom board requires waiting >=10ms after the bclk starts up before enabling the fsync. Is there a way for me to add this delay or account for it? I also see in the 'buffer_manager_to_tdm()' task there's a call to 'audio_clock_CS2100CP_init(i2c);' Is this the task responsible for starting the clock?

My next question is for the 'tdm.init' event. The AN00239 examples does some dac and adc resets and mclk/pll select output configs alongside some register writes. Since I'm only providing the clock signals to the custom board, I believe I don't need to do these writes and resets, is this an incorrect assumption?

If there's something that isn't quite clear, please let me know and I'll do my best to better explain.


View Solution
User avatar
mon2
XCore Legend
Posts: 1913
Joined: Thu Jun 10, 2010 11:43 am
Contact:

Post by mon2 »

Hi. A few comments to get this started...

1) on applying a delay, there are many options but one is to use timer.h and then:

Code: Select all

delay_milliseconds(delay_in_ms);
2) the CS2100CP is a PLL (clock generator) that is configured over the I2C bus. The passed parameter are the details (structure) of the port pins used to create the I2C interface. So this is a precision clock generator onboard the PCB. XMOS has some ability to drive ports based on clock edges whether they be external clocks or internal clocks but note that the resolution of the XMOS divisors are not as flexible as external PLL like this one of the Si5351A which is our device of choice.

3) On the tdm.init - cannot say on what and how you will use BUT do not allow for calls to hardware that is not present on your PCB. Assisted one developer who invested months of PCB respins in thinking there was a USB layout issue with impedance control but in our review, we found that the XMOS IP was performing retries which are BLOCKING in nature for this phantom hardware before timing out. These retries starved the USB IP code and would raise USB communication issues after a period of time. Bottom line, if you do not have the external hardware, remove or mask out the calls to such missing hardware to allow for quick code execution.

Hope others can chime to supply their comments...
User avatar
CousinItt
Respected Member
Posts: 360
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

Re starting the clock, look at tdm_master_impl.h in lib_i2s. tdm_master0 (an alias for tdm_master), calls tdm_init_ports which starts the clock. It also configures the fsync port.
User avatar
akp
XCore Expert
Posts: 578
Joined: Thu Nov 26, 2015 11:47 pm

Post by akp »

Here's the proper way to start the TDM master

Code: Select all

on tile[0]: clock clk_tdm_mclk = XS1_CLKBLK_2; //(or whatever)
on tile[0]: clock clk_tdm_bclk = XS1_CLKBLK_3; //(or whatever)
#define MCLK_BCLK_RATIO 2 // or whatever, e.g. if you have 24.576 MHz MCLK and want 12.288 MHz BCLK for 48kHz x 8ch x 32b TDM that would be 2

      configure_clock_src_divide(clk_tdm_bclk, p_tdm_mclk, MCLK_BCLK_RATIO >> 1);
      configure_port_clock_output(p_tdm_bclk, clk_tdm_bclk);
      tdm_master(...)
rgilio
Active Member
Posts: 36
Joined: Wed Jul 03, 2019 1:01 am

Post by rgilio »

CousinItt wrote: Fri Jul 26, 2019 1:47 pm Re starting the clock, look at tdm_master_impl.h in lib_i2s. tdm_master0 (an alias for tdm_master), calls tdm_init_ports which starts the clock. It also configures the fsync port.
Ah, I didn't realize the definition was also included in these header files. Thanks for pointing me there.
rgilio
Active Member
Posts: 36
Joined: Wed Jul 03, 2019 1:01 am

Post by rgilio »

akp wrote: Fri Jul 26, 2019 2:00 pm Here's the proper way to start the TDM master

Code: Select all

on tile[0]: clock clk_tdm_mclk = XS1_CLKBLK_2; //(or whatever)
on tile[0]: clock clk_tdm_bclk = XS1_CLKBLK_3; //(or whatever)
#define MCLK_BCLK_RATIO 2 // or whatever, e.g. if you have 24.576 MHz MCLK and want 12.288 MHz BCLK for 48kHz x 8ch x 32b TDM that would be 2

      configure_clock_src_divide(clk_tdm_bclk, p_tdm_mclk, MCLK_BCLK_RATIO >> 1);
      configure_port_clock_output(p_tdm_bclk, clk_tdm_bclk);
      tdm_master(...)
This is generally what I was doing before, but getting some background on the MCLK_BCLK_RATIO helped.
rgilio
Active Member
Posts: 36
Joined: Wed Jul 03, 2019 1:01 am

Post by rgilio »

mon2 wrote: Fri Jul 26, 2019 3:42 am Hi. A few comments to get this started...

1) on applying a delay, there are many options but one is to use timer.h and then:

Code: Select all

delay_milliseconds(delay_in_ms);
2) the CS2100CP is a PLL (clock generator) that is configured over the I2C bus. The passed parameter are the details (structure) of the port pins used to create the I2C interface. So this is a precision clock generator onboard the PCB. XMOS has some ability to drive ports based on clock edges whether they be external clocks or internal clocks but note that the resolution of the XMOS divisors are not as flexible as external PLL like this one of the Si5351A which is our device of choice.

3) On the tdm.init - cannot say on what and how you will use BUT do not allow for calls to hardware that is not present on your PCB. Assisted one developer who invested months of PCB respins in thinking there was a USB layout issue with impedance control but in our review, we found that the XMOS IP was performing retries which are BLOCKING in nature for this phantom hardware before timing out. These retries starved the USB IP code and would raise USB communication issues after a period of time. Bottom line, if you do not have the external hardware, remove or mask out the calls to such missing hardware to allow for quick code execution.

Hope others can chime to supply their comments...

1. My problem with the delay_milliseconds() is that I needed to make sure the delay happens right after the starting of the BCLK which is happening inside of the tdm_master. I'm thinking I may need to modify the i2s_lib to account for this, I'll investigate some other paths too.
2. Since I'm not using the I2C bus for the mics, I don't think I need to init the CS2100CP clock in this particular instance.
3. The same applies from the above. I'm not using the DAC so I'll go ahead and not include those register writes.

Thanks for the help!
rgilio
Active Member
Posts: 36
Joined: Wed Jul 03, 2019 1:01 am

Post by rgilio »

I was looking at the clock and port documentation but couldn't find anything if this would cause any problems.
I figured in order to add the delay, I configure the out/in ports, start the clock and immediately wait 10ms before configuring the fsync port.
I'm unsure if if the clock needs to be in a stopped state while I'm configuring a port to use its signal. Reason being, the first line of this function is to stop the clock.

Original

Code: Select all

tdm_init_ports()
    stop_clock(clk);
    configure_out_port(p_fsync, clk, 0);
    for (size_t i = 0; i < num_out; i++)
        configure_out_port(p_dout[i], clk, 0);
    for (size_t i = 0; i < num_in; i++)
        configure_in_port(p_din[i], clk);
    start_clock(clk);
Modified to add delay.

Code: Select all

tdm_init_ports() 
    stop_clock(clk);
    for (size_t i = 0; i < num_out; i++)
        configure_out_port(p_dout[i], clk, 0);
    for (size_t i = 0; i < num_in; i++)
        configure_in_port(p_din[i], clk);
    start_clock(clk);
    delay_milliseconds(10);
    configure_out_port(p_fsync, clk, 0);
Quick Edit:

It looks like that the fsync signal isn't staying alive after 'tdm_init_ports' in either version. I've verified with a digital analog analyzer that if I start the clock manually and put a wait before "tdm_master()" I can see the fsync signal coming from the expansion header. Otherwise, the expansion header doesn't have any clock output. Maybe I don't know something about the TDM clock sequence that makes this behavior expected. I'm not quite sure why I'm not seeing anything come out though.
rgilio
Active Member
Posts: 36
Joined: Wed Jul 03, 2019 1:01 am

Post by rgilio »

I resolved the fsync signal not staying alive. I made sure that the I2S_NO_RESTART flag was being set and some other minor changes and it started to work.
I'm also receiving TDM data on the expansion header so I'll mark this as resolved.
Post Reply