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