cancel
Showing results for 
Search instead for 
Did you mean: 

Syncing external ADC / DAC Conversions

JKuzm.1314
Associate II
Just wondering if anyone has information on how to sync external ADC and DAC conversions?
The ADC and DAC are connected as SPI peripherals (separate SPI's) to an STM32G474RE.
The system needs to run in continuous mode with a configurable delay between the DAC and ADC conversions.
DAC leading the ADC.
Can the device's DMAMUX synchronization perhaps be used to accomplish something like this?

Thanks
 
-JK
3 REPLIES 3
MasterT
Senior III

I'd configure two timers as a pair master - slave:reset, to make them run synchronously. Than use master as trigger to SPI read from ADC, likely you have configured SPI as master in this link. Another timer does same things, triggers SPI write to DAC. I'd route  update_event from master timer as external signal for SPI-ADC, and CC1 for another timer. Changing CC1 you can easily shift phase adc-dac sequence.

Thanks for the reply.

The solution which is suggested is similar to what is currently implemented, except that the DAC SPI is running in DMA transfer mode.

Adding some details... A continuous output waveform ( ~100 kHz)  is required from the DAC, that is where the issue lies. Using the DAC in SPI DMA mode, a continuous waveform can be generated, but only if the DMA is configured in "Circular" mode. Otherwise, the SPI transfers are interrupted and a gap appears in the waveform. Unforunately the ADC and DAC lose sync when the DAC SPI DMA is in "Circular" mode.

Is there perhaps a way to operate both the ADC SPI and DAC SPI in DMA "Circular" mode and synchronize the transfers?

Thanks

 

 

"Unforunately the ADC and DAC lose sync when the DAC SPI DMA is in "Circular" mode."

Not clear what is "sync" , is it delay time in between dac conversion and adc conversion or is it phase shift  between outputed waveform and adc data? Regarding first it can't be lost if you configured two timers as suggested. 

I have a few projects that run in sync. Continuous DMA mode always, what is important how you start all system. 

1.  Make DMA buffer for DAC equal to DMA buffer of the ADC. Or at least proportional by integer numbers x2, x3 etc.

2.  Configure slave-reset timer, than master timer .  and here is the trick: call SPI config in the middle of master timer config subfunction, here is the example:

 

static void TIM2_Config(void)
{  
  __HAL_RCC_TIM2_CLK_ENABLE();  

  TIM_ClockConfigTypeDef  sClockSourceConfig  = {0};
  TIM_MasterConfigTypeDef sMasterConfig       = {0};
  TIM_OC_InitTypeDef      sConfigOC           = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  htim2.Instance                = TIM2;
  htim2.Init.Prescaler          = 0x0000;
  htim2.Init.CounterMode        = TIM_COUNTERMODE_UP;
  htim2.Init.Period             = 219; //179; // 0xBB
  htim2.Init.ClockDivision      = TIM_CLOCKDIVISION_DIV1;
//  htim2.Init.RepetitionCounter  = 0;
//  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  TIM2->CR1 |=  (TIM_CR1_ARPE);

  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
    
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }

  // HAL_TIM_PWM_MspInit(htim);
  //  Dma_Tmr(&htim2); 
  
  SPI2_Init();
  
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;  //TIM_TRGO_OC2REF;
  sMasterConfig.MasterSlaveMode     = TIM_MASTERSLAVEMODE_ENABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

// LDAC  
  sConfigOC.OCMode      = TIM_OCMODE_PWM1;
  sConfigOC.Pulse       = 84; 
  // na 4 pixela bol'she 4em CS, gdeto na 40 nsec, nado min = 20nsec
  sConfigOC.OCPolarity  = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCFastMode  = TIM_OCFAST_DISABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  
// NSS-CS  
  sConfigOC.Pulse       = 80;
  sConfigOC.OCPolarity  = TIM_OCPOLARITY_LOW;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
}
void dac_stop(void) 
{
  TIM2->CR1 &= ~(TIM_CR1_CEN);
  //?? CNTR reset ?
  __HAL_TIM_DISABLE_DMA(&htim2, TIM_DMA_UPDATE);
//LDAC  
  if (HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
//CS
  if (HAL_TIM_PWM_Stop(&htim2, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
}

void dac_start(void) 
{
  
  TIM2->CR1 |=  (TIM_CR1_CEN);
  
  __HAL_TIM_ENABLE_DMA(&htim2, TIM_DMA_UPDATE);
//CS
  if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2) != HAL_OK)
  {
    Error_Handler();
  }
//LDAC  
  if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
}

SPI:

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  
  GPIO_InitTypeDef GPIO_InitStruct = { 0};

  static DMA_HandleTypeDef  hdma_tim;

  if(hspi->Instance == SPI2) {
  // SPI2-Tx
  __HAL_RCC_DMA1_CLK_ENABLE();

  __HAL_RCC_SPI2_CLK_ENABLE();  
  __HAL_RCC_GPIOB_CLK_ENABLE();
    /**SPI2 GPIO Configuration    
    PB12     ------> SPI2_NSS
    PB13     ------> SPI2_SCK
    PB15     ------> SPI2_MOSI 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
//    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    hdma_tim.Instance                  = DMA1_Stream7;
    hdma_tim.Init.Channel              = DMA_CHANNEL_3;
    
    hdma_tim.Init.Direction            = DMA_MEMORY_TO_PERIPH;
    hdma_tim.Init.PeriphInc            = DMA_PINC_DISABLE;
    hdma_tim.Init.MemInc               = DMA_MINC_ENABLE;
    hdma_tim.Init.PeriphDataAlignment  = DMA_PDATAALIGN_HALFWORD;
    hdma_tim.Init.MemDataAlignment     = DMA_PDATAALIGN_HALFWORD;
    hdma_tim.Init.Mode                 = DMA_CIRCULAR;
    hdma_tim.Init.Priority             = DMA_PRIORITY_VERY_HIGH;
    hdma_tim.Init.FIFOMode             = DMA_FIFOMODE_DISABLE;


  TIM_HandleTypeDef *htim = &htim2;

  __HAL_LINKDMA(htim, hdma[TIM_DMA_ID_UPDATE], hdma_tim);

  HAL_DMA_Init(htim->hdma[TIM_DMA_ID_UPDATE]);

//  HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);
//  HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);

  HAL_NVIC_SetPriority(DMA1_Stream7_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream7_IRQn);
  }
}


void SPI2_Init(void)
{
  hspi2.Instance                = SPI2;
  hspi2.Init.Mode               = SPI_MODE_MASTER;
  hspi2.Init.Direction          = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize           = SPI_DATASIZE_16BIT;
  hspi2.Init.CLKPolarity        = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase           = SPI_PHASE_1EDGE;
  hspi2.Init.NSS                = SPI_NSS_HARD_OUTPUT;
//  hspi2.Init.NSS                = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler  = SPI_BAUDRATEPRESCALER_2;
  hspi2.Init.FirstBit           = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode             = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation     = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial      = 10;

  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
  __HAL_SPI_ENABLE(&hspi2);
/*
      addr = 0x40003800;
      temp = (*(uint32_t*)addr);
      Serial.print(F("\n\tReg: "));
      Serial.print(addr, HEX);
      prnt_binf(temp);
*/  
}

 

than

TIM2_Config();
Serial.print(F("\tdone."));

htim2.hdma[TIM_DMA_ID_UPDATE]->XferCpltCallback = TransferComplete;
htim2.hdma[TIM_DMA_ID_UPDATE]->XferErrorCallback = TransferError ;
htim2.hdma[TIM_DMA_ID_UPDATE]->XferHalfCpltCallback = TransferHalfComplete;

Serial.print(F("\n\thal_dma_start_it..."));
delay(100);
if (HAL_DMA_Start_IT(htim2.hdma[TIM_DMA_ID_UPDATE], (uint32_t) out, SPI2_Write, (2 *OUT_BUFF)) != HAL_OK)
{
Error_Handler();
}
Serial.print(F("\tdone."));

Serial.print(F("\n\tdac start..."));
delay(100);
dac_start();
Serial.print(F("\n\tSetup done."));

 

 As you can see DAC in use has LDAC pin, to trigger conversion by external GPIO, max5717.