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