Electronics - STM32 - Using the ADC peripheral with a potentiometer

ADC is one on the most famous peripheral on a microcontroller.
In this tutorial we will see that it's possible to retrieve data from a potentiometer and send them in an array.

You are certainly wondering how to use a such thing on a STM32?

That's a good question.

To see this, let's use the STM32F103ZE-SK board, and guess what?
There is already a potentiometer on it!

What a wonderful world (that's what Louis Armstrong would have said) isn't it?

(If you didn't have a potentiometer on your board, you could weld it or use a breadboard.)

Explanation

In the code below, we will retrieve data caught by the ADC from the angle of our potentiometer.

But first of all, we have to know that in this board, the potentiometer is linked with the GPIOC and the pin 4.

The ADC has to be configured with the ADC1 and the channel 14.

Let's see now how to catch values from this famous ADC.

The ADC in the STM32F103ZE-SK has 12-bit.

It means that the maximum value available is 2^12 = 4096.
So we can handle 4096 different values, from 0 to 4095.

Notice that all values with the ADC are in hexadecimal:

  • 0 = 0x00 ;
  • ...
  • 4095  = 0xFFF.

But what we would want is to have a value in volt, of course.

For example, if the potentiometer was at the minimum (turned on the left), its value would have been 0 (or almost).
And if the potentiometer was turned at maximum on the right, its value would have been 4095 (or 0xFFF).

Our board is powered by a tension of 3.3V.
So let's use a cross-multiplication to transform our hexadecimal value into a volt one.

  • 3.3 / 4095 = 0.0008058608
  • or  3.3 / 0xFFF = 0.0008058608

So, each value caught by our ADC will be multiplied by this number.
And the result will be a voltage.

For example if the ADC retrieved 2700, the voltage would have been:

  • (3.3 / 4095) * 2700 = 2.1758
  • or 0.0008058608 * 2700 = 2.1758

And in our example the array will be filled with a value each second during 32 seconds.
So it's up to you to move the potentiometer and see that all values will be catched depending of the angle of our potentiometer.

Let's see this in details.

Code

#include "stm32f10x.h"
#include "stxng.h"

#define ARRAY_SIZE 32

/**
 * Classic delay
 */
void badprogDelay(u32 myTime) {
    u32 i;
    RCC_ClocksTypeDef RCC_Clocks;

    RCC_GetClocksFreq(&RCC_Clocks);
    // i = myTime * 68
    i = myTime * (RCC_Clocks.SYSCLK_Frequency >> 20);

    for (; i != 0; i--)
        ;
}

/**
 * Initialization of ADC and the poentiomenter's GPIO
 */
float badprogADC() {
    /// declaring GPIO stuff
    GPIO_InitTypeDef GPIO_InitStructure;
    // declaring ADC struct
    ADC_InitTypeDef ADC_InitStructure;

    // deinit ADC
    ADC_DeInit(ADC1);

    // enabling clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    // enabling ADC clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    // C - Init the GPIO with the structure - Testing ADC
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    // init ADC struct
    ADC_StructInit(&ADC_InitStructure);

    // setting the ADC struct
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 1;

    // init adc
    ADC_Init(ADC1, &ADC_InitStructure);

    // enable ADC
    ADC_Cmd(ADC1, ENABLE);

    // start ADC1 calibration and check the end
    ADC_StartCalibration(ADC1);

    // configure ADC_IN14
    ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 1, ADC_SampleTime_7Cycles5);

    // start ADC -> retrieve the ADC value from the potentiometer
    // and add it into ADC1->DR with:
    // ADCx->CR2 |= CR2_EXTTRIG_SWSTART_Set;
    // this without using ADC1->DR o_O
    // CR2 = Configuration Register2 -> it seems to be a config with
    // a binary number -> for example 1000010100101010 which sets all
    // registers with default values
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    // check the end of the ADC1 calibration
    // setting ADC1->DR with:
    // if ((ADCx->CR2 & CR2_CAL_Set) != (uint32_t)RESET)
    while (ADC_GetCalibrationStatus(ADC1) == SET)
        ;

    // convert valueADC to valueVolt -> valueADC * (MAX VOLT / 2^12)
    // and also :
    // ADC_SoftwareStartConvCmdsoftwareS,
    // ADC_GetCalibrationStatus
    // with
    // return (uint16_t) ADCx->DR;
    uint16_t valueADC = ADC_GetConversionValue(ADC1);

    // convert the "uint_16 valueADC" into a "float valueVolt"
    // Volt = 3.3
    // ADC = 12 bits, so 2^12 = 4096
    float valueVolt = valueADC * (3.3 / 4095);

    return valueVolt;
}

/**
 * Init the array with '\0';
 */
void badprogInitArray(float *myArray, int size) {
    int i = 0;
    while (i < size) {
        myArray[i] = '\0';
        ++i;
    }
}

/*******************************************************************************
 * Main, what else?
 *******************************************************************************/
int main(void) {
    float theArray[ARRAY_SIZE];
    int i = 0;

    // init with 0 the whole array
    badprogInitArray(theArray, ARRAY_SIZE);

    // our beautiful infinite loop while i < 32
    while (-2013) {
        if (i < ARRAY_SIZE) {
            theArray[i] = badprogADC();
            badprogDelay(100000);                          // 1 sec
            ++i;
        }
    }
    return 0;
}

Conclusion

A great way to start capturing analog data all around you.
Good job, you've made it! laugh
 

Comments

Comment: 

What is the sampling rate for ADC_SampleTime_7Cycles5?

Comment: 

Why is stxng.h? It isn't available as default header in CubeIDE

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.