SAI DMA double buffer problem

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

SAI DMA double buffer problem

2,682 Views
andersesbensen
Contributor I

Hi i am quite new to MQX and the Kinetis family.

 

I am trying to build a system using a K24 that uses the SAI/I2S interface together with the DMA to send and receive data across an AES/EBU interface.

 

I have based my code on the example in this post https://community.freescale.com/thread/335561 but have difficulties getting the system to run stable.

 

The SAI is running slave mode.  Bitclock is 12.288 Mhz, Fs is 48 Khz and in RX i receive 8x32 bit words in each frame. In TX direction only 4x8 bit word are received.

 

My problem is that i from time to time get data corruption on the I2S data, both RX and TX are affected, but error rate in RX seems to be highest.

 

Only way i have been able to get the system  running stable for longer period of time is by violating the rules for setting interrupt priorities, and increasing the DMA irq priorities to 2. This way i get stable data, but this lead to other problems with the timer system, and other task are affected ( LedTask).

 

Relevant part of my code is attached below.

 

The DMA is running 5 ms frame (IRQ at half buffer size) and the isr is being serviced each 5 ms.  Therefore I would not expect the DMA interrupt to be that critical since in theory have up to 5ms to either read or write new data into the different half's of the DMA buffers.

 

I am wondering why I need to call EDMA_HAL_HTCDSetHalfCompleteIntCmd from the ISR for each interrupt, since I have not been able to find anything in the reference manual that indicates that this is cleared after each irq.  Also as I see it this call is the only thing that could be really time critical on the ISR's.

 

Any suggestions are welcome .

 

#define MQX_LED_TASK_PRIORITY 15

#define MQX_PCM_TASK_PRIORITY 8  // The MQX RTOS using priority levels 1-7 so please start your task priorities at 8.

 

 

#define DMA_TX_PRIO 4 // according to user guide 4 and above are valid values

#define DMA_RX_PRIO 4 // according to user guide 4 and above are valid values

 

 

#define TX_DMA_BUFFER_LENGTH (4*1*240*2)  // 4 RFP's 1 samples pr tick- 5ms frames -> 240 ticks  Doublebuffer*2

#define RX_DMA_BUFFER_LENGTH (4*2*240*2)  // 4 RFP's 2 samples pr tick- 5ms frames -> 240 ticks  Doublebuffer*2

#define SAI_WATER_MARK_SIZE 4

 

 

static void I2sInit(void)

{

  reg_base = g_saiBase[0]; // Register base for SAI/I2S

 

 

  sai_tx_user_cfg.mclk_source = kSaiMclkSourceSysclk; /*!< Master clock source. @internal gui name="MCLK source" id="CfgMclkSource" */

  sai_tx_user_cfg.channel = 1;                        /*!< Which FIFO is used to transfer. @internal gui name="Channel" id="Channel" */

  sai_tx_user_cfg.sync_mode = kSaiModeAsync;          /*!< Synchronous or asynchronous. @internal gui name="Mode" id="Mode" */

  sai_tx_user_cfg.protocol = kSaiBusI2SType;          /*!< I2S left, I2S right or I2S type. @internal gui name="Protocol" id="BusType" */

  sai_tx_user_cfg.slave_master = kSaiSlave;           /*!< Master or slave. @internal gui name="Master / Slave mode" id="MasterSlave" */

  sai_tx_user_cfg.bclk_source = kSaiBclkSourceBusclk; /*!< Bit clock from master clock or other modules.  */

  sai_tx_user_cfg.watermark = SAI_WATER_MARK_SIZE;    /*!< When to send interrupt or dma request. @internal gui name="Watermark" id="Watermark" */

  sai_tx_user_cfg.dma_source = 1;                     /*!< Dma request source. @internal gui name="DMA request value" id="DmaRequest" */

 

 

  g_format_tx.bits = 8;

  g_format_tx.mclk = 12288000;

  g_format_tx.mono_stereo = kSaiStereo;

  g_format_tx.sample_rate = 48000;

 

 

  sai_rx_user_cfg.mclk_source = kSaiMclkSourceSysclk; /*!< Master clock source. @internal gui name="MCLK source" id="CfgMclkSource" */

  sai_rx_user_cfg.channel = 1;                        /*!< Which FIFO is used to transfer. @internal gui name="Channel" id="Channel" */

  sai_rx_user_cfg.sync_mode = kSaiModeSync;           /*!< Synchronous or asynchronous. @internal gui name="Mode" id="Mode" */

  sai_rx_user_cfg.protocol = kSaiBusI2SType;          /*!< I2S left, I2S right or I2S type. @internal gui name="Protocol" id="BusType" */

  sai_rx_user_cfg.slave_master = kSaiSlave;           /*!< Master or slave. @internal gui name="Master / Slave mode" id="MasterSlave" */

  sai_rx_user_cfg.bclk_source = kSaiBclkSourceBusclk; /*!< Bit clock from master clock or other modules. */

  sai_rx_user_cfg.watermark = SAI_WATER_MARK_SIZE;    /*!< When to send interrupt or dma request. @internal gui name="Watermark" id="Watermark" */

  sai_rx_user_cfg.dma_source = 0;                     /*!< Dma request source. @internal gui name="DMA request value" id="DmaRequest" */

 

 

  g_format_rx.bits = 32;

  g_format_rx.mclk = 12288000;

  g_format_rx.mono_stereo = kSaiStereo;

  g_format_rx.sample_rate = 48000;

 

 

  SAI_DRV_TxInit(0, &sai_tx_user_cfg, &sai_tx_state);

 

 

  SAI_DRV_RxInit(0, &sai_rx_user_cfg, &sai_rx_state);

 

 

  SAI_DRV_TxConfigDataFormat(0, &g_format_tx);

  SAI_DRV_RxConfigDataFormat(0, &g_format_rx);

 

 

  // Set framesize

  I2S_BWR_TCR4_FRSZ(reg_base,31u); // 32 x 8 bit frames in TX

 

 

  I2S_BWR_RCR4_FRSZ(reg_base,7u);  // 8 x 32 bit frames in RX

 

 

  I2S_WR_TMR(reg_base, 0xFFFFEEEE); // Enable 4 *8 bit control slots in TX (each forth slot in the first I2S half)

  I2S_WR_RMR(reg_base, 0xFFFFFF00); // Enable all 8 slots in Rx

}

 

void DmaRxInit(void)

{

  uint16_t i;

  uint32_t rx_fifo_addr;

  rx_fifo_addr = SAI_DRV_RxGetFifoAddr(0, 1);

 

  /* DMA */

  i2s0_rx_tcd = &edma_rx_transfer_descriptor[0];  //(edma_software_tcd_t *) malloc(sizeof(edma_software_tcd_t)+1);

  memset(i2s0_rx_tcd, 0, sizeof(edma_software_tcd_t));

  g_edma_rx_userconfig.chnArbitration = kEDMAChnArbitrationRoundrobin;

  g_edma_rx_userconfig.notHaltOnError = false;

  EDMA_DRV_Init(&g_edma_rx_state, &g_edma_rx_userconfig);

 

  EDMA_DRV_RequestChannel(0, kDmaRequestMux0I2S0Rx, &dmaCh0);

 

  EDMA_DRV_ConfigLoopTransfer(&dmaCh0, i2s0_rx_tcd, kEDMAPeripheralToMemory, (uint32_t) rx_fifo_addr,

                             (uint32_t) rx_ctrl_buffer, 4, 4*SAI_WATER_MARK_SIZE, RX_DMA_BUFFER_LENGTH<<2, 1);

  EDMA_DRV_StartChannel(&dmaCh0);

 

  SAI_DRV_RxSetDmaCmd(0, true);

  EDMA_DRV_InstallCallback(&dmaCh0, RxDmaCallback, (void *) &CbContext);

  EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE_PTR, 0, true);

  EDMA_DRV_StartChannel(&dmaCh0);

 

 

  OSA_InstallIntHandler(DMA0_IRQn, MQX_DMA0_IRQHandler);

  NVIC_SetPriority(DMA0_IRQn,DMA_RX_PRIO);

}

 

 

void DmaTxInit(void)

{

  uint32_t tx_fifo_addr;

  tx_fifo_addr = SAI_DRV_TxGetFifoAddr(0, 1);

 

  /* DMA */

  i2s0_tx_tcd = &edma_tx_transfer_descriptor[0];  //(edma_software_tcd_t *) malloc(sizeof(edma_software_tcd_t)+1);

  memset(i2s0_tx_tcd, 0, sizeof(edma_software_tcd_t));

  g_edma_tx_userconfig.chnArbitration = kEDMAChnArbitrationRoundrobin;

  g_edma_tx_userconfig.notHaltOnError = false;

  EDMA_DRV_Init(&g_edma_tx_state, &g_edma_tx_userconfig);

 

  EDMA_DRV_RequestChannel(1, kDmaRequestMux0I2S0Tx, &dmaCh1);

 

  EDMA_DRV_ConfigLoopTransfer(&dmaCh1, i2s0_tx_tcd, kEDMAMemoryToPeripheral, (uint32_t)tx_ctrl_buffer,

                              (uint32_t) tx_fifo_addr, 1, SAI_WATER_MARK_SIZE , TX_DMA_BUFFER_LENGTH, 1);

 

  EDMA_DRV_StartChannel(&dmaCh1);

 

  SAI_DRV_TxSetDmaCmd(0, true);

  EDMA_DRV_InstallCallback(&dmaCh1, TxDmaCallback, (void *) &CbContext);

  EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE_PTR, 1, true);

  EDMA_DRV_StartChannel(&dmaCh1);

 

  OSA_InstallIntHandler(DMA1_IRQn, MQX_DMA1_IRQHandler);

  NVIC_SetPriority(DMA1_IRQn,DMA_TX_PRIO);

}

 

void TxDmaCallback(void *param, edma_chn_status_t status)

{

  uint32_t TxFBytes;

#ifdef TX_DMA_DEBUG

  GPIO_DRV_SetPinOutput(kGpioM4_DEBUG_6);

#endif

  TxFBytes = EDMA_HAL_HTCDGetFinishedBytes(DMA_BASE_PTR,1);

 

  if(TxFBytes >= 0x3c0)

  {

    _event_set(event_ptr, 0x04);

    EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE_PTR, 1, true);

  }

  else

  {

    _event_set(event_ptr, 0x08);

    EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE_PTR, 1, true);

  }

#ifdef TX_DMA_DEBUG

  GPIO_DRV_ClearPinOutput(kGpioM4_DEBUG_6);

#endif

}

 

void RxDmaCallback(void *param, edma_chn_status_t status)

{

  uint32_t  FBytes;

  #ifdef RX_DMA_DEBUG

  GPIO_DRV_SetPinOutput(kGpioM4_DEBUG_7);

#endif

 

  FBytes = EDMA_HAL_HTCDGetFinishedBytes(DMA_BASE_PTR,0);

 

  if(FBytes >= 0x1e00)

  {

    _event_set(event_ptr, 0x01);

    EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE_PTR, 0, true);

  }

  else

  {

    _event_set(event_ptr, 0x02);

    EDMA_HAL_HTCDSetHalfCompleteIntCmd(DMA_BASE_PTR, 0, true);

  }

#ifdef RX_DMA_DEBUG

  GPIO_DRV_ClearPinOutput(kGpioM4_DEBUG_7);

#endif

}

 

static void PcmTask(uint32_t param)

{

  uint16_t y;

  uint8_t rfp_id=0;

  uint16_t cnt=0;

 

  I2sInit();

 

  DmaRxInit();

  DmaTxInit();

 

  // Start I2S interface

  SAI_DRV_RxStartModule(0);

  SAI_DRV_TxStartModule(0);

 

  _event_create("global");

  _event_open("global", &event_ptr);

 

  while(1)

  {

    uint16_t i = 0;

    _event_wait_any(event_ptr, 0x0f, 0);

 

    uint32_t ev_val;

    _event_get_value(event_ptr, &ev_val);

 

    i = 0;

    /*******************************************

    *   Rx processing

    *******************************************/

 

 

    if(ev_val & 1) // First half processed

    {

      _event_clear(event_ptr,1);

      GPIO_DRV_SetPinOutput(kGpioM4_DEBUG_5);

      for (i = 0; i < (RX_DMA_BUFFER_LENGTH>>1);i+=8 )

      {

        PutRxData((rx_ctrl_buffer[i+0]  & 0xFF000000) >> 24, 0);

        PutRxData((rx_ctrl_buffer[i+1]  & 0xFF000000) >> 24, 1);

        PutRxData((rx_ctrl_buffer[i+2]  & 0xFF000000) >> 24, 2);

        PutRxData((rx_ctrl_buffer[i+3]  & 0xFF000000) >> 24, 3);

      }

      GPIO_DRV_ClearPinOutput(kGpioM4_DEBUG_5);

    }

 

 

    if(ev_val & 2) // second half process

    {

      _event_clear(event_ptr,2);

      GPIO_DRV_SetPinOutput(kGpioM4_DEBUG_5);

      for (i = 0; i < (RX_DMA_BUFFER_LENGTH>>1); i+=8)

      {

          PutRxData((rx_ctrl_buffer[(RX_DMA_BUFFER_LENGTH>>1)+i+0]  & 0xFF000000) >> 24, 0);

          PutRxData((rx_ctrl_buffer[(RX_DMA_BUFFER_LENGTH>>1)+i+1]  & 0xFF000000) >> 24, 1);

          PutRxData((rx_ctrl_buffer[(RX_DMA_BUFFER_LENGTH>>1)+i+2]  & 0xFF000000) >> 24, 2);

          PutRxData((rx_ctrl_buffer[(RX_DMA_BUFFER_LENGTH>>1)+i+3]  & 0xFF000000) >> 24, 3);

      }

      GPIO_DRV_ClearPinOutput(kGpioM4_DEBUG_5);

    }

 

 

    /*******************************************

    *   Tx processing

    *******************************************/

    if(ev_val & 4)

    {

      //_event_clear(event_ptr,4);

      GPIO_DRV_SetPinOutput(kGpioM4_DEBUG_4);

      for (i = 0; i < (TX_DMA_BUFFER_LENGTH>>1);i+=4)

      {

        GetTxData(&tx_ctrl_buffer[i+0],0);

        GetTxData(&tx_ctrl_buffer[i+1],1);

        GetTxData(&tx_ctrl_buffer[i+2],2);

        GetTxData(&tx_ctrl_buffer[i+3],3);

      }

      _event_clear(event_ptr,4);

      GPIO_DRV_ClearPinOutput(kGpioM4_DEBUG_4);

    }

    if(ev_val & 8)

    {

      //_event_clear(event_ptr,8);

      GPIO_DRV_SetPinOutput(kGpioM4_DEBUG_4);

      for (i = 0; i < (TX_DMA_BUFFER_LENGTH>>1);i+=4)

      {

        GetTxData(&tx_ctrl_buffer[(TX_DMA_BUFFER_LENGTH>>1)+i+0],0);

        GetTxData(&tx_ctrl_buffer[(TX_DMA_BUFFER_LENGTH>>1)+i+1],1);

        GetTxData(&tx_ctrl_buffer[(TX_DMA_BUFFER_LENGTH>>1)+i+2],2);

        GetTxData(&tx_ctrl_buffer[(TX_DMA_BUFFER_LENGTH>>1)+i+3],3);

      }

      _event_clear(event_ptr,8);

      GPIO_DRV_ClearPinOutput(kGpioM4_DEBUG_4);

    }

  }// while(1)

}

 

 

Only other task running is :

 

static void LedTask(uint32_t param)

{

  while (1)

  {

    LED1_TOGGLE;

    delay_ms(50);

    LED4_TOGGLE;

    delay_ms(50);

    LED2_TOGGLE;

    delay_ms(50);

    LED3_TOGGLE;

    delay_ms(500);

  }

}

Labels (1)
Tags (4)
0 Kudos
4 Replies

1,385 Views
manhnguyen
NXP Employee
NXP Employee

Hi

0 Kudos

1,385 Views
andersesbensen
Contributor I

Hi

It seems like a I managed to get the system stable, by doing the following.

1.  I moved the DMA isr's out of MQX context and increased their priority to 2. The only thing done in the DMA callback's is to check the DMA state using EDMA_HAL_HTCDGetFinishedBytes, and calling EDMA_HAL_HTCDSetHalfCompleteIntCmd to get interrupt for the next half way point.

2   Since moving DMA isr's out of MQX context disallows me to use OS features like semaphores and event to sync with my task directly form the isr, I added two new interrupt handlers (RX/TX) that i trig from the DMA kernel isr's using NVIC->STIR = XXX_IRQn.  The two new interrupt handler are handled inside MQX context and allows my to used OS features for synchronozation.

Based on the above, my conclusion still is that the call to EDMA_HAL_HTCDSetHalfCompleteIntCmd is very time critical, since i need to be called from high priority isr to work in my setup.

I still wonder why I need to recall this function on each interrupt. Also i am confused about the following is statement describing the INTHALF bit in the DMA reference.

".... If BITER is set, do not use INTHALF. Use INTMAJOR instead."  I would expect this to be the case when running the in this mode

I have tried to enable both INTMAJOR  and  INTHALF, but I still need to reenable INTHALF, each time the INTHALF ISR is issued.

Anyone that can clarify this to me.

Br Anders

0 Kudos

1,385 Views
bosleymusic_com
Contributor IV

I actually wrote the original loop DMA code you are referencing in the hyperlink - for whatever reason the half interrupt command disappears , i.e. the flag is cleared, so you have to set it each time in order to keep a true circular buffer going. I figured that out by watching the flag in the register and never was offered any explanation as to why, or given a better way to do it.

0 Kudos

1,385 Views
isaacavila
NXP Employee
NXP Employee

Hello Anders,

Could you please share with us your project?

Best Regards,

Isaac

0 Kudos