4
votes

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?

1
What is the resulting sample rate given your clock frequency and sample time setting? In many cases the ADC sample rate will be too fast to reasonably handle using interrupts. Using DMA might be a better solution, also for a single value conversion, you can simply read the value directly from the ADC register, handling the interrupt only really makes sense if you were to place multiple conversions in a buffer to be processed asynchronously - and then DMA is still a better solution. You can then handle the much lower rate DMA interrupt on buffers of multiple samples.Clifford
You should certainly set the interrupt priority before enabling the interrupt.Clifford
Go through the check list in step 16 of the tutorial, specifically whether the interrupt is pending in the NVIC.Clifford
Thank you for your advice, Clifford. I'll keep it in mind going forward in my projects. I can't answer the sample rate, I'm not proficient in STM32 yet. I understand that DMA is my best option. The purpose of this specific usage was to get a better understanding of how interrupts are handled. I'm not trying to do anything specific right now, other than understand what my options are, and how to use them. :)user1350065
Yes; I looked at the tutorial, and it too makes the same point about DMA. 601.5 sample time is relatively long..Clifford

1 Answers

4
votes

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.