STM32 ADC MULTI CHANNEL WITHOUT DMA

In the past I have covered ADC Multiple channels in STM32 with DMA. Although, the process was fine and DMA works pretty good, users were experiencing the problem with the while loop. Some users reported that the while loop never runs, and they were correct too. When the ADC channel frequency is high, the DMA interrupts gets triggered very often and the control always remains in the callback function, and the while loop remains inactive forever.

So I have decided to write this tutorial, in which I will cover the multiple channels but without the use of DMA. Although with the new CubeMX this is not an option by default, so we will modify some inbuilt functions. This will not only solve the problem with the while loop, but it will also let you use any channel of ADC at any point in the code.
Basically, you do the conversion of whatever channel you want, and whenever you want.

CUBEMX SETUP

The ADC Setup is shown below

  • I have selected 3 channels i.e CHANNEL 0, CHANNEL 1 and the TEMP SENSOR CHANNEL.
  • Since we are using multiple channels, we need to enable the Scan Conversion Mode
  • I have also enabled the Continuous Conversion, it’s not needed though. You can disable it if you want

  • As I am using 3 channels, select the Rank as 3
  • I have set the different Sampling Time for each channel here

That’s all for the CubeMX setup. Now let’s see the code



Some Insight into the code

First of all we will modify the generated ADC_INIT function as shown below

 
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 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  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;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
//  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
//  */
//  sConfig.Channel = ADC_CHANNEL_0;
//  sConfig.Rank = 1;
//  sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
//  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
//  {
//    Error_Handler();
//  }
//  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
//  */
//  sConfig.Channel = ADC_CHANNEL_1;
//  sConfig.Rank = 2;
//  sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
//  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
//  {
//    Error_Handler();
//  }
//  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
//  */
//  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
//  sConfig.Rank = 3;
//  sConfig.SamplingTime = ADC_SAMPLETIME_112CYCLES;
//  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
//  {
//    Error_Handler();
//  }
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */
  • I have commented Out the part where the channels gets configured.
  • So the channels can not be configured in the beginning.
  • Also copy these, since we will write separate functions and paste them there
  • Also mekae sure to change the number of conversions to 1, which was 3 by default (hadc1.Init.NbrOfConversion = 1)

 
void ADC_Select_CH0 (void)
{
	ADC_ChannelConfTypeDef sConfig = {0};
	  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
	  */
	  sConfig.Channel = ADC_CHANNEL_0;
	  sConfig.Rank = 1;
	  sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
	  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
	  {
	    Error_Handler();
	  }
}

void ADC_Select_CH1 (void)
{
	ADC_ChannelConfTypeDef sConfig = {0};
	  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
	  */
	  sConfig.Channel = ADC_CHANNEL_1;
	  sConfig.Rank = 1;
	  sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES;
	  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
	  {
	    Error_Handler();
	  }
}

void ADC_Select_CHTemp (void)
{
	ADC_ChannelConfTypeDef sConfig = {0};
	  /** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
	  */
	  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
	  sConfig.Rank = 1;
	  sConfig.SamplingTime = ADC_SAMPLETIME_112CYCLES;
	  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
	  {
	    Error_Handler();
	  }
}
  • In the above code, I have created 3 functions for selecting 3 channels of ADC
  • I have only copied the respective code in each channel
  • Also make sure that the rank of each channel is set to 1 (sConfig.Rank=1)


  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

	  ADC_Select_CH0();
	  HAL_ADC_Start(&hadc1);
	  HAL_ADC_PollForConversion(&hadc1, 1000);
	  ADC_VAL[0] = HAL_ADC_GetValue(&hadc1);
	  HAL_ADC_Stop(&hadc1);

	  ADC_Select_CH1();
	  HAL_ADC_Start(&hadc1);
	  HAL_ADC_PollForConversion(&hadc1, 1000);
	  ADC_VAL[1] = HAL_ADC_GetValue(&hadc1);
	  HAL_ADC_Stop(&hadc1);

	  ADC_Select_CHTemp();
	  HAL_ADC_Start(&hadc1);
	  HAL_ADC_PollForConversion(&hadc1, 1000);
	  ADC_VAL[2] = HAL_ADC_GetValue(&hadc1);
	  HAL_ADC_Stop(&hadc1);

	  Temp = ((3.3*ADC_VAL[2]/4095 - V25)/Avg_Slope)+25;
	  HAL_Delay (1000);

  }

Now in the while loop, we perform the following operations

  • Select the channel you want to convert
  • Start the ADC
  • Poll for the conversion to complete
  • Get the ADC Value
  • Stop the ADC

This is basically all. So whenever you need to convert a channel, you follow the set of steps (line 7 – line 11), and for the rest of the time, you can continue with any other operation.



Result

You can see above that all the 3 channels of ADC shows different values. To see the full working, check the video below

Check out the Video Below








Info

You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

12 Comments. Leave new

  • Thank you

    Reply
  • Hello,
    I have a question regarding the Sampling time in the functions ADC_Select. Why you put differnt sampling times for each channel? What implies the term cycles in the sampling time of the ADC? Why did you put 28Cycles and 84 cycles and not 3 Cycles and 15 Cycles?
    Thank you in advance,

    Reply
  • Alternatively, the discontinuous mode could be used together with the code generated by CubeMx. The discontinuous mode conversions could be set to 1 and then HAL_ADC_Start(&hadc1); would have to be called before the next channel read, without changing the channel config each time. This would make it a bit faster, though the order of reads would matter.

    Reply
  • Hello, I apply the same operations on the STM32F767ZI NUCLEO card, but when I say RUN, the first value comes, but a fixed value comes, I cannot receive data on other channels. Is there an exception in the f7 series?

    Reply
    • Hello, I have the same board that you and if you follow correctly the video you can’t have troubles, meabe you didn’t stop the ADC, remember, you need start and stop the ADC, every time that you want see the value of other channels

      Reply
  • thanks a lot made it much simpler

    Reply
  • keilc function //adc_select_channel// is invalid in c99

    Reply
  • This was helpful. Would have taken me forever to figure it out alone. But it seems like things have changed. STM32MXCUBE generates code that adds all the configured channels to a group (inspect MX_ADC_Init()). If you want to do a single conversion with polling you disable all the channels in the group except the one you want. To disable a channel you set sConfig.Channel to the channel you want (eg, ADC_CHANNEL_4), then you set sConfig.Rank to ADC_RANK_NONE. Then you call HAL_ADC_ConfigChannel(&hadc, &sConfig); Do that for all but one channel. Then when you run a conversion you will get the only enabled channel.

    Reply
    • I think there is no more ADC_RANK_NONE entry in the sConfig.Rank.

      Reply
    • This was a great help. The code was not working as expected. But when I disable all the channels except the one I want to read, everything works!

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

keyboard_arrow_up

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×