Analog to Digital Converters (ADC) are involved in several embedded projects. From the capture of UI potentiometers to digitalization of sensor data, the use of ADC is manifest in number of cases. STM32 may have one or more ADCs. An ADC is a complex hardware, that takes room on silicon and that requires power to operate. Therefore, few ADCs are usually embedded in a MCU. In order to achieve conversions on several channels (pins), a single ADC is sequentially connected to multiples input pins.
ADCs peripherals can operate on a single channel or on multiples channels sequentially. You can perform a single conversion on demand (or a single sequence), or let ADC work continuously.
In this tutorial, you will setup ADC to work continuously on a single channel. If you understand what’s done here, you will be able to adapt the configuration to your needs.
Not any MCU pin can be elected for ADC conversion. You need to refer to the datasheet to discover which pins are connected to ADC input mux.
Note that ADC connection is not considered as an Alternate Function, as we saw for USART pins. ST calls this an Additional Function instead. It is reported in the pin definitions tables of the datasheet.
For example, PA0 and PA1 are connected to ADC input 0 and 1 respectively:
In the STM32F072RB device, there is only one ADC, but the input mux features 16 inputs (0 to 15). There is no reason to prefer a channel among others, unless you have pin restrictions. In the lab, let us arbitrarily choose the ADC channel 11. According to the table below, channel 11 is connected to pin PC1.
Ultimately, you have the make sure that PC1 is not involved in some board-level function (such as LED, button, USB…). The best source of information is the board schematics.
Here, we can see that PC1 is totally free for our purpose.
The code below sets the ADC for a single continuous conversion on channel 11. As usual, the first step is to configure associated GPIO in the correct mode (analog here).
Next, ADC clock is enabled, followed by ADC registers settings. Refer to the reference manual to grasp a complete understanding of what is done there.
Add the following code to existing bsp.c file in the my_project project:
/*
* ADC_Init()
* Initialize ADC for a single channel conversion
* on channel 11 -> pin PC1
*/
void BSP_ADC_Init()
{
// Enable GPIOC clock
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
// Configure pin PC1 as analog
GPIOC->MODER &= ~GPIO_MODER_MODER1_Msk;
GPIOC->MODER |= (0x03 <<GPIO_MODER_MODER1_Pos);
// Enable ADC clock
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// Reset ADC configuration
ADC1->CR = 0x00000000;
ADC1->CFGR1 = 0x00000000;
ADC1->CFGR2 = 0x00000000;
ADC1->CHSELR = 0x00000000;
// Enable continuous conversion mode
ADC1->CFGR1 |= ADC_CFGR1_CONT;
// 12-bit resolution
ADC1->CFGR1 |= (0x00 <<ADC_CFGR1_RES_Pos);
// Select PCLK/2 as ADC clock
ADC1->CFGR2 |= (0x01 <<ADC_CFGR2_CKMODE_Pos);
// Set sampling time to 28.5 ADC clock cycles
ADC1->SMPR = 0x03;
// Select channel 11
ADC1->CHSELR |= ADC_CHSELR_CHSEL11;
// Enable ADC
ADC1->CR |= ADC_CR_ADEN;
// Start conversion
ADC1->CR |= ADC_CR_ADSTART;
}
Add the prototype declaration to bsp.h:
/*
* ADC functions
*/
void BSP_ADC_Init (void);
Now it is time to test this:
// Main program
void main()
{
uint32_t i;
// Configure System Clock
SystemClock_Config();
// Initialize Debug Console
BSP_Console_Init();
my_printf("Console ready!\r\n");
// Initialize and start ADC on PC1
BSP_ADC_Init();
my_printf("ADC ready!\r\n");
// Main loop
while(1)
{
// Wait here until ADC EOC
while ((ADC1->ISR & ADC_ISR_EOC) != ADC_ISR_EOC);
// Report result to console
my_printf("ADC value = %d\r\n", ADC1->DR);
// Wait about 200ms
for (i=0; i<500000; i++);
}
}
ADC conversion result is available in the Data Register (DR). It is a good idea to make sure that last conversion is done before reading this register. This is achieved by polling the EOC (End Of Conversion) flag of ADC1 peripheral.
Save all, build and run:
![]() |
![]() ![]() |
Note that you could as well use the debugger to monitor ADC results, but in a less “real-time” fashion.
To further test the code, one should cook a little bit of hardware.
First, locate the PC1 pin on the Nucleo board header:
PC1 corresponds to the A4 “Arduino” pin. Leaving PC1 floating may produce random conversion results. Connecting PC1 to GND should display 0 for the ADC value. Connecting PC1 to the +3.3V pin should display something close to 4095 (0x0FFF, the full range for a 12-bit value).
If these simple tests succeed, then it seems that everything is working fine.
To modulate the ADC input continuously, you can wire a potentiometer this way:
STM Studio® is a software that can display program variable in real-time. Of course, there are few limitations. Basically, you can monitor:
This is still an incredibly powerful way for finding out bugs or tuning computation loop in control applications (ex. tuning a PID controller)
First, you need to install STM Studio. You can get STM Studio from ST website :
Because we can only monitor global variables, let’s add a global variable to store ADC result:
// GLobal variables
uint16_t ADC_result;
// Main program
void main()
{
uint32_t i;
// Configure System Clock
SystemClock_Config();
// Initialize Debug Console
BSP_Console_Init();
my_printf("Console ready!\r\n");
// Initialize and start ADC on PC1
BSP_ADC_Init();
my_printf("ADC ready!\r\n");
// Main loop
while(1)
{
// Wait here until ADC EOC
while ((ADC1->ISR & ADC_ISR_EOC) != ADC_ISR_EOC);
// Report result to console
ADC_result = ADC1->DR;
my_printf("ADC value = %d\r\n", ADC_result);
// Wait about 200ms
for (i=0; i<500000; i++);
}
}
Build the project, verify that you have no errors or warning, and then flash your target board. Check in your terminal that the program is working well, just as before.
Start STM Studio application from Windows start menu.
Change the ST-Link protocol to SWD (Serial Wire Debug Interface)
Go to the main menu File → Import variables or click
In the Import variables from executable windows, use the button on the right of the Excutable file field to browse for your .elf project executable file. You will find the .elf file in the following subdirectory from your project folder under the \Debug folder.
STM Studio then reports a list of all the variables that you can “spy”:
Select ADC_result, and then click the Import button. Then Close this window.
Drag & drop the ADC_result variable from the Variable settings zone to the VarViewer 1 zone:
Then set the Viewer range according to what you expect from the variable. 12-bit ADC delivers values from 0 to 4095.
Now go to the menu Option → Acquisition Settings or click
Set the Graphical refresh rate to 5ms, and uncheck the Log to file option. You may also change the Acquisition Rate accordingly. Click OK to close the window.
Go to menu Run → Start or click the button.
If you get the window below, make sure that you terminate the OpenOCD session under Eclipse and retry. You cannot have both connected to the ST-Link at the same time.
You can now monitor the fluctuations of the voltage applied on PC1 pin, just like with an oscilloscope. You can even make sure that STM Studio and the data you print in the console are the same (as it should be).
You can also play with the various options you have to display data (Curve, Bar Graph, Table):
You may also change the acquisition settings, you can zoom (in/out) curves, and even record data to a file for further analysis or reporting purposes (Excel, Matlab…).
Note that you cannot flash the code while STM Studio is running.
You don’t have to close STM Studio, just hit the stop button before flashing a new code and then restart monitoring process.
STM Studio is bond to the .elf file now, and will update variable addresses automatically if necessary.
In this tutorial, you have setup ADC for a single channel continuous conversion and make use of STM-Studio to real-time monitor the conversion result.