cancel
Showing results for 
Search instead for 
Did you mean: 

I2S DMA in NORMAL mode for double-buffering, not halving a single one!

Zaher
Senior II

Hello everyone!

I was wondering what might go wrong with I2S DMA configured in normal mode for audio streaming with double-buffering technique. I'm not talking about circular buffer here (ping-pong) where a single buffer is used in halves in HC and TC interrupts.

I have spent quite some time trying to figure it out, but seems the codec doesn't output any audio when DMA is in 'NORMAL' mode. The init sequence of codec doesn't work, as well, which indicates the first 16 dummy bytes that must be sent for the codec to set/initialize the registers never make it to the codec.

The only thing that comes to my mind now are the HC/TC IRQs, their callbacks, and whether they were registered when DMA is configured in 'NORMAL' mode.

 This is for STM32F413 + WM8994

11 REPLIES 11
LCE
Principal

Wasn't there something that DBM only works in CIRCULAR mode? Which makes sense, because NORMAL mode means that the DMA stops when the first buffer + length given to it is done.

From RM0090:

"A double-buffer stream works as a regular (single buffer) stream with the difference that it

has two memory pointers. When the Double buffer mode is enabled, the Circular mode is

automatically enabled (CIRC bit in DMA_SxCR is don’t care) and at each end of transaction,

the memory pointers are swapped."

In case you are using a buffer chain, you could use the half-complete callback to set a new buffer address to the currently unused M0 / M1 register and so on.

Anyway, check that the init sequence is correct (old Cube error, basic DMA init / clock enable must happen before peripheral DMA init) and that interrupts are enabled.

And I'm not sure how current HAL version handles DBM, when I started using that it wasn't really working, so I built my own I2S / SAI DMA with DBM start function.

Zaher
Senior II

I was just looking into that, I mean the DBM mode (DMA_SxCR_DBM). I didn't know this was supported in hardware or HAL, so I was trying to implement it in the program. My current HAL seems to have 'HAL_DMAEx_MultiBufferStart()' but not sure if this is the way to do it with I2S. I was trying dozens of scenarios, switching buffer pointers like you pointed out, using BSP_AUDIO_OUT_ChangeBuffer(). and so on, but none worked out for me.

Zaher
Senior II

I found this in the HAL DMA driver:

        /* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */
        if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET)
        {
          /* Disable the half transfer interrupt */
          hdma->Instance->CR  &= ~(DMA_IT_HT);
        }
 
      /* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */
      else
      {
        if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET)
        {// Rest of the block has been omitted}
 

When DMA is configured in 'NORMAL' mode, these interrupts never fire. I know that in NORMAL mode, you will have to switch buffers and initiate the transaction over and over again. unlike the case with ping-pong buffer. But why HC/TC interrupts are disabled when DMA is in NORMAL mode?

LCE
Principal

I would start with a simple sine buffer in circular mode, no DBM yet.

If you then get I2S data output, then start working on the DBM mode.

I just checked my code, I'm actually using "HAL_DMAEx_MultiBufferStart_IT()", I just build my own I2S / SAI start function around that.

And there were some manual DBM enable settings I did in DMA peripheral init, but I'm not sure if that is still needed with current HAL_DMAEx_MultiBufferStart_IT:

From my I2S init, as I said, that might be redundant...

DMA_Stream_TypeDef *pDmaStream;
uint32_t u32RegTemp = 0;
 
/* DBM - double buffer mode activation
		- AFTER LINK to DMA!
		- BEFORE DMA enable!
	 */
		pDmaStream = (DMA_Stream_TypeDef *)hDMA_I2S_1.Instance;
 
		u32RegTemp 		= pDmaStream->CR;
		pDmaStream->CR 	&= ~DMA_SxCR_EN;
		pDmaStream->CR 	|= DMA_SxCR_DBM;
		u32RegTemp 		&= DMA_SxCR_EN;
		pDmaStream->CR 	|= u32RegTemp;
		/* CT - current buffer reset */
		pDmaStream->CR 	&= ~DMA_SxCR_CT;

Zaher
Senior II

Audio is working fine in circular mode. I have several players working, as well as, a sine wave example. I just wanted to switch to double-buffer (two separate buffers) because I believe this makes things better when you process one large buffer while the other one is sent to the codec using DMA.

I will try to do the same in my I2S DMA init and see what else needs to be changed. I believe your code does exactly what I'm looking for.

Well, just try circular mode.

And as I said below, start simpler, just one sine buffer output in CIRCULAR mode, maybe use the HC/TC interrupts to toggle a pin or count just to check.

LCE
Principal

Okay, at least something...

Here's my I2S Rxstarter:

  • DMA set to circular
  • for RX
  • same callback for M0 and M1 HC / TC
uint8_t I2sRxDmaDbmStart(I2S_HandleTypeDef *phI2S, uint8_t *pu8DataM0, uint8_t *pu8DataM1, uint16_t u16Size)
{
	uint8_t u8RetVal = HAL_OK;
 
	if( (pu8DataM0 == NULL) || (u16Size == 0) )
	{
		uart_printf("\n\r# ERR: I2sRxDmaDbmStart(): (pu8DataM0 == NULL) || (u16Size == 0)\n\r");
		return  HAL_ERROR;
	}
 
	if( phI2S->State != HAL_I2S_STATE_READY )
	{
		uart_printf("\n\r# ERR: I2sRxDmaDbmStart(): State != READY\n\r");
		return HAL_BUSY;
	}
 
	/* set state and reset error code */
	phI2S->State      	= HAL_I2S_STATE_BUSY_RX;
	phI2S->ErrorCode  	= HAL_I2S_ERROR_NONE;
	phI2S->pRxBuffPtr 	= (uint16_t *)pu8DataM0;
	phI2S->RxXferSize 	= u16Size;
	phI2S->RxXferCount	= u16Size;
 
	/* init field not used in handle to zero */
	phI2S->pTxBuffPtr 	= NULL;
	phI2S->TxXferSize 	= 0;
	phI2S->TxXferCount	= 0;
 
/* set the I2S Rx DMA half/complete transfer callbacks - for first buffer M0 */
	phI2S->hdmarx->XferHalfCpltCallback 	= I2sDmaRxHalfCpltCB;
	phI2S->hdmarx->XferCpltCallback 		= I2sDmaRxCpltCB;
 
/* set I2S Rx DMA callbacks for DOUBLE buffer mode, DBM bit set in DMA->CR */
	/* -> same callbacks as for M0 */
	/* Set the M1 I2S Rx DMA half/complete transfer callbacks */
	phI2S->hdmarx->XferM1HalfCpltCallback 	= I2sDmaRxHalfCpltCB;
	phI2S->hdmarx->XferM1CpltCallback 		= I2sDmaRxCpltCB;
 
	/* set the DMA error callback */
	phI2S->hdmarx->XferErrorCallback 		= I2sDmaErrorCB;
	/* no DMA Rx abort callback */
	phI2S->hdmarx->XferAbortCallback 		= NULL;
 
/* enable the Rx DMA Stream: interrupt & double buffer mode */
	u8RetVal = HAL_DMAEx_MultiBufferStart_IT(phI2S->hdmarx,
												(uint32_t)&phI2S->Instance->RXDR,
												(uint32_t)pu8DataM0,
												(uint32_t)pu8DataM1,
												(uint32_t)u16Size);
	if( u8RetVal != HAL_OK )
	{
		/* set I2S error code */
		phI2S->ErrorCode |= HAL_I2S_ERROR_DMA;
		phI2S->State = HAL_I2S_STATE_READY;
		uart_printf("\n\r# ERR: I2S HAL_DMAEx_MultiBufferStart_IT(), ErrorCode = %08lX\n\r", phI2S->ErrorCode);
		return u8RetVal;
	}
 
	/* enable Rx DMA Request */
	phI2S->Instance->CFG1 |= SPI_CFG1_RXDMAEN;
 
	/* enable I2S peripheral */
	phI2S->Instance->CR1 |= SPI_CR1_SPE;
 
	/* start the transfer */
	phI2S->Instance->CR1 |= SPI_CR1_CSTART;
 
	return u8RetVal;
}

Zaher
Senior II

Thank you very much for sharing this. So, even with DBM, the DMA is still configured in circular mode? Another issue is, I didn't find much info about DBM in the RM of my device. According to several resources, this feature is only available on the following series:

  1. STM32F427/437/429/439 (F4)
  2. STM32F746/756
  3. STM32F767/769/777/779
  4. STM32F446
  5. STM32F769/779
  6. STM32H745/755/747/757
  7. STM32H750/753/755/757
  8. STM32H7A3/7B3/7A1/7B1
  9. STM32G474/484/491/4A1
  10. STM32G4B1/4B2 series

Though not really sure about this list.

Zaher
Senior II

Sorry for confusion. I was trying to quickly search RM with the wrong keyword, but when I read the entire section related to the DMA, I found the following:

Double-buffer mode

This mode is available for all the DMA1 and DMA2 streams. The double-buffer mode is enabled by setting the DBM bit in the DMA_SxCR register. A double-buffer stream works as a regular (single buffer) stream with the difference that it has two memory pointers. When the double-buffer mode is enabled, the circular mode is automatically enabled (CIRC bit in DMA_SxCR is not relevant) and at each end of transaction, the memory pointers are swapped.

So, if the circular mode is the default mode when DBM is enabled, the pointers circulate between beginning and end of a single buffer in a ping-pong fashion or between two distinct buffers?