cancel
Showing results for 
Search instead for 
Did you mean: 

STM32U5 SAI in I2S mode + GPDMA

maxborglowe
Associate III

Hi,

I have a board with an STM32U575 at the core.
I'm attempting to set up SAI receiver in I2S mode, with 32-bit 48kHz sampling. See my configuration of SAI A below:

Screenshot 2024-02-06 at 05.31.13.png

I also want to receive the data continuously in circular mode via DMA (or GPDMA in the case of this MCU).
Reading other sources, I've concluded that the following setup should be established:

Screenshot 2024-02-06 at 05.33.01.png

The audio DMA functions can be seen below. The Audio_Init() function is called once in the main function, and the CpltCallback functions are called by DMA interrupts.

 

 

 

 

 

 

 

 

 

static uint16_t audio_rx[AUDIO_BUFFER_SIZE];
static uint16_t audio_tx[AUDIO_BUFFER_SIZE];

void Audio_Init(){
	audio_ready_flag = AUDIO_DATA_FREE;

	if(HAL_SAI_Receive_DMA(&AUDIO_RX_PORT, (uint8_t *)audio_rx, AUDIO_BUFFER_SIZE) != HAL_OK){
		Error_Handler();
	}
	if(HAL_SAI_Transmit_DMA(&AUDIO_TX_PORT, (uint8_t *)audio_tx, AUDIO_BUFFER_SIZE) != HAL_OK){
		Error_Handler();
	}
}

void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hi2s){
	audio_ready_flag = AUDIO_DATA_READY_HALF;
}

void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hi2s){
	audio_ready_flag = AUDIO_DATA_READY_FULL;
}

 

 

 

 

 

 

 

 

 

(I'm omitting all the HAL stuff, since I've already showed the setup in the above images. I want to note that all of the HAL code seems to be configured correctly --> no errors or warnings)

When my hardware has no audio source connected to it, I would assume that the audio data stored via DMA in the audio_rx buffer should be equal to 0 (and maybe some weak noise).

However, my buffer is filled with what appears to be 16-bit garbage values:

Screenshot 2024-02-06 at 05.56.49.png

Why is this? I've successfully implemented this code on my old build, which used a STM32G4.

I'm very thankful for any help I can receive on this issue!
Best regards,
Max Borglowe

1 ACCEPTED SOLUTION

Accepted Solutions
LCE
Principal

You are lucky, your SAI and your ADC are working correctly.

Now your 32bit buffer shows perfect noise values around 0, with about 100 dB SNR, the least 15 to 16 bits noisy only.

Check the 2's complement data format: https://en.wikipedia.org/wiki/Two%27s_complement

Your ADC ist just noisy around 0, example for 2's complement:

8 bit 2'sc:

1111 1111 = -1 !

0000 0000 = 0

0111 1111 = +127

1000 000 = -127

When working with audio formats, you should have heard about that. 😉
If not, now you know... :D

 

So it was mostly a 16 / 32 bit problem.

View solution in original post

21 REPLIES 21
LCE
Principal

What hardware is connected, a real codec? If yes, codec setup correct?

I would do the following:

1) initialize the buffers with 0

2) check all the I2S clocks and data lines with a scope

3) connect I2S RX and TX data lines directly, so that RX buffer should exactly display TX 

4) for sync'd starting, start the SAI slaves first, then the master

maxborglowe
Associate III

Hi LCE, thank you for your response.

1. I have initialized audio_rx and audio_tx buffers to 0, but this does not change the outcome.

2. All the I2S signals appear have the correct format. The transferred data seems just as erratic as the 16-bit garbage values shown in my first post. So maybe the error stems from software?

3. What do you mean with this?

4. This system only has SAI masters - one for ADC and one for DAC.

sebxx4
Associate II

Hello, I have a similar problem with my nucleo H503RB. I'm trying to make it work with Pmod I2S2 module (ADC+DAC). For now I've connected only ADC via I2S1. This is my HW configuration:

 

sebxx4_1-1707660104152.png

I2S config:

sebxx4_5-1707660629738.png

GPDMA config:

sebxx4_2-1707660369875.png

Code:

sebxx4_3-1707660442629.png

Output:

sebxx4_4-1707660529093.png

 

As you can see, I have no data in the buffer. I've checked clocks and data line with scope and it's okay here. The DMA interrupt also fires. So what can I do with it?

 

LCE
Principal

> 3. What do you mean with this?

> 4. This system only has SAI masters - one for ADC and one for DAC.

To get the external codec / ADC / DAC out of the equation, you could try to connect the SD pins directly.

What do you have connected? Especially at the TX, it's better to start simple, like outputting tx_buf[i++] = i  

I'm not sure if having 2 masters really makes sense, because they will not be synchronized a 100%, check the LRCKs on the scope. If you have some other reasons for not syncing, that might be okay. But unsynced, you need all the I2S clocks to the outside world twice.

sebxx4
Associate II

Could you help me too, please?

What do you mean by "connect the SD pins directly"? I have two separate audio transceivers - one ADC (PCM1821) and one DAC (PCM5100). Seems pointless to connect the SD of these two, if that's what you're implying?

Syncing is not an issue for me, as I want to alter the audio received from the ADC (so some delay is OK). I have used two I2S masters for my previous board, but in that case I did not have to use SAI.

Below is the pinout on my STM32U575, if that is of any help:

Screenshot 2024-02-13 at 05.13.31.png

@sebxx4 respectfully, get your own thread instead of cluttering mine with information irrelevant to the original post.

LCE
Principal

Okay, then forget about the syncing.

 

> What do you mean by "connect the SD pins directly"?

> I have two separate audio transceivers - one ADC (PCM1821) and one DAC (PCM5100).

> Seems pointless to connect the SD of these two, if that's what you're implying?

That's how I test the SAI without external components. If they are synced and SAI setup is okay, I find the TX data in the RX buffers.

Right now you have the DAC and the ADC in the equation, are you 100 % sure that these are set up correctly?

Anyway, check further:

1) GPIO settings correct? Check & compare alt functions with datasheet.

2) What do you see on the scope? All clocks as they should be?

3) Scope again, what's happening on ADC data line? Maybe the ADC data output is still high impedance, so is your GPIO SD input -> noise, maybe try a pull-down R.

4) Maybe the external components need a master clock?

5) DMA interrupts enabled and ISRs called?

6) RX data is ***, but is it changing? (means it is at least running)

I just see:

> I want to note that all of the HAL code seems to be configured correctly --> no errors or warnings

Haha, good one... successful compilation and correct functioning is not the same.

maxborglowe
Associate III

I've excluded the DAC for the time being, trying to evaluate only the ADC.

1. The GPIOs are correct according to the datasheet.

2. Here's what I'm getting on the scope. Every frame is transferred in 8 bits, which kind of makes sense when you look at the way pData is setup in this function:

HAL_StatusTypeDef HAL_SAI_Receive_DMA(SAI_HandleTypeDef *hsai, uint8_t *pData, uint16_t Size)

Screenshot 2024-02-23 at 05.15.07.png

Could it be that I'm messing up here?

#define AUDIO_BUFFER_SIZE 64

uint32_t audio_rx[AUDIO_BUFFER_SIZE];

#define AUDIO_RX_PORT hsai_BlockA1 //hsai_BlockA1 pre-defined by HAL

//initializing call of the SAI receive via DMA function
HAL_SAI_Receive_DMA(&AUDIO_RX_PORT, (uint8_t *)audio_rx, AUDIO_BUFFER_SIZE);

3. Tried pulling down the data line, but still the same result as above. I'm ruling out tristate, since the data line seems to be driven high.

4. I've used this ADC with an STM32G4 MCU, where I used a simple I2S interface without needing a master clock. On this device, everything worked perfectly.

5. Here's the NVIC, where the ISRs are enabled both for the SAI1, and for the GPDMA channel it's using.

maxborglowe_0-1708662776988.png

6. The RX data changes randomly and constantly.

Here's how the HAL SAI code is setup, generated from CubeMX:

SAI_HandleTypeDef hsai_BlockA1;
SAI_HandleTypeDef hsai_BlockB1;
DMA_NodeTypeDef Node_GPDMA1_Channel14;
DMA_QListTypeDef List_GPDMA1_Channel14;
DMA_HandleTypeDef handle_GPDMA1_Channel14;

/* SAI1 init function */
void MX_SAI1_Init(void)
{

  /* USER CODE BEGIN SAI1_Init 0 */

  /* USER CODE END SAI1_Init 0 */

  /* USER CODE BEGIN SAI1_Init 1 */

  /* USER CODE END SAI1_Init 1 */

  /* USER CODE BEGIN SAI1_Init 1 */

  /* USER CODE END SAI1_Init 1 */

  hsai_BlockA1.Instance = SAI1_Block_A;
  hsai_BlockA1.Init.AudioMode = SAI_MODEMASTER_RX;
  hsai_BlockA1.Init.Synchro = SAI_ASYNCHRONOUS;
  hsai_BlockA1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
  hsai_BlockA1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
  hsai_BlockA1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
  hsai_BlockA1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_48K;
  hsai_BlockA1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
  hsai_BlockA1.Init.MckOutput = SAI_MCK_OUTPUT_DISABLE;
  hsai_BlockA1.Init.MonoStereoMode = SAI_STEREOMODE;
  hsai_BlockA1.Init.CompandingMode = SAI_NOCOMPANDING;
  if (HAL_SAI_InitProtocol(&hsai_BlockA1, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_32BIT, 2) != HAL_OK)
  {
    Error_Handler();
  }
  hsai_BlockB1.Instance = SAI1_Block_B;
  hsai_BlockB1.Init.AudioMode = SAI_MODEMASTER_TX;
  hsai_BlockB1.Init.Synchro = SAI_ASYNCHRONOUS;
  hsai_BlockB1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
  hsai_BlockB1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
  hsai_BlockB1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
  hsai_BlockB1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_48K;
  hsai_BlockB1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
  hsai_BlockB1.Init.MckOutput = SAI_MCK_OUTPUT_DISABLE;
  hsai_BlockB1.Init.MonoStereoMode = SAI_STEREOMODE;
  hsai_BlockB1.Init.CompandingMode = SAI_NOCOMPANDING;
  hsai_BlockB1.Init.TriState = SAI_OUTPUT_NOTRELEASED;
  if (HAL_SAI_InitProtocol(&hsai_BlockB1, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_32BIT, 2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SAI1_Init 2 */

  /* USER CODE END SAI1_Init 2 */

}
static uint32_t SAI1_client =0;

void HAL_SAI_MspInit(SAI_HandleTypeDef* saiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  DMA_NodeConfTypeDef NodeConfig;
/* SAI1 */
    if(saiHandle->Instance==SAI1_Block_A)
    {
    /* SAI1 clock enable */
    if (SAI1_client == 0)
    {
       __HAL_RCC_SAI1_CLK_ENABLE();

    /* Peripheral interrupt init*/
    HAL_NVIC_SetPriority(SAI1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(SAI1_IRQn);
    }
    SAI1_client ++;

    /**SAI1_A_Block_A GPIO Configuration
    PA8     ------> SAI1_SCK_A
    PA9     ------> SAI1_FS_A
    PA10     ------> SAI1_SD_A
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF13_SAI1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* Peripheral DMA init*/

    NodeConfig.NodeType = DMA_GPDMA_2D_NODE;
    NodeConfig.Init.Request = GPDMA1_REQUEST_SAI1_A;
    NodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
    NodeConfig.Init.Direction = DMA_PERIPH_TO_MEMORY;
    NodeConfig.Init.SrcInc = DMA_SINC_FIXED;
    NodeConfig.Init.DestInc = DMA_DINC_INCREMENTED;
    NodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
    NodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
    NodeConfig.Init.SrcBurstLength = 1;
    NodeConfig.Init.DestBurstLength = 1;
    NodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
    NodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
    NodeConfig.Init.Mode = DMA_NORMAL;
    NodeConfig.RepeatBlockConfig.RepeatCount = 1;
    NodeConfig.RepeatBlockConfig.SrcAddrOffset = 0;
    NodeConfig.RepeatBlockConfig.DestAddrOffset = 0;
    NodeConfig.RepeatBlockConfig.BlkSrcAddrOffset = 0;
    NodeConfig.RepeatBlockConfig.BlkDestAddrOffset = 0;
    NodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
    NodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
    NodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
    if (HAL_DMAEx_List_BuildNode(&NodeConfig, &Node_GPDMA1_Channel14) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_InsertNode(&List_GPDMA1_Channel14, NULL, &Node_GPDMA1_Channel14) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_SetCircularMode(&List_GPDMA1_Channel14) != HAL_OK)
    {
      Error_Handler();
    }

    handle_GPDMA1_Channel14.Instance = GPDMA1_Channel14;
    handle_GPDMA1_Channel14.InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
    handle_GPDMA1_Channel14.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
    handle_GPDMA1_Channel14.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
    handle_GPDMA1_Channel14.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
    handle_GPDMA1_Channel14.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
    if (HAL_DMAEx_List_Init(&handle_GPDMA1_Channel14) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel14, &List_GPDMA1_Channel14) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(saiHandle, hdmarx, handle_GPDMA1_Channel14);

    if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel14, DMA_CHANNEL_PRIV) != HAL_OK)
    {
      Error_Handler();
    }

    }
    if(saiHandle->Instance==SAI1_Block_B)
    {
      /* SAI1 clock enable */
      if (SAI1_client == 0)
      {
       __HAL_RCC_SAI1_CLK_ENABLE();

      /* Peripheral interrupt init*/
      HAL_NVIC_SetPriority(SAI1_IRQn, 0, 0);
      HAL_NVIC_EnableIRQ(SAI1_IRQn);
      }
    SAI1_client ++;

    /**SAI1_B_Block_B GPIO Configuration
    PB3 (JTDO/TRACESWO)     ------> SAI1_SCK_B
    PB5     ------> SAI1_SD_B
    PB6     ------> SAI1_FS_B
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_5|GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF13_SAI1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    }
}

 

LCE
Principal

Oh my, the DMA setup is completely different from the G4 / F7 / H7 that I know.

 

But there is definitely one problem:

SCLK should be 4 times higher for 2 x 32 clock cycles within 1 LRCK cycle, but you actually seem to set it up correctly with SAI_PROTOCOL_DATASIZE_32BIT.

I would read the SAI registers where all the clock settings are done. Maybe there's a HAL bug?

Are there any SAI examples from ST for that U575? I would compare DMA and SAI setup.

 

And check if your ADC really runs as SAI_I2S_STANDARD, not as left justified (as most audio ADCs that I have used).