cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L552 I2S DMA and CDC continuous transfer problem

Dhanaraj
Associate II

I have an ADC connected to my MCU using I2S. MCU configured in I2S master receive mode. MCU supplying the ADC with 12MHZ MCLK. MCU sampling freq is 48 KHZ(Actual is 46.875 KHZ), Number of slots are 4, Data size is 16bits and I2S standard protocol. Using the Peripheral to Memory DMA in Normal mode, Half word data width, Medium priority. 

DMA receive size is 188. Since the data size is half word, I am expecting 376 bytes. I choose this size to time 1 msec data. 

HAL_SAI_Receive_DMA(&hsai_BlockB2, (uint8_t *)&i2s_adc_dma_data.sai_dma_data_buf[0], I2S_DMA_RX_SIZE);

I am sending the DMA data directly to PC through CDC. CDC Tx buff si

I2S Half complete callback:

usb_ret = CDC_Transmit_FS((uint8_t*)&i2s_adc_dma_data.sai_dma_data_buf[0], I2S_DMA_RX_SIZE);

I2S full complete callback:

usb_ret = CDC_Transmit_FS((uint8_t*)&i2s_adc_dma_data.sai_dma_data_buf[I2S_DMA_RX_SIZE], I2S_DMA_RX_SIZE);

My application requires continuous streaming of the ADC data to PC for 10 to 20 seconds. But I am not able to stream roughly more than 100 msec without loosing the DMA data. I am trying to find a way to sync the I2S DMA and USB CDC. Is it the host's problem that disturbing the transfer? If yes, what are the other options I have here?

 

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)

{

uint8_t usb_ret;

 

if(i2s_adc_dma_data.start_i2s != false) {

HAL_SAI_Receive_DMA(&hsai_BlockB2, (uint8_t *)&i2s_adc_dma_data.sai_dma_data_buf[0], I2S_DMA_RX_SIZE);

//memcpy(&i2s_adc_dma_data.sai_data_buf_app[I2S_DMA_RX_SIZE], &i2s_adc_dma_data.sai_dma_data_buf[I2S_DMA_RX_SIZE], I2S_DMA_RX_SIZE);

//i2s_adc_dma_data.cdc_error = true;

if(cdc_status != TX_EVENT_INPROGRESS) {

usb_ret = CDC_Transmit_FS((uint8_t*)&i2s_adc_dma_data.sai_dma_data_buf[I2S_DMA_RX_SIZE], I2S_DMA_RX_SIZE);

i2s_adc_dma_data.cdc_error = false;

if (USBD_OK == usb_ret)

{

cdc_status = TX_EVENT_INPROGRESS;

}

else if(usb_ret != USBD_BUSY)

{

log_info("USB TX error. Stopping DMA");

HAL_SAI_Abort(&hsai_BlockB2);

i2s_adc_dma_data.cdc_error = true;

}

}

else if(cdc_status == TX_EVENT_INPROGRESS){ //Last transfer is still pending

log_info("Prev USB TX pending. Stopping DMA");

HAL_SAI_Abort(&hsai_BlockB2);

i2s_adc_dma_data.cdc_error = true;

}

}

}

 

void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai)

{

uint8_t usb_ret;

 

//memcpy(&i2s_adc_dma_data.sai_data_buf_app[0], &i2s_adc_dma_data.sai_dma_data_buf[0], I2S_DMA_RX_SIZE);

if(i2s_adc_dma_data.start_i2s != false && cdc_status != TX_EVENT_INPROGRESS) {

usb_ret = CDC_Transmit_FS((uint8_t*)&i2s_adc_dma_data.sai_dma_data_buf[0], I2S_DMA_RX_SIZE);

i2s_adc_dma_data.cdc_error = false;

if (USBD_OK == usb_ret)

{

cdc_status = TX_EVENT_INPROGRESS;

}

else if(usb_ret != USBD_BUSY)

{

log_info("USB TX error. Stopping DMA");

HAL_SAI_Abort(&hsai_BlockB2);

i2s_adc_dma_data.cdc_error = true;

}

}

else if(cdc_status == TX_EVENT_INPROGRESS){ //Last transfer is still pending

log_info("Prev USB TX pending. Stopping DMA");

HAL_SAI_Abort(&hsai_BlockB2);

i2s_adc_dma_data.cdc_error = true;

}

}

 

 

 

3 REPLIES 3
Feng XIONG
ST Employee

Is your data flow ADC -> SAI(I2S) -> USB CDC (Host PC)? If my understanding is incorrect, please draw a block diagram for illustration.

It’s noticed that you put too many stuffs in HAL_SAI_RxCpltCallback() and HAL_SAI_RxHalfCpltCallback(), such as the CDC data transmit, log messages, etc. These two functions are called in the ISR and had better only handle the critical tasks and exit as soon as possible to avoid blocking other tasks’ processing, especially for your time critical user cases. Please move the data tx/rx out of the ISR and don’t put any log in it as well.

When using DMA for data transmission, the data will be automatically stored in the RAM.
To synchronize the data, two buffers can be used for example. Buffer1 for ADC and buffer2 for I2S. Each circular buffer has two pointers - one for read and the other for write. If your RAM is large enough to accommodate 10~20 seconds of data, it's also possible to use one pointer to represent where the data is.
Once the accumulated data in buffer1 reaches the threshold, the SAI transmission can be triggered. The same applies to buffer2. The trigger can be performed in the ISR, but it's recommended to create another task in the app to perform the actual transmission.
The threshold provides a buffer. For example, if ADC is delayed for whatever reason and can't provide enough data for I2S, you can set the threshold higher to start transmission at a later time when there is more data to cover the interference.

You can test them module by module, then link them step by step. Thus, it’s easier to find where the problem is.

Thanks for your reply. 

External ADC -> STM SAI -> STM CDC -> Host PC. 

My I2S DMA trigger rate is correct at 47khz. My CDC transfer rate is lower than the 47khz which is preventing me sync the I2S DMA Rx and CDC Tx. Even if I use the double buffer it'll increase CDC Tx only for some time before hitting the same problem.  MCU is STM32L552ZETxQ and the ram is 256kb. Not enough to hold all the data together. What's the recommended CDC Tx size? As per cube, the CDC Tx buffer size is 2046. and I am not sure if there's a delay in the USB middleware which is converting the USB CDC data to a virtual com port data.

I also tried the dedicated buffer and copied the DMA buffer to the data buffer. And used it for CDC Tx. Used a Msec timer for CDC Tx. The results are all similar. 

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)

{

memcpy(&i2s_adc_dma_data.sai_data_buf_app[dma_hit_cnt * I2S_DMA_RX_SIZE], &i2s_adc_dma_data.sai_dma_data_buf[I2S_DMA_RX_SIZE], I2S_DMA_RX_SIZE);

dma_hit_cnt ++;

 

if((dma_hit_cnt % 20) == 0){

i2s_adc_dma_data.count = dma_hit_cnt;

i2s_adc_dma_data.i2s_valid_data = true;

}

}

void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai)

{

memcpy(&i2s_adc_dma_data.sai_data_buf_app[dma_hit_cnt * I2S_DMA_RX_SIZE], &i2s_adc_dma_data.sai_dma_data_buf[0], I2S_DMA_RX_SIZE);

dma_hit_cnt ++;

}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  uint8_t usb_ret;
  if(htim->Instance == TIM2) {
    HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
    msec_cnt ++;
    if(i2s_adc_dma_data.i2s_valid_data != false && cdc_status != TX_EVENT_INPROGRESS){ // We have some data to process and last CDC transfer is complete
      i2s_adc_dma_data.i2s_valid_data = false;
      uint32_t buf_point = i2s_adc_dma_data.prv_count * I2S_DMA_RX_SIZE;
 
      usb_ret = CDC_Transmit_FS((uint8_t*)&i2s_adc_dma_data.sai_data_buf_app[buf_point], (i2s_adc_dma_data.count - i2s_adc_dma_data.prv_count) * I2S_DMA_RX_SIZE);
      if (USBD_OK == usb_ret)
      {
         cdc_status = TX_EVENT_INPROGRESS;
      }
      else if(usb_ret != USBD_BUSY)
      {
        cdc_status = TX_ERROR;
        log_debug("USB TX error");
      }
      i2s_adc_dma_data.prv_count = i2s_adc_dma_data.count;
    }
  }
}
 

void CDC_txCmplt_callback(void)

{

cdc_status = TX_COMPLETE_EVENT;

}

 

Feng XIONG
ST Employee

Can the I2S Rx DMA and CDC Tx share a single circular buffer? This would involve the I2C writing data to the buffer, and once enough data is present for the CDC Tx to send out one packet, the I2S task would inform the CDC task to begin transfer, with the process repeating as necessary.