1
votes

In my project, I am using Master SPI communication to get analog data from external ADC. My MCU is STM32F746ZGTX. My system needs to work real time so I used SPI DMA Receive and Transmit functions.

I am reading all external ADC data correctly with SPI polling without using DMA. In SPI polling, I am first sending control byte to external ADC, in that time program is waiting in while(SPI_Ready) loop and then starts to receive all ADC data. This scenario works perfectly.

But I do not want to wait in while(SPI_Ready) loop in my every ADC reading. Because It affects my real time calculations. That's why I switched my functions to DMA.

My new algorithm is like that in below:

  1. Generate External GPIO Interrupt with falling edge trigger to sense data ready output of external ADC.
  2. Make chip select pin low to start communication with external ADC
  3. Send read command to External ADC with HAL_SPI_Transmit_DMA() function.
  4. In HAL_SPI_TxCpltCallback function, trigger HAL_SPI_Receive_DMA()
  5. In HAL_SPI_RxCpltCallback function, buffer received ADC data and make chip select pin high to terminate communication.

When I use this algorithm, I am getting always 0xFF values in my ADC buffer. It seems like even if ADC is not sending raw data, because of triggering receive DMA, My MCU sends clock and sense all logic high signal as a received data.

I am sharing my codes in below. If you have any sugggestion where I am wrong, please share your opinions.

 /* SPI1 init function */
 static void MX_SPI1_Init(void)
 {

   hspi1.Instance = SPI1;
   hspi1.Init.Mode = SPI_MODE_MASTER;
   hspi1.Init.Direction = SPI_DIRECTION_2LINES;
   hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
   hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
   hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
   hspi1.Init.NSS = SPI_NSS_SOFT;
   hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
   hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
   hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
   hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
   hspi1.Init.CRCPolynomial = 7;
   hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
   hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
   if (HAL_SPI_Init(&hspi1) != HAL_OK)
   {

   }
  }

  void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
  {

     GPIO_InitTypeDef GPIO_InitStruct;
     if(hspi->Instance==SPI1)
     {
        /* USER CODE BEGIN SPI1_MspInit 0 */

        /* USER CODE END SPI1_MspInit 0 */
        /* Peripheral clock enable */
        __HAL_RCC_SPI1_CLK_ENABLE();

        /**SPI1 GPIO Configuration    
        PA5     ------> SPI1_SCK
        PA6     ------> SPI1_MISO
        PB5     ------> SPI1_MOSI 
        */
        GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = GPIO_PIN_5;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

        /* SPI1 DMA Init */
        /* SPI1_RX Init */
        hdma_spi1_rx.Instance = DMA2_Stream0;
        hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;
        hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_spi1_rx.Init.Mode = DMA_NORMAL;
        hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
        {

        }

        __HAL_LINKDMA(hspi,hdmarx,hdma_spi1_rx);

        /* SPI1_TX Init */
        hdma_spi1_tx.Instance = DMA2_Stream3;
        hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
        hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
        hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_spi1_tx.Init.Mode = DMA_NORMAL;
        hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
        hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
        if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
        {

        }

         __HAL_LINKDMA(hspi,hdmatx,hdma_spi1_tx);

        /* SPI1 interrupt Init */
        HAL_NVIC_SetPriority(SPI1_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(SPI1_IRQn);
        /* USER CODE BEGIN SPI1_MspInit 1 */

        /* USER CODE END SPI1_MspInit 1 */

        /* USER CODE BEGIN SPI1_MspInit 1 */

        /* USER CODE END SPI1_MspInit 1 */
        }

        }

void HAL_SPI_MspDeInit(SPI_HandleTypeDef* hspi)
{

  if(hspi->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspDeInit 0 */

  /* USER CODE END SPI1_MspDeInit 0 */
  /* Peripheral clock disable */
  __HAL_RCC_SPI1_CLK_DISABLE();

  /**SPI1 GPIO Configuration    
   PA5     ------> SPI1_SCK
   PA6     ------> SPI1_MISO
   PB5     ------> SPI1_MOSI 
  */
  HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6);

  HAL_GPIO_DeInit(GPIOB, GPIO_PIN_5);

  /* USER CODE BEGIN SPI1_MspDeInit 1 */
  /* Peripheral DMA DeInit*/
  HAL_DMA_DeInit(hspi->hdmarx);
  HAL_DMA_DeInit(hspi->hdmatx);

  /* Peripheral interrupt Deinit*/
  HAL_NVIC_DisableIRQ(SPI2_IRQn);
 /* USER CODE END SPI1_MspDeInit 1 */
 }
}

/* External Interrupt for data ready output of ADC */
void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */

  /* USER CODE END EXTI15_10_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  adc_selectADC(); /* Make Chip Select pin low */
  HAL_SPI_Transmit_DMA (&hspi1, &controlByte, 1);
}

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
   if (hspi->Instance == hspi1.Instance)
   {
       /* Transmit is completed */
       /* Trigger receive DMA to get raw data from external ADC */
       HAL_SPI_Receive_DMA (&hspi1, (uint8_t*)adcRecBuffer, 24);
   }
}

void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
   if (hspi->Instance == hspi1.Instance)
   {
       /* Receive is completed */
       adc_deselectADC(); /* Make Chip Select pin high */
   }         
}

***Working Algorithm without using DMA or Interrupt:***

/* External Interrupt for data ready output of ADC */
void EXTI15_10_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI15_10_IRQn 0 */

  /* USER CODE END EXTI15_10_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
  /* USER CODE BEGIN EXTI15_10_IRQn 1 */

  adc_selectADC(); /* Make Chip Select pin low */

  while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
  HAL_SPI_Transmit(&hspi1, &controlByte, 1,1);
  while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);

  while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
  HAL_SPI_Receive(&hspi1, (uint8_t*)adcRecBuffer, 24, 1);
  while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);

  adc_deselectADC(); /* Make Chip Select pin high*/

}
1
Your code isn't showing what you do for initialising the DMA - clock setup and stream configuration. Please include the initialisation code here as well.Sigve Kolbeinson
Hello. Thanks for correction. I added related code blocks.dredg
So the observed behaviour of the blocking code is that the STM32 clocks out a command byte and reads back 24 bytes. You imply that the DMA code transmits the command byte and then reads 24 bytes, but the ADC doesn't send clock out data on MISO, so the read bytes are all 0xFF - is this correct? This suggest the ADC hasn't received the command, so I would look at the timing of both sets of code - eg the time from CS going low to the transmission of the command - could the ADC not be ready in time and so miss the command? Is the command bytes transmitted the same between the two versions?Sigve Kolbeinson
Yes. Your approach is correct but I can not understand that it is same logic with working one. I send command byte when ADC data ready output comes to me. Yes, command bytes transmitted is exactly same in two version.dredg
So at a high level, the DMA does what it is supposed to - transmit one byte, and receive 24 bytes. Then I would look at the SPI signals - Is the timing between CS going low and the start of transmit the same? How about the timing between end of transmit and start of receive? SPI clock frequency? Is the CS low the entire time?Sigve Kolbeinson

1 Answers

0
votes

SPI normally is full-duplex, meaning that "reading" is actually master generating clocks and transmitting zeroes. In STM HAL implementation, "receive" function just sends the data you have in read buffer. Might be zeroes, might be garbage, which your ADC interprets as some commands and enters some sort of bad state.

Try doing TransmitReceive of 2 25 byte buffers with your command ID first ("control byte"), followed by 24 zero bytes in TX buffer. In response you should get RX buffer of size 25, where first byte can be discarded. This way you need to handle only RXCplt interrupt, where you release ADC CS pin.