Ads

Get STM32 tutorial using HAL at $10 for a limited time!

Sunday, July 19, 2015

STM32F4 Discovery Tutorial 7 - ADC

In this tutorial, I will share how to use ADC on STM32F4 Discovery to read analog voltage. ADC is stands for Analog to Digital Converter. Microcontrollers are digital component, so they only understand discrete/digital signals. Therefore if you want to read analog voltage that can be from various sensors, you need an ADC. STM32F407 has 3 ADC that can work independently. Every ADC have 18 channels. 16 channels are external and connected to GPIO pin. 2 channels are internal that connected to internal temperature sensor and ADC voltage reference.

When you use ADC, you can choose which ADC and its channel from this table:


Once you have select which ADC and its pin that you want to use, you can start to coding. In this example, I will use ADC1 on channel 9 (PB1). I will connect trimpot to PB1 as a source for analog input voltage. 

This is code for setup ADC. I made this code on a function called ADC_Config().
void ADC_Config(void)
{
    // Enable clock for ADC1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    // Init GPIOB for ADC input
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Init ADC1
    ADC_InitTypeDef ADC_InitStruct;
    ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStruct.ADC_ExternalTrigConv = DISABLE;
    ADC_InitStruct.ADC_ExternalTrigConvEdge = 
        ADC_ExternalTrigConvEdge_None;
    ADC_InitStruct.ADC_NbrOfConversion = 1;
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStruct.ADC_ScanConvMode = DISABLE;
    ADC_Init(ADC1, &ADC_InitStruct);
    ADC_Cmd(ADC1, ENABLE);

    // Select input channel for ADC1
    // ADC1 channel 9 is on PB1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 1, 
        ADC_SampleTime_84Cycles);
}
This is the function for read ADC value:
uint16_t ADC_Read(void)
{
    // Start ADC conversion
    ADC_SoftwareStartConv(ADC1);
    // Wait until conversion is finish
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));

    return ADC_GetConversionValue(ADC1);
}
This is the full code of this tutorial. In the main program, ADC1 reads the value of trimpot that connected to PB1 every 1s then send it to PC via USART1. Here I used three library that I made from previous tutorial for setting clock to 168MHz, delay using SysTick timer, and USART.
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_adc.h"
#include "clock.h"
#include "delay.h"
// USART library (USART1 on PB6 and PB7)
#include "usart.h"
#include <stdio.h>

// For ADC value
uint16_t adcValue = 0;
// For ADC value in string
char sAdcValue[5];

void ADC_Config(void)
{
    // Enable clock for ADC1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    // Init GPIOB for ADC input
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Init ADC1
    ADC_InitTypeDef ADC_InitStruct;
    ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStruct.ADC_ExternalTrigConv = DISABLE;
    ADC_InitStruct.ADC_ExternalTrigConvEdge = 
        ADC_ExternalTrigConvEdge_None;
    ADC_InitStruct.ADC_NbrOfConversion = 1;
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStruct.ADC_ScanConvMode = DISABLE;
    ADC_Init(ADC1, &ADC_InitStruct);
    ADC_Cmd(ADC1, ENABLE);

    // Select input channel for ADC1
    // ADC1 channel 9 is on PB1
    ADC_RegularChannelConfig(ADC1, ADC_Channel_9, 1, 
        ADC_SampleTime_84Cycles);
}

uint16_t ADC_Read(void)
{
    // Start ADC conversion
    ADC_SoftwareStartConv(ADC1);
    // Wait until conversion is finish
    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));

    return ADC_GetConversionValue(ADC1);
}

int main(void)
{
    // Set clock to 168MHz
    // Don't forget to add defined symbol "HSE_VALUE=8000000"
    // in compile configuration
    CLOCK_SetClockTo168MHz();
    // Init delay function
    DELAY_Init();
    // Init USART
    // Here I'm using USART1 (PB6 Tx, PB7 Rx)
    USART_Config();
    // ADC config
    ADC_Config();

    while (1)
    {
        // Read ADC value
        adcValue = ADC_Read();
        // Convert to string
        sprintf(sAdcValue, "%i\n", adcValue);

        // Send to PC via USART1
        USART_PutString(sAdcValue);

        DELAY_Ms(1000);
    }
}
This is the link for download these 3 library: clock, delay, and usart.

8 comments :

  1. I read lot of articles and really like this article. This information is definitely useful for everyone in daily life. Fantastic job.

    OET Coaching in Melbourne
    OET Training in Melbourne
    OET Coaching for Dentists in Melbourne
    OET Coaching for Nurses in Melbourne

    ReplyDelete
  2. thx for your blog, after building the following error appears:
    collect2.exe: error: ld returned 1 exit status
    I added many components:
    C library
    Retarget printf
    semihosting
    M4 CMSIS Core
    CMSIS boot
    RCC
    GPIO
    Flash
    ADC
    USART
    Moreover I checked the toolchain path and its Ok (I correctly do tutorial2 :) thx for it)
    Any help please

    ReplyDelete
  3. I already test this code and I get this message:
    GCC HOME: C:\Program Files (x86)\GNU Tools ARM Embedded\4.9 2015q3\bin
    compile:
    [mkdir] Skipping E:\CooCox & STM32F4 Discovery\ADC\adc\Debug\bin because it already exists.
    [mkdir] Skipping E:\CooCox & STM32F4 Discovery\ADC\adc\Debug\obj because it already exists.
    [cc] Error reading history.xml: org.xml.sax.SAXParseException: The entity name must immediately follow the '&' in the entity reference.
    [cc] 200
    [cc] 10 total files to be compiled.
    [cc] arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Wall -ffunction-sections -g -O0 -c -DSTM32F407VG -DSTM32F4XX -DUSE_STDPERIPH_DRIVER -D__ASSEMBLY__ "-IE:\CooCox & STM32F4 Discovery\ADC\cmsis_lib" "-IE:\CooCox & STM32F4 Discovery\ADC" "-IE:\CooCox & STM32F4 Discovery\ADC\cmsis_boot" "-IE:\CooCox & STM32F4 Discovery" "-IE:\CooCox & STM32F4 Discovery\ADC\cmsis_lib\include" -IE:\ "-IE:\CooCox & STM32F4 Discovery\ADC\cmsis" '"E:\CooCox & STM32F4 Discovery\ADC\delay.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_lib\source\stm32f4xx_usart.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_boot\startup\startup_stm32f4xx.c"' '"E:\CooCox & STM32F4 Discovery\ADC\main.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_lib\source\stm32f4xx_rcc.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_lib\source\stm32f4xx_adc.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_lib\source\stm32f4xx_gpio.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_lib\source\stm32f4xx_flash.c"' '"E:\CooCox & STM32F4 Discovery\ADC\cmsis_boot\system_stm32f4xx.c"' '"E:\CooCox & STM32F4 Discovery\ADC\clock.c"'
    [cc] Starting link
    [cc] arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -g -nostartfiles -Wl,-Map=ADC.map -O0 -Wl,--gc-sections -LC:\CooCox\CoIDE\configuration\ProgramData\ADC -Wl,-TC:\CooCox\CoIDE\configuration\ProgramData\ADC/arm-gcc-link.ld -g -o ADC.elf ..\obj\delay.o ..\obj\stm32f4xx_usart.o ..\obj\startup_stm32f4xx.o ..\obj\main.o ..\obj\stm32f4xx_rcc.o ..\obj\stm32f4xx_adc.o ..\obj\stm32f4xx_gpio.o ..\obj\stm32f4xx_flash.o ..\obj\system_stm32f4xx.o ..\obj\clock.o
    [cc] c:/program files (x86)/gnu tools arm embedded/4.9 2015q3/bin/../lib/gcc/arm-none-eabi/4.9.3/../../../../arm-none-eabi/lib/armv7e-m\libg.a(lib_a-sbrkr.o): In function `_sbrk_r':
    [cc] sbrkr.c:(.text._sbrk_r+0xc): undefined reference to `_sbrk'
    [cc] collect2.exe: error: ld returned 1 exit status

    BUILD FAILED
    Total time: 2 seconds

    ReplyDelete
  4. I correctly add libraries clock, delay, and usart then the program is compiled with success :) but when I debug I get:
    GDB Hardware Debugger suspended
    thread[1] suspended signal 'SIGINT' received. descrption:interrupt
    4 default_Handler() startup_stm32xx.c:414 0x08000520
    3 () 0xfffffff9
    2 _svfprintf_r() 0x08001ddc
    1 0x6078af00

    Any help is welcome

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Hello,

    How to improve the program code to properly sample your input signal, eg. Sinusoidal frequency fm = 10kHz?
    How to set the sampling frequency fs = 300kHz?
    Then we have 30 samples per period Tm = 1 / fm.

    Please reply!
    Thank you very much and regards

    Wojtek Polish

    ReplyDelete
  7. It is only missing enabling GPIO B Peripheral.
    Add the code below after line 19.

    //Enable the clock on GPIO B
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

    ReplyDelete