首页 > 解决方案 > STM32 SPI DMA 在外部中断处传输

问题描述

我有一个通过 SPI 连接到 STM32H7x MCU 的 ADC 芯片。MCU 是主 spi,但只要准备好传输新值,ADC 就会发送“数据就绪”信号。我的旧实现(有效)监听外部中断(数据就绪)并调用 SPI 传输(KEIL 驱动程序)来读回值。它传输固定的 32 字节并读取 32 字节同步。到目前为止一切正常。

问题是当我将采样率提高到每秒 20,000 个样本以上时,它变得太慢并且 cpu 被中断太多次,我无能为力。我的目标是每秒达到 32K 样本。Keil spi驱动使用DMA under layer但仍然每秒中断cpu 32K次太多了。

我认为应该有一种方法可以配置低级 DMA 以在外部事件(数据就绪信号)发生时开始传输。我的目标是设置 DMA 以多次读取每个 DRDY 信号的值(例如 128 次并将其写入缓冲区),然后给我一个中断。这样我只需要每秒处理 32000/128 = 250 次,这是合理的。

我使用 CubeMX 创建低级 SPI DMA 驱动程序,到目前为止我有这样的东西:

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)


{

  GPIO_InitTypeDef GPIO_InitStruct;
  HAL_DMA_MuxSyncConfigTypeDef pSyncConfig;
  if(hspi->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();


/**SPI2 GPIO Configuration    
PB4     ------> SPI2_NSS
PA12    ------> SPI2_SCK
PC2     ------> SPI2_MISO
PC3     ------> SPI2_MOSI 
*/
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF7_SPI2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

/* SPI2 DMA Init */
/* SPI2_TX Init */
hdma_spi2_tx.Instance = DMA1_Stream0;
hdma_spi2_tx.Init.Request = DMA_REQUEST_SPI2_TX;
hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi2_tx.Init.MemInc = DMA_MINC_DISABLE;
hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_spi2_tx.Init.Mode = DMA_NORMAL;
hdma_spi2_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK)
{
  _Error_Handler(__FILE__, __LINE__);
}

pSyncConfig.SyncSignalID = HAL_DMAMUX1_SYNC_EXTI0;
pSyncConfig.SyncPolarity = HAL_DMAMUX_SYNC_RISING_FALLING;
pSyncConfig.SyncEnable = ENABLE;
pSyncConfig.EventEnable = DISABLE;
pSyncConfig.RequestNumber = 1;
if (HAL_DMAEx_ConfigMuxSync(&hdma_spi2_tx, &pSyncConfig) != HAL_OK)
{
  _Error_Handler(__FILE__, __LINE__);
}

__HAL_LINKDMA(hspi,hdmatx,hdma_spi2_tx);

/* SPI2_RX Init */
hdma_spi2_rx.Instance = DMA1_Stream1;
hdma_spi2_rx.Init.Request = DMA_REQUEST_SPI2_RX;
hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_spi2_rx.Init.Mode = DMA_CIRCULAR;
hdma_spi2_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH;
hdma_spi2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK)
{
  _Error_Handler(__FILE__, __LINE__);
}

pSyncConfig.SyncSignalID = HAL_DMAMUX1_SYNC_DMAMUX1_CH0_EVT;
pSyncConfig.SyncPolarity = HAL_DMAMUX_SYNC_NO_EVENT;
pSyncConfig.SyncEnable = DISABLE;
pSyncConfig.EventEnable = ENABLE;
pSyncConfig.RequestNumber = 1;
if (HAL_DMAEx_ConfigMuxSync(&hdma_spi2_rx, &pSyncConfig) != HAL_OK)
{
  _Error_Handler(__FILE__, __LINE__);
}

__HAL_LINKDMA(hspi,hdmarx,hdma_spi2_rx);

/* SPI2 interrupt Init */
HAL_NVIC_SetPriority(SPI2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI2_IRQn);


/* USER CODE BEGIN SPI2_MspInit 1 */

  /* USER CODE END SPI2_MspInit 1 */
  }

}

SPI 和 DMA 初始化如下:

static void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn);
  /* DMA1_Stream1_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);
  /* DMAMUX1_OVR_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMAMUX1_OVR_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMAMUX1_OVR_IRQn);

}

/* SPI2 init function */
static void MX_SPI2_Init(void)
{

  /* SPI2 parameter configuration*/
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_32BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 7;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  hspi2.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
  hspi2.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
  hspi2.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  hspi2.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  hspi2.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
  hspi2.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
  hspi2.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
  hspi2.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
  hspi2.Init.IOSwap = SPI_IO_SWAP_DISABLE;

  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

外部数据就绪连接到 PB0 GPIO。从这一点来看,我不知道如何告诉 DMA 监听外部事件以及如何设置 TX/RX 缓冲区地址,以及如何告诉 DMA 我有一个能够读取 128 个块的 RX 缓冲区以及如何配置 DMA 以引发中断当 RX 缓冲区已满时。

重点是不使用 HAL_SPI_Transmit_DMA 功能。因为这需要我手动处理外部 DRDY 中断,我最终会得到与我已经拥有的相同的解决方案。

标签: stm32spidma

解决方案


我不完全了解 STM32H7 DMA 的实现。

在其他 STM32 控制器上,您可以选择 DMA 触发中断,例如定时器溢出。问题是,在触发器开始您的第一次 DMA 传输后,它必须更改为无法自动完成的 SPI RX 缓冲区空触发器。

您还必须检查 EXT 引脚中断是否可以用作 DMA 触发器。

H7 DMA 模块比我通常使用的 DMA 控制器先进得多,因此有可能解决上述任务。


推荐阅读