I2s loopback and sending a ethernet stream

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
leutholl
Member++
Posts: 19
Joined: Wed Feb 04, 2015 11:41 pm

I2s loopback and sending a ethernet stream

Post by leutholl »

Hi.

I managed to get the I2S looback working on my xCore200-MultiChannel board. I also wrote a Ethernet Layer 1 (no mac, no ip) task that sends a 100byte bitstream through the PHY for the receiving party to recognize the audio samples. This for it's own is also working. however, as soon as I combine the loopback with my code things get screwed up and I don't know why. I tried inter-task communication with interface and with streaming channel but they seem to behave as they are synchronous. As soon as I make use of the channel the loopback is stopping and making a high-pitched noise. (clock?)

all I want to do is to let the loopback run, copy the samples to my routine and send the ethernet bitstream along with the loopback. The timing of the Ethernet is maintained correctly but none of my audio samples will arrive on this end.

Also I wonder if I can refactor the sketch to be I2S slave (can the Codecs on the xCORE 200 MultiChannel board generate the MCLK so I can be I2S slave, no samples found for the I2S slave). tile 1 is full because of the rgmii stuff and I'm not 100% sure if the clock blocks on both tiles interfere with each other (hence the I2S slave question).

A simple looback with a copy of the samples to be further processed by another task (with it's own timing) would help me. Many thanks!

Code: Select all

// Copyright (c) 2015-2016, XMOS Ltd, All rights reserved
#include <xs1.h>
#include <platform.h>
#include "ethernet.h"
#include "smi.h"
#include "debug_print.h"
#include <stdio.h>
#include <print.h>
#include "i2s.h"
#include "i2c.h"
#include "gpio.h"

//ETHERNET
#define BASIC_CONTROL_REG                  0x0

on tile[1]: rgmii_ports_t rgmii_ports = RGMII_PORTS_INITIALIZER; // Fixed RGMII ports on Tile 1
on tile[1]: port p_smi_mdio = XS1_PORT_1C;
on tile[1]: port p_smi_mdc = XS1_PORT_1D;
on tile[1]: port p_eth_reset = XS1_PORT_4A;

//I2S
/* Ports and clocks used by the application */
on tile[0]: out buffered port:32 p_lrclk = XS1_PORT_1G;
on tile[0]: out buffered port:32 p_bclk = XS1_PORT_1H;
on tile[0]: in port p_mclk = XS1_PORT_1F;
on tile[0]: out buffered port:32 p_dout[4] = {XS1_PORT_1M, XS1_PORT_1N, XS1_PORT_1O, XS1_PORT_1P};
on tile[0]: in buffered port:32 p_din[4] = {XS1_PORT_1I, XS1_PORT_1J, XS1_PORT_1K, XS1_PORT_1L};
on tile[0]: clock mclk = XS1_CLKBLK_1;
on tile[0]: clock bclk = XS1_CLKBLK_2;
on tile[0]: port p_i2c = XS1_PORT_4A;
on tile[0]: port p_gpio = XS1_PORT_8C;

#define SAMPLE_FREQUENCY 48000
#define MASTER_CLOCK_FREQUENCY 24576000
#define CS5368_ADDR           0x4C // I2C address of the CS5368 DAC
#define CS5368_GCTL_MDE       0x01 // I2C mode control register number
#define CS5368_PWR_DN         0x06
#define CS4384_ADDR           0x18 // I2C address of the CS4384 ADC
#define CS4384_MODE_CTRL      0x02 // I2C mode control register number
#define CS4384_PCM_CTRL       0x03 // I2C PCM control register number

typedef interface samples_if {
    void buffer(size_t ch, int32_t s);
  } samples_if;


//I2S
enum gpio_shared_audio_pins {
  GPIO_DAC_RST_N = 1,
  GPIO_PLL_SEL = 5,     // 1 = CS2100, 0 = Phaselink clock source
  GPIO_ADC_RST_N = 6,
  GPIO_MCLK_FSEL = 7,   // Select frequency on Phaselink clock. 0 = 24.576MHz for 48k, 1 = 22.5792MHz for 44.1k.
};

//ETHERNET
// An enum to manage the array of connections from the ethernet component
// to its clients.
enum eth_clients {
  ETH_TO_ICMP,
  NUM_ETH_CLIENTS
};

enum cfg_clients {
  CFG_TO_ICMP,
  CFG_TO_PHY_DRIVER,
  NUM_CFG_CLIENTS
};

#define timed_printf \
  { timer t; int u; t :> u; /* GETTIME instruction */ \
    printint(u); printstr(" "); \
  } printf

void reset_codecs(client i2c_master_if i2c)
{
  /* Mode Control 1 (Address: 0x02) */
  /* bit[7] : Control Port Enable (CPEN)     : Set to 1 for enable
   * bit[6] : Freeze controls (FREEZE)       : Set to 1 for freeze
   * bit[5] : PCM/DSD Selection (DSD/PCM)    : Set to 0 for PCM
   * bit[4:1] : DAC Pair Disable (DACx_DIS)  : All Dac Pairs enabled
   * bit[0] : Power Down (PDN)               : Powered down
   */
  i2c.write_reg(CS4384_ADDR, CS4384_MODE_CTRL, 0b11000001);

  /* PCM Control (Address: 0x03) */
  /* bit[7:4] : Digital Interface Format (DIF) : 0b1100 for TDM
   * bit[3:2] : Reserved
   * bit[1:0] : Functional Mode (FM) : 0x11 for auto-speed detect (32 to 200kHz)
   */
  i2c.write_reg(CS4384_ADDR, CS4384_PCM_CTRL, 0b00010111);

  /* Mode Control 1 (Address: 0x02) */
  /* bit[7] : Control Port Enable (CPEN)     : Set to 1 for enable
   * bit[6] : Freeze controls (FREEZE)       : Set to 0 for freeze
   * bit[5] : PCM/DSD Selection (DSD/PCM)    : Set to 0 for PCM
   * bit[4:1] : DAC Pair Disable (DACx_DIS)  : All Dac Pairs enabled
   * bit[0] : Power Down (PDN)               : Not powered down
   */
  i2c.write_reg(CS4384_ADDR, CS4384_MODE_CTRL, 0b10000000);

  unsigned adc_dif = 0x01;  // I2S mode
  unsigned adc_mode = 0x03; // Slave mode all speeds

  /* Reg 0x01: (GCTL) Global Mode Control Register */
  /* Bit[7]: CP-EN: Manages control-port mode
   * Bit[6]: CLKMODE: Setting puts part in 384x mode
   * Bit[5:4]: MDIV[1:0]: Set to 01 for /2
   * Bit[3:2]: DIF[1:0]: Data Format: 0x01 for I2S, 0x02 for TDM
   * Bit[1:0]: MODE[1:0]: Mode: 0x11 for slave mode
   */
  i2c.write_reg(CS5368_ADDR, CS5368_GCTL_MDE, 0b10010000 | (adc_dif << 2) | adc_mode);

  /* Reg 0x06: (PDN) Power Down Register */
  /* Bit[7:6]: Reserved
   * Bit[5]: PDN-BG: When set, this bit powers-own the bandgap reference
   * Bit[4]: PDM-OSC: Controls power to internal oscillator core
   * Bit[3:0]: PDN: When any bit is set all clocks going to that channel pair are turned off
   */
  i2c.write_reg(CS5368_ADDR, CS5368_PWR_DN, 0b00000000);
}

static char gpio_pin_map[4] =  {
  GPIO_DAC_RST_N,
  GPIO_ADC_RST_N,
  GPIO_PLL_SEL,
  GPIO_MCLK_FSEL
};


[[combinable]]
void ar8035_phy_driver(client interface smi_if smi,
                client interface ethernet_cfg_if eth) {
  ethernet_link_state_t link_state = ETHERNET_LINK_DOWN;
  ethernet_speed_t link_speed = LINK_100_MBPS_FULL_DUPLEX;
  const int phy_reset_delay_ms = 1;
  const int link_poll_period_ms = 1000;
  const int phy_address = 0x4;
  timer tmr;
  int t;
  tmr :> t;
  p_eth_reset <: 0;
  delay_milliseconds(phy_reset_delay_ms);
  p_eth_reset <: 1;

  smi_configure(smi, phy_address, LINK_100_MBPS_FULL_DUPLEX, SMI_DISABLE_AUTONEG);

  // LOOPBACK MODE - REMOTE LOOPBACK
  /*
    // read debug register 0xB
    smi.write_reg(phy_address, 0x1D, 0xB);
    uint16_t debug_reg_0xb = smi.read_reg(phy_address, 0x1E);

    // read debug register 0x11
    smi.write_reg(phy_address, 0x1D, 0x11);
    uint16_t debug_reg_0x11 = smi.read_reg(phy_address, 0x1E);

    // disable hibernate
    debug_reg_0xb = debug_reg_0xb & ~ (1 << 0x15);
    // enable external loopback
    debug_reg_0x11 = debug_reg_0x11 | (1 << 0x0);

    // write debug register 0xB
    smi.write_reg(phy_address, 0x1D, 0xB);
    smi.write_reg(phy_address, 0x1E, debug_reg_0xb);

    // write debug register 0x11
    smi.write_reg(phy_address, 0x1D, 0x11);
    smi.write_reg(phy_address, 0x1E, debug_reg_0x11);

    // write control register
    smi.write_reg(phy_address, BASIC_CONTROL_REG, 0xA000); // 100Mbits half duplex, remote loopback
  */

  while (smi_phy_is_powered_down(smi, phy_address));

  while (1) {
    select {
    case tmr when timerafter(t) :> t:

      ethernet_link_state_t new_state = smi_get_link_state(smi, phy_address);
      // Read AR8035 status register bits 15:14 to get the current link speed
      if (new_state == ETHERNET_LINK_UP) {
        link_speed = (ethernet_speed_t)(smi.read_reg(phy_address, 0x11) >> 14) & 3;
      }
      if (new_state != link_state) {
        link_state = new_state;
        eth.set_link_state(0, new_state, link_speed);
      }

      t += link_poll_period_ms * XS1_TIMER_KHZ;
      break;
    }
  }
}


void anet_transmitter(client ethernet_cfg_if cfg,
                        client ethernet_rx_if rx,
                        client ethernet_tx_if tx,
                        streaming chanend c) {

      const int packet_period_us = 45; //45 uS each packet
      timer tmr;
      unsigned t;  // changed from int
      const int len = 100;

      unsigned char txbuf[101] = {0};
      txbuf[2] = 0x03; //meta-data for anet stream
      for (int i=4;i<=100;i++) {
          txbuf[i]=0;
      }

      timed_printf("Test started\n");

      tmr :> t;
      t += packet_period_us * XS1_TIMER_MHZ;

      while (1)
      {
        select {
           case  c :> uint32_t s:   //get the sample from the stream channel - prepare txbuf array
               //LEFT - odd channels
               for (int ch=1;ch<=8;ch++) {
                      //1st sample buffer
                      txbuf[(ch-1)*3+4+0] = s>>24 && 0xFF;  //bit 16..24
                      txbuf[(ch-1)*3+4+1] = s>>16 && 0xFF;  //bit 08..16
                      txbuf[(ch-1)*3+4+2] = s>>8  && 0xFF;  //bit 00..08
                      //2nd sample buffer
                      txbuf[(ch-1)*3+52+0] = s>>24 && 0xFF;
                      txbuf[(ch-1)*3+52+1] = s>>16 && 0xFF;
                      txbuf[(ch-1)*3+52+2] = s>>8  && 0xFF;
                }
                //RIGHT - even channels
                for (int ch=9;ch<=16;ch++) {
                    //1st sample buffer
                     txbuf[(ch-9)*3+28+0] = s>>24 && 0xFF;
                     txbuf[(ch-9)*3+28+1] = s>>16 && 0xFF;
                     txbuf[(ch-9)*3+28+2] = s>>8  && 0xFF;
                     //2nd sample buffer
                     txbuf[(ch-9)*3+76+0] = s>>24 && 0xFF;
                     txbuf[(ch-9)*3+76+1] = s>>16 && 0xFF;
                     txbuf[(ch-9)*3+76+2] = s>>8  && 0xFF;
                }
           break;

           case tmr when timerafter(t) :> void:
             t += packet_period_us * XS1_TIMER_MHZ;
             tx.send_packet(txbuf, len, ETHERNET_ALL_INTERFACES);
             //timed_printf("A packet was sent\n");
             break;
        }
      }
}

[[distributable]]
void i2s_to_anet(server i2s_callback_if i2s,
                 client i2c_master_if i2c,
                 client output_gpio_if dac_reset,
                 client output_gpio_if adc_reset,
                 client output_gpio_if pll_select,
                 client output_gpio_if mclk_select,
                 streaming chanend c) {

  int32_t buff[8] = {0};

  while (1)
  {
    select {
    case i2s.init(i2s_config_t &?i2s_config, tdm_config_t &?tdm_config):
          i2s_config.mode = I2S_MODE_I2S;
          i2s_config.mclk_bclk_ratio = (MASTER_CLOCK_FREQUENCY/SAMPLE_FREQUENCY)/64;

          // Set CODECs in reset
          dac_reset.output(0);
          adc_reset.output(0);

          // Select 48Khz family clock (24.576Mhz)
          mclk_select.output(1);
          pll_select.output(0);

          // Allow the clock to settle
          delay_milliseconds(2);

          // Take CODECs out of reset
          dac_reset.output(1);
          adc_reset.output(1);

          reset_codecs(i2c);
          break;

    case i2s.receive(size_t index, int32_t sample):
          buff[index] = sample;
          c <: buff[index];  //send the sample to the stream channel.
          /*
           * AS SOON AS THE ABOVE LINE IS IN THE I2S LOOPBACK IS SILENT AS IF THE CHANNEL WOULD BE SYNCHRONOUS
           */
          break;

    case i2s.send(size_t index) -> int32_t sample:
          sample = buff[index];
          break;

    case i2s.restart_check() -> i2s_restart_t restart:
          restart = I2S_NO_RESTART;
          break;
    }

  }
}

int main()
{
  //Ethernet
  ethernet_cfg_if i_cfg[NUM_CFG_CLIENTS];
  ethernet_rx_if i_rx[NUM_ETH_CLIENTS];
  ethernet_tx_if i_tx[NUM_ETH_CLIENTS];
  streaming chan c_rgmii_cfg;
  smi_if i_smi;

  //I2S
  interface i2s_callback_if i_i2s;
  interface i2c_master_if i_i2c[1];
  interface output_gpio_if i_gpio[4];

  streaming chan c;  //inter-task communication which carries a sample

  par {
    //I2S
    on tile[0]: {
            /* System setup, I2S + Codec control over I2C */
            configure_clock_src(mclk, p_mclk);
            start_clock(mclk);
            i2s_master(i_i2s, p_dout, 4, p_din, 4, p_bclk, p_lrclk, bclk, mclk);
          }
    on tile[0]: [[distribute]] i2c_master_single_port(i_i2c, 1, p_i2c, 100, 0, 1, 0);
    on tile[0]: [[distribute]] output_gpio(i_gpio, 4, p_gpio, gpio_pin_map);


    //Ethernet
    on tile[1]: rgmii_ethernet_mac(i_rx, NUM_ETH_CLIENTS,
                                   i_tx, NUM_ETH_CLIENTS,
                                   null, null,
                                   c_rgmii_cfg,
                                   rgmii_ports, 
                                   ETHERNET_DISABLE_SHAPER);
    on tile[1].core[0]: rgmii_ethernet_mac_config(i_cfg, NUM_CFG_CLIENTS, c_rgmii_cfg);
    on tile[1].core[0]: ar8035_phy_driver(i_smi, i_cfg[CFG_TO_PHY_DRIVER]);
    on tile[1]: smi(i_smi, p_smi_mdio, p_smi_mdc);

    //application
    on tile[0]: anet_transmitter(i_cfg[CFG_TO_ICMP], i_rx[ETH_TO_ICMP], i_tx[ETH_TO_ICMP], c);
    on tile[0]: [[distribute]] i2s_to_anet(i_i2s, i_i2c[0], i_gpio[0], i_gpio[1], i_gpio[2], i_gpio[3], c);
  }

  return 0;
}


peter
XCore Addict
Posts: 230
Joined: Wed Mar 10, 2010 12:46 pm

Post by peter »

It looks to me that the issue is going to be that the I2S handler is working on a sample at a time, but the channel to the ethernet buffering is going to back up pretty quickly while the anet_transmitter() core is busy sending a packet to the RGMII. I think you need to look at de-coupling the sending of blocks of data to the RGMII while still handling the sample-by-sample reception from the I2S. The I2S task should probably be putting data into a buffer and then simply passing the pointer to the buffer that the anet_transmitter() can send on to ethernet.
leutholl
Member++
Posts: 19
Joined: Wed Feb 04, 2015 11:41 pm

Post by leutholl »

Yes I decoupled the two tasks even more but adding a third one which takes a push/pull interface call and forwards to sample... This way the I2S is happy and I get the data. I thought that using streaming channels or interfaces this would work directly as using streaming channels is asynchronous but apparently it is still disturbing the I2S callback so a third tasked helped...

I do still have small problem to understand the callback data: Where is the "index" documented in the I2S callback to know how it is called in regards of timing and channel? is index 0 the first ADC channel and the 2nd call would be index 1 for the 2nd ADC channel or are they interleaved somehow?

Many thanks
peter
XCore Addict
Posts: 230
Joined: Wed Mar 10, 2010 12:46 pm

Post by peter »

The index is the audio channel in the frame, so for a stereo signal it will be index 0, 1, 0, 1...
leutholl
Member++
Posts: 19
Joined: Wed Feb 04, 2015 11:41 pm

Post by leutholl »

ok thanks for clarification! So for a stereo it works like this:

a frame (where the i2s.restart_check() is invoked to mark a start of a frame) consist of two i2s.receive callbacks. One with index 0 for the left channel and one with index 1 for the right channel. After this the i2s.send callback is invoked with the same logic to send the buffer to the DAC.

With 8ch a frame will invoke each callback 8 times with index from 0..7. First the i2s.receive for index 0 and 1 then i2s.send for index 0 and 1 then the same for index 2 and 3 and so on...

To find out when all samples got delivered the frame is signaled by looking at the i2s.restart_check(). Or by counting the indexes up to the total number of channel

Is this correct?
peter
XCore Addict
Posts: 230
Joined: Wed Mar 10, 2010 12:46 pm

Post by peter »

That is correct. The restart_check() will be called once per frame so you can synchronise using that call. There should be a call to send() and receive() for each index in the frame in between every restart_check().

It is possible to use the frame-based version of i2s if you want to have more time between the interface calls. You will then receive a full frame of data and have to provide a full frame of data for the send/receive calls. This usually makes it easier to merge i2s handling with other tasks as the timing constraints are more relaxed.
leutholl
Member++
Posts: 19
Joined: Wed Feb 04, 2015 11:41 pm

Post by leutholl »

Thanks peter for the pointer to the frame_master which indeed relaxes serving the samples... it's working here! I also wanted to try the slave mode but as there is no sample I don't know how to set the codec's gpio and clocking system. couldn't find any example on this and the documentation just "mentions" slave mode.
Post Reply