使用 DMA 提高 SPI6 的性能

Increasing performance with DMA for SPI6

我正在使用 STM32H7 和 HAL 库。在我的板上,SPI6 用于与外部 DAC (DAC8734) 通信。通信工作得很好(使用 DMA)。目标是每 8µs 更新一次 DAC 以模拟交流信号。为此,我使用 TIM15 基本定时器。定时器在其中断内部调用 DMA 的传输功能。传输完成后,缓冲区将在 DMA_Interrupt_Handler 中递增,因为我无法连续向 DAC 发送数据(DAC 需要 high/low 在 CS 线上触发以更新其通道)。有什么方法可以提高我的表现吗?

这里是 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();
}

这里是 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);

在 TIM15_IRQHandler 内部,我调用 DMA 传输:

    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); 

传输后调用 BDMA IRQ 处理程序:

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);

我现在的问题是,我并没有真正获得任何性能提升。我假设这是因为我手动增加了我的 Crrct_Size_Buffer,但我不能一次发送所有数据,因为 DAC(它需要它的 high/low 触发器)。有谁知道如何提高性能? 如果您需要更多信息,请随时询问。抱歉我的英语不好,我不是本地人:)

感谢您的帮助!

看起来 STM32H7 SPI 控制器能够在每 24 位帧之后自行发送 CS 脉冲。关键参数是SPI->CFG1中的DSIZE字段和SPI->CFG2中的slave select管理位。

设置DSIZE告诉控制器一帧中的位数。将其设置为 23(比实际帧大小小一)有两种效果。

  • 当向TXDR(发送数据寄存器)写入一个完整的32位uint32_t值时,最高8位将被忽略,24位将被移出到MOSI pin.
  • CS 可以在每 24 位帧后脉冲。

来自参考手册部分Slave select (SS) 管脚管理

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).

如果SPI发送FIFO中有更多的数据帧,它会开始移出下一个,但是没有了,所以它会等待下一次写入TXDR

现在,每 8µs 写入一个 32 位寄存器就足以触发整个传输序列。它可以通过定时器和 DMA 通道实现自动化,每次传输都不需要软件中断。

设置DMA通道从内存缓冲区(应该填充到32位)复制32位到SPI发送寄存器。 让 DMA 传输由定时器更新事件触发,而不是由 SPI 触发。 将定时器设置为 125 kHz,并在更新事件上生成 DMA 请求(UDE 位在DIER注册)。