首页 > 解决方案 > 使用 DMA for SPI6 提高性能

问题描述

我正在使用 STM32H7 和 HAL 库。在我的板上,SPI6 用于与外部 DAC (DAC8734) 通信。通信工作正常(使用 DMA)。目标是每隔 8µs 更新一次 DAC 以模拟 AC 信号。为此,我使用 TIM15 基本计时器。定时器在其中断内部调用 DMA 的发送函数。传输完成后,缓冲区将在 DMA_Interrupt_Handler 中增加,因为我无法连续向 DAC 发送数据(DAC 需要和 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(需要其高/低触发),我不能一次发送所有数据。有谁知道如何提高性能?如果您需要更多信息,请随时询问。对不起我的英语不好,我不是本地人:)

谢谢您的帮助!

标签: cembeddedstm32dmadac

解决方案


看起来 STM32H7 SPI 控制器能够在每 24 位帧之后单独脉冲 CS。关键参数是 中的DSIZE字段SPI->CFG1和从机选择管理位SPI->CFG2

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

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

来自参考手册部分从属选择 (SS) 引脚管理

硬件 SS 管理 ( SSM = 0)

SS 输出使能 ( SSOE = 1)

...

c) 当SSOM=1,SP=000并且SS 在数据帧之间被脉冲非活动状态,并在 减一(1 到 14)MIDI>1的值定义的多个 SPI 时钟周期内保持非活动状态。MIDI

如果 SPI 发送 FIFO 中有更多数据帧,它将开始移出下一个数据帧,但没有更多数据帧,因此它将等待下一次写入TXDR.

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

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


推荐阅读