Analog inputs are very important in any microcontroller application that has to read the state of an input in the real world.

The STM32F103 ADC configuration has many steps and allows for many features to be used for fast and accurate measurements.

The code in this example is based on the work of Andrey Koryagin and can be found, along with other examples, here: Avislab stm32f103 Examples

Overview of Code

The code used in this example includes many components as the ADC value is output through a UART connection.

The sections that will be discussed are:

  • Basic Configuration
  • Operation Modes
  • Conversion Modes
  • Triggers and Interrupts

The sections that will be addressed in another post are:

  • UART communication
  • System Clock Configuration

Initial Configuration

The first step of configuring the ADC is configuring the GPIO to route the input to the ADC as described in STM32 GPIO in C.

//ADC
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef  GPIO_InitStructure;

GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;      // Analog Input
GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1 ;		// ADC1 (PA1 on STM32)
GPIO_Init(GPIOA, &GPIO_InitStructure);

The ADC clock is then configured to be 1/6 of the system clock(Can be 2,4,6,8) or 12MHz. Though the clock divider can be less, the ADC on the STM32F103 has a maximum frequency of 14MHz.

The clock is then enabled.

//clock for ADC (max 14MHz --> 72/6=12MHz)
RCC_ADCCLKConfig (RCC_PCLK2_Div6);
// enable ADC system clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

ADC Operation Configuration

This Section will discuss the many configuration parameters for the ADCs.

ADC Modes

The ADC mode determines how the two ADCs in the microcontroller will function in relation to each other. The description of all the modes can be found in the Official STM32 Documentation.

The ADC Modes available are as listed below:

#define ADC_Mode_Independent                       ((uint32_t)0x00000000)
#define ADC_Mode_RegInjecSimult                    ((uint32_t)0x00010000)
#define ADC_Mode_RegSimult_AlterTrig               ((uint32_t)0x00020000)
#define ADC_Mode_InjecSimult_FastInterl            ((uint32_t)0x00030000)
#define ADC_Mode_InjecSimult_SlowInterl            ((uint32_t)0x00040000)
#define ADC_Mode_InjecSimult                       ((uint32_t)0x00050000)
#define ADC_Mode_RegSimult                         ((uint32_t)0x00060000)
#define ADC_Mode_FastInterl                        ((uint32_t)0x00070000)
#define ADC_Mode_SlowInterl                        ((uint32_t)0x00080000)
#define ADC_Mode_AlterTrig                         ((uint32_t)0x00090000)

The mode used in this application is independant as only a single ADC is in use.

ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;

ADC Conversion Modes

The ADC Conversion mode determines whether the ADC reads multiple pre-configured inputs or if it reads one at a time. The Continuous conversion mode determines whether the ADC runs a single conversion or if the registers are continually updated without input from the CPU.

Here, we disable scan and enable continuous conversion as we want constant output.

ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	// we work in continuous sampling mode

External Triggers and EOC Interrupts

The ADCs can be configured so that the enable signal comes from an external interrupt without requiring any cpu cycles.

Here, we have continuous readings so no trigger is enabled.

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;

A very important feature that is not covered in this code sample is End of Comversion Interrupts.

Data Align and Number of Channels

Here we align the read data from the ADC to the right of the data register. This makes the data output consistant so the read values are in a known location. The number of channels is additionally set as 1.

Each ADC can have up to 9 channels in the STM32F103 chip.

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;

Channel Config

Next, the Channels have to be configured and initialized.

Here, we configure Channel 1 of ADC1 to have Rank 1 and a sample time of 28.5 cycles. In a multi-channel conversion, the rank would define the order the channels are read in.

The sample time depends on the size of the measurement and the desired sampling time. The time can be calculated as (1*number_of_bits+.5)+sampling_time. Using this calculation, we find that 28.5 cycles - (1 cycle * 12 bits + .5 cycles) = 16 cycles extra sample time. An increased sample time will increase the stability of the measurements.

ADC_RegularChannelConfig(ADC1,ADC_Channel_1, 1,ADC_SampleTime_28Cycles5); // define regular conversion config

ADC_Init ( ADC1, &ADC_InitStructure);	//set config of ADC1

ADC Calibration

ADC Calibration is not always necessary but is included for the sake of completeness.

//enable ADC
ADC_Cmd (ADC1,ENABLE);	//enable ADC1

//ADC calibration (optional, but recommended at power on)
ADC_ResetCalibration(ADC1);	// Reset previous calibration
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);	// Start new calibration (ADC must be off at that time)
while(ADC_GetCalibrationStatus(ADC1));

Enable ADC

After Calibration, the ADC needs to be re-enabled and the conversion enabled.

// start conversion
ADC_Cmd (ADC1,ENABLE);	//enable ADC1
ADC_SoftwareStartConvCmd(ADC1, ENABLE);	// start conversion (will be endless as we are in continuous mode)

Reading the ADC

In this code, the ADC is read using ADC_GetConversionValue to read the output of the last conversion. This output is then repeatedly output over uart.

while (1)
	{
		adc_value = ADC_GetConversionValue(ADC1);
		sprintf(buffer, "%d\r\n", adc_value);
		USARTSend(buffer, sizeof(buffer));
	}

Full Code

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_adc.h"
#include "stdio.h"
#include "misc.h"

void usart_init(void)
{
	/* Enable USART1 and GPIOA clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

	/* NVIC Configuration */
	NVIC_InitTypeDef NVIC_InitStructure;
	/* Enable the USARTx Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Configure the GPIOs */
	//GPIO_Configuration();
	GPIO_InitTypeDef GPIO_InitStructure;

	/* Configure USART1 Tx (PA.09) as alternate function push-pull */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* Configure USART1 Rx (PA.10) as input floating */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* Configure the USART1 */
	//USART_Configuration();
	USART_InitTypeDef USART_InitStructure;

	/* USART1 configuration ------------------------------------------------------*/
	/* USART1 configured as follow:
	          - BaudRate = 115200 baud
	          - Word Length = 8 Bits
	          - One Stop Bit
	          - No parity
	          - Hardware flow control disabled (RTS and CTS signals)
	          - Receive and transmit enabled
	          - USART Clock disabled
	          - USART CPOL: Clock is active low
	          - USART CPHA: Data is captured on the middle
	          - USART LastBit: The clock pulse of the last data bit is not output to
	                           the SCLK pin
	 */
	USART_InitStructure.USART_BaudRate = 115200;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(USART1, &USART_InitStructure);

	/* Enable USART1 */
	USART_Cmd(USART1, ENABLE);

	/* Enable the USART1 Receive interrupt: this interrupt is generated when the
		USART1 receive data register is not empty */
	//USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

}

void USARTSend(char *pucBuffer, unsigned long ulCount)
{
    //
    // Loop while there are more characters to send.
    //
    while(ulCount--)
    {
        USART_SendData(USART1, *pucBuffer++);// Last Version USART_SendData(USART1,(uint16_t) *pucBuffer++);
        /* Loop until the end of transmission */
        while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
        {
        }
    }
}

void SetSysClockTo72(void)
{
	ErrorStatus HSEStartUpStatus;
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();

    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);

    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS)
    {
        /* Enable Prefetch Buffer */
    	//FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);

        /* Flash 2 wait state */
        //FLASH_SetLatency( FLASH_Latency_2);

        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);

        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);

        /* PCLK1 = HCLK/2 */
        RCC_PCLK1Config( RCC_HCLK_Div2);

        /* PLLCLK = 8MHz * 9 = 72 MHz */
        RCC_PLLConfig(0x00010000, RCC_PLLMul_9);

        /* Enable PLL */
        RCC_PLLCmd( ENABLE);

        /* Wait till PLL is ready */
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }

        /* Select PLL as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);

        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */

        /* Go to infinite loop */
        while (1)
        {
        }
    }
}

int main(void)
{
	char buffer[50] = {'\0'};
	int adc_value;

	SetSysClockTo72();

	//USART1
	usart_init();
	USART_SendData(USART1, '\r');

	//ADC
	ADC_InitTypeDef ADC_InitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	// input of ADC (it doesn't seem to be needed, as default GPIO state is floating input)
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_1 ;		// that's ADC1 (PA1 on STM32)
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	//clock for ADC (max 14MHz --> 72/6=12MHz)
	RCC_ADCCLKConfig (RCC_PCLK2_Div6);
	// enable ADC system clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

	// define ADC config
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	ADC_InitStructure.ADC_ScanConvMode = DISABLE;
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;	// we work in continuous sampling mode
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	ADC_InitStructure.ADC_NbrOfChannel = 1;

	ADC_RegularChannelConfig(ADC1,ADC_Channel_1, 1,ADC_SampleTime_28Cycles5); // define regular conversion config
	ADC_Init ( ADC1, &ADC_InitStructure);	//set config of ADC1

	// enable ADC
	ADC_Cmd (ADC1,ENABLE);	//enable ADC1

	//	ADC calibration (optional, but recommended at power on)
	ADC_ResetCalibration(ADC1);	// Reset previous calibration
	while(ADC_GetResetCalibrationStatus(ADC1));
	ADC_StartCalibration(ADC1);	// Start new calibration (ADC must be off at that time)
	while(ADC_GetCalibrationStatus(ADC1));

	// start conversion
	ADC_Cmd (ADC1,ENABLE);	//enable ADC1
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);	// start conversion (will be endless as we are in continuous mode)

	while (1)
	{
		adc_value = ADC_GetConversionValue(ADC1);
		sprintf(buffer, "%d\r\n", adc_value);
		USARTSend(buffer, sizeof(buffer));
	}
}