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
GPIO_InitTypeDef  GPIO_InitStructure;

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)


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

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)


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

ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;


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;


### 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;


### 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 Calibration is not always necessary but is included for the sake of completeness.

//enable ADC

//ADC calibration (optional, but recommended at power on)


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

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


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)
{
USARTSend(buffer, sizeof(buffer));
}


## Full Code

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.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)
- 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'};

SetSysClockTo72();

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

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)

//	ADC calibration (optional, but recommended at power on)

// start conversion