en.radzio.dxp.pl

Analog to digital converter (ADC)
Every STM32 microcontroller has at least one 12-bit analog to digital converter. In STM32F100RBT6 microcontroller there are one ADC with 16 input channels. In this example we use one of analog input of MCU to control PWM duty of LED brightness (described in lesson 6). Now, we must connect external potentiometer to STM32VLDISCOVERY board like on the schematic.

ADC periperal is described in section 10 of RM0041 document. So read this section now.

Now, I will introduce next part of "AntiLib" project : antilib_adc.h. In this file are some intuitive macros for easier configuration of ADC peripheral. There are macros that define sample time :

#define SAMPLE_TIME_1_5   0
#define SAMPLE_TIME_7_5   1
#define SAMPLE_TIME_13_5  2
#define SAMPLE_TIME_28_5  3
#define SAMPLE_TIME_41_5  4
#define SAMPLE_TIME_55_5  5
#define SAMPLE_TIME_71_5  6
#define SAMPLE_TIME_239_5 7

and shifting macros for each channel :

#define ADC_SAMPLE_TIME0(x) (x << 0)
.
.
.
#define ADC_SAMPLE_TIME9(x) (x << 27)

#define ADC_SAMPLE_TIME10(x) (x << 0)
.
.
.
#define ADC_SAMPLE_TIME17(x) (x << 21)

With this macros configuration of sample time for channels are clear and readable :

ADC1->SMPR2 = ADC_SAMPLE_TIME0(SAMPLE_TIME_239_5);

To define lenght of sequence is prepared macro :

#define ADC_SEQUENCE_LENGTH(x) (x << 20)

This macro shift macro parameter by 20 bits. Sequence lenght is numbered from 0. For only one channel in sequence configuration looks like this :

ADC1->SQR1 = ADC_SEQUENCE_LENGTH(0); // One channel in sequence

I'm prepare also macros for definig each sequence channels :

// SQR3
#define ADC_SEQ1(x) (x << 0)
#define ADC_SEQ2(x) (x << 5)
.
.
.
#define ADC_SEQ6(x) (x << 25)
// SQR2
#define ADC_SEQ7(x) (x << 0)
.
.
.
#define ADC_SEQ12(x) (x << 25)
// SQR1
#define ADC_SEQ13(x) (x << 0)
.
.
#define ADC_SEQ16(x) (x << 15)

Complete configuration of ADC peripheral :

ADC1->CR2   = ADC_CR2_ADON | ADC_CR2_CONT;    // Turn on ADC, enable continuos mode
ADC1->SQR1  = ADC_SEQUENCE_LENGTH(0);         // One channel in sequence
ADC1->SQR3  = ADC_SEQ1(0);                    // ADC channel 0 is first in sequence
ADC1->SMPR2 = ADC_SAMPLE_TIME0(SAMPLE_TIME_239_5); // sample time for first channel

ADC1->CR1   = ADC_CR1_EOCIE;                  // Enable interrupt form End Of Conversion
NVIC_EnableIRQ(ADC1_IRQn);                    // Enable interrupt form ACD1 peripheral

ADC1->CR2  |= ADC_CR2_ADON;                   // Turn on conversion

Now we can define handler for interrupt from ADC End of Conversion :

void ADC1_IRQHandler (void)
{
if(ADC1->SR & ADC_SR_EOC)
  {
  TIM3->CCR3 = ADC1->DR; // Copy value of analog input to PWM duty register
  }
}

Complete source code of example :

//=============================================================================
// STM32VLDISCOVERY tutorial
// Lesson 7. Analog to digital converter (ADC)
// Copyright : Radoslaw Kwiecien
// http://en.radzio.dxp.pl
// e-mail : radek(at)dxp.pl
//=============================================================================
#include "stm32f10x.h"
#include "antilib_gpio.h"
#include "antilib_adc.h"
//=============================================================================
// Defines
//=============================================================================
#define LED_BLUE_GPIO GPIOC
#define LED_BLUE_PIN 8

#define AIN0_GPIO GPIOA
#define AIN0_PIN 0
//=============================================================================
// main function
//=============================================================================
int main(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN | RCC_APB2ENR_ADC1EN;
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;

#if (LED_BLUE_PIN > 7)
LED_BLUE_GPIO->CRH = (LED_BLUE_GPIO->CRH & CONFMASKH(LED_BLUE_PIN)) | GPIOPINCONFH(LED_BLUE_PIN, GPIOCONF(GPIO_MODE_OUTPUT2MHz, GPIO_CNF_AFIO_PUSHPULL));
#else
LED_BLUE_GPIO->CRL = (LED_BLUE_GPIO->CRL & CONFMASKL(LED_BLUE_PIN)) | GPIOPINCONFL(LED_BLUE_PIN, GPIOCONF(GPIO_MODE_OUTPUT2MHz, GPIO_CNF_AFIO_PUSHPULL));
#endif

#if (AIN0_PIN > 7)
AIN0_GPIO->CRH = (AIN0_GPIO->CRH & CONFMASKH(AIN0_PIN)) | GPIOPINCONFH(AIN0_PIN, GPIOCONF(GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG));
#else
AIN0_GPIO->CRL = (AIN0_GPIO->CRL & CONFMASKL(AIN0_PIN)) | GPIOPINCONFL(AIN0_PIN, GPIOCONF(GPIO_MODE_INPUT, GPIO_CNF_INPUT_ANALOG));
#endif

AFIO->MAPR = AFIO_MAPR_TIM3_REMAP; // Full TIM3 remap

TIM3->PSC = 23; // Set prescaler to 24 (PSC + 1)
TIM3->ARR = 4096; // Auto reload value 4096 (PWM resolution 12-bits)
TIM3->CCR3 = 0; // Start value channel 3

TIM3->CCMR2 = TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1; // PWM mode on channel 3

TIM3->CCER = TIM_CCER_CC3E; // Enable compare on channel 3
TIM3->CR1 = TIM_CR1_CEN; // Enable timer

ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_CONT; // Turn on ADC, enable continuos mode
ADC1->SQR1 = ADC_SEQUENCE_LENGTH(0); // One channel in sequence
ADC1->SQR3 = ADC_SEQ1(0); // ADC channel 0 is first in sequence
ADC1->SMPR2 = ADC_SAMPLE_TIME0(SAMPLE_TIME_239_5); // sample time for first channel

ADC1->CR1 = ADC_CR1_EOCIE; // Enable interrupt form End Of Conversion
NVIC_EnableIRQ(ADC1_IRQn); // Enable interrupt form ACD1 peripheral

ADC1->CR2 |= ADC_CR2_ADON; // Turn on conversion

while (1) {}
}
//=============================================================================
// ADC1 Interrupt handler
//=============================================================================
void ADC1_IRQHandler (void)
{
if(ADC1->SR & ADC_SR_EOC)
{
TIM3->CCR3 = ADC1->DR;
}
}
//=============================================================================
// End of file
//=============================================================================

Changing value of analog input should change LED brightness.

 

 
(c) Radosław Kwiecień