K20 i2s/SAI via DMA "clicking" problem

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

K20 i2s/SAI via DMA "clicking" problem

Jump to solution
1,016 Views
jeremygordon
Contributor II

Hi,

I am working on a custom K20 based board (MK20DX256) trying to output a sin(x) wave via i2s/sai. My custom board is pretty spartan, 16mhz crystal, the K20 and a codec hooked up via i2c and i2s. I'm using a SEGGER JLink to flash the K20. I'm using KDS 3.0, MQX Lite and processor expert, however I'm using raw PDD calls rather than components for the i2s and DMA to better understand the code I'm writing.

My problem is the i2s output has "clicks" in it which I can confirm by using a logic analyzer and an oscilloscope to observe the i2s bus coming from the K20 and the audio coming out from the codec.

You can clearly see the clicks in the audio waveform coming out of the codec (screen shot attached), but since that waveform is output from a codec connected to the i2s bus, it does not prove the problem is in my K20 code; it could be in the codec. To prove it was my K20 code that was the source of the problem, I used a logic analyzer to observe the i2s bus data itself (rather than the analog output), and then exported that data as a CSV file which I loaded into Google Sheets and graphed (screenshot attached). The same discontinuities are present in the graphed i2s bus data.

For good measure, I converted the CSV data into a .WAV audio file (audio file attached) and it sounds identical to the output from the codec, which is to say, has clicks in it. To be sure that the sin(x) method I'm using works, I wrote a small program to output nextsinevalue()to a .WAV file and it sounds as expected (i.e. no clicks).

I don't have any other interrupts or DMA channels going that I know of; I've looked through the MQX Lite code and don't see anything naughty happening behind the scenes.

The problem manifests regardless of it there is a debugger, JLink or logic analyzer connected to the board. After pouring through my code and comparing it to processor expert generated components, some semi-related sample code in the SDK and of course the various PDF application notes around i2s, I'm stuck. Any ideas from i2s and/or DMA experienced folk?

Screenshots, audio and code below. The interesting bits are in Task1_task(), configure_i2s() and configure_dma().The remaining code is mostly to configure the external codec over i2c.

Thanks!

Jeremy

Screen Shot 2015-08-02 at 5.14.51 PM.png

Screen Shot 2015-08-02 at 5.15.17 PM.png

Screen Shot 2015-08-02 at 5.18.56 PM.png

/* ###################################################################

**     Filename    : mqx_tasks.c

**     Project     : breakout

**     Processor   : MK20DX256VLH7

**     Component   : Events

**     Version     : Driver 01.00

**     Compiler    : GNU C Compiler

**     Date/Time   : 2015-07-21, 00:07, # CodeGen: 3

**     Abstract    :

**         This is user's event module.

**         Put your event handler code here.

**     Contents    :

**         Task1_task - void Task1_task(uint32_t task_init_data);

**

** ###################################################################*/

/*!

** @file mqx_tasks.c

** @version 01.00

** @brief

**         This is user's event module.

**         Put your event handler code here.

*/        

/*!

**  @addtogroup mqx_tasks_module mqx_tasks module documentation

**  @{

*/        

/* MODULE mqx_tasks */

#include "Cpu.h"

#include "Events.h"

#include "mqx_tasks.h"

#include "SEGGER_RTT_Conf.h"

#include "SEGGER_RTT.h"

#include "SIM_PDD.h"

#include "PORT_PDD.h"

#include "SAI_PDD.h"

#include "DMA_PDD.h"

#include "DMAMUX_PDD.h"

#include "lwevent.h"

#ifdef __cplusplus

extern "C" {

#endif

/* User includes (#include below this line is not maintained by Processor Expert) */

#include <math.h>

LWEVENT_STRUCT sent, recv;

_mqx_uint get_register_blocking(uint8_t reg, uint8_t* bytes, uint32_t len) {

  uint8_t sbytes[1] = { reg };

  LDD_TError error = CI2C1_MasterSendBlock(CI2C1_DeviceData, sbytes, 1, LDD_I2C_NO_SEND_STOP);

  if( error ) {

  return error;

  }

  // wait for the i2c bus for a max of 1/10th of a second

  MQX_TICK_STRUCT timeout;

  _time_init_ticks(&timeout, _time_get_ticks_per_sec() >> 4);

  _mqx_uint err = _lwevent_wait_for(&sent, 1, TRUE, &timeout);

  if( err != MQX_OK ) {

  return err;

  }

  error = CI2C1_MasterReceiveBlock(CI2C1_DeviceData, bytes, len, LDD_I2C_SEND_STOP);

  if( error ) {

  return error;

  }

  return _lwevent_wait_for(&recv, 1, TRUE, &timeout);

}

_mqx_uint set_register_blocking(uint8_t reg, uint8_t value, LDD_I2C_TSendStop SendStop) {

  uint8_t bytes[2] = { reg, value };

  LDD_TError error = CI2C1_MasterSendBlock(CI2C1_DeviceData, bytes, 2, SendStop);

  if( error ) {

  return error;

  }

  // wait for the i2c bus for a max of 1/10th of a second

  MQX_TICK_STRUCT timeout;

  _time_init_ticks(&timeout, _time_get_ticks_per_sec() >> 4);

  return _lwevent_wait_for(&sent, 1, TRUE, &timeout);

}

_mqx_uint set_register_blocking2(uint8_t reg, uint8_t value, unsigned char value2, LDD_I2C_TSendStop SendStop) {

  uint8_t bytes[3] = { reg, value, value2 };

  LDD_TError error = CI2C1_MasterSendBlock(CI2C1_DeviceData, bytes, 3, SendStop);

  if( error ) {

  return error;

  }

  // wait for the i2c bus for a max of 1/10th of a second

  MQX_TICK_STRUCT timeout;

  _time_init_ticks(&timeout, _time_get_ticks_per_sec() >> 1);

  return _lwevent_wait_for(&sent, 1, TRUE, &timeout);

}

_mqx_int get_status_blocking(uint8_t reg, uint8_t len) {

  uint8_t status[6];

  _mqx_int error = get_register_blocking(reg, status, len);

  if( error != ERR_OK ) {

  SEGGER_RTT_printf(0, "status(0x%x): error getting status (0x%x)\n", reg, error);

  } else {

  SEGGER_RTT_printf(0, "status(0x%x): ", reg);

  for( uint8_t i = 0; i < len; i++ ) {

  SEGGER_RTT_printf(0, "%02x", status[i]);

  }

  SEGGER_RTT_printf(0, "\n");

  }

  return error;

}

////////////////////////////////////////////////

// https://forum.pjrc.com/threads/15748-Teensy3-I2S-with-DMA?p=33044&viewfull=1#post33044

int16_t audf, audx, audy, audd,b,inx,iny;

int32_t nnn=0;

float PI = 3.14159265358979323846;

void initsinevalue()

{

  audf = 35 + (30 % 48);                                // midi note number

  float f = (440.0 / 32) * pow(2, ((float)audf - 9) / 12);  // Hz.  For realz, use a lookup table.

  audd = 2.0 * sinf(PI*f/44100) * 32767;                     // delta (q15_t)

  audx = 0;

  audy = 0.9 * 32767;                                       // start somewhere near full-scale

}

int16_t nextsinevalue()

{

  nnn++;

//  if(nnn>44100) {nnn=0;initsinevalue();};                                // reset every second

//  if(nnn>22050){nnn=0;audx=audx<<1;if(audx==0)audx=1;b=audx;};return;  // marching blip

// audx+=4;if(nnn>512){nnn=0;audx=-2048;};b=audx;return;                // stair

//  b = 0xACCF0010; audx=0xACCF; return;                                 // const pattern

  audx+=((audd*audy)>>15)&0xFFFFu; audy-=((audd*audx)>>15)&0xFFFFu;      // sinewaves http://cabezal.com/misc/minsky-circles.html

  return audx;

}

////////////////////////////////////////////////

void configure_i2s(void) {

  SIM_PDD_SetClockGate(SIM_BASE_PTR, SIM_PDD_CLOCK_GATE_PORTC, PDD_ENABLE);

  // configure pins for i2s

  PORT_PDD_SetPinMuxControl(PORTC_BASE_PTR, 1, PORT_PDD_MUX_CONTROL_ALT6);

  PORT_PDD_SetPinMuxControl(PORTC_BASE_PTR, 3, PORT_PDD_MUX_CONTROL_ALT6);

  PORT_PDD_SetPinMuxControl(PORTC_BASE_PTR, 2, PORT_PDD_MUX_CONTROL_ALT6);

  PORT_PDD_SetPinMuxControl(PORTC_BASE_PTR, 6, PORT_PDD_MUX_CONTROL_ALT6);

  // power up the i2s peripheral

  SIM_PDD_SetClockGate(SIM_BASE_PTR, SIM_PDD_CLOCK_GATE_I2S0, PDD_ENABLE);

  // juice the interrupt priority level of the tx interrupt

  NVICIP35 = NVIC_IP_PRI35(0x00);

  // hook the is2 interrupt

  _int_install_isr(INT_I2S0_Tx, I2S0_isr, NULL);

  // enable interrupts for the i2s peripheral

  NVICISER1 |= NVIC_ISER_SETENA(0x08);

  // disable the mclk divider so we can configure it

  I2S_PDD_EnableMclkDivider(I2S0_BASE_PTR, PDD_DISABLE);

  // busy wait for the clock divider to become disabled

  while(I2S_PDD_GetMclkDividerState(I2S0_BASE_PTR) != PDD_DISABLE) {

  }

  // configure the i2s master clock (16MHz * 12 / 17 = 11.29MHz)

  I2S_PDD_SetMclkClockSource(I2S0_BASE_PTR, I2S_PDD_ER_OSC0);

  I2S_PDD_SetMclkFraction(I2S0_BASE_PTR, 12 - 1);

  I2S_PDD_SetMclkDivider(I2S0_BASE_PTR, 17 - 1);

  // enable the mclk divider now that we've configured it

  I2S_PDD_EnableMclkDivider(I2S0_BASE_PTR, PDD_ENABLE);

  // disable the transmitter so we can configure it

  I2S_PDD_EnableTxDevice(I2S0_BASE_PTR, PDD_DISABLE);

  // busy wait for the device to become disabled before we start configuring the transmitter

  while(I2S_PDD_GetTxDeviceState(I2S0_BASE_PTR) != PDD_DISABLE) {

  }

  // request a callback when there is only three words left in the FIFO

  I2S_PDD_SetTxFifoWatermark(I2S0_BASE_PTR, 3);

  I2S_PDD_SetTxSynchronousMode(I2S0_BASE_PTR, I2S_PDD_TX_ASYNCHRONOUS_MODE);

  // configure the bit clock to be computed from the master clock, and to be output to the codec

  I2S_PDD_SetTxBitClockSource(I2S0_BASE_PTR, I2S_PDD_MASTER_CLOCK_1_SOURCE);

  I2S_PDD_SetTxBitClockPolarity(I2S0_BASE_PTR, I2S_PDD_BIT_CLOCK_ACTIVE_LOW);

  I2S_PDD_SetTxBitClockDirection(I2S0_BASE_PTR, I2S_PDD_BIT_CLOCK_OUTPUT);

  // bclk (11.29MHz / 8 = 1.411MHz because 1.411MHz / 16-bits / stereo = 44.1kHz)

  I2S_PDD_SetTxBitClockDivider(I2S0_BASE_PTR, 3);

  // we are only going to use one transmit channel (and it's corresponding FIFO)

  I2S_PDD_EnableTxDataChannel(I2S0_BASE_PTR, I2S_PDD_DATA_CHANNEL_0);

  // first word in the frame sets the start of word flag

  I2S_PDD_SetTxFifoWordFlag(I2S0_BASE_PTR, 0);

  // two words per frame (16-bit left + 16-bit right = 32-bit frame)

  I2S_PDD_SetTxFrameSize(I2S0_BASE_PTR, 2 - 1);

  // frame sync is 16 bit clocks long (one half of the frame)

  I2S_PDD_SetTxSyncWidth(I2S0_BASE_PTR, 16 - 1);

  // set up some i2s options to be "standard"

  I2S_PDD_SetTxShiftDirection(I2S0_BASE_PTR, 0x10); //tThe I2S_PDD_MSB_FIRST macro is wrong

  I2S_PDD_SetTxFrameSyncEarly(I2S0_BASE_PTR, I2S_PDD_ONE_BIT_BEFORE_DATA);

  I2S_PDD_SetTxFrameSyncPolarity(I2S0_BASE_PTR, I2S_PDD_FRAME_SYNC_ACTIVE_LOW);

  // we're outputting the frame sync to the codec, not sync'ing to an external clock

  I2S_PDD_SetTxFrameSyncDirection(I2S0_BASE_PTR, I2S_PDD_FRAME_SYNC_OUTPUT);

  // the not first word in the frame (i.e. second) is 16-bits long (i.e. the right channel)

  I2S_PDD_SetTxWordNWidth(I2S0_BASE_PTR, 16 - 1);

  // the first word in the frame is 16-bits long (i.e. the left channel)

  I2S_PDD_SetTxWord0Width(I2S0_BASE_PTR, 16 - 1);

  // the low 16-bits of each 32-bit FIFO entry is where our data is stored

  I2S_PDD_SetTxFirstBitShifted(I2S0_BASE_PTR, 15);

  // we're not masking any words in the frame (i.e. we're playing stereo, not mono)

  I2S_PDD_WriteTxTimeSlotMaskReg(I2S0_BASE_PTR, 0);

  // configure what happens in stop and debug mode

  I2S_PDD_EnableTxInStopMode(I2S0_BASE_PTR, PDD_ENABLE);

  I2S_PDD_EnableTxInDebugMode(I2S0_BASE_PTR, PDD_ENABLE);

}

static uint32_t i2s_tx_buffer[128 / sizeof(uint32_t)];

void DMA0_isr(pointer data_ptr) {

  // there shouldn't be any errors in this interrupt, but just in case...

  uint32_t err = DMA_PDD_ReadErrorStatusReg(DMA_BASE_PTR);

  if( err != 0 ) {

  SEGGER_RTT_printf(0, "DMA err 0x%x\n", err);

  // let's go ahead and clear errors for all channels, just to be safe

  DMA_PDD_ClearErrorFlags(DMA_BASE_PTR, DMA_PDD_ALL_CHANNELS, 0);

  }

  // clear the interrupt, but just for channel 0

  DMA_PDD_ClearInterruptFlags(DMA_BASE_PTR, 0, 0);

  // figure out why this interrupt was triggered

  if( DMA_PDD_GetDoneFlag(DMA_BASE_PTR, 0) ) {

  for(int i = sizeof(i2s_tx_buffer) / sizeof(short) / 2 / 2; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2; i++ ) {

  ((int16_t*)i2s_tx_buffer)[i * 2 + 0] = ((int16_t*)i2s_tx_buffer)[i * 2 + 1] = nextsinevalue();

  }

  // hardware clears the done flag, we don't need to

  //DMA_PDD_ClearDoneFlag(DMA_BASE_PTR, 0);

  } else {

  for(int i = 0; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2 / 2; i++ ) {

  ((int16_t*)i2s_tx_buffer)[i * 2 + 0] = ((int16_t*)i2s_tx_buffer)[i * 2 + 1] = nextsinevalue();

  }

  }

}

void DMA0_Error_isr(pointer data_ptr) {

  uint32_t err = DMA_PDD_ReadErrorStatusReg(DMA_BASE_PTR);

  // clear the error

  if( err != 0 ) {

  SEGGER_RTT_printf(0, "DMA error 0x%x\n", err);

  DMA_PDD_ClearChannelErrorFlag(DMA_BASE_PTR, 0);

  }

}

void configure_dma(void) {

  // set the DMA complete interrupt priority to 8

  NVICIP0 = NVIC_IP_PRI0(0x80);

  // attach the interrupt handler

  _int_install_isr(INT_DMA0, DMA0_isr, NULL);

  // enable the DMA interrupt

  NVICISER0 |= NVIC_ISER_SETENA(0x01);

  // set the DMA error interrupt priority to 8

  NVICIP16 = NVIC_IP_PRI16(0x80);

  // attach the interrupt handler

  _int_install_isr(INT_DMA_Error, DMA0_Error_isr, NULL);

  // enable the DMA error interrupt

  NVICISER0 |= NVIC_ISER_SETENA(0x00010000);

  // power up the DMA peripheral

  SIM_PDD_SetClockGate(SIM_BASE_PTR, SIM_PDD_CLOCK_GATE_DMA, PDD_ENABLE);

  SIM_PDD_SetClockGate(SIM_BASE_PTR, SIM_PDD_CLOCK_GATE_DMA_MULTIPLEXOR, PDD_ENABLE);

  // disable all requests

  DMA_PDD_ClearRequestEnable(DMA_BASE_PTR, DMA_PDD_ALL_CHANNELS, 0);

  DMA_PDD_ClearErrorInterruptEnable(DMA_BASE_PTR, DMA_PDD_ALL_CHANNELS, 0);

  DMA_PDD_CancelTransfer(DMA_BASE_PTR);

  DMA_PDD_ClearInterruptFlags(DMA_BASE_PTR, DMA_PDD_ALL_CHANNELS, 0);

  // initialize the DMA basics

  DMA_PDD_EnableMinorLoopMapping(DMA_BASE_PTR, PDD_ENABLE);

  DMA_PDD_EnableDebug(DMA_BASE_PTR, PDD_ENABLE);

  // reset all 16 DMA channels

  for(uint32_t ch = 0; ch < 16; ch++ ) {

  DMA_PDD_WriteClearEnableRequestReg(DMA_BASE_PTR, ch);

  DMA_PDD_WriteClearErrorReg(DMA_BASE_PTR, ch);

  DMA_PDD_WriteEnableRegs(DMA_BASE_PTR, ch);

  DMA_PDD_WriteClearInterruptRequestReg(DMA_BASE_PTR, ch);

  for( uint32_t i = 0; i < 8; i++ ) {

  ((uint32_t*)0x40009000)[ch * 8 + i] = 0;

  }

  }

  // set up the DMA source

  DMA_PDD_SetSourceAddress(DMA_BASE_PTR, 0, i2s_tx_buffer);

  DMA_PDD_SetSourceAddressOffset(DMA_BASE_PTR, 0, 2);

  DMA_PDD_SetSourceDataTransferSize(DMA_BASE_PTR, 0, 1);

  DMA_PDD_SetLastSourceAddressAdjustment(DMA_BASE_PTR, 0, -sizeof(i2s_tx_buffer));

  // set up the DMA destination

  DMA_PDD_SetDestinationAddress(DMA_BASE_PTR, 0, &I2S0_TDR0);

  DMA_PDD_SetDestinationAddressOffset(DMA_BASE_PTR, 0, 0);

  DMA_PDD_SetDestinationDataTransferSize(DMA_BASE_PTR, 0, 1);

  // set up the loop offsets

  DMA_PDD_SetByteCount32(DMA_BASE_PTR, 0, 2);

  DMA_PDD_WriteBeginningMajorLoopCountReg(DMA_BASE_PTR, 0, sizeof(i2s_tx_buffer) / 2);

  DMA_PDD_WriteCurrentMajorLoopCountReg(DMA_BASE_PTR, 0, sizeof(i2s_tx_buffer) / 2);

  DMA_PDD_WriteLastDestinationAddressAdjustmentReg(DMA_BASE_PTR, 0, 0);

  // enable the half and complete transfer interrupts

  DMA_PDD_EnableTransferHalfInterrupt(DMA_BASE_PTR, 0, PDD_ENABLE);

  DMA_PDD_EnableTransferCompleteInterrupt(DMA_BASE_PTR, 0, PDD_ENABLE);

  DMA_PDD_EnableErrorInterrupt(DMA_BASE_PTR, 0);

  DMA_PDD_EnableErrorInterrupt(DMA_BASE_PTR, 3);

  // force the DMA to stall

  //DMA_PDD_SetStallTime(DMA_BASE_PTR, 0, 3);

  // set the I2S peripheral as the source of the mux

  DMAMUX_PDD_WriteChannelConfigurationReg(DMAMUX_BASE_PTR, 0, DMAMUX_PDD_CHANNEL_SOURCE_15);

  DMAMUX_PDD_EnableChannel(DMAMUX_BASE_PTR, 0, PDD_ENABLE);

  // enable the DMA

  DMA_PDD_WriteSetEnableRequestReg(DMA_BASE_PTR, 0);

}

/*

** ===================================================================

**     Event       :  Task1_task (module mqx_tasks)

**

**     Component   :  Task1 [MQXLite_task]

**     Description :

**         MQX task routine. The routine is generated into mqx_tasks.c

**         file.

**     Parameters  :

**         NAME            - DESCRIPTION

**         task_init_data  -

**     Returns     : Nothing

** ===================================================================

*/

void Task1_task(uint32_t task_init_data)

{

  for( int i = 0; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2; i++ ) {

  ((unsigned short*)i2s_tx_buffer)[i * 2 + 0] = 0;

  ((unsigned short*)i2s_tx_buffer)[i * 2 + 1] = 0;

  }

  // initialize the i2c sent and recv event

  _lwevent_create(&sent, LWEVENT_AUTO_CLEAR);

  _lwevent_clear(&sent, 1);

  _lwevent_create(&recv, LWEVENT_AUTO_CLEAR);

  _lwevent_clear(&recv, 1);

  // hardware reset the codec

  Bit1_ClrVal(Bit1_DeviceData);

  _time_delay_ticks(_time_get_ticks_per_sec() / 2);

  Bit1_SetVal(Bit1_DeviceData);

  _time_delay_ticks(_time_get_ticks_per_sec() / 2);

  // configure the i2c address to 0x30

  CI2C1_SelectSlaveDevice(CI2C1_DeviceData, LDD_I2C_ADDRTYPE_7BITS, 0x30 >> 1);

  /// Initialize the codec

  // Select Page 0

  set_register_blocking(0x0, 0x0, LDD_I2C_SEND_STOP);

  // Initialize the device through software reset

  set_register_blocking(0x1, 0x1, LDD_I2C_SEND_STOP);

  get_status_blocking(36, 3);

  get_status_blocking(42, 6);

  // MCLK = 11.2896 MHz, BLCK = 1.4112 MHz, WCLK = 44.1 kHz

  // NDAC = 1, MDAC = 8, dividers powered on

  set_register_blocking2(0xb, 0x81, 0x88, LDD_I2C_SEND_STOP);

  /// Configure codec's internal LDOs to provide power

  // Select Page 1

  set_register_blocking(0x0, 0x1, LDD_I2C_SEND_STOP);

  // Power up AVDD LDO

  set_register_blocking(0x2, 0x9, LDD_I2C_SEND_STOP);

  // Disable weak AVDD

  set_register_blocking(0x1, 0x8, LDD_I2C_SEND_STOP);

  // Enable Master Analog Power Control, Power up AVDD LDO

  set_register_blocking(0x2, 0x1, LDD_I2C_SEND_STOP);

  // Set the REF charging time to 40ms

  set_register_blocking(0x7b, 0x1, LDD_I2C_SEND_STOP);

  // De-pop: 5 time constants, 6k resistance

  set_register_blocking(0x14, 0x25, LDD_I2C_SEND_STOP);

  // Route LDAC/RDAC to HPL/HPR

  set_register_blocking2(0xc, 0x8, 0x8, LDD_I2C_SEND_STOP);

  // Power up HPL/HPR drivers

  set_register_blocking(0x9, 0x30, LDD_I2C_SEND_STOP);

  // Unmute HPL/HPR driver, 0dB Gain

  set_register_blocking2(0x10, 0x0, 0x0, LDD_I2C_SEND_STOP);

  // wait for 2.5s for things to power up with no "pop"

  _time_delay_ticks(_time_get_ticks_per_sec() * 2.5);

  // Select Page 0

  set_register_blocking(0x0, 0x0, LDD_I2C_SEND_STOP);

  // DAC => 0dB

  set_register_blocking2(0x41, 0x00, 0x00, LDD_I2C_SEND_STOP);

  // Power up LDAC/RDAC

  set_register_blocking(0x3f, 0xd6, LDD_I2C_SEND_STOP);

  // Unmute LDAC/RDAC

  set_register_blocking(0x40, 0x00, LDD_I2C_SEND_STOP);

  get_status_blocking(36, 3);

  get_status_blocking(42, 6);

  // initialize the sin wave

  initsinevalue();

  // configure the i2s peripheral

  configure_i2s();

  // configure the dma

  configure_dma();

  // enable the i2s transmitter and bit clock

  I2S_PDD_EnableTxBitClock(I2S0_BASE_PTR, PDD_ENABLE);

  // reset the transmit fifos

  I2S_PDD_TxFifoReset(I2S0_BASE_PTR);

  // the transmit fifo should send requests to the DMA MUX

  I2S_PDD_EnableTxFifoRequestDma(I2S0_BASE_PTR, PDD_ENABLE);

  // turn on the transmit device

  I2S_PDD_EnableTxDevice(I2S0_BASE_PTR, PDD_ENABLE);

  int counter = 0;

  while(1) {

  get_status_blocking(36, 3);

  get_status_blocking(42, 6);

  _time_delay_ticks(_time_get_ticks_per_sec());

  counter++;

  }

}

/* END mqx_tasks */

#ifdef __cplusplus

}  /* extern "C" */

#endif

/*!

** @}

*/

/*

** ###################################################################

**

**     This file was created by Processor Expert 10.5 [05.21]

**     for the Freescale Kinetis series of microcontrollers.

**

** ###################################################################

*/

Labels (1)
0 Kudos
1 Solution
639 Views
jeremygordon
Contributor II

Ok, found a solution to my "clicking" problem, but I don't entirely understand why the approach in my original post does not work.

The solution was to change way in which I was checking to see which half of the DMA buffer I should be filling with data, which implies that the "clicking" was incorrectly overwriting the buffer the DMA was trying to work from.

I was using the "done" flag which I was checking using DMA_PDD_GetDoneFlag(DMA_BASE_PTR, 0). As far as I can tell from the docs should tell me if the major loop / transfer is complete or not. It was (mostly) alternating between being set and not set but occasionally getting it wrong (with the glitchy output as a symptom). I also tried checking CITER and BITER values with similar flaky results. Of course I tried inverting my "if" condition just in case I was not understanding the flags / values correctly. Inverting the if condition definitely changed my output, but for the worse (along the lines of what I would have expected it to look / sound like to invert the order of filling the DMA source buffers).

The solution was to instead use the current value of the source address register to determine which half of the buffer I should be filling with data, like so:

  if( DMA_PDD_GetSourceAddress(DMA_BASE_PTR, 0) < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2 ) {

    for(int i = sizeof(i2s_tx_buffer) / sizeof(short) / 2 / 2; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2; i++ ) {

      ((int16_t*)i2s_tx_buffer)[i * 2 + 0] = ((int16_t*)i2s_tx_buffer)[i * 2 + 1] = nextsinevalue();

    }

    // hardware clears the done flag, we don't need to

    //DMA_PDD_ClearDoneFlag(DMA_BASE_PTR, 0);

  } else {

    for(int i = 0; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2 / 2; i++ ) {

      ((int16_t*)i2s_tx_buffer)[i * 2 + 0] = ((int16_t*)i2s_tx_buffer)[i * 2 + 1] = nextsinevalue();

    }

  }

Happy sin(x) wave output now on the i2s bus and out of the analog outputs of the codec.

Again, not sure why the done flag and/or the other methods I tried failed to work reliably, but this alternate method makes sense and works so I'm rolling with it.

Thanks for reading and hope this helps someone else struggling with similar problems.

View solution in original post

0 Kudos
1 Reply
640 Views
jeremygordon
Contributor II

Ok, found a solution to my "clicking" problem, but I don't entirely understand why the approach in my original post does not work.

The solution was to change way in which I was checking to see which half of the DMA buffer I should be filling with data, which implies that the "clicking" was incorrectly overwriting the buffer the DMA was trying to work from.

I was using the "done" flag which I was checking using DMA_PDD_GetDoneFlag(DMA_BASE_PTR, 0). As far as I can tell from the docs should tell me if the major loop / transfer is complete or not. It was (mostly) alternating between being set and not set but occasionally getting it wrong (with the glitchy output as a symptom). I also tried checking CITER and BITER values with similar flaky results. Of course I tried inverting my "if" condition just in case I was not understanding the flags / values correctly. Inverting the if condition definitely changed my output, but for the worse (along the lines of what I would have expected it to look / sound like to invert the order of filling the DMA source buffers).

The solution was to instead use the current value of the source address register to determine which half of the buffer I should be filling with data, like so:

  if( DMA_PDD_GetSourceAddress(DMA_BASE_PTR, 0) < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2 ) {

    for(int i = sizeof(i2s_tx_buffer) / sizeof(short) / 2 / 2; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2; i++ ) {

      ((int16_t*)i2s_tx_buffer)[i * 2 + 0] = ((int16_t*)i2s_tx_buffer)[i * 2 + 1] = nextsinevalue();

    }

    // hardware clears the done flag, we don't need to

    //DMA_PDD_ClearDoneFlag(DMA_BASE_PTR, 0);

  } else {

    for(int i = 0; i < sizeof(i2s_tx_buffer) / sizeof(short) / 2 / 2; i++ ) {

      ((int16_t*)i2s_tx_buffer)[i * 2 + 0] = ((int16_t*)i2s_tx_buffer)[i * 2 + 1] = nextsinevalue();

    }

  }

Happy sin(x) wave output now on the i2s bus and out of the analog outputs of the codec.

Again, not sure why the done flag and/or the other methods I tried failed to work reliably, but this alternate method makes sense and works so I'm rolling with it.

Thanks for reading and hope this helps someone else struggling with similar problems.

0 Kudos