Attached is a sample code that enables SAI module as I2S (stereo) slave in synchronous mode (TX and RX share BCLK and Frame synch). Uses DMA for transfer and the TWR-AUDIO_SGTL Rev B1 (board with 24.576Mhz oscillator).
Adding a couple of more data:
- Configures the SGTL5000 as I2S master and a 48Khz audio
- The application takes the audio from the codec line in and sends it to line out and headphone outputs.
- Works on a TWR-K60D100M
- Enables the codec 5-band graphic equalizer when SW1 is pressed
- Compiled and tested on CW10.2 with latest patches.
Carlos,
Upon further investigation, I noticed that the output signals are indeed there; they are just very low amplitude (~40mVp-p), and they seem to be on opposite channels between the Line Out and Headphones. I'll play with adjusting the output volume and see if that makes things better.
-Brian
Brian,
As you noticed, the channels on line-in and line-out are inverted, this is a known issue and documented on the HW errata:
The samples are not modified by the firmware, hence the output should be the same amplitude, but this would depend on the load as well and the volume on the codec headphone amplifier plus the DAC volume configure. You may want to change those parameters to get the desired output.
Carlos,
No; the attenuation circuit resides inside an 1/8" audio jack adapter that I used in this case. I believe the TWR-AUDIO-SGTL audio circuit is just fine -- it does not appear to induce much/any attenuation at all.
Thanks again for all your help!
-Brian
Thank you, Carlos!
I tried the code and had to modify the project a little to get it to run with the embedded OSJTAG configuration. The project seems to indeed output a 2kHz sinusoid on the Line Out port, however I noticed that there are small glitches in the output signal. I also can't tell if, after pressing SW1, the Line In to Line Out feature is working or not. Any hints as to how I might correct the sine output glitches (it seems like something in the buffer management might be off by one or something)? Also, should I expect the Line In to Line Out mechanism to work?
Thank you for any help you can provide!
-Brian
I don't exactly have a demo project, what I do have is my working audio-interface MQX task that processes I2S (as a slave)in TDM mode using DMA. It keeps per-channel ping-pong buffers that the DMA has de-interleaved, and for each calls an external function to 'do something' for each TX/RX channel.
/******************************************************************************
*
* FILE NAME: Audio.c
*
* DESCRIPTION: Audio interface
*
#include "Os.h"
#include "Audio.h"
#include "Codec.h"
/******************************************************************************
* Module defines
*****************************************************************************/
#define TX_DMA_Chan 5
#define RX_DMA_Chan 4
#define TS_Mask 0xFFFFFCE7UL
#define AUDIO_NUM_CHANNELS 4
#define AUDIO_BYTES_PER_SAMPLE 2 // 16-bit samples
#define AUDIO_SAMPLES_PER_BLOCK 160 // 160 samples in each block (5ms @ 32 kHz)
/******************************************************************************
* Module type definitions
*****************************************************************************/
//The following structure matchs the TDM stream word order, and is sized to implement
// full double-buffering (ping-pong) of each stream. On each DMA interrupt, a counter
// is set to 0 or 1 to indicate the starting point of the curent working set.
typedef struct
{
s16_t samples[AUDIO_SAMPLES_PER_BLOCK];
} audioBlock_t;
/******************************************************************************
* Module variables
*****************************************************************************/
audioCallbackRxTxBlockFunc_f rx_tx_block_callback = NULL; // Callback function for rx/tx blocks
static volatile audioBlock_t RxDMA_Buf[AUDIO_NUM_CHANNELS][2], TxDMA_Buf[AUDIO_NUM_CHANNELS][2];
static u16_t SampleSetOffset;
/******************************************************************************
* Module function prototypes
*****************************************************************************/
static void_t SSI1Init();
/******************************************************************************
* Public functions
*****************************************************************************/
/******************************************************************************
* NAME: Audio_Task
*
* DESCRIPTION:
* Audio processing task. Set up the I2C, thence the CoDec, thence SSI in
* TDM and DMA to buffer Audio I/O.
*
* PARAMETERS:
* param - not used.
*
* RETURN: none.
*****************************************************************************/
void_t Audio_Task( u32_t param )
{
// Configure CoDecs
Codec_Init();
// Now for the Kinetis IIS port & DMA
SSI1Init();
for(;//ever
{
uint16_t i;
_task_block();
// GPIOA_PCOR |= 1<<10; //debug out to 0
if( NULL != rx_tx_block_callback )
{
for( i = 0; i < AUDIO_NUM_CHANNELS; i++ )
{
rx_tx_block_callback( i, (const audioBlock_t*) &RxDMA_Buf[i][SampleSetOffset], (audioBlock_t*) &TxDMA_Buf[i][SampleSetOffset] );
}
}
// GPIOA_PSOR |= 1<<10; //debug out back to 1
}
}
/******************************************************************************
* Module functions
*****************************************************************************/
/*DMA ISR*/
static pointer Pblock_td_ptr;
void BlockDMA( pointer user_isr_ptr )
{
// GPIOA_PCOR |= 1<<10; //debug out to 0
DMA_CINT = DMA_CINT_CINT(RX_DMA_Chan); //Clear this interrupt request
SampleSetOffset = 0; //based on current DMA iteration
if( (DMA_CITER_ELINKNO(RX_DMA_Chan) & DMA_CITER_ELINKNO_CITER_MASK ) > (AUDIO_SAMPLES_PER_BLOCK + 2))
SampleSetOffset = 1;
_task_ready(Pblock_td_ptr);
// GPIOA_PSOR |= 1<<10; //debug out back to 1
}
/******************************************************************************
* NAME: SSI1Init
*
* DESCRIPTION:
* Initializes the device. Allocates memory for the device data
* structure, allocates interrupt vectors and sets interrupt
* priority, sets pin routing, sets timing, etc.
* This method can be called only once. Before the second call
* of Init() the Deinit() must be called first.
*
* PARAMETERS:
* param - not used.
*
* RETURN: none.
*****************************************************************************/
static void_t SSI1Init( void_t )
{
uint32_t temp;
/* SIM_SCGC6: I2S=1 & DMAMUX=1*/ //Turn on module clocks to bring registers into address space
SIM_SCGC6 |= (SIM_SCGC6_I2S_MASK | SIM_SCGC6_DMAMUX_MASK) ;
SIM_SCGC7 |= (SIM_SCGC7_DMA_MASK) ;
/* Interrupt vector(s) priority setting */ //No interrupts for I2S, only DMA RX
/* NVICIP79: PRI79=0x80 */
// NVICIP79 = (u8_t)0x80U;
/* NVICISER2: SETENA|=0x8000 */
// NVICISER2 |= (u32_t)0x8000UL;
PORTE_PCR10 = PORT_PCR_MUX(4); //TXD
PORTE_PCR7 = PORT_PCR_MUX(4); //RXD
PORTE_PCR12 = PORT_PCR_MUX(4); //TX_BCLK
PORTE_PCR11 = PORT_PCR_MUX(4); //TX_FS
// PORTE_PCR9 = PORT_PCR_MUX(4); //RX_BCLK
// PORTE_PCR8 = PORT_PCR_MUX(4); //RX_FS
// PORTE_PCR6 = PORT_PCR_MUX(4) | PORT_PCR_DSE_MASK ;//MCLK out as I2S MCLK
I2S0_CR = // Set Configuration register
/* I2S_CR_SYSCLKEN_MASK | */I2S_CR_SYNCTXFS_MASK | I2S_CR_RFRCLKDIS_MASK | I2S_CR_TFRCLKDIS_MASK
| I2S_CR_I2SMODE(0)/*1)*/ | I2S_CR_SYN_MASK | I2S_CR_NET_MASK | I2S_CR_SSIEN_MASK;
I2S0_TMSK = TS_Mask; /* Set Transmit time slot mask register */
I2S0_RMSK = TS_Mask; /* Set Receive time slot mask register */
I2S0_TCR = // Set Transmit configuration register
/* I2S_TCR_TFDIR_MASK | I2S_TCR_TXDIR_MASK |*/ I2S_TCR_TXBIT0_MASK | I2S_TCR_TFEN0_MASK | I2S_TCR_TFSL_MASK | I2S_TCR_TEFS_MASK;
I2S0_TCCR = // Set Transmit Clock control register
I2S_TCCR_WL(7) | I2S_TCCR_DC(16-1);
I2S0_RCR = // Set Receive configuration register
/*I2S_RCR_RFDIR_MASK | I2S_RCR_RXDIR_MASK |*/ I2S_RCR_RXBIT0_MASK | I2S_RCR_RFEN0_MASK | I2S_RCR_RFSL_MASK | I2S_RCR_REFS_MASK;
I2S0_RCCR = // Set Receive Clock control register, actually ignored in SYN mode
I2S_RCCR_WL(7) | I2S_RCCR_DC(16-1);
I2S0_FCSR = // Set FIFO Control/Status register
I2S_FCSR_RFWM1(AUDIO_NUM_CHANNELS) | I2S_FCSR_TFWM1(AUDIO_NUM_CHANNELS) | I2S_FCSR_RFWM0(AUDIO_NUM_CHANNELS) | I2S_FCSR_TFWM0(AUDIO_NUM_CHANNELS);
I2S0_IER = // Set Interrupt enable register: DMA only!!
I2S_IER_RDMAE_MASK | I2S_IER_TDMAE_MASK;// | I2S_IER_ROE0EN_MASK | I2S_IER_TUE0EN_MASK ;
// I2S_ACNT
I2S0_ACNT = 0; /* Clear AC97 control register */
// OSC_CR |= 0X80;
temp = _task_get_id_from_name("Audio");
Pblock_td_ptr = _task_get_td(temp);
_int_install_isr( (INT_DMA0+RX_DMA_Chan), BlockDMA, 0);
_cortex_int_init((INT_DMA0+RX_DMA_Chan), 4, TRUE);
DMA_CR = DMA_CR_EMLM_MASK | DMA_CR_ERCA_MASK | DMA_CR_EDBG_MASK; //Round robin DMA
DMA_SADDR(TX_DMA_Chan) = DMA_SADDR_SADDR(&TxDMA_Buf);
DMA_SOFF(TX_DMA_Chan) = DMA_SOFF_SOFF(2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE); //Jump thru buffers on each transfer
DMA_ATTR(TX_DMA_Chan) = DMA_ATTR_SMOD(0) | DMA_ATTR_SSIZE(1) | DMA_ATTR_DMOD(0) | DMA_ATTR_DSIZE(1); //All straight 16-bits
//After each minor loop, source moves back to the first buffer, next word
DMA_NBYTES_MLOFFYES(TX_DMA_Chan)= DMA_NBYTES_MLOFFYES_SMLOE_MASK
| DMA_NBYTES_MLOFFYES_MLOFF(-(AUDIO_NUM_CHANNELS*2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE)+AUDIO_BYTES_PER_SAMPLE)
| DMA_NBYTES_MLOFFYES_NBYTES(AUDIO_NUM_CHANNELS*AUDIO_BYTES_PER_SAMPLE);
DMA_SLAST(TX_DMA_Chan) = DMA_SLAST_SLAST(-(2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE
+ AUDIO_NUM_CHANNELS*2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE-AUDIO_BYTES_PER_SAMPLE) ); //After Major loop, back to the beginning of each channel buffer
DMA_DADDR(TX_DMA_Chan) = DMA_DADDR_DADDR(&I2S0_TX0); //Send to I2S transmit FIFO
DMA_DOFF(TX_DMA_Chan) = DMA_DOFF_DOFF(0); //Constant address
DMA_CITER_ELINKNO(TX_DMA_Chan) = DMA_CITER_ELINKNO_CITER(2*AUDIO_SAMPLES_PER_BLOCK);
DMA_DLAST_SGA(TX_DMA_Chan) = DMA_DLAST_SGA_DLASTSGA(0);
DMA_CSR(TX_DMA_Chan) = DMA_CSR_BWC(3); //No TX interrupts, RX req comes last so do all on RX ints
DMA_BITER_ELINKNO(TX_DMA_Chan) = DMA_BITER_ELINKNO_BITER(2*AUDIO_SAMPLES_PER_BLOCK);
DMA_SADDR(RX_DMA_Chan) = DMA_SADDR_SADDR(&I2S0_RX0); //pull from I2S receive FIFO
DMA_SOFF(RX_DMA_Chan) = DMA_SOFF_SOFF(0);
DMA_ATTR(RX_DMA_Chan) = DMA_ATTR_SMOD(0) | DMA_ATTR_SSIZE(1) | DMA_ATTR_DMOD(0) | DMA_ATTR_DSIZE(1); //All straight 16-bits
//After each minor loop, dest moves back to the first buffer, next word
DMA_NBYTES_MLOFFYES(RX_DMA_Chan)= DMA_NBYTES_MLOFFYES_DMLOE_MASK
| DMA_NBYTES_MLOFFYES_MLOFF(-(AUDIO_NUM_CHANNELS*2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE)+AUDIO_BYTES_PER_SAMPLE)
| DMA_NBYTES_MLOFFYES_NBYTES(AUDIO_NUM_CHANNELS*AUDIO_BYTES_PER_SAMPLE);
DMA_SLAST(RX_DMA_Chan) = DMA_SLAST_SLAST(0);
DMA_DADDR(RX_DMA_Chan) = DMA_DADDR_DADDR(&RxDMA_Buf);
DMA_DOFF(RX_DMA_Chan) = DMA_DOFF_DOFF(2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE); //Jump thru buffers on each transfer
DMA_CITER_ELINKNO(RX_DMA_Chan) = DMA_CITER_ELINKNO_CITER(2*AUDIO_SAMPLES_PER_BLOCK);
DMA_DLAST_SGA(RX_DMA_Chan) = DMA_DLAST_SGA_DLASTSGA(-(2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE
+ AUDIO_NUM_CHANNELS*2*AUDIO_SAMPLES_PER_BLOCK*AUDIO_BYTES_PER_SAMPLE-AUDIO_BYTES_PER_SAMPLE) ); //After Major loop, back to the beginning of each channel buffer
DMA_CSR(RX_DMA_Chan) = DMA_CSR_BWC(3) | DMA_CSR_INTHALF_MASK | DMA_CSR_INTMAJOR_MASK;
DMA_BITER_ELINKNO(RX_DMA_Chan) = DMA_BITER_ELINKNO_BITER(2*AUDIO_SAMPLES_PER_BLOCK);
DMAMUX_CHCFG(TX_DMA_Chan) = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(15);
DMAMUX_CHCFG(RX_DMA_Chan) = DMAMUX_CHCFG_ENBL_MASK | DMAMUX_CHCFG_SOURCE(14);
DMA_SERQ = DMA_SERQ_SERQ(RX_DMA_Chan); //All set to go, enable DMA channel(s)!
DMA_SERQ = DMA_SERQ_SERQ(TX_DMA_Chan);
I2S0_CR |= I2S_CR_RE_MASK | I2S_CR_TE_MASK; /* Enable the IIS device */
}
/******************************************************************************
* End of file
*****************************************************************************/