ADC STM32F4 Discovery Board with HAL ADC Driver – Polling Method

In this tutorial, we will learn about the ADC of STM32F4 discovery Board. We will learn to use built-in analog to digital converter modules of the STM32F407VG microcontroller. This microcontroller comes with the STM32F4 Discovery Board. Firstly, we will see an introduction of ADC modules of STM32F4. Secondly, we will see ADC HAL drivers for STM32F4 series microcontrollers. In the end, we will see examples to measure analog voltage connected with one of the ADC input pins using Keil uvision and HAL Libraries. 

STM32F4 Discovery Board ADC modules 

As mentioned in the introduction, this development board contains an ARM Cortex-M4 based STM32F407VG chip. This MCU chip has many built-in user programmable peripherals and ADC modules are also available. STM32F407VG microcontroller has three successive approximation type ADC modules such as ADC1, ADC2, and ADC3. These ADC modules share 16 analog input channels. This means there are a total of 16 analog input channels available for the STM32F4 discovery board. 

ADC Pins STM32F4

STM32F407VG6T microcontroller ADC supports 6-bit, 8-bit, 10-bit, and 12-bit configurable resolution. Furthermore, it supports three modes of A/D conversion such as single, continuous, scan, or discontinuous mode. The result of the ADC is stored in left or right-aligned 16-bit data register.

The following table shows the input pins for each analog channel and ADC modules.

Analog ChannelADC1ADC2ADC3
AN0PA0PA0PA0
AN1PA1PA1PA1
AN2PA2PA2PA2
AN3PA3PA3PA3
AN4PA4PA4PF6
AN5PA5PA5PF7
AN6PA6PA6PF8
AN7PA7PA7PF9
AN8PB0PB0PF10
AN9PB1PB1PF3
AN10PC0PC0PC0
AN11PC1PC1PC1
AN12PC2PC2PC2
AN13PC3PC3PC3
AN14PC4PC4PF4
AN15PC5PC5PF5

STM32F407VG6T Microcontroller ADC Resolution 

The ADC resolution can be defined as the smallest input voltage at the analog pin that an ADC can identify and increments its digital value. The maximum and minimum digital output value of ADC depends on the number of bits of the ADC. For example, for a 8-bit ADC, the digital output value will be between 0-255, for a 10-bit ADC, the digital output value will be between 0-1023 and for a 12-bit ADC, the digital output value will be between 0-4095. 

ADC resolution can be defined as: 

Resolution = ( Operating voltage of ADC ) / 2^(number of bits ADC)

For example, the operating voltage of STM32F4 series microcontroller is 3.3V and if we configure the ADC in 12-bit mode:

Resolution = 3.3V/2^12 = 3.3/4095 = 0.8mV

Therefore, for every 0.8mV on ADC input, the digital value will increment and if we apply 3.3V to the input pin of ADC, the digital output value of ADC will be 4095. 

Similarly, if we measured a digital value of ADC with an STM32F4 microcontroller, we can convert it back into a voltage by multiplying it with the resolution value. For example, the measured digital output value is 3500, we can convert it into a voltage by multiplying it by 0.8mV.

Input Voltage = 3500 x 0.8mV = 2800mV or 2.8V

We can use ADC of STM32F4 for various applications such as AC voltage measurement, AC current measurement, Power measurement, etc.

STM32F407xxx HAL ADC Driver 

In this section, we will discuss CubeHAL ADC APIs which are used to configure and initialize ADC channels of STM32F4 series microcontrollers. 

ADC Handler Struct

The most important C struct is ADC_HandleTypeDef. This C struct is used to handle and configure ADC peripherals. 

typedef struct
{
  ADC_TypeDef      *Instance;                 
  ADC_InitTypeDef   Init;                        
  __IO uint32_t         NbrOfCurrentConversionRank; 
  DMA_HandleTypeDef         *DMA_Handle;           
  HAL_LockTypeDef               Lock;                        
  __IO uint32_t                 State;                      
  __IO uint32_t                 ErrorCode;                  
}ADC_HandleTypeDef;

Let’s see the function of each member of C struct ADC_HandleTypeDef. 

  • *Instance : It is a pointer to the ADC_TypeDef C struct which is a descriptor for the ADC module we want to use such as ADC1, ADC2 and ADC3.
  • Init: It is an instance of the C struct ADC_InitTypeDef which is used to select ADC features such as clock prescaler selection, resolution selection, scan mode, data alignment, interrupt/polling method, number of conversion, etc. 
  • NbrOfCurrentConversionRank:  ADC number of current conversion rank
  •  *DMA_Handle: Pointer to a DMA handler. We will discuss it next tutorial

ADC Initialization Struct

ADC_InitTypeDef C struct is used to configure ADC.

typedef struct
{
  uint32_t ClockPrescaler;                                                          
  uint32_t Resolution;                                                           
  uint32_t DataAlign; 
  uint32_t ScanConvMode; 
  uint32_t EOCSelection;
  FunctionalState ContinuousConvMode;
  uint32_t NbrOfConversion;
  FunctionalState DiscontinuousConvMode; 
  uint32_t NbrOfDiscConversion; 
  uint32_t ExternalTrigConv; 
  uint32_t ExternalTrigConvEdge; 
  FunctionalState DMAContinuousRequests;                
}ADC_InitTypeDef;

Configuring ADC Clock

The clock for ADC peripheral circuit is common to all ADCs (ADCCLK). Clock for ADC modules is generated from APB2 bus and it can be further divided by programmable Prescaler value of Fpclk/2, 4, 6,8. ClockPrescaler is used to select the ADC Clock Prescaler. The important point to note here is that the clock is common to all ADC modules. 

In HAL ADC driver, the prescaler value can be set by using one of these #define directives. 

#define ADC_CLOCK_SYNC_PCLK_DIV2    0x00000000U
#define ADC_CLOCK_SYNC_PCLK_DIV4    ((uint32_t)ADC_CCR_ADCPRE_0)
#define ADC_CLOCK_SYNC_PCLK_DIV6    ((uint32_t)ADC_CCR_ADCPRE_1)
#define ADC_CLOCK_SYNC_PCLK_DIV8    ((uint32_t)ADC_CCR_ADCPRE)

Setting ADC Resolution 

Resolution member of ADC_InitTypeDef C struct selects the ADC resolution such as 6-bit, 8-bit, 10-bit, and 12-bit. Note that the higher is the resolution, the lower will be the conversion speed and vice versa. Resolution can be selected by setting this member to one of these values:

#define ADC_RESOLUTION_12B  0x00000000U 
#define ADC_RESOLUTION_10B  ((uint32_t)ADC_CR1_RES_0)
#define ADC_RESOLUTION_8B   ((uint32_t)ADC_CR1_RES_1)
#define ADC_RESOLUTION_6B   ((uint32_t)ADC_CR1_RES)
  • Scan Mode Selection (ScanConvMode) : It is used to enable/disable scan conversion mode. 

Data Alignment (DataAlign)

It is used to set the format of digital output data either in left-justified or right-justified format. Data alignment can be selected by setting DataAlign to one of these values:

#define ADC_DATAALIGN_RIGHT      0x00000000U
#define ADC_DATAALIGN_LEFT       ((uint32_t)ADC_CR2_ALIGN)
  • Continuous conversion mode ( ContinuousConvMode ): It selects either A/D conversion will be performed either in single and continuous mode. 

Features of other members will be discussed in the upcoming tutorials on STM32F4 discovery board ADC. 

STM32F4 ADC Single-channel and Single Conversion Mode Example

Now let’s see an example to measure analog voltage input using STM32F4 discovery board ADC in single-channel and single conversion mode. In this mode, ADC takes one sample from one analog channel. This is the simplest mode to use. 

 In this example, we will configure ADC2 channel 0 in single conversion mode. 

First, enable the clock for the ADC GPIO pins port. For example, AN0 channel input is multiplexed with a PA0 pin. Therefore, enable the clock to PORTA. 

 __HAL_RCC_GPIOA_CLK_ENABLE();

 The next step is to configure the corresponding GPIO pin as an analog input pin using GPIO_InitTypeDef C struct. In the previous two tutorials, we have learned to use GPIO pins of STM32F4 discovery board as digital inputs and digital outputs. Same C struct GPIO_InitTypeDef  is used to configure the gpio pin as an analog pin. 

Hence, configures the PA0 pin as an analog input pin using the HAL_GPIO_Init() function.

/* Configure and initialize PA0 pin as analog input pin for A/D conversion */
void Configure_GPIO(void)
{
	 __HAL_RCC_GPIOA_CLK_ENABLE(); // enable clock to GPIOA
	GPIO_InitTypeDef ADCpin; //create an instance of GPIO_InitTypeDef C struct
	ADCpin.Pin = GPIO_PIN_0; // Select pin PA0
	ADCpin.Mode = GPIO_MODE_ANALOG; // Select Analog Mode
	ADCpin.Pull = GPIO_NOPULL; // Disable internal pull-up or pull-down resistor
	HAL_GPIO_Init(GPIOA, &ADCpin); // initialize PA0 as analog input pin
}

HAL ADC Configuration 

The next step is to configure ADC and channel selection. But before that enable the clock to ADC using __HAL_RCC_ADCx_CLK_ENABLE(). Here, x is an ADC number. For example, we are using ADC2 in this tutorial. Therefore, enable the clock to ADC2 like this: 

__HAL_RCC_ADC2_CLK_ENABLE()

The second important step in ADC configuration is to select parameters such as resolution, data alignment, end of conversion event, etc. As we discussed earlier, ADC_HandleTypeDef C struct is used to set these parameters using the HAL_ADC_Init() function. 

First create an instance of ADC_HandleTypeDef C struct as a global variable. Because we will use the same instance for other ADC HAL functions also. 

ADC_HandleTypeDef myADC2Handle;

Using the above created instance of ADC_HandleTypeDef, select ADC module. 

myADC2Handle.Instance = ADC2; // create an instance of ADC2

Set the resolution to 8-bit with ADC_InitTypeDef C struct  instance “Init” and its member “Resolution”. 

myADC2Handle.Init.Resolution = ADC_RESOLUTION_12B; // select 12-bit resolution 

We are using ADC2 in single-channel and single-conversion mode. EOCSelection is used to select single conversion mode. By setting EOCSelection to ADC_EOC_SINGLE_CONV, ADC2 channel0 will give the digital output after every single A/D conversion. 

myADC2Handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; //select  single conversion as a end of conversion event

Aligned the digital output data in the right justified format.  

myADC2Handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; // set digital output data right justified

At the end, pass the pointer of myADC2Handle to HAL_ADC_Init() function to initialize ADC2 according to the above settings. 

HAL_ADC_Init(&myADC2Handle); // initialize AD2 with myADC2Handle configuration settings

HAL Select ADC Channel

Till now we have only configured ADC2 module and we have not configured which channel of ADC2 we want to use. HAL_ADC_ConfigChannel configure the channels. 

HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef* hadc, ADC_ChannelConfTypeDef* sConfig)

The first argument to this function is ADC_HandleTypeDef C struct which we have already defined above. The second argument is the ADC_ChannelConfTypeDef C struct. It is used to select channels, set rank, sampling time, and offset. 

In these lines, first, we create an instance of ADC_ChannelConfTypeDef  C struct with the name of “Channel_AN0”. After that select channel, rank, sampling time. In the end, pass both required arguments to HAL_ADC_ConfigChannel() function to select channel_0 for the ADC2 module.

ADC_ChannelConfTypeDef Channel_AN0; // create an instance of ADC_ChannelConfTypeDef
Channel_AN0.Channel = ADC_CHANNEL_0; // select analog channel 0
Channel_AN0.Rank = 1; // set rank to 1
Channel_AN0.SamplingTime = ADC_SAMPLETIME_15CYCLES; // set sampling time to 15 clock cycles
HAL_ADC_ConfigChannel(&myADC2Handle, &Channel_AN0); // select channel_0 for ADC2 module. 

Read ADC Digital Output

Inside the main code, where you want to read the ADC value, start the ADC peripheral using the HAL_ADC_Start() function by passing myADC2Handle pointer as an argument that contains the configuration information for the ADC2. 

HAL_ADC_Start(&myADC2Handle);

After that, wait for the single conversion to complete using HAL_ADC_PollForConversion() function. The first argument to poll for conversion routine is a pointer to a ADC_HandleTypeDef structure that contains the configuration information for the ADC2. The second argument is a timeout for adc conversion polling. 

HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout);

This function returns a HAL_StatusTypeDef struct that defines the state of A/D conversion. After completing a single conversion, it will return HAL_OK.

typedef enum 
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

By using if condition, we check if conversion is completed or not. Here we specified the timeout of 5ms. 

if(HAL_ADC_PollForConversion(&myADC2Handle, 5) == HAL_OK)

 To read the ADC converted values, use the HAL_ADC_GetValue() function.Here, we read the digital output value and save it in “Adc_value” variable of size unit32_t. 

Adc_value  = HAL_ADC_GetValue(&myADC2Handle); // read digital value and save it inside uint32_t variable

 Stop the ADC peripheral using HAL_ADC_Stop().

HAL_ADC_Stop(&myADC2Handle); // stop conversion 

STM32F4 Single Conversion Code

#include "stm32f4xx_hal.h"

void Configure_GPIO(void);
void configure_ADC2_Channel_0(void);
void Delay_ms(volatile int time_ms);

uint32_t Adc_value;
ADC_HandleTypeDef myADC2Handle;
int main(void)
{
	Configure_GPIO(); // initialize PA0 pin 
	configure_ADC2_Channel_0(); // configure ADC2
	while(1)
	{
	 HAL_ADC_Start(&myADC2Handle); // start A/D conversion
		if(HAL_ADC_PollForConversion(&myADC2Handle, 5) == HAL_OK) //check if conversion is completed
		{
		Adc_value  = HAL_ADC_GetValue(&myADC2Handle); // read digital value and save it inside uint32_t variable
		}
		HAL_ADC_Stop(&myADC2Handle); // stop conversion 
		Delay_ms(200);
	}
}

/* Configure and initialize PA0 pin as analog input pin for A/D conversion */
void Configure_GPIO(void)
{
	 __HAL_RCC_GPIOA_CLK_ENABLE(); // enable clock to GPIOA
	GPIO_InitTypeDef ADCpin; //create an instance of GPIO_InitTypeDef C struct
	ADCpin.Pin = GPIO_PIN_0; // Select pin PA0
	ADCpin.Mode = GPIO_MODE_ANALOG; // Select Analog Mode
	ADCpin.Pull = GPIO_NOPULL; // Disable internal pull-up or pull-down resistor
	HAL_GPIO_Init(GPIOA, &ADCpin); // initialize PA0 as analog input pin
}
void configure_ADC2_Channel_0(void)
{
__HAL_RCC_ADC2_CLK_ENABLE(); // enable clock to ADC2 module
	myADC2Handle.Instance = ADC2; // create an instance of ADC2
	myADC2Handle.Init.Resolution = ADC_RESOLUTION_12B; // select 12-bit resolution 
	myADC2Handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; //select  single conversion as a end of conversion event
	myADC2Handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; // set digital output data right justified
	myADC2Handle.Init.ClockPrescaler =ADC_CLOCK_SYNC_PCLK_DIV8; 
	HAL_ADC_Init(&myADC2Handle); // initialize AD2 with myADC2Handle configuration settings
	
  /*select ADC2 channel */
	ADC_ChannelConfTypeDef Channel_AN0; // create an instance of ADC_ChannelConfTypeDef
	Channel_AN0.Channel = ADC_CHANNEL_0; // select analog channel 0
	Channel_AN0.Rank = 1; // set rank to 1
	Channel_AN0.SamplingTime = ADC_SAMPLETIME_15CYCLES; // set sampling time to 15 clock cycles
	HAL_ADC_ConfigChannel(&myADC2Handle, &Channel_AN0); // select channel_0 for ADC2 module. 
}
void Delay_ms(volatile int time_ms)
{
	      int j;
        for(j = 0; j < time_ms*4000; j++)
            {}  /* excute NOP for 1ms */
}

To see the output of the above code, connect a potentiometer with the STM32F4 discovery board as shown in this connection diagram.

STM32F4 Discovery Board ADC tutorial
PotentiometerSTM32F4 discovery board
Right PinGND
Center PinPA0
Left Pin3.3V

First create a project in Keil for STM32F4. If you don’t know how to use STM32F4 series microcontrollers in Keil MDK for ARM, you can read these getting started guides:

To check the output of digital output value of ADC, compile and build the project. After that click on the Debugger button in Keil uvision.

Open debugger in Keil uvision for ADC value

After that this debugging window will appear. Here select the “Adc_value” variable and add it to the watch window. After that click on the run button or press F5 on your keyboard. You will see the digital output value as soon as you rotate the knob of variable resistor which is connected with STM32F4 discovery board.

STM32F4 ADC digital value output in keil uvision

STM32F4 – Convert Digital Value into Voltage

Let’s say you want to measure the input voltage which is connected with the PA0 pin of STM32F4 discovery board. But the above code gives you only digital value output. We can easily convert this measured digital value into voltage by multiplying it with the resolution. We have defined the formula for resolution at the start of the tutorial. 

In this example, we have selected a 12-bit resolution. Therefore, we can get the analog voltage by multiplying a digital value with 0.8mV. 

double voltage;
int main(void)
{
	Configure_GPIO(); // initialize PA0 pin 
	configure_ADC2_Channel_0(); // configure ADC2
	while(1)
	{
	 HAL_ADC_Start(&myADC2Handle); // start A/D conversion
		if(HAL_ADC_PollForConversion(&myADC2Handle, 5) == HAL_OK) //check if conversion is completed
		{
		Adc_value  = HAL_ADC_GetValue(&myADC2Handle); // read digital value and save it inside uint32_t variable
		voltage = (Adc_value * 8 ) / 10000;
		}
		HAL_ADC_Stop(&myADC2Handle); // stop conversion 
		Delay_ms(200);
	}
}

In the coming tutorial, we will see how to use STM32F4 ADC with DMA, Interrupt and Timers to get ADC samples.

STM32F4 ADC in Continuous Conversion Mode

In the last section, we have learned to use STM32F4 in single conversion mode. Single conversion mode is useful only for slow applications where we need to take A/D conversion 1-2 samples per second only. But there are many applications where we need to take ADC samples as fast as possible. In these applications, we can use STM32F4 ADC in continuous conversion mode. 

Continuous conversion mode can be used with single-channel or multiple analog channels. In this tutorial, we will discuss the single-channel continuous conversion mode. This mode repeatedly performs A/D conversion indefinitely. The ADC converts the channels continuously without any intervention from the processor. Unlike single-conversion mode, it continues to perform A/D conversion as soon as it completes the current A/D conversion. We do not need to start and stop the ADC conversion.

STM32F4 Continouse Conversion Mode Code  

#include "stm32f4xx_hal.h"
void Configure_GPIO(void);
void configure_ADC1_Channel_0(void);
void Delay_ms(volatile int time_ms);

ADC_HandleTypeDef myADC2Handle;
 uint32_t rawValue;
 float temp;
int main(void)
{
   Configure_GPIO(); // initialize PA0 pin 
	configure_ADC1_Channel_0(); // configure ADC2
	while(1)
	{

 //HAL_ADC_PollForConversion(&myADC2Handle, HAL_MAX_DELAY);

 rawValue = HAL_ADC_GetValue(&myADC2Handle);
 temp = ((float)rawValue) / 4095 * 3300;
	}
}

/* Configure and initialize PA0 pin as analog input pin for A/D conversion */
void Configure_GPIO(void)
{
	 __HAL_RCC_GPIOA_CLK_ENABLE(); // enable clock to GPIOA
	GPIO_InitTypeDef ADCpin; //create an instance of GPIO_InitTypeDef C struct
	ADCpin.Pin = GPIO_PIN_0; // Select pin PA0
	ADCpin.Mode = GPIO_MODE_ANALOG; // Select Analog Mode
	ADCpin.Pull = GPIO_NOPULL; // Disable internal pull-up or pull-down resistor
	HAL_GPIO_Init(GPIOA, &ADCpin); // initialize PA0 as analog input pin
}
void configure_ADC1_Channel_0(void)
{
__HAL_RCC_ADC1_CLK_ENABLE(); // enable clock to ADC1 module
	myADC2Handle.Instance = ADC1; // create an instance of ADC2
	myADC2Handle.Init.ClockPrescaler =ADC_CLOCK_SYNC_PCLK_DIV8; 
	myADC2Handle.Init.Resolution = ADC_RESOLUTION_12B; // select 12-bit resolution 
	myADC2Handle.Init.ScanConvMode = DISABLE;
	myADC2Handle.Init.ContinuousConvMode = ENABLE;
	myADC2Handle.Init.DiscontinuousConvMode = DISABLE;
	myADC2Handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; // set digital output data right justified
        myADC2Handle.Init.NbrOfConversion = 1;
        myADC2Handle.Init.DMAContinuousRequests = DISABLE;
        myADC2Handle.Init.EOCSelection = ADC_EOC_SEQ_CONV;

	HAL_ADC_Init(&myADC2Handle); // initialize AD2 with myADC2Handle configuration settings
	
  /*select ADC2 channel */
	ADC_ChannelConfTypeDef Channel_AN0; // create an instance of ADC_ChannelConfTypeDef
	Channel_AN0.Channel = ADC_CHANNEL_0; // select analog channel 0
	Channel_AN0.Rank = 1; // set rank to 1
	Channel_AN0.SamplingTime = ADC_SAMPLETIME_480CYCLES; // set sampling time to 15 clock cycles
	HAL_ADC_ConfigChannel(&myADC2Handle, &Channel_AN0); // select channel_0 for ADC2 module. 
}
void Delay_ms(volatile int time_ms)
{
	      int j;
        for(j = 0; j < time_ms*4000; j++)
            {}  /* excute NOP for 1ms */
}

Related ADC tutorials:

Leave a Comment