0
\$\begingroup\$

I want to sample a signal at 44kHz.

I've tried to set up the ADC with DMA and calculated the conversion time so that I get that 44kHz sampling frequency.

My thinking is that with continuous mode ADC, after each ADC conversion immediately the next one starts, so by choosing the conversion time I get my 44kHz = (1/conv_time).

Turns out this didn't work, I got much fewer samples than expected. Only when I set up the ADC triggered by a timer I got my correct 44kHz frequency.

Can anyone explain to me where is my thinking wrong with the first attempt (without timer)?

(I'm using STM32L432KC MCU)

Straight from the reference manual: The total conversion time is calculated as follows: TCONV = Sampling time + 12.5 ADC clock cycles Example: With FADC_CLK = 80 MHz and a sampling time of 2.5 ADC clock cycles: TCONV = (2.5 + 12.5) ADC clock cycles = 15 ADC clock cycles = 187.5 ns (for fast channels)

So sampling freq = 1/Tconv if I understand it correctly (https://www.st.com/resource/en/reference_manual/dm00151940-stm32l41xxx42xxx43xxx44xxx45xxx46xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf page 389)

My code since you asked for it:

float32_t FFT_buffer_in[2048];
uint32_t analogInput;
.
.
.
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if (i<2048)
    {
        FFT_buffer_in[i] = (3.3*analogInput)/4095;
        i++;
    }
    else
    {
        i = 0;
        doFFT();
    }

}
.
.
.
int main(void)
{
.
.
.
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_DAC1_Init();
  MX_TIM2_Init();
  MX_TIM1_Init();
  MX_USART2_UART_Init();

   arm_rfft_fast_init_f32(&fft_handler, 2048);

   HAL_ADC_Start_DMA(&hadc1, &analogInput, 1);
.
.
.
}
//ADC SETUP:
static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Common config
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel
  */
  sConfig.Channel = ADC_CHANNEL_6;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}

These are just fragments from the code, If more is needed - let me know

\$\endgroup\$
11
  • \$\begingroup\$ What ADC??? They are not all created equal.... Some ADC's do not have a 'fixed' conversion time, some do. Without sharing the datasheet of the device in question, nobody can tell you much of what's going on here. \$\endgroup\$
    – Kyle B
    Mar 5 at 20:55
  • \$\begingroup\$ Internal ADC of STM32L432KC. To my understanding, it does have a fixed conversion time but I can be wrong, that's why I'm asking \$\endgroup\$
    – blankMCU
    Mar 5 at 21:04
  • 1
    \$\begingroup\$ How exactly you configured the ADC to adjust the conversion time? We are not mind-readers you know, and your code may have bugs. Can you please post the source code? \$\endgroup\$
    – Justme
    Mar 5 at 21:09
  • 1
    \$\begingroup\$ "so by choosing the conversion time", how did you choose it, exactly? \$\endgroup\$ Mar 5 at 21:10
  • \$\begingroup\$ Make it easy for those you want to help you. Convert the STM part number into a datasheet link. They're usually hundreds of pages so give a page number too. \$\endgroup\$
    – Transistor
    Mar 5 at 21:15
3
\$\begingroup\$

You really mustn't do something as complex as an fft in a callback-that breaks basically all timing constraints. Your callback is probably still doing the fft while the next conversion is complete. This alone suffices to break things.

Other than that, on chips like the stm32, interrupts can (and do) have variable latency, and also other interrupts can preempt yours or still be running with a higher priority.

The timer is an actual deterministic element meant for exactly this, triggering things at deterministic intervals. Using it is not only convenient, but the only way to guarantee constant rate of anything in your MCU.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you for the answer, appreciate it! The FFT is done only AFTER the samples are acquired but other things that you mentioned make sense. \$\endgroup\$
    – blankMCU
    Mar 6 at 16:27
  • \$\begingroup\$ DoFFT is part of your callback \$\endgroup\$
    – mmmm
    Mar 7 at 12:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.