Search code examples
embeddedmicrocontrollerstm32

STM32F3 Interrupt-driven ADC Callback


I've been trying to learn through the STM32F3Discovery board how to use the ADC with interrupt-driven callback to move ADC data into a user-defined variable.

I have followed two sources to build my code. The Visual-GDB tutorial located here.

As well as the generic CubeMX setup for my device with the HAL library.

Here are relevant functions to my program:

System Clock Initialization

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_PeriphCLKInitTypeDef PeriphClkInit;

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = 16;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12|RCC_PERIPHCLK_ADC34;
  PeriphClkInit.Adc12ClockSelection = RCC_ADC12PLLCLK_DIV1;
  PeriphClkInit.Adc34ClockSelection = RCC_ADC34PLLCLK_DIV1;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

ADC Initialization

static void MX_ADC1_Init(void)
{
  ADC_MultiModeTypeDef multimode;
  ADC_ChannelConfTypeDef sConfig;

  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;

  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  multimode.Mode = ADC_MODE_INDEPENDENT;
  if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.SamplingTime = ADC_SAMPLETIME_601CYCLES_5;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;

  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
}

IRQ handling, callback & main program loop

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
    adcVal1 = HAL_ADC_GetValue(AdcHandle);
}

void ADC_IRQHandler()
{
    HAL_ADC_IRQHandler(&hadc1);
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();

  MX_GPIO_Init();
  MX_ADC1_Init();

  HAL_ADC_Start_IT(&hadc1); // REF1
  HAL_NVIC_EnableIRQ(ADC1_2_IRQn); // REF2
  HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
  while (1)
  {
  }
}

My expectation is that my ADC should be set up in regular, constant conversion mode. Lines marked with REF1 and REF2 should enable end of conversion interrupts, which my ADC_IRQHandler and HAL_ADC_ConvCpltCallback should subsequently be invoked, storing my hadc1->Instance->DR register value in variable adcVal1.

When debugging, the hadc1->Instance->DR register will update as expected, reading the correct voltage values that I set on the ADC channel. So I know that the ADC read is functioning correctly. When setting a breakpoint in my callback function, the function is never called, and so I can conclude there is an error in my logic in initiating or handling the ADC IRQ, so adcVal1 is always 0.

Can anyone provide correct setup of IRQ handling for my device, or point out my error?


Solution

  • The IRQ handler name ADC_IRQHandler is correct for the STM32F407 used in the tutorial, which has a single ADC, but you are using a different part - the name differs, so your handler is not really a handler - just an unused function. If you do not override the appropriate handler with a function of the same name, the default "do nothing" handler will be invoked.

    The symbol naming convention for IRQ handlers is straightforward, IRQ XXXXX_IRQn, has a handler called XXXXX_IRQHandler, so for ADC1_2_IRQn, the handler is ADC1_2_IRQHandler

    To be sure for your specific target, check the the interrupt handler weak symbols defined in the file normally named startup_stm32f3xxx.s (where the xxx part defines your specific target - startup_stm32f303x8.s for example).

    Note that the header stm32sxxx.h (stm32f303x8.h for example):

    /* Aliases for __IRQn */
    #define ADC1_IRQn           ADC1_2_IRQn
    ...
    
    /* Aliases for __IRQHandler */
    #define ADC1_IRQHandler           ADC1_2_IRQHandler
    ...
    

    Using these aliases makes sense only if you are not using the other peripheral with interrupts, since you can only have one handler for both peripherals.