This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC3200: I2S - Slave Mode - Strange Behaviour

Part Number: CC3200


Hi All,

Questions/Clarification around using the I2S as a Slave, in CPU/Interrupt mode, 8000kHz sampling, 16 bit samples.

Approach:
1) Set up peripheral operational parameters (CPU mode, slave, clocks etc.)
2) Enable interrupts
3) Enable TX mode
4) Interrupts start firing every 64us and drain samples from my data buffers, 'playing' the audio as expected.

1. If I enable the interrupts just after the "enable tx" then about one time in 10 the peripheral will not operate properly. Why does this 30us delay to first loading the tx
 regsiter result in some kind of 'lock up'/ non start?

How do I know it is not near some kind of 'margin' and that it may fail again in some circumstances even if the interrupt is enabled before starting. In short, I want
to understand the failure, and know that if I enable the interrupt in time, that it will be reliable.

2. I noted that XERR and RERR bits in the status register get set when I operate the peripheral. These are not handled in the TI driver code and aren't fully explained anywhere. How should these conditions be handled? Why are they happening?

Other error flags are in the defines - overruns, syncs and so on. Same question for those, though I haven't seen them happen.

2. I sometimes see my code get stuck in the I2SGBLEnable() function in the while loop. This is in i2s.c in the TI code. It is reading back a global status register after writing it. What can cause this to fail? Is there any recommended fix?

3. When I enable the peripheral, I see on the scope that one interrupt fires immediately, presumably because the TX reg is empty. Then another fires again soon after, and then there is often (but not always) an abnormally long delay before the third interrupt (say 160 us). Then the interrupts return to regularity. I understand the first two being close, where the tx buff is initially starved, but why is there a delay until the third interrupt and why is this variable (I have verified there are no other interrupts on my system that can cause this delay, and anyway, I would never write interrupt code that could delay other interrupts by this amount)

It seems clear that the peripheral starts up in a funny way. I'd like an explanation here to lay to rest my fears that everything is stable and working fine.

See attached diagram from scope.

4. No-one has recommendations for receiving the left channel say of a mono I2S. You'll see in attached code my workaround. It consists of the following:

Enable I2S for rx etc.
When first interrupt fires, briefly configure the Frame Sync pin as GPIO and check it's level.
Reconfigure as frame sync and continue
Use the reading of Frame Sync GPIO to 'sense' the left channel after which alternate samples (right channel)
are dropped.

Anything wrong with this?

Thanks for your help
Ciarán

#include "proj.h"

//Driverlib includes
#include "hw_types.h"
#include "hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_mcasp.h"
#include "rom.h"
#include "rom_map.h"
#include "interrupt.h"
#include "prcm.h"


#include "pinmux.h"
#include "hal.h"
#include "hw_memmap.h"
#include "i2s.h"
#include "i2s_drvr.h"
#include "trace.h"
#include "debug.h"
#include "audio.h"

//---------------------------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------------------------
#define AUDIO_SAMPLING_FREQ	  8000
#define AUDIO_BIT_DEPTH				16
#define AUDIO_CHANNELS				1
#define AUDIO_BIT_CLK				(AUDIO_SAMPLING_FREQ*AUDIO_BIT_DEPTH*AUDIO_CHANNELS)

//---------------------------------------------------------------------------------------------
// Types
//---------------------------------------------------------------------------------------------
typedef enum
{
	I2S_AUDIO_IDLE,
	I2S_AUDIO_RX_MODE, 
	I2S_AUDIO_TX_MODE		
} i2s_audio_mode_t;

typedef struct
{
	// Rx Buffer
	int16_t* rx_buffer;
	uint32_t rx_buffer_sz;
	uint16_t rx_write_index;	// Point in the rx buffer where we are writing data from the I2S peripheral

	// Tx Buffer
	int16_t* tx_buffer;
	uint32_t tx_buffer_sz;
	uint16_t tx_read_index;		// Point in the tx buffer where we are reading from to fill the I2S peripheral	
	
	i2s_audio_mode_t mode;
	bool read_channel_synced;	// Flag to say we have syncd with correct channel (LEFT)
	uint8_t read_toggle;        // Drop every second rcvd word
	uint8_t write_toggle;       // Used to only increment tx pointer on every second tx (.ie tx same word on both channels, then increment)
	
	// Debug stuff
	uint32_t rx_cnt;
	uint32_t tx_cnt;
	uint32_t rerr;
	uint32_t rovern;
	uint32_t rsync_err;
	uint32_t xerr; 	
	uint32_t xsyncerr;
	uint32_t xundrn;
	uint32_t tx_ints;
	uint32_t rx_ints;	
	
} i2s_data_t;


//---------------------------------------------------------------------------------------------
// Module Variables
//---------------------------------------------------------------------------------------------
static volatile i2s_data_t i2s_data;

//---------------------------------------------------------------------------------------------
// Local Function Declarations
//---------------------------------------------------------------------------------------------
static void __attribute__((interrupt)) i2s_isr(void);
static uint32_t i2s_get_next_tx_word(void);
static void i2s_start_peripheral(i2s_audio_mode_t mode);

//---------------------------------------------------------------------------------------------
// Global Functions
//---------------------------------------------------------------------------------------------
/**
Init the i2s driver. It simulates a 'ping pong' buffer. The audio layer is built for a ping pong
so we simulate that here to ease integation. The size provided here is the size of each of the ping 
pongs and the ping pong buffers are contiguous in memory.

@return						void
*/
void i2s_init(void)
{
	//
    // Reset the peripheral
    //
	MAP_PRCMPeripheralReset(PRCM_I2S);
	
    //
    // Enable the clock for the McASP
    //
	MAP_PRCMPeripheralClkEnable(PRCM_I2S, PRCM_RUN_MODE_CLK);
}

void i2s_stop(void)
{
	trace_i2s(1, "I2S: stop");
	
	i2s_data.mode = I2S_AUDIO_IDLE;
	
	hal_nvic_int_disable(INT_I2S);
	
	// Disable the tx and rx interrupts, then disable the peripheral
	MAP_I2SIntDisable(I2S_BASE, I2S_INT_XDATA | I2S_INT_RDATA);
	MAP_I2SDisable(I2S_BASE);
	
	hal_orange_led_off();
}

/**
Init the i2s driver for rx. It simulates a 'ping pong' buffer. The audio layer is built for a ping pong
so we simulate that here to ease integation. The size provided here is the size of each of the ping pongs 
and the ping pong buffers are contiguous in memory.

@param[in] rx_buff_ptr		pointer to first ping pong and of course the second one is contiguous with it
@param[in] rx_buff_sz		size of each of the ping pongs
@return						void
*/
void i2s_rx_start(uint16_t* rx_buff_ptr, uint16_t rx_buff_sz)
{
	i2s_data.rx_buffer = (int16_t*)rx_buff_ptr;
	i2s_data.rx_buffer_sz = rx_buff_sz;
	i2s_data.rx_write_index = 0;
	i2s_data.mode = I2S_AUDIO_RX_MODE;
	i2s_data.read_toggle = 0;
	i2s_data.read_channel_synced = false;

	trace_i2s(TRACE_ALL, "%s(%08x, %d)", __FUNCTION__, rx_buff_ptr, rx_buff_sz);
	
	i2s_start_peripheral(I2S_AUDIO_RX_MODE);
}

/**
Init the i2s driver for tx. It simulates a 'ping pong' buffer. The audio layer is built for a ping pong
so we simulate that here to ease integation. The size provided here is the size of each of the ping pongs
and the ping pong buffers are contiguous in memory.

@param[in] tx_buff_ptr		pointer to first ping pong and of course the second one is contiguous with it
@param[in] tx_buff_sz		size of each of the ping pongs
@return						void
*/
void i2s_tx_start(uint16_t* tx_buff_ptr, uint16_t tx_buff_sz)
{
	i2s_data.tx_buffer = (int16_t*)tx_buff_ptr;
	i2s_data.tx_buffer_sz = tx_buff_sz;
	i2s_data.tx_read_index = 0;	
	i2s_data.write_toggle = 0;
	i2s_data.mode = I2S_AUDIO_TX_MODE;
	
	trace_i2s(TRACE_ALL, "%s()", __FUNCTION__);
	
	i2s_start_peripheral(I2S_AUDIO_TX_MODE);	

	hal_orange_led_on();
}

//---------------------------------------------------------------------------------------------
// Local Functions
//---------------------------------------------------------------------------------------------
static void i2s_start_peripheral(i2s_audio_mode_t mode)
{
	hal_red_led_off();
	MAP_PRCMPeripheralReset(PRCM_I2S);		
#ifdef TEST_I2S_ALONE	
	hal_nvic_interrupt_register(INT_I2S, INT_PRIORITY_LVL_3, i2s_test_handler_isr);
#else
	hal_nvic_interrupt_register(INT_I2S, INT_PRIORITY_LVL_1, i2s_isr);
#endif
	
	if (mode == I2S_AUDIO_TX_MODE)
	{
		MAP_I2SIntDisable(I2S_BASE, I2S_INT_RDATA);
		MAP_I2SIntEnable(I2S_BASE, I2S_INT_XDATA);		
	}
	else
	{
		MAP_I2SIntDisable(I2S_BASE, I2S_INT_XDATA);
		MAP_I2SIntEnable(I2S_BASE, I2S_INT_RDATA);		
	}

	MAP_PRCMI2SClockFreqSet(AUDIO_BIT_CLK);
	MAP_I2SConfigSetExpClk(I2S_BASE, AUDIO_BIT_CLK, AUDIO_SAMPLING_FREQ, I2S_SLOT_SIZE_16 | I2S_PORT_CPU | I2S_MODE_SLAVE);

    // RX bit clock // CMA_DBG can this be removed
	MAP_I2SSerializerConfig(I2S_BASE, I2S_DATA_LINE_1, I2S_SER_MODE_RX, I2S_INACT_LOW_LEVEL);

	// TX bit clock
	MAP_I2SSerializerConfig(I2S_BASE, I2S_DATA_LINE_0, I2S_SER_MODE_TX, I2S_INACT_LOW_LEVEL);	
	
	// Enable the interrupt in the NVIC (works if enabled here, before peripheral turned on)
	hal_nvic_int_enable(INT_I2S);
	
	// Finally, Enable TX / RX mode
	if (mode == I2S_AUDIO_TX_MODE)
	{	
		hal_red_led_on();		
		MAP_I2SEnable(I2S_BASE, I2S_MODE_TX_ONLY);
	}
	else
	{
		MAP_I2SEnable(I2S_BASE, I2S_MODE_TX_RX_SYNC);
	}
	// Enabling NVIC interrupt here results in 30us delay to first interrupt and occasional failure
	//hal_nvic_int_enable(INT_I2S);
}

// hook into audio subsystem
static uint32_t i2s_get_next_tx_word(void)
{
	uint16_t word = (uint32_t)i2s_data.tx_buffer[i2s_data.tx_read_index++];

	if (i2s_data.tx_read_index == i2s_data.tx_buffer_sz)
	{
		Audio_SetBufferEvent(FILL_BUFFER_A);
	}
	else if (i2s_data.tx_read_index == (i2s_data.tx_buffer_sz * 2))
	{
		Audio_SetBufferEvent(FILL_BUFFER_B);
		i2s_data.tx_read_index = 0;
	}
	return word;
}

// About 2us at -O1 gcc 5.7.1
void __attribute__((interrupt)) i2s_isr(void)
{
	uint32_t i2s_status = 0;
	uint32_t rcv_data = 0;
	hal_red_led_off();

	i2s_status = I2SIntStatus(I2S_BASE);
	
	if (i2s_status & I2S_STS_RDATA)
	{
		// Always get word then clear interrupt
		I2SDataGetNonBlocking(I2S_BASE, I2S_DATA_LINE_1, &rcv_data);
		I2SIntClear(I2S_BASE, I2S_STS_RDATA);
		
		if (i2s_data.mode == I2S_AUDIO_RX_MODE)
		{					
			if (!i2s_data.read_channel_synced)
			{
				pinmux_pin63_as_gpio_input();
				if (!pinmux_pin63_high())
				{
					i2s_data.read_channel_synced = true;
					i2s_data.read_toggle = 1;
				}
				pinmux_pin63_as_i2s_fs();
			}
			else
			{
				if (i2s_data.read_toggle)
				{				
					// Store sample and increment
					i2s_data.rx_buffer[i2s_data.rx_write_index++] = (int16_t)rcv_data;
			
					// If first ping buffer is full signal to audio layer
					if (i2s_data.rx_write_index == i2s_data.rx_buffer_sz)
					{
						Audio_SetBufferEvent(BUFFER_A_FULL);
					}
					else if (i2s_data.rx_write_index == (i2s_data.rx_buffer_sz * 2))
					{
						Audio_SetBufferEvent(BUFFER_B_FULL);
						i2s_data.rx_write_index = 0;
					}	
					i2s_data.rx_cnt++;	
				}
				i2s_data.read_toggle ^= 1;
			}				
		}
		i2s_data.rx_ints++;		
	}

	if (i2s_status & I2S_STS_XDATA)
	{
		static int16_t tx_word = 0;
		if (i2s_data.mode == I2S_AUDIO_TX_MODE)
		{
			if (i2s_data.write_toggle)
			{
				tx_word = 0x5555;/*i2s_get_next_tx_word(); CMA_DBG */
			}
			i2s_data.write_toggle ^= 1;	
		}
		else
		{
			tx_word = 0;
		}
		hal_green_led_on();
		// Always put word then clear interrupt
		if (I2SDataPutNonBlocking(I2S_BASE, I2S_DATA_LINE_0, tx_word))
		{
			trace_bare(1, "I2S TX ERR");
		}
		I2SIntClear(I2S_BASE, I2S_STS_XDATA);	
		i2s_data.tx_ints++;
		i2s_data.tx_cnt++;
	}	
	
	if (i2s_status & I2S_STS_RERR) 
	{
		i2s_data.rerr++;
	}
	if (i2s_status & I2S_STS_RSYNCERR)
	{
		i2s_data.rsync_err++;
	}
	if (i2s_status & I2S_STS_ROVERN)
	{
		i2s_data.rovern++;
	}
	if (i2s_status & I2S_STS_XERR)
	{
		i2s_data.xerr++;
	}
	if (i2s_status & I2S_STS_XSYNCERR)
	{
		i2s_data.xsyncerr++;
	}
	if (i2s_status & I2S_STS_XUNDRN)
	{
		i2s_data.xundrn++;
	}
	hal_green_led_off();
}

#ifndef RELEASE
void i2s_show(void)
{
	trace_bare(1, "I2S: tx=%u rx=%u t_ints=%u r_ints=%u xerr=%u xsyn=%u xundr=%u rerr=%u rsyn=%u rorn=%u", i2s_data.tx_cnt, i2s_data.rx_cnt, i2s_data.tx_ints, i2s_data.rx_ints, i2s_data.xerr, i2s_data.xsyncerr, i2s_data.xundrn, i2s_data.rerr, i2s_data.rsync_err, i2s_data.rovern);
	trace_bare(1, "GLBCTL  = %08x", HWREG(I2S_BASE + MCASP_O_GBLCTL));
}

void i2s_clear_stats(void)
{
	// rx stats
	i2s_data.rx_cnt = 0;
	i2s_data.rx_ints = 0;
	i2s_data.rerr = 0;
	i2s_data.rsync_err = 0;
	i2s_data.rovern = 0;
	
	//tx stats 
	i2s_data.tx_cnt = 0;
	i2s_data.tx_ints = 0;
	i2s_data.xerr = 0;
	i2s_data.xsyncerr = 0;
	i2s_data.xundrn = 0;
}

void i2s_tmp_test(void)
{
	if (i2s_data.tx_ints >  16000)
	{
		i2s_stop();
		i2s_show();
		i2s_clear_stats();
	}
}
#endif

  • Hi Ciaran,

    I want to make clear that officially we do not support I2S slave for the CC3200. The device was not designed with this requirement, so I may not have answers to unexpected behavior specific to this setup.

    2a. XERR and RERR and the error codes returned by these bits are described in the TRM.

    2b. When you see this I2SGBLEnable issue, what are you writing and what is the base address you are writing to? If you are attempting to write to a reserved address, it may never read back that way the function expects.

    For questions 1 and 3, do you see similar interrupt behavior on your setup with I2S Master?

    Best regards,
    Sarah
  • For the record I want to add an observation regarding question Q2b raised in this thread, i.e. that sometimes in slave mode I observed the code hang in I2SGBLEnable (). The answer is that inside I2SEnable() a number of separate calls to I2SGBLEnable () set various fields in the GBLCTL (global control) register. Some of these will fail in SLAVE mode if the bit clock is not already hitting the peripheral from the Master. In my case the master is a GSM part that only works as I2S master and therefore I need my CC3200 to be a slave. So the code will hang configuring the I2S unless the GSM has first been configured to drive the I2S clock. As mentioned here and elsewhere, TI do not overtly state support for I2S slave mode so the requirement I have just described is not documented anywhere.

    The quirk I mention here is strange -- it seems funny to me that the configuration of the peripheral cannot complete until the an external bit clock is present. I would have expected the internal peripheral clock to have been sufficient. It seems that certain short-cuts were taken in implementing a slimmed down version of the McASP. This is pure speculation on my part - perhaps TI can clarify. It is unfortunate that the system specifiers chose to ignore slave mode because given that the CC3200 is intended to work as the 'micro'/'system processor' in small connected designs it was always likely that engineers would want to hook it up to GSM or other digital audio parts which themselves can have quite inflexible digital audio designs -- the assumption being that with its added flexibility, the system micro will always be able to interwork with it.

    Anyhow, I have verified the slave audio to the extent that I feel it will work in my design. I think it's one of those things that either works or doesn't, and now it seems to be working quite well, once I've navigated the undocumented quirks. Fingers crossed!

    Ciarán