1
votes

I have a ADC chip connected via SPI to STM32H7x MCU. MCU is master spi, but ADC sends "Data ready" signal whenever new values are ready to transfer. My old implementation (which works) listens to external interrupt (Data ready) and calls SPI transfer (KEIL driver) to read back values. It transfers fixed 32 bytes and reads 32 bytes sync. Everything works fine up to here.

Problem is when I increase sampling rate above 20,000 samples per second, it gets too slow and cpu is getting interrupted too many times and I can't do anything else. My goal is to go up to 32K sample per second. Keil spi driver uses DMA under layer but still interrupting cpu 32K times per second is too much.

I think there should be a way to configure low level DMA to start transferring when external event (Data ready signal) happens. My goal is to setup DMA in a way to reads values back on every DRDY signal for number of times (for example 128 time and writes it on a buffer) and then gives me an interrupt. This way I only have to deal with 32000/128 = 250 times per second which is quit reasonable.

I use CubeMX to create low level SPI DMA driver, so far I have something like this:

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 and DMA initializing goes as:

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

}

External Data ready are connected to PB0 GPIO. From this point I have no clue how to tell DMA to listen to external event and how to set TX/RX buffer addresses, and also how to tell DMA I have a RX buffer capable of 128 reading blocks and how to config DMA to raise interrupt when RX buffer are full.

Whole point is to not using HAL_SPI_Transmit_DMA function. Because that requires that I handle external DRDY interrupt manually, and I will end up with same solution that I already have.

2
You use heavy libraries which add massive overhead. Start doing it using registers and you will be OK. Just short interrupt routine which starts the transmission. on 2000MHz clock you have almost 6500 clocks available and you need 50-700___________
My question was about configuring DMA, I know I can remove extra stuff from SPI driver. I simply don't like to make 32K interrupts per second into mcu even if mcu has enough cycles. Other parts of software doing heavy calculations (mostly FFT and matrix multiplication) and 32K interrupts still upsets whole system.Keivan

2 Answers

0
votes

I don't know the STM32H7 DMA implementation exactly.

On other STM32 controllers you can select the DMA trigger interrupt to be e.g. a timer overflow. Problem is, after the trigger has started your first DMA transfer it has to be changed to SPI RX buffer empty trigger which can not be done automatically.

Also you have to check if the EXT Pin interrupt can be used as a DMA trigger.

The H7 DMA module is much more advanced then the DMA controllers I usually work with so there could be the possibility to solve the tasks mentioned.

0
votes

For H7 myabe you must open interrupt, clear PR will trig sync operation. This is a very small feature. No interrupts saved, just SPI operations saved (using DMA)