使用 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
注册)。
我正在使用 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
andMIDI>1
the SS is pulsed inactive between data frames, and kept inactive for a number of SPI clock periods defined by theMIDI
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
注册)。