This tutorial gives an example of using one of the internal ADCs of the MCU.
- 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.
Hyperpanel OS V10.04.02 for MB997D, MB1225 or higher, available in the Download section.
Binary file
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)
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.
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.
- 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.
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)