cancel
Showing results for 
Search instead for 
Did you mean: 

Problem with I2S TX / RX Circular DMA on STM32H743ZI2 board

JOatl
Associate II

I have a problem with DMA on this board that was not present on an F407 using code generated by Cube.

I2S2 has been configured as master half duplex and IS23 as slave half duplex (fed clock and WS of I2S2). The plan was to have a MEMS microphone on the slave and DAC on the I2S3.

I/O buffers for transfers have been placed in D2 RAM, cache then invalidated before setting off receive DMA (circular) and subsequently cleaned before setting off transmit DMA (circular).

Next is where I have been having issues, I have a tried and tested (F407) FIFO setup in a while loop to grab data and feed it through to TX. I have the problem that my DMA appears to block and mess up the while loop and each of the callbacks creating unexpected orders of code execution and intermittent hanging.

I and D cache enabled. Could this be a cache maintancne problem? If I disable the caches I still have issues.

__SECTION_RAM_D2
 
uint16_t txBuf[128];
 
__SECTION_RAM_D2
 
uint16_t rxBuf[128];
 
uint8_t txstate = 0;
 
uint8_t rxstate = 0;
 
 
#define LED_TIMEOUT PCM_SAMPLE_RATE / (128 / 4) // 1 second.
 
uint32_t LED3Timeout = 0;
 
uint32_t LED4Timeout = 0;
 
uint32_t LED5Timeout = 0;
 
uint32_t LED6Timeout = 0;
 
uint16_t fifobuf[256];
 
uint8_t fifo_w_ptr = 0;
 
uint8_t fifo_r_ptr = 0;
 
uint8_t fifo_read_enabled = 0;
 
 
 
int main(void)
 
{
 
 
 SCB_EnableICache();
 
 SCB_EnableDCache();
 
 
 HAL_Init();
 
 SystemClock_Config();
 
 MX_GPIO_Init();
 
 MX_DMA_Init();
 
 MX_I2S2_Init();
 
 MX_I2S3_Init();
 
 
   SCB_InvalidateDCache_by_Addr((uint32_t*)(((uint32_t)rxBuf) & ~(uint32_t)0x1F), sizeof(rxBuf));
 
   HAL_I2S_Receive_DMA(&hi2s3, &rxBuf[0], 64); 
 
 
   SCB_CleanDCache_by_Addr((uint32_t*)(((uint32_t)txBuf) & ~(uint32_t)0x1F), sizeof(txBuf));
 
   HAL_I2S_Transmit_DMA(&hi2s2, &txBuf[0], 64);
 
 
 while (1)
 
 {
 
    if (txstate == 1)
 
    {
 
       if (fifo_read_enabled == 1)
 
       {
 
          memcpy(&txBuf[0], &fifobuf[fifo_r_ptr], 128);
 
          fifo_r_ptr += 64;
 
       }
 
       SCB_CleanDCache_by_Addr((uint32_t*)(((uint32_t)txBuf) & ~(uint32_t)0x1F), sizeof(txBuf) / 2);
 
       txstate = 0;
 
    }
 
    if (rxstate == 1)
 
    {
 
       SCB_InvalidateDCache_by_Addr((uint32_t*)(((uint32_t)rxBuf) & ~(uint32_t)0x1F), sizeof(rxBuf) / 2);
 
       memcpy(&fifobuf[fifo_w_ptr], &rxBuf[0], 128);
 
       fifo_w_ptr += 64;
 
       if (fifo_w_ptr - fifo_r_ptr > 128)
 
       {
 
          fifo_read_enabled = 1;
 
       }
 
       rxstate = 0;
 
    }
 
    if (txstate == 2)
 
    {
 
       if (fifo_read_enabled == 1)
 
       {
 
          memcpy(&txBuf[64], &fifobuf[fifo_r_ptr], 128);
 
          fifo_r_ptr += 64;
 
       }
 
       SCB_CleanDCache_by_Addr((uint32_t*)(((uint32_t)&txBuf[64]) & ~(uint32_t)0x1F), sizeof(txBuf) / 2);
 
       txstate = 0;
 
    }
 
    if (rxstate == 2)
 
    {
 
       SCB_InvalidateDCache_by_Addr((uint32_t*)(((uint32_t)&rxBuf[64]) & ~(uint32_t)0x1F), sizeof(rxBuf) / 2);
 
       memcpy(&fifobuf[fifo_w_ptr], &rxBuf[64], 128);
 
       fifo_w_ptr += 64;
 
       rxstate = 0;
 
    }
 
 }
 
}

Here are the callbacks:

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	
	// Error LED.
	if (LED3Timeout > 0)
	{
		LED3Timeout--;
	}
	if (txstate != 0)
	{
		HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_SET);
		LED3Timeout = LED_TIMEOUT;
	}
	else if (LED3Timeout == 0)
	{
		HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_RESET);
	}
	
	// Ready to transmit
	txstate = 1;
	
}
void HAL_I2S_RxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	
	// Error LED.
	if (LED4Timeout > 0)
	{
		LED4Timeout--;
	}
	if (rxstate != 0)
	{
		HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_SET);
		LED4Timeout = LED_TIMEOUT;
	}
	else if (LED4Timeout == 0)
	{
		HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_RESET);
	}
	
	// Ready to receive
	rxstate = 1;
	
}
void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	
	// Error LED.
	if (LED5Timeout > 0)
	{
		LED5Timeout--;
	}
	if (txstate != 0)
	{
		HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
		LED5Timeout = LED_TIMEOUT;
	}
	else if (LED5Timeout == 0)
	{
		HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
	}
	
	// Ready to transmit
	txstate = 2;
	
}
void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	
	// Error LED.
	if (LED6Timeout > 0)
	{
		LED6Timeout--;
	}
	if (rxstate != 0)
	{
		HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_SET);
		LED6Timeout = LED_TIMEOUT;
	}
	else if (LED6Timeout == 0)
	{
		HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, GPIO_PIN_RESET);
	}
	
	// Ready to receive
	rxstate = 2;
	
}

15 REPLIES 15

So two issues (1) when I enabled the HSE for the I2s PLL I took its default cube value of 25 MHz when it should have been 8 which explains the 1/3 speeds. (2) I am still getting DMA interrupts constantly firing - rate dependent upon D2PPRE2 setting which I believe indicates that it must constantly feel it has data / is ready for more. Will have a look at the registers content tomorrow, thanks so far.

Piranha
Chief II

Variables used in both - main code and interrupts - must be defined as volatile.

https://blog.regehr.org/archives/28

The address and size of buffers (or their parts), on which D-cache invalidation is done, must be aligned to D-cache line size (32 B). In your case the rxBuf address most likely is not properly aligned.

JOatl
Associate II

I have set up a new project using cubemx with only I2S2 in master half-duplex in attempt to simplify / isolate the issues I am seeing. With caches enabled, txBuf in D2 RAM and the circular DMA started using:

SCB_CleanDCache_by_Addr((uint32_t*)(((uint32_t)txBuf) & ~(uint32_t)0x1F), sizeof(txBuf));
	HAL_I2S_Transmit_DMA(&hi2s2, &txBuf[0], 64);

I am still experiencing DMA DMA1_Stream0_IRQHandler immediately re-entering (only calling HAL_DMA_IRQHandler(&hdma_spi2_tx); inside the IRQ).

I have a voltatile flag that triggers a call to main code to clean the cache after every TX complete callback.

Here are the DMA initialisation properties (HAL) for the H7:

hdma_spi2_tx.Instance = DMA1_Stream0;
    hdma_spi2_tx.Init.Request = DMA_REQUEST_SPI2_TX;
    hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.Mode = DMA_CIRCULAR;
    hdma_spi2_tx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

And then for my working STM32F407:

hdma_spi2_tx.Instance = DMA1_Stream4;
    hdma_spi2_tx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_spi2_tx.Init.Mode = DMA_CIRCULAR;
    hdma_spi2_tx.Init.Priority = DMA_PRIORITY_LOW;
    hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;

I have noticed within DMA1_Stream0_IRQHandler for the STM32H7 if I look at the data under hdma_spi2_tx.Instance address I can see FCR register flag intermittently set to DMA_SxFCR_FS_2, this does not happen on the F407. All other regs are the same.

Here are the registers I was observing (before and after HAL_DMA_IRQHandler I see FCR have intermittently 0x20, FIFO empty)

 0693W0000059yY6QAI.png

> I am still experiencing DMA DMA1_Stream0_IRQHandler immediately re-entering

How do you know it's immediate?

Do you toggle a pin to observe the interrupt entry/exit, comparing to the I2S stream?

JW

Setting a pin on ISR entry and resetting it on ISR exit. The pin ends up outputing a signal at around 200 kHz, the speed of this signal is proportional to the D2PPRE2 clock rate

As I've said above

> This has to be WS/[buffer halfsize] so something is very broken.

What's the fill of the signal, i.e. what percentage of time is spent in the ISR?

Find out why does it interrupt so often. Strip down the program to absolute minimum to avoid any interference from other parts; maybe better, start a new minimum project, with only the single I2S.

JW