cembeddedstm32dmadac

Increasing performance with DMA for SPI6


I am using the STM32H7 and the HAL libraries. On my board the SPI6 is used to communicate with an external DAC (DAC8734). The communication works perfectly fine (with DMA). The goal is to update the DAC every like 8µs to simulate an AC signal. For that I use the TIM15 basic timer. The timer calls inside of its interrupt the transmit function of the DMA. After completion of the transmission the buffer will get incremented in the DMA_Interrupt_Handler, as I am not able to continuously send data to the DAC (DAC needs and high/low trigger on the CS line for updating its channel). Is there any way to increase my performance?

here the code for TIM15:

__HAL_RCC_DMA1_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
__HAL_RCC_BDMA_CLK_ENABLE();

TIM_ClockConfigTypeDef SClockSourceConfigDMA;
TIM_SlaveConfigTypeDef sSlaveConfigDMA;
TIM_MasterConfigTypeDef sMasterConfigDMA;
TIM_IC_InitTypeDef sConfigICDMA;

htim15.Instance = TIM15;                    //TIM15 must be synchron to TIM5         --> 40 MHz, Baseclock is 200 Mhz
htim15.Init.Prescaler = 300;//300;//15;         //Max. for good sin: Pre = 50 & Per = 16 & DIV4
htim15.Init.CounterMode = TIM_COUNTERMODE_UP;
htim15.Init.Period = 5;//4;                     //Period = 5 & Prescaler = 100 für 200 kHz -->
htim15.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim15.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim15) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

SClockSourceConfigDMA.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim15, &SClockSourceConfigDMA) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

if (HAL_TIM_IC_Init(&htim15) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

sSlaveConfigDMA.SlaveMode = TIM_SLAVEMODE_TRIGGER;
sSlaveConfigDMA.InputTrigger = TIM_TS_ITR2;
if (HAL_TIM_SlaveConfigSynchronization(&htim15, &sSlaveConfigDMA) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

sMasterConfigDMA.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfigDMA.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfigDMA) != 
HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

sConfigICDMA.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigICDMA.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigICDMA.ICPrescaler = TIM_ICPSC_DIV1;
sConfigICDMA.ICFilter = 1;
if (HAL_TIM_IC_ConfigChannel(&htim15, &sConfigICDMA, TIM_CHANNEL_1) != 
HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

if (HAL_TIM_IC_ConfigChannel(&htim15, &sConfigICDMA, TIM_CHANNEL_2) != 
HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

if (HAL_TIM_IC_ConfigChannel(&htim15, &sConfigICDMA, TIM_CHANNEL_3) != 
HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

if (HAL_TIM_IC_ConfigChannel(&htim15, &sConfigICDMA, TIM_CHANNEL_4) != 
HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

__HAL_TIM_ENABLE_IT(&htim15, TIM_IT_UPDATE);
__HAL_TIM_ENABLE_IT(&htim15, TIM_IT_CC1);
__HAL_TIM_ENABLE_IT(&htim15, TIM_IT_CC2);
__HAL_TIM_ENABLE_IT(&htim15, TIM_IT_CC3);
__HAL_TIM_ENABLE_IT(&htim15, TIM_IT_CC4);

SystemCoreClockUpdate();
}

here the code for DMA:

//Setting the configuration for the DMA tx --> this is the configuration for         SPI6 as Trigger
hdma_spi6_tx_init.Instance                  = BDMA_Channel2;                        //Choose BDMA, for SPI6 is connected to DMAMUX2
//hdma_spi6_tx_init.DMAmuxChannel->CCR      = 0b1100;                               //Selects SPI6 for DMAMUX2
hdma_spi6_tx_init.Init.Request              = BDMA_REQUEST_SPI6_TX;                 //BDMA (DMAUX2) for TX of SPI6
hdma_spi6_tx_init.Init.Direction            = DMA_MEMORY_TO_PERIPH;                 //Transfering from Memory to Peripherie (2, S.632)
hdma_spi6_tx_init.Init.PeriphInc            = DMA_PINC_ENABLE;                      //Incrementing the address register todo: maybe enable
hdma_spi6_tx_init.Init.MemInc               = DMA_MINC_ENABLE;                      //Incrementing the memory address register
hdma_spi6_tx_init.Init.PeriphDataAlignment  = DMA_PDATAALIGN_BYTE;                  //Data size: Byte, because SPI6 is transferring 8-Bit at the time
hdma_spi6_tx_init.Init.MemDataAlignment     = DMA_MDATAALIGN_BYTE;                  //Memory data size: Byte, because thats the size of the other registers
hdma_spi6_tx_init.Init.Mode                 = DMA_NORMAL;                           //Peripheral flow control mode (S.632)
hdma_spi6_tx_init.Init.Priority             = DMA_PRIORITY_VERY_HIGH;                   //High Priority for transfer
hdma_spi6_tx_init.Init.FIFOMode             = DMA_FIFOMODE_ENABLE;                  //Direct mode for transfer (todo:FIFO enable)
hdma_spi6_tx_init.Init.FIFOThreshold        = DMA_FIFO_THRESHOLD_FULL;              //Wait for full FIFO
hdma_spi6_tx_init.Init.MemBurst             = DMA_MBURST_SINGLE;                    //One byte sized burst for memory
hdma_spi6_tx_init.Init.PeriphBurst          = DMA_PBURST_SINGLE;                    //One byte sized burst for peripheral

//Setting the configuration for the BDMA (S.653 + S.663)
bdma_spi6_init.CPAR  = BDMA_REQUEST_SPI6_TX;            //Peripheral register address for SPI6
bdma_spi6_init.CMAR  = (uint8_t *) Crrct_Size_Buffer;   //Memory register address
bdma_spi6_init.CNDTR = 0xFFFF;//0x1F2;                  //Total number of data to transfer
bdma_spi6_init.CCR  |= 0x3098;
//  Bits for CCR           (0 << 15) ||             //Double-buffer mode off
//                         (0 << 14) ||             //Memory-to-memory mode off
//                         (1 << 13) ||             //Priority level high
//                         (1 << 12) ||             //Priority level high
//                         (0 << 11) ||             //Memory size: 8-Bit
//                         (0 << 10) ||             //Memory size: 8-Bit
//                         (0 <<  9) ||             //Peripheral size: 8-Bit
//                         (0 <<  8) ||             //Peripheral size: 8-Bit
//                         (1 <<  7) ||             //Peripheral as destination, enable Memory increment mode
//                         (0 <<  6) ||             //Memory as source, disable Peripheral increment mode
//                         (0 <<  5) ||             //Circular mode disabled
//                         (1 <<  4) ||             //Read from Memory
//                         (1 <<  3) ||             //Enable transfer error interrupt
//                         (0 <<  2) ||             //Disable half transfer interrupt
//                         (0 <<  1) ||             //Disable transfer complete interrupt
//                         (0 <<  0);

if (HAL_DMA_Init(&hdma_spi6_tx_init) != HAL_OK)
{
    _Error_Handler(__FILE__, __LINE__);
}

__HAL_LINKDMA( hspi, hdmatx, hdma_spi6_tx_init);

Inside the TIM15_IRQHandler I call the DMA transmit:

    SCB_CleanDCache_by_Addr( (uint8_t *) Crrct_Size_Buffer, sizeof(Crrct_Size_Buffer)/sizeof(Crrct_Size_Buffer[0]));    //Clear memory space for TxBuffer

    HAL_SPI_Transmit_DMA(&hspi6, (uint8_t *) Crrct_Size_Buffer, 3); 

After transmission the BDMA IRQ Handler is called:

Crrct_Size_Buffer[0] = Crrct_Size_Buffer[IRQ_Counter[0]+3];                                                         
Crrct_Size_Buffer[1] = Crrct_Size_Buffer[IRQ_Counter[0]+4];
Crrct_Size_Buffer[2] = Crrct_Size_Buffer[IRQ_Counter[0]+5];

if(IRQ_Counter[0] < (NumberOfSamples-1)*3 )                                                                         
{
    IRQ_Counter[0] = IRQ_Counter[0] + 3;
}
else
{
    IRQ_Counter[0] = 0;
}

HAL_GPIO_WritePin(DAC_LDAC_GPIO_Port,DAC_LDAC_Pin, GPIO_PIN_SET);                                                   //LDAC high/low to update the command register
HAL_GPIO_WritePin(DAC_LDAC_GPIO_Port,DAC_LDAC_Pin, GPIO_PIN_RESET);

HAL_DMA_IRQHandler(&hdma_spi6_tx_init);

My problem is right now, that I don't really get any performance increase. I assume it is because I manually increment my Crrct_Size_Buffer, but I cant just send all the data at once, because of the DAC (which need its high/low trigger). Does anyone have any idea how to increase the performance? If you need more information please don't hesitate to ask. Sorry for my bad English I am no native :)

Thank you for your help!


Solution

  • It looks like the STM32H7 SPI controller is capable of pulsing CS after every 24 bit frame on its own. The key parameters are the DSIZE field in SPI->CFG1 and the slave select management bits in SPI->CFG2.

    Setting DSIZE tells the controller the number of bits in one frame. Setting it to 23 (one less than the actual frame size) has two effects.

    From the Reference Manual section Slave select (SS) pin management

    Hardware SS management (SSM = 0)

    SS output enable (SSOE = 1)

    ...

    c) When SSOM=1, SP=000 and MIDI>1 the SS is pulsed inactive between data frames, and kept inactive for a number of SPI clock periods defined by the MIDI value decremented by one (1 to 14).

    If there were more data frames in the SPI transmit FIFO, it would start shifting out the next one, but there are no more, so it will wait for the next write to TXDR.

    Now, writing a single 32 bit register every 8µs is sufficient to trigger the whole transmit sequence. It can be automated with a timer and a DMA channel, requiring no software interrupts for every transmit.

    Set up the DMA channel to copy 32 bits from the memory buffer (which should be padded to 32 bits) to the SPI transmit register. Have the DMA transfer triggered by a timer update event, not by SPI. Set the timer to 125 kHz, and to generate a DMA request on update events (UDE bit in the DIER register).