cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H7A3 SAI with DMA for real time DAC emulation with no software overhead

Linas L
Senior II

Hello,

In my project I need to do two 10kHz feedback loops, and I want to make as much in hardware, so I don't have to waste time communicating with dual channel DAC.

So my bright idea is to use a fast 200ksps 24b DAC for generating analog setpoints. In theory, I should be able to connect PCM DAC to SAI, and use circular DMA to transfer my setpoints to DAC with basically no software interaction. This means I will have an array with two variables aligned to 4 inside memory, and DMA should keep copying data to DAC.

Tested this method with software transfer and all looks great, but having trouble configuring SAI to work with DMA in circular mode

  hsai_BlockA1.Instance = SAI1_Block_A;
  hsai_BlockA1.Init.Protocol = SAI_FREE_PROTOCOL;
  hsai_BlockA1.Init.AudioMode = SAI_MODEMASTER_TX;
  hsai_BlockA1.Init.DataSize = SAI_DATASIZE_24;
  hsai_BlockA1.Init.FirstBit = SAI_FIRSTBIT_MSB;
  hsai_BlockA1.Init.ClockStrobing = SAI_CLOCKSTROBING_FALLINGEDGE;
  hsai_BlockA1.Init.Synchro = SAI_ASYNCHRONOUS;
  hsai_BlockA1.Init.OutputDrive = SAI_OUTPUTDRIVE_ENABLE;
  hsai_BlockA1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
  hsai_BlockA1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_FULL;
  hsai_BlockA1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_192K;
  hsai_BlockA1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
  hsai_BlockA1.Init.MonoStereoMode = SAI_STEREOMODE;
  hsai_BlockA1.Init.CompandingMode = SAI_NOCOMPANDING;
  hsai_BlockA1.Init.TriState = SAI_OUTPUT_NOTRELEASED;
  hsai_BlockA1.Init.PdmInit.Activation = DISABLE;
  hsai_BlockA1.Init.PdmInit.MicPairsNbr = 0;
  hsai_BlockA1.Init.PdmInit.ClockEnable = SAI_PDM_CLOCK1_ENABLE;
  hsai_BlockA1.FrameInit.FrameLength = 64;
  hsai_BlockA1.FrameInit.ActiveFrameLength = 32;
  hsai_BlockA1.FrameInit.FSDefinition = SAI_FS_STARTFRAME;
  hsai_BlockA1.FrameInit.FSPolarity = SAI_FS_ACTIVE_LOW;
  hsai_BlockA1.FrameInit.FSOffset = SAI_FS_FIRSTBIT;
  hsai_BlockA1.SlotInit.FirstBitOffset = 0;
  hsai_BlockA1.SlotInit.SlotSize = SAI_SLOTSIZE_32B;
  hsai_BlockA1.SlotInit.SlotNumber = 2;
  hsai_BlockA1.SlotInit.SlotActive = SAI_SLOTACTIVE_ALL;
  if (HAL_SAI_Init(&hsai_BlockA1) != HAL_OK)
  {
    Error_Handler();
  }

Based on RM, all I have to do is to set correct addresses, data size, and enable DMA, enable SAI, and it should work, but what I get is immediate TE interrupt (transfer Error) even when SAI is not enabled !

To configure the audio subblock for DMA transfer, set DMAEN bit in the SAI_xCR1 register.
The DMA request is managed directly by the FIFO controller depending on the FIFO
threshold level (for more details refer to Section 56.4.9: Internal FIFOs). DMA transfer
direction is linked to the SAI audio subblock configuration:
• If the audio block operates as a transmitter, the audio block FIFO controller outputs a
DMA request to load the FIFO with data written in the SAI_xDR register.
• If the audio block is operates as a receiver, the DMA request is related to read
operations from the SAI_xDR register.
 
Follow the sequence below to configure the SAI interface in DMA mode:
1. Configure SAI and FIFO threshold levels to specify when the DMA request is launched.
2. Configure SAI DMA channel.
3. Enable the DMA.
4. Enable the SAI interface.
Note: Before configuring the SAI block, the SAI DMA channel must be disabled.

LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2);
 
LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_6, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_6, LL_DMA_PRIORITY_HIGH);
LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_6, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_6, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_6, LL_DMA_PDATAALIGN_WORD); LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_6, LL_DMA_MDATAALIGN_WORD);
 
  LL_DMA_SetPeriphRequest(DMA2,LL_DMA_STREAM_6,LL_DMAMUX1_REQ_SAI1_A);
 
LL_DMA_ConfigTransfer(DMA2, LL_DMA_STREAM_6, LL_DMA_DIRECTION_MEMORY_TO_PERIPH|
                                               LL_DMA_PRIORITY_HIGH             |
                                               DMA_SxCR_PFCTRL             |
                                               LL_DMA_PERIPH_NOINCREMENT        |
                                               LL_DMA_MEMORY_INCREMENT          |
                                               LL_DMA_PDATAALIGN_WORD           |
                                               LL_DMA_MDATAALIGN_WORD);
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_6, 256);
LL_DMA_ConfigAddresses(DMA2, LL_DMA_STREAM_6, (uint32_t)&aSRC_Const_Buffer, (uint32_t)0x40015820, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
  LL_DMA_EnableIT_TC(DMA2, LL_DMA_STREAM_6);
  LL_DMA_EnableIT_TE(DMA2, LL_DMA_STREAM_6);
 
  LL_DMA_EnableFifoMode(DMA2,LL_DMA_STREAM_6);
 
  HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);

And this is how it looks to start:

  LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_6);
 
  MX_SAI1_Init();
 
  SAI1_ACR1 |= SAI_xCR1_DMAEN;
  MX_DMA_Init();
 
  LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_6);// (imidiatly generates TE DMA IRQ)
 
  __HAL_SAI_ENABLE(&hsai_BlockA1);
 

So I have 2 questions:

  1. Can I have Circular DMA mode ? (since where is two modes DMA_SxCR_PFCTRL or LL_DMA_MODE_CIRCULAR. if Pheripheram mode is used, this means I will have to reenable DMA at TC ? or use ping pong DMA mode
  2. Does any one see a mistake with my code? Why DMA is triggered immediately when I enable stream, while SAI is disabled?
  3. Maybe where is simple way to emulate DAC with no cumunication handling by software ? since with SPI dac it is very hard, you have to set CS low, enable single tranfer and on TC IRQ set CS high, or use chained timers with PWM to trigger DMA transfer and use PWM as chip select, so this is also looks a bit hard to do.

Hope some one sees my mistake ,

1 REPLY 1
Linas L
Senior II

Problem was found. Switched to AXI RAM so DMA could reach it.

Or need to use MDMA for that :)