target icon

Purpose

This tutorial gives an example of using one of the internal ADCs of the MCU.

list icon

Prerequisites
  • A STMicroelectronics discovery kit with STM32F407VG MCU, order code STM32F407-DISC1 (replaces STM32F4DISCOVERY).
  • USB 2.0 male to micro B cable (supplied with evaluation kit).
  • A full installation of an Hyperpanel OS release on a linux based PC including tools for flashing and debugging. This tutorial requires release V10.04.02 for MB997D or higher. Hyperpanel OS releases are available for free in the Download section of the website.
  • A Lunix PC with ARM gcc compiler, ARM gdb debugger and minicom installed.
  • A text editor to edit or modify source codes (We are using vi in our demo).
  • An analog potentiometer similar to the Rotation Sensor V2 used in this tutorial.

list icon

Software release

Hyperpanel OS V10.04.02 for MB997D, MB1225 or higher, available in the Download section.

list icon

Binary file

list icon

Hardware rework

The evaluation kit uses a USB port for power and debugger access. This connector is managed by a ST-LINK part, controlled by a STM32F103CBT6 (U2 on the board). The ST-LINK supports a virtual COM port on U2 pin 12 (ST-LINK_TX) and U2 pin 13 (ST-LINK_RX) but these pins are not connected to the USART of the STM32F407. To access to serial port for traces and messages, the solution is to use two flying wires to connect ST-LINK virtual COM port to the ASY1 HyperPanelOS serial port (see image):

U2 (STM32F103CBT6)                          P2 (CONNECTOR)
PIN 12 (ST-LINK_TX) <---------------------> PC11 (ASY1_RX)
PIN 13 (ST-LINK_RX) <---------------------> PC10 (ASY1_TX)

cd icon

Installation

1. On www.tutorial.hyperpanel.com, select Download from the main menu.

2. Download “Hyperpanel OS V10.04.02 for MB997D” or higher release for the MB997D kit.

3. On your PC Linux, copy and unzip the zip file in your root directory, for example:

cp hypv100402.zip /home/hyperpanel
cd /home/hyperpanel
unzip hypv100402.zip

4. With a text editor, update hhome environment variable in the stm32m4 file:

cd ~/hypv100402/shells
vi stm32m4

Update the first line, according to your root directory:

export hhome=/home/hyperpanel/hypv100402

5. Save this file and an execute the command:

source stm32m4

6. From this tutorial, use the button “Binary file” to download the zip file containing the binary. Unzip this file:

  unzip hpos-tuto316-bin.zip

7. Copy this binary file in HyperPanelOS release:

cp adc.bin ~/hypv100402/boards/stm32m4/exe

8. Connect the potentiometer to the MB997D.

POTENTIOMETER   MB997D   SIGNAL   WIRE
Gnd              GND      Ground   Black 
Power input      3V       Power    Red 
Signal output    PC2      Analog   Orange

9. Connect the MB997D board to a USB port on your Linux PC using the USB cable supplied with the board. Wait for a window to appear, then close it.

10. Open a Terminal window and run minicom to get access to the Hyperpanel OS serial port and to the application messages:

minicom -D /dev/ttyACM0 -b 115200

11. Open another Terminal window on your computer and enter the following commands:

cd ~/hypv100402/shells
source stm32m4
exe

12. Upload software to the board:

hgdb
romload stm32m4 adc

You should see messages similar to these:

Open On-Chip Debugger 0.10.0+dev-00001-g0ecee83-dirty (2017-02-10-06:53)
Licensed under GNU GPL v2 
For bug reports, read 
    http://openocd.org/doc/doxygen/bugs.html 
0x00002ed6 in ?? () 
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000314c msp: 0x10002000
auto erase enabled
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x20000046 msp: 0x10002000
wrote 393216 bytes from file adc.bin in 11.143908s (34.458 KiB/s)

13. You can now press on the reset button (black button) of the MB997D. Hyperpanel OS and the application will start.

list icon

To go further

If you want to edit the source code, write modifications, compile, link, etc., here’s how to do it :

 

  • From this tutorial, use the button “Source code” to download the zip file containing the source file. Unzip this file:
unzip hpos-tuto316-source.zip
  • Copy this source code in HyperPanelOS release:
cp adc.c ~/hypv100402/user/stm32app
  • Copy the link file in Hyperpanel release:
cp adc.lst ~/hypv100402/boards/stm32m4/exe
  • You can edit and make any modifications you want in the source file.
cd ~/hypv100402/user/stm32app
vi adc.c
  • Save the source file.
  • Compile the source file:
cd ~/hypv100402/user/stm32app
cmm adc
  • Call the linker to create the new binary file:
exe
lhypos adc
  • You can now upload this new binary to the board, in the same way as in the previous Installation chapter.

align left icon

Description
  • The app gives an “how to use” example of using an internal ADC of the MCU.
  • The ADC is used in single mode. Every second, a request for a sample is done.
  • The app use two sub-channels of the ADC. One for the potentiometer and one for the internal temperature sensor.

browser icon

Links

Let’s try

Code

hypos-tuto316-source

/*
**  adc.c - Sample code for HyperpanelOS ==================================  **
**                                                                           **
**  This simple code is located into the Application Container, it is run    **
**  by the VMK sub-operating system. On the other hand, the I/O container    **
**  runs all the drivers that are VMIO Finite State machines.                **
**                                                                           **
**  The goal of this small aplication is to use internal ADC of the MCU.     **
**                                                                           **
**  =======================================================================  **
*/

/* Purpose of this module ---------------------------------------------------
   
   The STM32F407 include a 12-bit ADC. It include 19 multiplexed channels,
   allowing to measure signals from 16 external sources, two internal sources
   and a rereference voltage channel. The A/D conversion can be performed in
   single or continuous conversion mode. The result of ADC is stored into a
   16-bit register.

   To access to HyperpanelOS ADC driver, we use the "iolib" simplified
   interface to "drv.c" driver. So this tutorial also serves as a "how to use"
   of the "iolib" library.

   In this simple example, we use the ADC in single mode to get a variable
   voltage from a potentiometer (from 0mv to 3000mV) by reading the sub-channel
   number 1 from the ADC1 device.

   We also get values from the temperature sensor of the MCU by reading the
   sub-channel 16 from the ADC1 device.

   The boards contains ADC driver static configuration :

   static const Adc_inpdef adc_inpdef[ADC_NBINP] = /+ High-level device def  +/
     BEGIN                             /+ ---------------------------------- +/
       "ADC1"                        , /+ uname     MUX input name           +/
        0                            , /+ fastcap   Fast sampling capacity   +/
        0                            , /+ gaincap   Amplifier available      +/
        12                           , /+ swpos     MUX input selector (PC2) +/
        0                            , /+ lsfifonum FIFO low-level identifier+/
        0                            , /+ lsfdmanum DMA low-level identifier +/
   
       "TEMP"                        , /+ uname     MUX input name           +/
        0                            , /+ fastcap   Fast sampling capacity   +/
        0                            , /+ gaincap   Amplifier available      +/
        16                           , /+ swpos     MUX input selector       +/
        0                            , /+ lsfifonum FIFO low-level identifier+/
        0                            , /+ lsfdmanum DMA low-level identifier +/
     END                             ; /+ ---------------------------------- +/

    Potentiometer voltage : The potentiometer is powered by the board (GND and
3V pins), the analog signal from the potentiometer deliver a continus tension
from 0V to 3v, and is connected to PC2 (cf. board pins definitions).

   Temperature : The temperature sensor has to generate a voltage that varies
linearly with temperature. It is internally connected to the ADC1_IN16 input
channel which is used to convert the sensor output voltage into a digital value.
The conversion is (for the stm32m4, cf. RM0090 p 413 "Reading the tempoerature"):

    Temperature (in C) = ((Vsense - V25) / Avg_Slope) + 25

with (cf. stm32F407 datasheet p 138):

    Vsense = Value read from the ADC data register
    V25       = 0.76 V
    Avg_Slope = 2.5 mV/C

*/

/* Includes files and external references ...................................*/

#include <hypos.h>                     // Hyperpanel OS basic interfaces
#include <drv_asy.h>                   // Prototype of "asy_write"
#include <tsk_iolib.h>                 // Prototype of "io_open"


/* Internal defines of this module ------------------------------------------*/

#define  TICK                   10000  // Code for tick event
                                
                                
/* Internal global variables of this module ---------------------------------*/

static unsigned int   idto           ; // Timer identifier
static int            adc_iod        ; // IOD of device ADC
static int            adc_iod1       ; // IOD of device ADC\ADC1
static int            adc_iod2       ; // IOD of device ADC\TEMP
static int            cmess = 0      ; // Counter of messages
static char           mess[80]       ; // Message for traces 


/*  List of tags for the "open_xyz" procedures ------------------------------*/

static const char *adc_taglist      = ""                                      ; 


/*  Prototypes --------------------------------------------------------------*/

static int  wait_evt     (void)                                               ;
static void open_adc     (void)                                               ;
static void trace        (char*)                                              ;
    

/* Beginning of the code ----------------------------------------------------

loop_app_tsk        Entry point for create_task - Main event loop
wait_evt            Wait for events
open_adc            Open the ADC driver
trace               Send a trace on serial port

*/


/*  Procedure loop_app_tsk ----------------------------------------------------

    Purpose : This is our task main loop.
*/

int loop_app_tsk (void *param)
  {
    int             ev               ; // Our event
    char            list[80]         ; // Drive / tag list
    int             nb               ; // Number of drivers
    char            name[20]         ; // Driver name
    char            valstr[20]       ; // String value from tag
    int             valint           ; // Integer value from tag
    float           valfloat         ; // Integer value from tag
    char           *l, *p            ; // Pointer on "list"
    int             i                ; // Driver counter
    int             j = 0            ; // Character counter
    int             ret              ; // Return procedure code
    char            e[4],f[4]        ; // Integer / float part of temperature
    char            str[20]          ; // Character string

    trace("Hyperpanel OS - Tutorial adc app") ; // Message

/*****************************************************************************
 * Step 1 : Using information procedures to acces global info of all         *
 *          the drivers of the board.                                        *
 *****************************************************************************/

    ret = io_list_drivers(list       , // Ge the list of all the available
                   sizeof(list),&nb) ; // character drivers

    hsprintf(mess,"(list_drv) ret=%d nb=%d",ret,nb); // Return value
    trace(mess)                                    ; // of procedure

    for (l=list,p=l,i=0; i LT nb; i++) // Loop on all the driver name
      {
        while(*p NE '\n' LOGAND        // Search for the end of the line or
              *p NE 0)                 // the end of the list.
         {
           p++                    ;    // Next character
           j++                    ;    // Update character counter
         }                        

        Strncpy(name,l,j)      ;    // End of line reached, get the
        name[j] = 0            ;    // driver name and close the string

        trace(name)            ;    // Trace the current driver name
        j = 0                     ;    // Reset character counter
        p++                       ;    // Point on next character of the list
        l = p                     ;    // Point of first character of next name

      }

/*****************************************************************************
 * Step 1 : Driver initialization.                                           *
 *****************************************************************************/

  open_adc()                         ; // Open the ADC driver 

/*****************************************************************************
 * Step 1 : We start a timer that will send us an event every 1 second so    *
 * ------   that we can update the time.                                     *
 *****************************************************************************/

    set_uto(CLOCK      ,               // Timer mode: clock
            1000       ,               // Duration in milliseconds
            TICK       ,               // Event code
            0          ,               // Event reserve field
            &idto        )           ; // Timer identifier


/*****************************************************************************
 * Step 2 : Here is our main event loop. For each loop, we do as follows :   *
 * -------  - First we wait for an event.                                    *
 *          - Then if the event code is TICK we print "Hello World"          *
 *****************************************************************************/

    wait_evt                         : // Beginning of loop label

    ev = wait_evt()                  ; // Unschedule until an event is
                                       // received

    if ( ev == TICK )                  // If the event is the tick event
      {                                //
        trace("TICK event")         ;  // Send a trace

                                       // Voltage from potentiometer.

        strcpy(list,"SAMPLE")        ; // Get value from ADC/ADC1
        ret = io_getval(adc_iod1     , //
                        list         , //
                        sizeof(list)); //

        Strcpy(valstr,&list[7])      ; // Tag to int conversion.
        hsscanf(valstr,"%x",&valint) ; // 
        valint = (valint*3000)/4095  ; // Value normalization to mV.

        hsprintf(mess                , // Format the message with 
                 "Potentiometer %dmV", // current voltage in mV.
                 valint             ); //
        trace(mess)                  ; // Send a trace.

                                       // Temperature

        strcpy(list,"SAMPLE")        ; // Get value from ADC/TEMP
        ret = io_getval(adc_iod2     , //
                        list         , //
                        sizeof(list)); //

        Strcpy(valstr,&list[7])      ; // Tag to int conversion.
        hsscanf(valstr,"%x",&valint) ; // 
                                     
                                       // Convert value to temperature
                                       // (cf. formula in explanations).

        valfloat = ((valint/1000.f - 0.76f)/2.5f) + 22.0f ;

        valint = valfloat*10         ; // Integer part for temperature.
        hsprintf(str,"%d",valint)    ;
        Strncpy(e,str,2)  ; e[2]=0   ;
        Strcpy (f,&str[2]); f[1]=0   ; // Fractional part of temperature.

        hsprintf(mess                , // Format the message with 
               "Temperature   %s.%sC", // current voltage in mV.
                 e,f                ); //
        trace(mess)                  ; // Send a trace.
      }
    else
      {
        trace("Other event")        ;  // Send a trace
      }

    goto wait_evt                    ; // Wait for the next event

    return 0                         ;
  }

/*  Procedure wait_evt ------------------------------------------------------*/
/*
    Purpose : Unschedule until the next event is received, whatever it is.
*/

static int wait_evt (void)
  {
    unsigned int waitlist[8][3]      ; // Parameter of "waitevt_task"

/*****************************************************************************
 * Step 1 : Build a list with one WAIT_CODEINT entry that will accept all    *
 * ------   the event codes ranging from 0 to 20000. Then call               *
 *          "waitevt_task", we will be unscheduled until the next event will *
 *          be received                                                      *
 *****************************************************************************/

    waitlist[0][0]  = WAIT_CODEINT   ; // All events with
    waitlist[0][1]  = 0              ; // a code between 0
    waitlist[0][2]  = 20000          ; // and 20000

    waitevt_task(waitlist ,            // Address of waiting list
                 1        ,            // Size of "waitlist[]"
                 0        ,            // maximum waiting time = no
                 0        )          ; // Do not purge previous events

/*****************************************************************************
 * Step 2 : Here we are scheduled again. The VMK has written into its        *
 * ------   global variable "task_evt" a copy of the event that has          *
 *          scheduled us again.                                              *
 *****************************************************************************/

    return task_evt.code             ; // Return event code
  }

/*  Procedure open_adc ------------------------------------------------------*/
/*
    Purpose : Open the ADC driver.
*/

static void open_adc(void)
  {
    int   ret                        ;  // Return procedure value

    char            list[200]        ; // Device list names for a driver
    char            name[20]         ; // Tag name
    char            taglist[80]      ; // Tag list
    int             nb               ; // Nomber of devices for a driver
    char           *l, *p            ; // Pointer on "list"
    int             i                ; // Tag counter
    int             j = 0            ; // Character counter

/* open .....................................................................*/

    ret = io_open("ADC","ADC1",adc_taglist,&adc_iod) ; // Open ADC\ADC1

    hsprintf(mess,"(open) adc_iod = 0x%08x ret=%d",
                                      adc_iod,ret);
    trace(mess)                                   ;

/* devices list .............................................................*/

    ret = io_list_devices("ADC",list,sizeof(list),&nb);

    hsprintf(mess,"(list_dev) ret=%d nb=%d", //
                                    ret,nb); // Return value
    trace(mess)                            ; // of procedure
    trace(list)                            ; // Device list.

/* Device tags list .........................................................*/

    ret = io_list_tags("ADC",list,sizeof(list),&nb);

    hsprintf(mess,"(list_tag) ret=%d nb=%d", //
                                    ret,nb); // Return value
    trace(mess)                            ; // of procedure

    for (l=list,p=l,i=0; i LT nb; i++)       // Loop on all the driver name
      {
        while(*p NE '\n' LOGAND              // Search for the end of line or
              *p NE 0)                       // the end of the list.
         {
           p++                             ; // Next character
           j++                             ; // Update character counter
         }                        

        Strncpy(name,l,j)                  ; // End of line reached, get the
        name[j] = 0                        ; // driver name and close the string

        j = 0                              ; // Reset character counter
        p++                                ; // Point on next char of the list
        l = p                              ; // Point of first char of next name

        if (!strcmp(name,"NUMDEV")) continue;// Just to avoid break point

        strcpy(taglist,name)               ; // Get default val of of the

        hsprintf(mess,"(tag) <%s>",          //
                                   taglist); //
        trace(mess)                        ; //

        io_getval(adc_iod,taglist          , // readable tags.
                   sizeof(taglist))        ; //
        hsprintf(mess,"(getval) <%s>",       //
                                   taglist); //
        trace(mess)                        ; //
      }

/* sub-channel allocation ...................................................*/

    ret = io_alloc_sub(adc_iod,"UNAME=ADC1",&adc_iod1);
    hsprintf(mess,"(alloc_sub) adc_iod1=0x%08x ret=%d",
                                         adc_iod1,ret);
    trace(mess)                                       ;

    ret = io_alloc_sub(adc_iod,"UNAME=TEMP",&adc_iod2);
    hsprintf(mess,"(alloc_sub) adc_iod2=0x%08x ret=%d",
                                         adc_iod2,ret);
    trace(mess)                                       ;

    strcpy(taglist,"FASTCAP")                         ; 
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"GAINCAP")                         ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"MODE")                            ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"IHYST")                           ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"GAIN")                            ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"SIGN")                            ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"SCALING")                         ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"SCAMUL")                          ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;

    strcpy(taglist,"SCADIV")                          ;  
    ret = io_getval(adc_iod1,taglist,sizeof(taglist)) ;
    hsprintf(mess,"(getval) <%s> ret=%d",taglist,ret) ;
    trace(mess)                                       ;
  }

/*  Procedure trace ---------------------------------------------------------*/
/*
    Purpose : Send a trace on serial port.
*/

static void trace(char *s)
  {
    char str[255]                    ; // Message to be sent

    hsprintf(str                     , // We build in "str" the message
           "%6d  %s\r\n"             , // to be sent
            cmess ++ ,s            ) ; //

    asy_write(0                      , // We send on ASY0
            (unsigned char*)str      , // the "mess" message
            strlen(str)            ) ; // Count of bytes to be sent
  }

 

Terminal

sauron  /home/hypwork/stm32m4/exe >> hgdb
GNU gdb (7.10-1ubuntu3+9) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".

man                    Display again this manual
stm32m4                Send the reset halt command to openocd
armdisconnect          Deconnexion command
peek adr               Read and display  a 32 bit value at adr
poke adr val           Write a val 32 bits value at adr
poke_m adr msk val     Write bits in a 32 bits word with a mask
peekrange base o1 o2   Read 32 bits words from base+o1 to base+o2
affichb adr size       Print a memory area starting at adr
vmio n                 Display the tnote_evt_s debug table
int                    Display the tnote_it debug table
rte                    Return from interrupt
romload stm32m4 app    Write the app executable file in flash
rom stm32m4 app        Connect to target and load app dbg symbols
gpio bank n state      Set GPIO n (0-15) of bank (1-9) to 0/1
clock 0/1/2/3          Output SYSCLK/PLLI2S/HSE/PLL to MO2/PC9
(gdb) romload stm32m4 adc
Open On-Chip Debugger 0.10.0+dev-00001-g0ecee83-dirty (2017-02-10-06:53)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
0x2001174a in ?? ()
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x00005bfc msp: 0x10002000
auto erase enabled
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x20000046 msp: 0x10002000
wrote 393216 bytes from file adc.bin in 10.398664s (36.928 KiB/s)
Cannot access memory at address 0x8000f8d0
(gdb)