mic_array board and adc out time usage

Technical discussions related to any XMOS development kit or reference design. Eg XK-1A, sliceKIT, etc.
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

mic_array board and adc out time usage

Post by aclassifier »

I am using lib_i2s to output to the headset, like in AN00219_app_lores_DAS_fixed. ADC chip is Cirrus Logic CS43L21. Digging into and understanding the lib_i2s seems very hard to me. I am using the 7 mics array board.

To output to the ADC there are two consecutive writes:

Code: Select all

ch_headset_out <: output_to_dac;
ch_headset_out <: output_to_dac;
Q1. Why? The same output is done twice. My headset becomes wild if I output only one of them.

If my application starts with the loop which output these, the time used for these two outputs is getting much shorter (by microseconds) if I have a pause in writing to the ADC and then write to it again.

But this seemed like some code that gave this faster behaviour from init. If I instead do a single

Code: Select all

ch_headset_out <: output_to_dac;
while(1) {
    // .. the code
}
before the loop, then I did not have to make a pause in order to get the shorter value.

Q2. Why? I tried a delay after initialising the i2s_handler, but it was not there. Some synching?
fig9_219_scope_sampling_48khz_knock_come_version_2.4.9.jpg
Three scope pictures will show the details, cluttered with some other stuff, but it may be of help.
You do not have the required permissions to view the files attached to this post.
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
CousinItt
Respected Member
Posts: 367
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

i2s_master takes the number of output ports as an argument. Each output port delivers two channels of data per cycle of the left/right (aka word select) clock. Hence you have to deliver an even number of outputs on every cycle. If you only try to deliver one channel, it will see a long delay between the samples it needs, miss its timing deadlines, and go badly wrong.
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

Post by aclassifier »

Thanks, CousinItt! I have

Code: Select all

#define I2S_BOARD_NUM_DACS  1 // HW PORTS
on tile[1]: out buffered port:32 p_i2s_dac_data[I2S_BOARD_NUM_DACS] = {XS1_PORT_1P}; // PORT_I2S_DAC0

i2s_master (if_i2s_board, p_i2s_dac_data, I2S_BOARD_NUM_DACS, null, 0, p_i2s_bclk, p_i2s_lrclk, bclk, mclk);
Where do I catch the "2"? Stereo = 2 of course, but where do I get that point in the code?

If I add that first single write all that happens is that the next pairs of writes go faster. If I don't add that first single write, then the adc outputs must have a break before they become as fast as the one with the single output in front. How migh that be explained? In all the cases the sound is nice on the headset.
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
CousinItt
Respected Member
Posts: 367
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

In your code fragment, I2S_BOARD_NUM_DACS is used to indicate the number of ports, both to set the size of p_i2s_dac_data[] and as an argument to i2s_master(). A single CS43L21 contains two DACs, so the name of the constant may be confusing.

In the user guide, the description of the API doesn't spell this out explicitly. The best information is in the section "callback sequences", and it's the sample-based i2s sequence you should look at in this case. The example assumes two output ports (i.e. four output channels).
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

Post by aclassifier »

Thanks. Of course.. I guess I2S_BOARD_NUM_CS43L21 is a better name for the constant. XMOS had just "1" there, I try to understand and give names. Thanks!

Another matter. When I run my code at 64 kHz (as AN00220) with beamforming and ADC out, all is fine. 1 sample per frame.

However, if I change to 16 kHz (as AN00219) I'm neither certain if I pick out the correct sample (I do it the same way, 1 sample per frame) nor I don't understand why the headset is silent (except for some startup tics). The CS43L21 has "frequency" on the charge pump and on the beep. I see in the data sheet that "The frequency of the MCLK must be an integer multiple of, and synchronous with, the system sample rate, Fs." Plus, the mic array I assume that the DSP in that chip needs to have something set. Plus, the PLL's pulses are shared among the mics and the dacs.

I have struggled with this for quite some time. I could zip the code and attach it here if it's too much in the dark without it. I am lost.
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
CousinItt
Respected Member
Posts: 367
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

I'm not familiar with the microphone array, not sure how I can help.

Regarding the callback sequence, note that once the i2s master has been primed with the initial output set, the following input and output transfers are interleaved.

Clearly the decimators have to be set up correctly, as per the microphone array library guide. If you're changing the sampling rate but running the microphones at the same frequency, something's likely to go wrong.
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

Post by aclassifier »

CousinItt wrote: Thu Dec 16, 2021 8:08 pm Regarding the callback sequence, note that once the i2s master has been primed with the initial output set, the following input and output transfers are interleaved.
There are several places in i2s_master where I see a loop with two outputs fetching the adc output from the callback i2s.send(i) (which is where the two channel outputs end). You wouldn't have that particular place in the code? It is so strange, the behaviour I see. But then, it's probably not standard procedure to switch off by not running the code, one would rather just set the adc output gain to zero. But then I would have suffered more cycles to get the values out.

All the parameters to the decimators etc. should in my code change as they must. I have used conditional compilation [1] since I can compile this, but I have seen arrays of const-arrays for the dynamic cases. But that's where to look, I agree.

[1] https://www.teigfam.net/oyvind/home/tec ... n_per_time
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
CousinItt
Respected Member
Posts: 367
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

In rev 3.0.0rc2 of lib_i2s, i2s_master_impl.h contains the code for the sample-based I2S master.

In i2s_ratio_n(), the output is primed by this code, which:

Code: Select all

    //Preload word 0
    if(mode == I2S_MODE_I2S){
        for(size_t i=0;i<num_out;i++)
            p_dout[i] @ 2 <: bitrev(i2s_i.send(i*FRAME_WORDS));          
            ...
     //Now preload word 1
	    ...
         for(unsigned i=0;i<calls_per_pair;i++){
             if(if_call_num < num_out)
                 p_dout[if_call_num] <: bitrev(i2s_i.send(if_call_num*FRAME_WORDS+1));

(FRAME_WORDS is defined to be 2)

output_word() is called after the output is primed. That function does most of the work after initial setup is done: it handles output of the word clock and bit clock, and the send() and receive() calls for "normal" running. After that there's a loop containing the restart check and another couple of output_word() calls, so one iteration of the loop per frame. The loop continues forever, since the AN00219 app restart check always returns I2S_NO_RESTART. If the restart check ever returned something else, there is some special mop-up code to handle the final receive() calls, and then the master terminates or restarts as appropriate.
User avatar
aclassifier
Respected Member
Posts: 507
Joined: Wed Apr 25, 2012 8:52 pm

Post by aclassifier »

Great! When I look at this code, AN00219 has (in i2s_handler) uses mode==I2S_MODE_LEFT_JUSTIFIED, which make the two loops quite different.

This code is somewhat magic to me, but there are total_clk_pairs and calls_per_pair in word 1 and not word 0. Might this explain why my outputting one word first fixes the faster mode?

Code: Select all

//Preload word 0
if(mode == I2S_MODE_I2S){
    for(size_t i=0;i<num_out;i++)
        p_dout[i] @ 2 <: bitrev(i2s_i.send(i*FRAME_WORDS));
    partout(p_lrclk, 1, 0);
    for(size_t i=0;i<num_in;i++)
        asm("setpt res[%0], %1"::"r"(p_din[i]), "r"(32+1));
    lr_mask = 0x80000000;
    partout(p_bclk, 1<<ratio, clk_mask);
 } else { // ONE LOOP
    for(size_t i=0;i<num_out;i++)
       p_dout[i] <: bitrev(i2s_i.send(i*FRAME_WORDS));
 }
 p_lrclk <: lr_mask;
 i2s_output_clock_pair(p_bclk,  clk_mask);

 //This is non-blocking
 lr_mask = ~lr_mask;
 p_lrclk <: lr_mask;

 //Now preload word 1
unsigned if_call_num = 0;
  // OUTER AND INNER LOOP
 for(unsigned clk_pair=0; clk_pair < total_clk_pairs;clk_pair++){
     for(unsigned i=0;i<calls_per_pair;i++){
         if(if_call_num < num_out)
             p_dout[if_call_num] <: bitrev(i2s_i.send(if_call_num*FRAME_WORDS+1));

         if_call_num++;
     }
     //This is blocking
     i2s_output_clock_pair(p_bclk,  clk_mask);
 }
I am looking at trying to run with lower frequency, both for the sampler and decimator and adc. They certainly are connected, both in HW and SW. But I will start another thread for that. But I need to have something to say there before I write it (..!)
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
User avatar
CousinItt
Respected Member
Posts: 367
Joined: Wed May 31, 2017 6:55 pm

Post by CousinItt »

Sorry, I missed that - I was assuming it was I2S. The default power-on state for the CS43L21 is to accept left-justified audio data, and this is not modified by the AN00219 initialisation code (at the start of i2s_handler). With justified formats the audio data words are in phase with the word clock (shifted to the front or back end as appropriate). In I2S there's a one-bit delay. One of the functions of this priming code is to set the relative timing of the word clock and the audio data, and it's much simpler for left-justified.

The clock pairs are to do with the ratio between the master clock and the bit clock.

You really have to follow the sequence as shown in the user guide (adapted for the number of input and output channels you have). If it works correctly at the default sampling rate you should be confident that the I2S interface handling is fine. If you then try to drop the sampling rate and it goes wrong I think it would most likely be due to something not being correctly updated to reflect the new ratios between the clock rates and the sampling rate.