STM32 Tutorial

Autor: AliOs, 2012 (http://www.keytosmart.com, Kategorie ARM / STM32F10xxx Cortex-M3)

Von der Homepage übernommen und in diesem Text gesammelt von H. Högl, Hochschule Augsburg, <Hubert.Hoegl@hs-augsburg.de>.

Dieser Text beschreibt wie man das STM32VLDiscovery Board mit GNU Tools programmieren kann. Es wird die Yagarto Umgebung (Eclipse) von Michael Fischer verwendet. Auch der Startup-Code und die Linker Skripte sind von Michael Fischer.

Inhalt

1   Setting ARM GCC development environment

URL: http://www.keytosmart.com/setting-arm-gcc-development-environment/

GCC Development Environment

As we mentioned before, we are going to stick with free software tools. So we are going to use free and open source GCC compiler to develop programs for ARM Cortex microcontrollers. As we are going to work from windows environment there are couple serious choices that are pretty similar. One is using CodeSourcery Lite edition orYagarto Gnu ARM toolchain. Both tools work same as they use same GCC compiler and other tools. Both seems to be supported frequently. CodeSourcery claim that they are updating Lite Edition twice a year, while Yagarto is doing this more frequently depending on updates of separate tools. So your choice won’t affect your final rezult.

When installed you can easily check if everything works fine by opening command line tool and writing simple command that check version of ARM GCC compiler:

arm-none-eabi-gcc.exe -v
img/arm_compiler_version_check-300x151.jpg

Both can be used with Eclipse IDE and require makefile. The only difference - is a path to tools when compiling. Download any or both of these and install to your machine. Next step is to prepare Eclipse IDE Here you find a great step-by-step instructions how to set up this environment (you can skip debugger settings for now).

img/ARM_eclipse-272x300.png

ST-Link Utility

When program is compiled we need somehow to upload it to microcontroller Flash or RAM memory. For this there is a great ST-Link Utility developed by STMicroelectronics. Its documentation ca be found here. As STM32-Discovery is already equipped with ST-Link hardware we only need to plug USB cable and start working. Once installed, in settings select SWD protocol to start talking to STM32-Discovery board:

img/ST-Link-SWD-protocol-230x300.png

After confirmation you should see a connection information:

img/ST-Link-connected-300x192.png

These are all tools that will give you a start in writing programs for STM32 microcontrollers and loading them to chip.

2   Setting up Eclipse and Code Sourcery lite for STM32 Discovery Development

  1. Januar 2012

URL: http://www.keytosmart.com/setting-eclipse-code-sourcery-lite-stm32-discovery-development/

Here is a step by step guide to setting up a Open Source Stm32 Arm based embedded development tool chain with the Eclipse IDE.

It is targeted for the ST32-Discovery demo board under Windows (This was implemented under Windows 7 64bit) and uses the open source Code Sourcery version of the Gnu Arm cross assembler.

The project will be set up to use the Arm CMSIS Library which provides a common infrastructure for Arm MCU’s and the Standard Peripheral Library so you should be able to run any of the STM32 Discovery example projects with this configuration.

First The Requirements.

This is what you will need to download

The Java JRE from here http://www.java.com/en/download/index.jsp if you don’t already have Java installed (This is needed to run Eclipse)

The Eclipse Helios IDE for C/C++ Developers available from here http://www.eclipse.org/downloads/packages/release/helios/sr1

The STLink Utility from here http://www.st.com/stonline/products/support/micro/files/um0892.zip

The ARM-based 32-bit MCU STM32F10x Standard Peripheral Library v3.5.0 from here http://www.st.com/internet/com/SOFTWARE_RESOURCES/SW_COMPONENT/FIRMWARE/stm32f10x_stdperiph_lib.zip

The STM32VLDISCOVERY firmware package from here http://www.st.com/stonline/products/support/micro/files/an3268.zip

The IA32 Windows Installer of the Code Sourcery Lite Arm GNU Tool chain formhttp://www.codesourcery.com/sgpp/lite/arm/portal/release1294

Preparing the Workspace.

Eclipse works with workspaces which are folders on the file system containing related projects , lets set up a folder for our projects.

I created the folder D:stm32-discovery-projects which we will cll <project-base> from now on.

Now within <project-base>

  • Create a folder “workspace”
  • Unzip the Standard Peripheral Library into <project-base>
img/01-directory1.jpg

Install The Code Sourcery EABI tool chain for Windows

Use the Windows IA32 Installer and accept all the default options.

img/02-cose-sourcery-300x214.jpg

When that is installed, go to the /<codesourcery install dir>/Sourcery G++ Lite/bin folder and copy cs-rm.exe to rm.exe

Installing Eclipse

First install Java if you don’t already have Java installed on your system.

Eclipse has no set-up program , you just unzip it to somewhere on your file system , you may want to give the folder another name rather then “eclipse” such as “eclipse-stm32-devel” so you can have multiple versions of Eclipse. We will call this <eclipse-base>, also at this point it’s useful to create a short cut to the “<eclipse-base>eclipse.exe” on the desktop to run Eclipse easily.

OK Now Run the “<eclipse-base>eclipse.exe” and you will get this dialog ..

img/03-select-workspace-300x156.jpg

Set the workspace to the one you created earlier <project-base>workspace and tick “Use this as a default and do not ask again”. All going well you should get the initial Eclipse screen.

img/04-eclipse-intial-desktop-300x206.jpg

Now select Help->Install new software

img/05-install-new-software-300x108.jpg

and click Available Software Sites

img/06-avail-sites-300x88.jpg

Type in “cdt” into the filter so we can narrow the list down, select the http://download.eclipse.org/tools/cdt/releases/heliosLocation and then click OK

img/07-work-with-300x154.jpg

Select the new download site from the Work with drop down, and then select C/C++ GCC Cross Compiler Support

Click on Next and continue on with the install Wizard and let the Cross Compiler Support install.

img/08-installing-300x145.jpg

You will then be asked to Restart Eclipse, do that now.

Creating a Stm32 Arm based project Template

A Small point of information in the following I set up the Debug target only , afterwards I realised it would have been better to apply all the settings to the All Configurations target so both debug and release are set-up broadly the same

Next we create a project called Template. We will use this as a base project so it can be copied when you want to start an new project with all the following settings in place.

img/09-Create-project-269x300.jpg

Select Cross-Compile Project and Cross GCC and click Next>

img/10-cross-compile-300x262.jpg

Put in the tool command prefix arm-none-eabi- ( note the trailing – ) and then browse to the directory you installed the Code Sorcery Gnu Arm Tool chain and select the bin folder then select Finish

Next Click on the Go to workbench Icon...

img/11-goto-work-bench.jpg

And we get the C/C++ Perspective.

img/12-eclipse-project-view-300x215.jpg

Right Click on the Template project in the project explorer and then select C/C++ Build->Tool Chain Editor

img/13-toolchaineditor-300x158.jpg

Click Select Tools.. and add the GCC Assembler with Add tool–>

img/14-add-assembler-300x142.jpg

Now we need to correct the assembler name for the cross compiler version as this is setup as the GCC assembler as there is no Cross Assembler listed.

img/15-change-assembler-path-300x179.jpg

We do this by selecting Settings->GCC Assembler and changing the Command to include the full cross compiler prefix.

Also in the General Section of GCC Assembler we need to set these Flags:

-mthumb -mcpu=cortex-m3

And in the Miscellaneous Sections for the the Cross GCC Compiler and Cross G++ Compiler we need these flags:

-c -mthumb -mcpu=cortex-m3 -mfix-cortex-m3-ldrd

And finally in the Miscellaneous Section for the the Linker section

-T “${ProjDirPath}\startup_src\stm32_flash.ld” -mthumb -mcpu=cortex-m3
 -mfix-cortex-m3-ldrd -Wl,-Map=linker.map -Wl,-cref -Wl,–gc-sections

Next ,OK the settings Dialog and then Right Click on the Template project again and select New Folder.

Type “src” and finish , do the same again for “startup_src” so we have the following folder structure..

img/16-folder-structure.jpg

Now copy the file <project-base>STM32F10x_StdPeriph_Lib_V3.3.0LibrariesCMSISCM3DeviceSupportSTSTM32F10xstartupTrueSTUDIOstartup_stm32f10x_md_vl.s to startup_src. Note that you can drag and drop or cut and paste the file into the folder.

Note due to a bug in Eclipse CDT, to be recognised as an assembly file the eclipse cdt needs assembly files to have an upper case S file extension, so now change the name tostartup_stm32f10x_md_vl.S , also it’s is likely that the file-name of the assembler has been incorrectly set-up, so you will need to right click on startup_stm32f10x_md_vl.S and choose properties then select C++ Build->settings and correct the entries for the assembler name and flags to the same as entered above.

Next right-click on startup_src and select New File, enter the filename stm32_flash.ld and Finish. Now Right click on the newly created file and select Open With -> Text Editor and paste the following text...

/*
* STM32 Discovery Linker Script
*/

_estack = 0x20002000;    /* global needed for startup_stm3210x_md_vl.S */
_minimum_stack_size = 0x100; /* 256 bytes miniumum stack size */

ENTRY(Reset_Handler)

MEMORY
{
  rom (rx)      : ORIGIN = 0x08000000, LENGTH = 128K
  ram (xrw)     : ORIGIN = 0x20000000, LENGTH = 8K
}

SECTIONS
{
  .text :
  {
    KEEP(*(.isr_vector))
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
    KEEP (*(.init))
    KEEP (*(.fini))
    . = ALIGN(4);
  } >rom

   /* .ARM.exidx is sorted, so has to go in its own output section.*/
   .ARM.exidx : {
        __exidx_start = .;
        *(.ARM.exidx* .gnu.linkonce.armexidx.*)
        __exidx_end = .;
    } >rom

    _sidata = .;         /* global needed for startup_stm3210x_md_vl.S */

  /*now start ram area with initialized data section */
  .data :
  {
    _sdata = .;        /* global needed for startup_stm3210x_md_vl.S */
    *(.data)
    *(.data*)
    . = ALIGN(4);
    _edata = .;        /* global needed for startup_stm3210x_md_vl.S */
  } >ram AT>rom

  /* uninitialized data section bss*/
  .bss :
  {
      . = ALIGN(4);
    _sbss = .;         /* global needed for startup_stm3210x_md_vl.S */
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    _ebss = .;        /* global needed for startup_stm3210x_md_vl.S */
  } >ram

  /* Generates an error if minimum stack size is not left in ram */
  ._check_stack_space :
  {
    . = ALIGN(4);
    . = . + _minimum_stack_size;
    . = ALIGN(4);
  } >ram

  .ARM.attributes 0 : { KEEP (*(.ARM.attributes)) }

  /* Remove debug information from the standard libraries */
  /DISCARD/ : {
      *(.note.GNU-stack)
    libc.a ( * )
    libgcc.a ( * )
    libm.a ( * )
  }

and finally Save the file

img/17-discovery-demo-files.jpg

Don’t copy the folders just the source files and readme.txt.

Also copy the files in stm32vldiscovery_packageUtilities to the src folder

img/18-files-in-utility.jpg

So your source folder should look like this…

img/19-src-floder-after-copy.jpg

Were not quite done yet , go back to the project properties (right click on the Template project->Properties) and select C++ General->Paths and Symbols and select the Source Location tab

Now use Add Folder.. to add the src and startup_src directory’s if they are not already there.

Then use the Link Folder.. button to add these paths (you need to set the Link to folder in the file system check box)

<project-base>\STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\CMSIS\CM3\CoreSupport
<project-base>\STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
<project-base>\STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\STM32F10x_StdPeriph_Driver\src
img/20-link-folder-dialog-300x100.jpg

When done it should look something like this:

img/21-source-files-floders-300x176.jpg

Now switch to the Include tab and select GCC then add the project src folder and the following paths , make sure you select GCC when you do this

Now we need to add the src folder in a special way , so that it will adjust to the project name when we make a copy of the project,

img/22-src-include-path-300x194.jpg

so we enter /${ProjName}/src and make sure the Is a workspace path check box is checked as above.

Now enter the following file system paths as well.

<project-base>\STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\CMSIS\CM3\CoreSupport
<project-base>\STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
<project-base>\STM32F10x_StdPeriph_Lib_V3.3.0\Libraries\STM32F10x_StdPeriph_Driver\inc

When done it should look something like this:

img/23-include-paths-300x122.jpg

Next we need to set some defines , go to the Properties->C++ Build->Settings->GCC Cross Compiler->Symbols and add the following defines

STM32F10X_MD_VL

USE_STDPERIPH_DRIVER

USE_STM32_DISCOVERY

img/24-defines-300x163.jpg

Next, Go to the Properties->C++ Build->Settings and select the Build Steps tab and enter the following in the Post-build-stepsCommand section and give it a description.

arm-none-eabi-objcopy -S  -O binary  “${ProjName}” “${ProjName}.bin”
img/25-create-binary-300x128.jpg

Finally go to the Build tab and Deselect Build Before Launch to stop the project being built every time you run the STLink Utility

You now have easy access to the utility and can now run the STLink Utility at any time from the tool-bar icon shown below ..

img/27-run-toolbar.jpg

Ok Right Click on the Template Project and select Build

**** Build of configuration Debug for project Template ****

**** Internal Builder is used for build               ****

arm-none-eabi-g++ -T D:/stm32-discovery-projects/workspace/Template\startup_src\stm32_flash.ld
-mthumb -mcpu=cortex-m3 -mfix-cortex-m3-ldrd -Wl,-Map,linker.map
-oTemplate startup_src\startup_stm32f10x_md_vl.o src\stm32f10x_it.o
src\main.o
src\STM32vldiscovery.o
StdPeriph_Lib_V3.3.0\stm32f10x_wwdg.o
StdPeriph_Lib_V3.3.0\stm32f10x_usart.o
StdPeriph_Lib_V3.3.0\stm32f10x_tim.o
StdPeriph_Lib_V3.3.0\stm32f10x_spi.o
StdPeriph_Lib_V3.3.0\stm32f10x_sdio.o
StdPeriph_Lib_V3.3.0\stm32f10x_rtc.o
StdPeriph_Lib_V3.3.0\stm32f10x_rcc.o
StdPeriph_Lib_V3.3.0\stm32f10x_pwr.o
StdPeriph_Lib_V3.3.0\stm32f10x_iwdg.o
StdPeriph_Lib_V3.3.0\stm32f10x_i2c.o
StdPeriph_Lib_V3.3.0\stm32f10x_gpio.o
StdPeriph_Lib_V3.3.0\stm32f10x_fsmc.o
StdPeriph_Lib_V3.3.0\stm32f10x_flash.o
StdPeriph_Lib_V3.3.0\stm32f10x_exti.o
StdPeriph_Lib_V3.3.0\stm32f10x_dma.o
StdPeriph_Lib_V3.3.0\stm32f10x_dbgmcu.o
StdPeriph_Lib_V3.3.0\stm32f10x_dac.o
StdPeriph_Lib_V3.3.0\stm32f10x_crc.o
StdPeriph_Lib_V3.3.0\stm32f10x_cec.o
StdPeriph_Lib_V3.3.0\stm32f10x_can.o
StdPeriph_Lib_V3.3.0\stm32f10x_bkp.o
StdPeriph_Lib_V3.3.0\stm32f10x_adc.o
StdPeriph_Lib_V3.3.0\misc.o
STM32F10x\system_stm32f10x.o
CoreSupport\core_cm3.o
arm-none-eabi-objcopy Template Template.bin -O binary
Build complete for project Template
Time consumed: 464  ms.

All going well there will be no errors , now use the tool-bar button above to run STLink…..

img/28-stlink-300x212.jpg

With File->Open File.. browse to <project-base>workspaceTemplateDebug and select Template.bin and open , you will be asked if you want to download to the device so say OK you will then get a Program Dialog make sure the address is0×08000000 and select Program

Next Select Target-> MCU Core from the main menu bar.

img/29-target-mcu-core-300x200.jpg

Press System Reset and then Run to Execute your program , both the Green and the Blue lights should flash together.

Some final notes

From the Project menu deselect Project->Build Automatically then Clean the project , You will also need to delete the filesTemplate.bin and linker.map from the Debug directory this will then give you a clean Template Project folder for copying.

The above only set-up the debug target , you will need to add the folder paths and other settings for the release target in a similar way.

To test out a STM32-Discovery demo project from the firmware library you will need to copy and then paste the Template project within Eclipse and give it the same name as the STM32-Discovery Demo project , then copy all the source files from the STM32 Discovery project source folder over the files in the new Eclipse project source folder. Build and your away.

Note that some of the demo projects will only work correctly from a hard reboot so read the readme.txt (yes:) , They will not work while the STLink is still connected so you will have to power cycle the STM32 Discovery board.

3   Hardware debugging working with Eclipse and Code Sourcery Lite

  1. Januar 2012

URL: http://www.keytosmart.com/hardware-debugging-working-eclipse-code-sourcery-lite/

This is a follow on from the Setting up Eclipse and Code Sourcery for STM32 Discovery Development. and so assumes you already have Eclipse working with the Code Sourcery Lite tool chain.

What you will need

Atollic True Studio Lite, which can be downloaded for free from the Atollic website here:

http://www.atollic.com/index.php/download/downloadstm32

Getting Started

Download and Install the Atollic True Studio Lite, this contains within the package a gdbserver which we will use to talk to the STLink on the STM32-Discovery board.

Configuring Eclipse

From the Eclipse main menu bar , select Help->Install new software and select the CDT - http://download.eclipse.org/tools/cdt/releases/helios that you set up earlier for the Work with drop down

You need to select C/C++ GDB Hardware debugging as shown below

img/01-install-debugger-300x279.jpg

Now continue on and install the software and restart Eclipse when you are asked.

From the main menu choose Run->Debug configurations...

img/02-debug-configurations-select-300x175.jpg

Now select GDB Hardware debugging and click on the New icon (circled in red below)

Select the Project first and then the C/C++ application with the Search Project.. button and finally give the debugging session a Name.

Which should look something like this after the Apply buttons is pressed:

img/03-debug-configurations-300x203.jpg

Next move on to the Debugger Tab and browse to the <code sourcery>Sorcery G++ Litebin folder to select the arm-none-eabi-gdb.exe program, then set the port number to 61234. Then finally click on Apply.

img/04-debug-configurations2-300x170.jpg

Close the Debug configuration Dialog and go to Run->External tools->External tools configuration...

img/05-Setup-debug-server-external-300x187.jpg

Browse to the ST-LINK_gdbserver program ( <Atollic>TrueSTUDIO STM32 Lite 1.4.0ServersST-LINK_gdbserver) in the location field and add the arguments -e -d which tells ST-LINK_gdbserver to run in persistent mode and to use SWD rather than JTAG for debugging.

Now select the Build tab

img/06-debug-extern-tools-config-300x206.jpg

Uncheck the Build before Launch check box and finally click on Apply.

Now the gdbserver can be started at any time from the Launch external tools tool bar icon shown below:

img/07-debug-run-external-toolbar-300x84.jpg

Starting a debug session

In order to start a debug session for you program you must first make sure the gdbserver is running so the first thing to do is to use the Launch External Tools tool-bar button which we set up previously to start up the gdbserver. As the gdbserver starts it will output start up information to the console so you can see if it started up all right.

Now start a debug session for you program, From the main menu select Run->Debug Configurations... as before and select the profile you set up previously under GDB Hardware Debugging and click Debug.

The perspective should now switch to the debug perspective, if not you can manually switch to the debug perspective by pressing the debug button on the right hand side of the tool bar.

img/08-debug-session-300x201.jpg

You are now ready to run and debug your STM32 Discovery program.

As you can see from the above screen shot the gdbserver is listed in the debug process window along with you program. Sometimes the gdbserver may crash or lock up, If that happens you can restart it by right clicking on it and selecting Terminate and Relaunch. If the gdbserver is not listed then you will need to restart it from the external tools tool-bar icon. Usually the gdbserver will only need to be started once after Eclipse has been started.

You will need to create a separate debug profile for each of the projects that you wish to debug.

4   ST32VLDiscovery project template for GCC

URL: http://www.keytosmart.com/st32vldiscovery-project-template-gcc/

In this tutorial we will set up a simple template for progamming ST32 -Discovery baord. For this we are going to use latest Code Sourcery and Eclipse IDE. To make things simpler we are going to use ARM-based 32-bit MCU STM32F10x Standard Peripheral Library v3.5.0 that can be downloaded from ST site. Also we are going to useSTM32VLDiscovery firmware package for example files. And why write our own linker, startup and make scripts. For this we are going to use Michael Fischers example project for yagarto.

demo/STM32Test.zip

We only need small modifications to fit our needs.

In this stage we assume that you have set up Eclipse and Code Sourcery and we can go further.

First of all create new C project in Eclipse File->New:

img/Eclipse_New_C_Project-256x300.jpg

Enter project name, select the path where project will be stored and select Makefile project->Empty project in Project type list. In Toolchain list select Other Toolchain. This will create empty project that will run make to compile project.

Now we can start adding files. In order to avoid significant mess in project folder it is good practice to create some logical folder structure. There are no particular rules how this can be one exactly but you will find what works best for you. In my case I create two root folders Device and Libraries. Then in Device folder create two subfolders - linker and startup.

img/project_folders.png

After our folders are set we need to copy source and library files that will be needed for our project. First of all we import linker scripts. Import stm32f103xb_flash.ld andstm32f103xb_ram.ld files to Device/linker folder from STM32Test/prj. Open each of them and edit memory configuration to fit your board like this

MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 128K
    RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 8K
}

As our Discovery board has 128K of flash and 8K of RAM memory. There are two linker scripts for two cases – one to run code from flash and another from ram memory. We will be using flash mode for now.

Next step is to import startup code. Now we import crt.c andvectors_stm32f10x_md.c files in to Device/startup directory from STM32Test/src. These are more general and well commented comparing to our earlier discussed. We don’t have edit anything here as startup files are ready for medium level arm cortex-m3 devices.

Now we have to import necessary libraries in to our project. These will be CMSIS and STM32Discovery specific. Import latest CMSIS library from downloaded Standard Peripheral Library in to Libraries folder. This library includes core support library and device support libraries. This is where register definitions are made, system level initializations and standardized access to processor features (Probably we will need to make separate post about CMSIS). From same location import STM32F10x_StdPeriph_Driver package where all peripheral access drivers are stored. Then import STM32Discovery board specific library from downloaded STM32VLDiscovery firmware package. It lies in an3268\stm32vldiscovery_package\Utilities folder. So import whole Utilities folder in to projects Library folder. We sould have following view in project explorer:

img/eclipse_imported_libraries-179x300.jpg

We have set up the base. Now all we need is a main program and makefile. stm32vldiscovery_package has a set of code examples. So we are going to use one. So import source files in to root project folder from an3268\stm32vldiscovery_package\Project\Examples\GPIOToggle:

img/project_files.jpg

import only files.

Last thing to do is to create makefile. I used make file from STM32Test package and slightly modified to comply with project directories and also added size function to display memories used on target. So Create new blank Makefile in project root folder and copy following contents to it:

  1 #
  2 #       !!!! Do NOT edit this makefile with an editor which replace tabs by spaces !!!!
  3 #
  4 ##############################################################################################
  5 #
  6 # On command line:
  7 #
  8 # make all = Create project
  9 #
 10 # make clean = Clean project files.
 11 #
 12 # To rebuild project do "make clean" and "make all".
 13 #
 14 
 15 ##############################################################################################
 16 # Start of default section
 17 #
 18 
 19 TRGT = arm-none-eabi-
 20 CC   = $(TRGT)gcc
 21 CP   = $(TRGT)objcopy
 22 AS   = $(TRGT)gcc -x assembler-with-cpp
 23 BIN  = $(CP) -O ihex
 24 
 25 MCU  = cortex-m3
 26 
 27 # List all default C defines here, like -D_DEBUG=1
 28 DDEFS = -DSTM32F10X_MD_VL -DUSE_STDPERIPH_DRIVER
 29 # List all default ASM defines here, like -D_DEBUG=1
 30 DADEFS =
 31 
 32 # List all default directories to look for include files here
 33 DINCDIR =
 34 
 35 # List the default directory to look for the libraries here
 36 DLIBDIR =
 37 
 38 # List all default libraries here
 39 DLIBS =
 40 
 41 #
 42 # End of default section
 43 ##############################################################################################
 44 
 45 ##############################################################################################
 46 # Start of user section
 47 #
 48 
 49 #
 50 # Define project name and Ram/Flash mode here
 51 PROJECT        = main
 52 RUN_FROM_FLASH = 1
 53 
 54 # List all user C define here, like -D_DEBUG=1
 55 UDEFS =
 56 
 57 # Define ASM defines here
 58 UADEFS =
 59 
 60 # List C source files here
 61 LIBSDIR    = ./Libraries
 62 CORELIBDIR = $(LIBSDIR)/CMSIS/CM3/CoreSupport
 63 DEVDIR  = $(LIBSDIR)/CMSIS/CM3/DeviceSupport/ST/STM32F10x
 64 STMSPDDIR    = $(LIBSDIR)/STM32F10x_StdPeriph_Driver
 65 STMSPSRCDDIR = $(STMSPDDIR)/src
 66 STMSPINCDDIR = $(STMSPDDIR)/inc
 67 DISCOVERY    = $(LIBSDIR)/Utilities
 68 DEVICE       = ./Device
 69 STARTUP      = $(DEVICE)/startup
 70 LINKER       = $(DEVICE)/linker
 71 SRC  = main.c
 72 SRC += $(CORELIBDIR)/core_cm3.c
 73 SRC += $(DEVDIR)/system_stm32f10x.c
 74 SRC += $(STARTUP)/crt.c
 75 SRC += $(STARTUP)/vectors_stm32f10x_md.c
 76 SRC += $(DISCOVERY)/STM32vldiscovery.c
 77 ## used parts of the STM-Library
 78 #SRC += $(STMSPSRCDDIR)/stm32f10x_usart.c
 79 #SRC += $(STMSPSRCDDIR)/stm32f10x_flash.c
 80 SRC  += $(STMSPSRCDDIR)/stm32f10x_gpio.c
 81 SRC  += $(STMSPSRCDDIR)/stm32f10x_rcc.c
 82 #SRC += $(STMSPSRCDDIR)/stm32f10x_spi.c
 83 #SRC += $(STMSPSRCDDIR)/stm32f10x_rtc.c
 84 #SRC += $(STMSPSRCDDIR)/stm32f10x_bkp.c
 85 #SRC += $(STMSPSRCDDIR)/stm32f10x_pwr.c
 86 #SRC += $(STMSPSRCDDIR)/stm32f10x_dma.c
 87 #SRC += $(STMSPSRCDDIR)/stm32f10x_tim.c
 88 SRC += $(STMSPSRCDDIR)/stm32f10x_exti.c
 89 SRC += $(STMSPSRCDDIR)/misc.c
 90 
 91 # List ASM source files here
 92 ASRC =
 93 
 94 # List all user directories here
 95 UINCDIR = $(CORELIBDIR) \
 96           $(DEVDIR) \
 97           $(STMSPINCDDIR) \
 98           $(DISCOVERY)
 99 # List the user directory to look for the libraries here
100 ULIBDIR =
101 
102 # List all user libraries here
103 ULIBS =
104 
105 # Define optimisation level here
106 OPT = -Os
107 
108 #
109 # End of user defines
110 ##############################################################################################
111 #
112 # Define linker script file here
113 #
114 ifeq ($(RUN_FROM_FLASH), 0)
115 LDSCRIPT = $(LINKER)/stm32f103xb_ram.ld
116 FULL_PRJ = $(PROJECT)_ram
117 else
118 LDSCRIPT = $(LINKER)/stm32f103xb_flash.ld
119 FULL_PRJ = $(PROJECT)_rom
120 endif
121 
122 INCDIR  = $(patsubst %,-I%,$(DINCDIR) $(UINCDIR))
123 LIBDIR  = $(patsubst %,-L%,$(DLIBDIR) $(ULIBDIR))
124 
125 ifeq ($(RUN_FROM_FLASH), 0)
126 DEFS    = $(DDEFS) $(UDEFS) -DRUN_FROM_FLASH=0 -DVECT_TAB_SRAM
127 else
128 DEFS    = $(DDEFS) $(UDEFS) -DRUN_FROM_FLASH=1
129 endif
130 
131 ADEFS   = $(DADEFS) $(UADEFS)
132 OBJS    = $(ASRC:.s=.o) $(SRC:.c=.o)
133 LIBS    = $(DLIBS) $(ULIBS)
134 MCFLAGS = -mcpu=$(MCU)
135 
136 ASFLAGS = $(MCFLAGS) -g -gdwarf-2 -Wa,-amhls=$(/dev/null) $(wildcard .dep/*)
137 
138 # *** EOF ***
139 

Now you're all set. Build project and upload .hex file to Discovery board. You should see blinking LEDs.

img/STM32discovery_animated_IO.gif

Same way other examples can be compiled and used.

Download project files here:

demo/STM32DiscoveryTemplate.zip

5   Programming STM32-Discovery using GNU tools: Startup code

URL: http://www.keytosmart.com/programming-stm32-discovery-gnu-tools-startup-code/

Start up code is run just after microcontroller is reset and is executed before main program. As linker script, startup code usually is implemented as universal code for all same microcontroller type. So usually you don’t need to write one from scratch. Anyway it is good to know what happens there anyway.

As we said linker script has to go along with startup code. This means that it will initialize MCU automatically according to data defined in linker. Startup code initializes variables, copies defined variables from Flash to RAM, initializes stack and then gives resources to main program. You will find that startup codes usually are written in assembly language, but this can also be done in C which is easier to read and modify if necessary. First of all in linker script we have pointed entry point to startup with following command:

ENTRY(handler_reset)

So in startup code this will be first function called.

 1 void handler_reset(void)
 2 {
 3   unsigned long *source;
 4   unsigned long *destination;
 5 // Copying data from Flash to RAM
 6 source = &_data_flash;
 7 for (destination = &_data_begin; destination < &_data_end;)
 8 {
 9 *(destination++) = *(source++);
10 }
11 // default zero to undefined variables
12 for (destination = &_bss_begin; destination < &_bss_end;)
13 {
14 *(destination++) = 0;
15 }
16 // starting main program
17 main();
18 }

Startup code takes source and destination addresses from linker script as external variables:

1 extern unsigned long _data_flash;
2 extern unsigned long _data_begin;
3 extern unsigned long _data_end;
4 extern unsigned long _bss_begin;
5 extern unsigned long _bss_end;
6 extern unsigned long _stack_end;

These we defined when described sections. So the only thing left for startup code is take values from source addresses in Flash memory and copy them to destination addresses in RAM. Also variables stored in .bss section are defaulted to zero values. So no need to null them in source when used.

Next step is that startup does is to allocate exception handler vector table. Due to ARM Cortex architecture the first address in vector table is used to store address of stack end. This is convenient and efficient way to define it.

 1 __attribute__ ((section(".interrupt_vector")))
 2 
 3 void (* const table_interrupt_vector[])(void) =
 4 {
 5 (void *) &_stack_end, // 0 - stack
 6 handler_reset, // 1
 7 handler_default, // 2
 8 handler_default, // 3
 9 handler_default, // 4
10 handler_default, // 5
11 handler_default, // 6
12 0, // 7
13 0, // 8
14 0, // 9
15 0, // 10
16 handler_default, // 11
17 handler_default, // 12
18 0, // 13
19 handler_default, // 14
20 handler_default, // 15
21 // peripherals
22 handler_default, // 0
23 handler_default, // 1
24 handler_default, // 2
25 handler_default, // 3
26 handler_default, // 4
27 -//-
28 handler_default, // 59
29 handler_default // 60
30 };

In linker script we defined that “.interrupt_vector” section is starting at 0×00000000 address so stack pointer is located at 0×00000000 address of Flash. Then goes reset handler which then copies variable data to RAM and nulls undefined variables and then it is over with startup code and all resources are alocated to main() routine.

1 void handler_default(void)
2 {
3 while (1)
4 {
5 //loop
6 }
7 }

handler default routine simply handles unexpected interrupts and puts MCU to endless loop. This is very simplified version of linker script and startup code. Ir explains thing pretty well I guess. For starters probably it is best to find working example of Arm Cortex M3 GCC code copy linker script and startup code as they are and focus on software writing. Barely you’ll need to change these soon.

6   Programming STM32-Discovery using GNU tools: Linker script

URL: http://www.keytosmart.com/programming-stm32-discovery-gnu-tools-linker-script/

How to setting up a development environment for ARM Cortex-M3 microcontroller explained at this post (development-environment). We decided that two equal choices will do same job – either CodeSourcery G++ Lite or Yagarto. Both use same base of GNU tool-set.

Developing with GCC tools

In order to get a working binary, there is a series tools involved during code development. Several tools are necessary to compile simple applications. These are: compiler, assembler, linker and binary generator. Each of them does it own task in a chain process. When you start compiling your project normally there is a linker invoked, which with correct parameters links libraries, object files

arm_gcc_toolchain-141x300.png

Once executable is generated, then binary image generator creates binary image (.bin or .hex) that can be uploaded to MCU. We won’t go in to details right now as this will be more convenient to do in later code examples. Lets get directly to code writing part which is very important for linker and tasks before main() routine.

Linker script

Practically speaking, linker script is a file that defines microcontroller specific features like memory map, memory sections, stack location and size. It may also contain application specific information if needed. Usually linker scripts are already written and can be found along example projects. So once set up there is no need to write them each time a new project is started, unless some modifications or additions are needed. Lets look at some key features of linker script.

First of all we need to describe memory blocks and size of microcontroller. There is a MEMORY command used. Further we are going to work with STM32 Discovery kit where STM32F100RB microcontroller is used which has 128KB of Flash and 8KB of SRAM. So first we define our memory types:

MEMORY

{
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 8K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
}

where “rwx” means rewritable and executable, “rx” – read only and executable memory. Executable is meant that code can be run from this memory. In ARM program can be executed directly from Flash or RAM memory. ORIGIN points to start address of memory region and Length defines size of particular memory type.

Each memory type is also divided in memory sections where different type of data is stored. One section is needed for variables, another for constants, code, stack, heap and so on. So we need to show linker how to divide memories in to section. For this command SECTIONS is used. First of all we need to define section where program code will be stored. This section is called .text:

SECTIONS

{
.text :
{
. = ALIGN(4);
KEEP(*(.interrupt_vector))
*(.text)
*(.text*)
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} > FLASH

Where . = ALIGN(4); indicates that section is aligned to the 4 byte boundary; KEEP(*(.interrupt_vector)) means that for this section optimization must be skipped during linking; .text indicates program code section; .rodata – space for storing constants; >FLASH means that sections are located in FLASH memory. You can add even more sections if needed. Dot indicates current address. So next we need to remember last used address by assigning this to variable:

_data_flash = .;

Now we can continue with next section – .data. This section usually is located in RAM memory and contains static data when variables have initially defined values.

.data : AT ( _data_flash )
{
. = ALIGN(4);
_data_begin = .;
*(.data)
*(.data*)
. = ALIGN(4);
_data_end = .;
} > RAM

Here we are using saved flash address AT ( _data_flash ) where we can find initial constants stored in flash that has to be loaded to RAM. This will be done by startup code. For now we create .data location where our constants will be loaded.

Next section is .bss. This is where undefined/uninitialized variables and globals will be stored.

.bss :

{
_bss_begin = .;
__bss_start__ = _bss_begin;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_bss_end = .;
__bss_end__ = _bss_end;
} > RAM

.bss section usually goes right after .data section.

And finally we need to define stack.

_stack_size = 1024;

stack_end = ORIGIN(RAM)+LENGTH(RAM);
_stack_begin = _stack_end - _stack_size;
. = _stack_begin;
._stack :
{
. = . + _stack_size;
} > RAM

Stack size can be changed or even expanded to end of RAM. But then linker won’t have a chance to warn about shortage of stack memory. Anyway there is lots of variants of defining sections so we won’t go much in to details.

Last thing to tell linker is where program has to start after reset. So we use simple line:

ENTRY(handler_reset);

This means that before main() program we need to initialize variables – copy constants from Flash to RAM, initialize stack and do other stuff if needed. Initialization is done in startup code. This part will be left for another post. Right now we’ve done following:

img/arm_sections-300x182.png

I won’t give final working linker script as it must go along with start-up code which will be discussed next.

7   Getting NewLib and printf to work with the STM32 and Code Sourcery Lite eabi

  1. Januar 2012

URL: http://www.keytosmart.com/newlib-printf-work-stm32-code-sourcery-lite-eabi/

The Code Sourcery Lite tool chain is provided with the newlib C Library from Redhat. Because this is an embedded toolchain some stub functions known as System Functions must be provided by the embedded system that would normally be provided by a host operating system.

The is shown by the fact that if you try to compile a C program that uses printf you will see the following error message:

C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-sbrkr.o): In function `_sbrk_r':
sbrkr.c:(.text+0x12): undefined reference to `_sbrk'
C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-writer.o): In function `_write_r':
writer.c:(.text+0x16): undefined reference to `_write'
C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-closer.o): In function `_close_r':
closer.c:(.text+0x12): undefined reference to `_close'
C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-lseekr.o): In function `_lseek_r':
lseekr.c:(.text+0x16): undefined reference to `_lseek'
C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-readr.o): In function `_read_r':
readr.c:(.text+0x16): undefined reference to `_read'
C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-fstatr.o): In function `_fstat_r':
fstatr.c:(.text+0x14): undefined reference to `_fstat'
C:\Program Files (x86)\CodeSourcery\Sourcery G++ Lite\arm-none-eabi\lib\thumb2\libc.a(lib_a-isattyr.o): In function `_isatty_r':
isattyr.c:(.text+0x12): undefined reference to `_isatty'
collect2: ld returned 1 exit status
Build error occurred, build is stopped

As you can see we need to provide the stub functions above for our program to compile and work. There are in fact a few more stub functions that we will need to provided for full C support but most can be a trivial implementation in an embedded system.

The main functions we need to concentrate on are _sbrk which is used by malloc _write which is used for all IO output and _read which is used for all IO input, all the other functions can simply return an error.

The newlib system call’s we need to provide are documented here.

Place the file below in your startup_src directory which was created earlier to provide basic newlib IO support.

  1 /*
  2  * newlib_stubs.c
  3  *
  4  *  Created on: 2 Nov 2010
  5  *      Author: nanoage.co.uk
  6  */
  7 #include
  8 #include
  9 #include
 10 #include
 11 #include "stm32f10x_usart.h"
 12 
 13 #ifndef STDOUT_USART
 14 #define STDOUT_USART 2
 15 #endif
 16 
 17 #ifndef STDERR_USART
 18 #define STDERR_USART 2
 19 #endif
 20 
 21 #ifndef STDIN_USART
 22 #define STDIN_USART 2
 23 #endif
 24 
 25 #undef errno
 26 extern int errno;
 27 
 28 /*
 29  environ
 30  A pointer to a list of environment variables and their values.
 31  For a minimal environment, this empty list is adequate:
 32  */
 33 char *__env[1] = { 0 };
 34 char **environ = __env;
 35 
 36 int _write(int file, char *ptr, int len);
 37 
 38 void _exit(int status) {
 39     _write(1, "exit", 4);
 40     while (1) {
 41         ;
 42     }
 43 }
 44 
 45 int _close(int file) {
 46     return -1;
 47 }
 48 /*
 49  execve
 50  Transfer control to a new process. Minimal implementation (for a system without processes):
 51  */
 52 int _execve(char *name, char **argv, char **env) {
 53     errno = ENOMEM;
 54     return -1;
 55 }
 56 /*
 57  fork
 58  Create a new process. Minimal implementation (for a system without processes):
 59  */
 60 
 61 int _fork() {
 62     errno = EAGAIN;
 63     return -1;
 64 }
 65 /*
 66  fstat
 67  Status of an open file. For consistency with other minimal implementations in these examples,
 68  all files are regarded as character special devices.
 69  The `sys/stat.h' header file required is distributed in the `include' subdirectory for this C library.
 70  */
 71 int _fstat(int file, struct stat *st) {
 72     st->st_mode = S_IFCHR;
 73     return 0;
 74 }
 75 
 76 /*
 77  getpid
 78  Process-ID; this is sometimes used to generate strings unlikely to conflict with other processes. Minimal implementation, for a system without processes:
 79  */
 80 
 81 int _getpid() {
 82     return 1;
 83 }
 84 
 85 /*
 86  isatty
 87  Query whether output stream is a terminal. For consistency with the other minimal implementations,
 88  */
 89 int _isatty(int file) {
 90     switch (file){
 91     case STDOUT_FILENO:
 92     case STDERR_FILENO:
 93     case STDIN_FILENO:
 94         return 1;
 95     default:
 96         //errno = ENOTTY;
 97         errno = EBADF;
 98         return 0;
 99     }
100 }
101 
102 /*
103  kill
104  Send a signal. Minimal implementation:
105  */
106 int _kill(int pid, int sig) {
107     errno = EINVAL;
108     return (-1);
109 }
110 
111 /*
112  link
113  Establish a new name for an existing file. Minimal implementation:
114  */
115 
116 int _link(char *old, char *new) {
117     errno = EMLINK;
118     return -1;
119 }
120 
121 /*
122  lseek
123  Set position in a file. Minimal implementation:
124  */
125 int _lseek(int file, int ptr, int dir) {
126     return 0;
127 }
128 
129 /*
130  sbrk
131  Increase program data space.
132  Malloc and related functions depend on this
133  */
134 caddr_t _sbrk(int incr) {
135 
136     extern char _ebss; // Defined by the linker
137     static char *heap_end;
138     char *prev_heap_end;
139 
140     if (heap_end == 0) {
141         heap_end = &_ebss;
142     }
143     prev_heap_end = heap_end;
144 
145 char * stack = (char*) __get_MSP();
146      if (heap_end + incr >  stack)
147      {
148          _write (STDERR_FILENO, "Heap and stack collision\n", 25);
149          errno = ENOMEM;
150          return  (caddr_t) -1;
151          //abort ();
152      }
153 
154     heap_end += incr;
155     return (caddr_t) prev_heap_end;
156 
157 }
158 
159 /*
160  read
161  Read a character to a file. `libc' subroutines will use this system routine for input from all files, including stdin
162  Returns -1 on error or blocks until the number of characters have been read.
163  */
164 
165 int _read(int file, char *ptr, int len) {
166     int n;
167     int num = 0;
168     switch (file) {
169     case STDIN_FILENO:
170         for (n = 0; n < len; n++) {
171 #if   STDIN_USART == 1
172             while ((USART1->SR & USART_FLAG_RXNE) == (uint16_t)RESET) {}
173             char c = (char)(USART1->DR & (uint16_t)0x01FF);
174 #elif STDIN_USART == 2
175             while ((USART2->SR & USART_FLAG_RXNE) == (uint16_t) RESET) {}
176             char c = (char) (USART2->DR & (uint16_t) 0x01FF);
177 #elif STDIN_USART == 3
178             while ((USART3->SR & USART_FLAG_RXNE) == (uint16_t)RESET) {}
179             char c = (char)(USART3->DR & (uint16_t)0x01FF);
180 #endif
181             *ptr++ = c;
182             num++;
183         }
184         break;
185     default:
186         errno = EBADF;
187         return -1;
188     }
189     return num;
190 }
191 
192 /*
193  stat
194  Status of a file (by name). Minimal implementation:
195  int    _EXFUN(stat,( const char *__path, struct stat *__sbuf ));
196  */
197 
198 int _stat(const char *filepath, struct stat *st) {
199     st->st_mode = S_IFCHR;
200     return 0;
201 }
202 
203 /*
204  times
205  Timing information for current process. Minimal implementation:
206  */
207 
208 clock_t _times(struct tms *buf) {
209     return -1;
210 }
211 
212 /*
213  unlink
214  Remove a file's directory entry. Minimal implementation:
215  */
216 int _unlink(char *name) {
217     errno = ENOENT;
218     return -1;
219 }
220 
221 /*
222  wait
223  Wait for a child process. Minimal implementation:
224  */
225 int _wait(int *status) {
226     errno = ECHILD;
227     return -1;
228 }
229 
230 /*
231  write
232  Write a character to a file. `libc' subroutines will use this system routine for output to all files, including stdout
233  Returns -1 on error or number of bytes sent
234  */
235 int _write(int file, char *ptr, int len) {
236     int n;
237     switch (file) {
238     case STDOUT_FILENO: /*stdout*/
239         for (n = 0; n < len; n++) {
240 #if STDOUT_USART == 1
241             while ((USART1->SR & USART_FLAG_TC) == (uint16_t)RESET) {}
242             USART1->DR = (*ptr++ & (uint16_t)0x01FF);
243 #elif  STDOUT_USART == 2
244             while ((USART2->SR & USART_FLAG_TC) == (uint16_t) RESET) {
245             }
246             USART2->DR = (*ptr++ & (uint16_t) 0x01FF);
247 #elif  STDOUT_USART == 3
248             while ((USART3->SR & USART_FLAG_TC) == (uint16_t)RESET) {}
249             USART3->DR = (*ptr++ & (uint16_t)0x01FF);
250 #endif
251         }
252         break;
253     case STDERR_FILENO: /* stderr */
254         for (n = 0; n < len; n++) {
255 #if STDERR_USART == 1
256             while ((USART1->SR & USART_FLAG_TC) == (uint16_t)RESET) {}
257             USART1->DR = (*ptr++ & (uint16_t)0x01FF);
258 #elif  STDERR_USART == 2
259             while ((USART2->SR & USART_FLAG_TC) == (uint16_t) RESET) {
260             }
261             USART2->DR = (*ptr++ & (uint16_t) 0x01FF);
262 #elif  STDERR_USART == 3
263             while ((USART3->SR & USART_FLAG_TC) == (uint16_t)RESET) {}
264             USART3->DR = (*ptr++ & (uint16_t)0x01FF);
265 #endif
266         }
267         break;
268     default:
269         errno = EBADF;
270         return -1;
271     }
272     return len;
273 }

The above assumes that USART2 will be used for stdout / stderr and stdin however you will need to initialise usart2 first for any output to occur.

If you wish to use another usart then you can set the defines for

STDOUT_USART
STDERR_USART
STDIN_USART

to the USART number you want in the Cross GCC’s defined symbols section (-D) i.e

STDOUT_USART=1

to change to usart1

Below is a simple example which will initialise USART2 to 115200 baud, 8bits , 1 stop bit , parity-none.

 1 /**
 2  Main file for use with newlib
 3  main.c
 4 **/
 5 #include "stm32f10x.h"
 6 #include
 7 
 8 void usart2_init();
 9 
10 int main(void)
11 {
12     //by default stdin/stdout are on usart2
13     usart2_init();
14     // turn off buffers, so IO occurs immediately
15     setvbuf(stdin, NULL, _IONBF, 0);
16     setvbuf(stdout, NULL, _IONBF, 0);
17     setvbuf(stderr, NULL, _IONBF, 0);
18 
19     iprintf("Greetings Earthlings");
20     while (1) {}
21 }
22 
23 void usart2_init(){
24     USART_InitTypeDef USART_InitStructure;
25     USART_InitStructure.USART_BaudRate = 115200;
26     USART_InitStructure.USART_WordLength = USART_WordLength_8b;
27     USART_InitStructure.USART_StopBits = USART_StopBits_1;
28     USART_InitStructure.USART_Parity = USART_Parity_No;
29     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
30     USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
31 
32     GPIO_InitTypeDef GPIO_InitStructure;
33     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
34     RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
35 
36     /* Configure USART Tx as push-pull */
37     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
38     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
39     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
40     GPIO_Init(GPIOA, &GPIO_InitStructure);
41 
42     /* Configure USART Rx as input floating */
43     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
44     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
45     GPIO_Init(GPIOA, &GPIO_InitStructure);
46 
47     /* USART configuration */
48     USART_Init(USART2, &USART_InitStructure);
49 
50     /* Enable USART */
51     USART_Cmd(USART2, ENABLE);
52 }

Note that you will probably want to turn off buffering on stdin otherwise you will have to type 1024 characters before they start flooding back to your program. Similar for printf either turn off buffering or use fflush to flush the output when needed, note that a newline seems to flush the output as well.

Check you linker script has a .ARM.exidx section otherwise you will get random crashes, the original linker script from part one omitted this and has since been updated.

8   Programming STM32F10x I/O port pins

  1. Juni 2012

URL: http://www.keytosmart.com/programming-stm32f10x-io-port-pins/

Previously we learned how to compile STM32VL Discovey projects that are included in package. But in order to understand how to write own programs we need to get to some basics. I think best place to start is input and output system (I/O). Before we begin to write some code lets go through whats in side STM32 ports. I you look in to STM32 reference manual you’ll find that I/O system is pretty flexible. Port pins are able to work in several modes:

img/STM32_IO_port_structure-300x165.png

Each port has several special GPIO registers. These include two 32-wide configuration registers (GPIOx_CRL and GPIOx_CRH), input register (GPIOx_IDR), output register (GPIOx_ODR), also there are bit set/reset (GPIOx_BSRR) and reset (GPIOx_BRR) registers and configuration lock (GPIOx_LCKR) register. Where x represents port letter: A, B, C, ...

According to this, port output register can be written as word wide (16-bit) register but also it is possible atomic bit manipulation with set/reset and reset registers. So in order to set one bit in port you don’t have to read-modify-write port value. This is dangerous situation where interrupt may occur in the middle of event. And surely registers can also be bit manipulated with bit banding functionality. Lock register is convenient when you need to prevent configuration registers. Once they are locked registers cannot be modified until unlocked.

And last thing before we can start programming I/O ports is clock source. Peripherals like GPIO, USART, timers, ADC and other are connected to Advanced High Speed Bus (AHB) matrix through Advanced Peripheral Buses (APB). There are two peripheral buses APB1 and APB2. They have clock prescallers allowing to select different speeds. Microcontroller ports are connected to APB2 bus so before using ports it is important to configure bus.

Lets blink some LEDs

Having some theory we can start writing code. From now on we have to decide which direction we should go. One way is to manipulate MCU registers directly or use Cortex Microcontroller Software Interface Standard called CMSIS. CMSIS gives more abstraction when programming hardware and also makes code portable among different Cortex microcontrollers. Of course CMSIS takes some space and requires some resources but this isn’t significant influence comparing to what you get. Some hobbyists tend to write code by accessing hardware directly – it is great if you want to dig in to hardware and have control of every operation, but from my point of view this would be stupid not to use some abstraction and save some time.

As always first we have to wet up our project with Standard Peripheral Library included. We can use same project template that we prepared in last tutorial. Just we are going to omit

STM32vldiscovery.h

device library that was designed specially for discovery board.

In our program we will read button status. If button is pressed it simply will toggle blue LED while button press will be indicated with green LED.

 1 // Includes
 2 
 3 #include "stm32f10x.h"
 4 #define LEDG            GPIO_Pin_9
 5 #define LEDB            GPIO_Pin_8
 6 #define LEDPORT         GPIOC
 7 #define LEDPORTCLK      RCC_APB2Periph_GPIOC
 8 #define BUTTON          GPIO_Pin_0
 9 #define BUTTONPORT      GPIOA
10 #define BUTTONPORTCLK   RCC_APB2Periph_GPIOA
11 //delay function
12 void Delay(__IO uint32_t nCount)
13 {
14   for(; nCount != 0; nCount--);
15 }
16 int main(void)
17 {
18   //flasher flag
19   uint32_t ledon=0;
20   //GPIO structure used to initialize port
21   GPIO_InitTypeDef GPIO_InitStructure;
22   //Enable clock on APB2 pripheral bus where button and LEDs are connected
23   RCC_APB2PeriphClockCmd(LEDPORTCLK | BUTTONPORTCLK,  ENABLE);
24   //select pins to initialize LED
25   GPIO_InitStructure.GPIO_Pin = LEDG|LEDB;
26   //select output push-pull mode
27   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
28   //highest speed available
29   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
30   GPIO_Init(LEDPORT, &GPIO_InitStructure);
31   //using same structure we will initialize button pin
32   //select pin to initialize button
33   GPIO_InitStructure.GPIO_Pin = BUTTON;
34   //select input floating
35   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
36   GPIO_Init(BUTTONPORT, &GPIO_InitStructure);
37 while (1)
38   {
39     //read button
40     if (GPIO_ReadInputDataBit(BUTTONPORT, BUTTON))
41     {
42         //green led on
43         GPIO_SetBits(LEDPORT, LEDG);
44         //toggle flasher
45         ledon ^= 1;
46         //dummy debounce
47         Delay(500000);
48         //green led off
49         GPIO_ResetBits(LEDPORT, LEDG);
50     }
51     if (ledon)
52     {
53         GPIO_SetBits(LEDPORT, LEDB);
54     }
55     else
56     {
57         GPIO_ResetBits(LEDPORT, LEDB);
58     }
59   }
60 }

To compile this project you need to include these libraries in stm32f10x_conf.h:

#include"stm32f10x_gpio.h"
#include"stm32f10x_rcc.h"

Also do same in Makefile. Download full CodeSourcery project here:

demo/STM32Discoverystart1.zip

9   STM32 interrupts and programming with GCC

URL: http://www.keytosmart.com/stm32-interrupts-programming-gcc/

Probably one of the key features in any microcontroller is interrupt system. ARM Cortex-M3 microcontrollers may have up to 256 interrupts sources. First 15 interrupt sources are called system exceptions. These exceptions rise within Cortex core like reset, NMI, hard fault and error, debug and SystTick timer interrupt. In the exception table they start from address 0x00000004 and are numbered from 1 to 15. There is no 0 number exception (FYI - the very top of exception table address is used to store starting point of stack pointer):

img/STM32_exception_vector_table-300x228.png

Each exception vector holds four byte wide address of service routine that is called when exception occurs. Exception table usually is located in startup code like this:

__attribute__ ((section(".vectors"), used))

void (* const gVectors[])(void) =
{
(void (*)(void))((unsigned long)&_estack),
ResetHandler,
NMI_Handler,
HardFault_Handler,
MemManage_Handler,
BusFault_Handler,
UsageFault_Handler,
...

It is located in .vectors memory section which is described in linker script and usually resides at very beginning of flash memory.

Inside NVIC of Cortex-M3

Nested Vectored Interrupt Controller (NVIC) is an essential part of Cortex processor. It is pretty complex module that takes care of interrupt processing logic. Additionally it holds control registers for SysTick timer and debug control. Generally speaking NVIC can support up to 240 external interrupts. Speaking of STM32F100RB microcontroller that resides in discovery board NVIC supports 41 maskable interrupt channel and 16 priority levels. As NVIC are closely coupled with processor core it is assured that interrupts are processed with low latency. NVIC supports some advanced interrupt handling modes including Interrupt preemption, tail chaining, late arrival. These features allow to reach low latency and more robust response. These features allow to avoid stack overhead when processing multiple interrupts that arrive pretty much at same time. For instance tail chaining mechanism allows skipping stack pop if there is another pending interrupt once current is completed. Refer to Cortex-M3 programming manual for more info.

Fe words about priority. Generally speaking every interrupt has associated an 8-bit priority level register. But not all bits are used to set priorities. STM32F100RB microcontroller has 16 priority levels what means that 4 MSB bits are used to set priorities. The lower priority number the higher priority. If needed these bits can be split in to two groups where you can create sub-priority levels for each preemptive priority. Subpriority levels are handy when two or more same priority level interrupts occur. Then one with higher subpriority will be handled first:

img/arm_cortex_interrupt_priority_register.png

Handling external interrupts

External interrupts are connected to NVIC through special external interrupt controller (EXTI). It allows to map multiple GPIO lines to NVIC external interrupt inputs. STM32F100RB interrupt controller consists of 18 edge detector lines. Each line can be configured to trigger on different event like rising edge, falling edge or both. As ports are 16-bit wide then 16 lines are dedicated to map port pins. For instance EXTI0 line can be mapped to pins 0 of all ports or any other combination.

img/EXTI_module-205x300.png

It is hardly possible to cover all exception handling functionality. More should come during practice as each individual case has it's own scent. When pushing projects to RTOS based applications these things start really matter. But for now lets make another code example where we will implement interrupt based routines.

img/EXTI_to-NVIC.png

Writing interrupt based code example

We are going to improve our previous code example where button were checked and LEDs lit in while(1) super-loop. This time we are going to configure so that button press would generate interrupt. Within interrupt service routine we will process button function and then return to loop. Also we are going to implement SysTick timer which would blink LED.

First of all we need to initialize EXTI module that would map button pin to EXTI0 line and then configure this line on NVIC controller where we will set up a priority and enable interrupt. To do so in button.c source file we create:

void ButtonInit(void)

{
  //EXTI structure to init EXT
  EXTI_InitTypeDef EXTI_InitStructure;
  //NVIC structure to set up NVIC controller
  NVIC_InitTypeDef NVIC_InitStructure;
  //Connect EXTI Line to Button Pin
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
  //Configure Button EXTI line
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;
  //select interrupt mode
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  //generate interrupt on rising edge
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  //enable EXTI line
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  //send values to registers
  EXTI_Init(&EXTI_InitStructure);
  //configure NVIC
  //select NVIC channel to configure
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
  //set priority to lowest
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
  //set subpriority to lowest
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
  //enable IRQ channel
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  //update NVIC registers
  NVIC_Init(&NVIC_InitStructure);
}

To use button as EXTI we need external interrupt to port pin.

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

This simply sets pin input for EXTI in AFIO_EXTCR register. As we map pin0 of GPIOA port this command sets least bit of AFIO_EXTICR1 register. Once mapping is done we need to configure EXTI module itself:

//Configure EXTI line 0

EXTI_InitStructure.EXTI_Line = EXTI_Line0;

//select interrupt mode

EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

//generate interrupt on rising edge

EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;

//enable EXTI line

EXTI_InitStructure.EXTI_LineCmd = ENABLE;

//send values to registers

EXTI_Init(&EXTI_InitStructure);

Here we select which EXTI line we need to configure. Then select Interrupt mode for this line (also EXTI lines can be configured to generate software interrupts). Then we select rising edge triggering source as our button pin is going high when pressed. And lastly we simply enable this line so it could output signals on EXTI line that goes in to NVIC.

NVIC also has to be set in order to process upcoming interrupts:

//select NVIC channel to configure

NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;

//set priority to lowest

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;

//set subpriority to lowest

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;

//enable IRQ channel

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

//update NVIC registers

NVIC_Init(&NVIC_InitStructure);

Here is same situation: we select which interrupt source to configure (EXT0_IRQn), then set priority and subpriority - both to 0 and lastly we enable that interrupt. After NVIC is initialized it can start processing incoming interrupts from EXTI0 line that is mapped to button pin.

In our template we are using separate source file (stm32f10x_it.c) where interrupt service routines are implemented. Here we write our EXTI0 handler:

void EXTI0_IRQHandler(void)

{
    //Check if EXTI_Line0 is asserted
    if(EXTI_GetITStatus(EXTI_Line0) != RESET)
    {
    LEDToggle(LEDG);
    }
    //we need to clear line pending bit manually
    EXTI_ClearITPendingBit(EXTI_Line0);
}

It toggles LED every time we press a button. One important thing about EXTI lines! Once line have been set it won't reset automatically so after single button press it stays high constantly and this leads chained interrupts as NVIC "thinks" that there are new interrupt requests incoming. To avoid this we need to reset EXTI line manually after we process interrupt. For this we use function:

EXTI_ClearITPendingBit(EXTI_Line0);

Additionally we wanted to run SysTickTimer which would blink a led every 1s. CMSIS core_cm3.c source has a nice function that initializes and starts Systick timer with single line:

SysTick_Config(15000000);

We only need to pass how many ticks to count between SysTick interrupts. Also we need to write our handler in stm32f10x_it.c file:

void SysTick_Handler(void)
{
    LEDToggle(LEDB);
}

We just toggle led on every SysTick interrupt.

After everything is set up we can write our main program:

// Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "leds.h"
#include "button.h"

int main(void)
{
  ButtonInit();
  LEDsInit();
  SysTick_Config(15000000);
  //__enable_irq ();
  while (1)
  {
      //interrupts does the job
  }
}

All we do is just initialize button, leds and start SysTick timer. Other work is done by interrupt routines. This saves processing power significantly. In battery operated devices processor is sent to one of power safe modes between interrupts or simply do other tasks while interrupts automatically work in background.

Download project file:

demo/STM32DiscoveryInterrupts.zip

10   Programming STM32 USART using GCC tools: Part 1

  1. Juni 2012

URL: http://www.keytosmart.com/programming-stm32-usart-gcc-tools-part-1/

When we need some feedback from microcontroller usually we use USART. It allows to output messages, debug information to terminal screen. Also data can be sent to MCU same way. For this purpose STM32 microcontrollers have more than one USART interface allowing to have multiple streams of data output and input.

img/stm32vldiscovery_usart_connection-300x219.jpg

USART interface is designed to be very versatile allowing to have lots of modes including LIN, IrDA, Smart card emulation, DMA based transmissions. But for now lets focus on standard USART communications we we could send and receive messages from terminal window.

STM32F100RB microcontroller in Discovery board has three USARTs (USART1, USART2 and USART3). Other higher level STM32 microcontrollers have even more. USART1 is connected to APB2 bus while other USART's are located on APB1. Every USART has a two DMA channels allowing data transfer between memory and Rx/Tx. Each USART has ten interrupt sources and all are mapped to same NVIC channel. So it is up to code to find out what triggered an interrupt. This is done by identifying flag in status register. Why don't we take a real example and see how things work.

For this we are going to program USART1 in our STM32VLDiscovery board. Discovery board only comes with naked pins that we have to connect to USART level converter like RS232 or USB. For this example we are going to use widely acceptable FT232RL based TTL to USB converter.

The FT232RL is a USB to serial UART interface with optional clock generator output. It's the essential tools for establishing communication between PC and mostly MCU.

img/Foca-FT232RL-300x225.jpg

So we connect board pins to converter as follows:

STM32VLDiscovery PA9 to Rx of FT232RL board

STM32VLDiscovery PA10 to Tx of FT232RL board

STM32VLDiscovery 3.3V to VCC of FT232RL board

STM32VLDiscovery GND to GND of FT232RL board

Using STM32F10x_StdPeriph_Driver library programming task becomes very easy. Lets use our template from previous example. This time we are going to add couple more files to project called usart.c and usart.h. Here we are gonna to write USART initialization function and byte read and write routines. Lets start with Initialization:

voidUsart1Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

USART_ClockInitTypeDef USART_ClockInitStructure;

//enable bus clocks

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

//Set USART1 Tx (PA.09) as AF push-pull

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);

//Set USART1 Rx (PA.10) as input floating

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

GPIO_Init(GPIOA, &GPIO_InitStructure);

USART_ClockStructInit(&USART_ClockInitStructure);

USART_ClockInit(USART1, &USART_ClockInitStructure);

USART_InitStructure.USART_BaudRate = 9600;

USART_InitStructure.USART_WordLength = USART_WordLength_8b;

USART_InitStructure.USART_StopBits = USART_StopBits_1;

USART_InitStructure.USART_Parity = USART_Parity_No ;

USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

//Write USART1 parameters

USART_Init(USART1, &USART_InitStructure);

//Enable USART1

USART_Cmd(USART1, ENABLE);

}

Here we have a standard situation where we have to take care of enabling peripheral clocks. We know that USART1 is located on APB2 peripheral bus as GPIOA and AFIO. All these are needed because USART1 pins alternate functions of port A. These has to be clocked in order to use them:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

Next task is to set up USART pins where Tx pin should behave as alternate function push-pull while Rx floating input. Once pins are set we need to take care of USART clock. As we are going to use standard set up we can use function helper that does this by setting default values:

USART_ClockStructInit(&USART_ClockInitStructure);

USART_ClockInit(USART1, &USART_ClockInitStructure);

Then we can set up USART specific settings by selecting baud rate, data word size, stop, parity bits and enable Rx and Tx modes. All this is done by setting values in USART_InitStructure as seen above. Last command simply enables USART1. From this moment USART1 is up and ready. New we can proceed to sending and receiving data functions:

void Usart1Put(uint8_t ch)
{
      USART_SendData(USART1, (uint8_t) ch);
      //Loop until the end of transmission
      while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
      {
      }
}
uint8_t Usart1Get(void){
     while ( USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
        return (uint8_t)USART_ReceiveData(USART1);
}

We call them Usart1Put and Usart1Get functions that sends and receives data bytes. You can see in both functions that it is important to wait for transmission complete before sending next byte or storing received data. While loops simply check for proper flags to be set.

In main program we can call these functions to send and receive single bytes of data as follows:

#include "stm32f10x.h"
#include "usart.h"
int main(void)
{
Usart1Init();
Usart1Put('A');
Usart1Put('R');
Usart1Put('M');
while (1)  {  }
}

Being able to send single byte is a good start where you can wrap this function with loops in order to send strings and so on. But what about sending numbers or specially formatted strings that would include some variable data. Writing these functions would be time consuming and meaningless because this has been done long time ago and is widely used. These are called iostream functions where data is sent by using standard streams with functions that allow formatting strings as you like. I/O streams originally were designed to support operation systems where data could be read from keyboard and sent to display or other output device. It seems that they fit well in embedded systems too with some tweaking. GCC tool chain uses a newlib C library that originally is used in Redhat and there fore it's not adapted for microcontroller where basically is no operating system. (Opposite situation is with AVR GCC tools where avrlibc is specially adapted to support AVR microcontrollers and works out of box). So when we start dealing with OS dependable functions we face some stubs. Stubs are called system call functions that are served by operating system. As in microcontroller basically there is no OS these functions face dead-end leading to compile errors. Basic trick to avoid this we need to take care of these syscalls by writing some implementation where many of them can be dummy. Understanding them all and writing your own implementations would be to hard - at least in the beginning, so there are ready sources that can be used. I prefer to start with newlib_stubs.cdeveloped by nanoage.co.uk. This seems to work fine with I/O stream functions. There is a dozen of other system functions but most important are _write(), _read() and _sbrk(). _write function takes care of writing characters to stdout and stderr; _read() is to read from stdin; and _sbrk() is needed for malloc related functions where it is important to know if dynamic memory doesn't collide with stack. In _read() and _write() functions we need to implement our byte send and read functions so when we call printf() function we could output string to USART:

int _read(int file, char *ptr, int len) {
    int n;
    int num = 0;
    switch (file) {
    case STDIN_FILENO:
        for (n = 0; n < len; n++) {
            char c = Usart1Get();
            *ptr++ = c;
            num++;
        }
        break;
    default:
        errno = EBADF;
        return -1;
    }
    return num;
}

and for writing to stdout and stderr:

int _write(int file, char *ptr, int len) {
    int n;
    switch (file) {
    case STDOUT_FILENO: /*stdout*/
        for (n = 0; n < len; n++) {
            Usart1Put(*ptr++ & (uint16_t)0x01FF);
        }
        break;
    case STDERR_FILENO: /* stderr */
        for (n = 0; n < len; n++) {
            Usart1Put(*ptr++ & (uint16_t)0x01FF);
        }
        break;
    default:
        errno = EBADF;
        return -1;
    }
    return len;
}

simply speaking you can direct streams where ever you want (LCD, SPI, I2C, etc.) by putting send/receive byte functions here.

After setting this we can send formatted strings and read any type of data by using stdio functions:

#include "stm32f10x.h"
#include "usart.h"
#include
int main(void)
{
    char ch[30];
    int32_t a1,a2;
    Usart1Init();
    printf("\r\n USART1 Test \r\n");
    printf("Enter any text: ");
    scanf("%s",ch);
    printf("\r\nYou entered: ");
    printf("%s\r\n",ch);
while (1)
  {
    printf("\r\nEnter first number: ");
    scanf("%ld",&a1);
    printf("%ld", a1);
    printf("\r\nEnter second number: ");
    scanf("%ld",&a2);
    printf("%ld\r\n", a2);
    printf("Sum: %ld + %ld = %ld\r\n",a1, a2, a1 + a2);
  }
}

And results in terminal screen:

img/stm32vldiscovery_usart_terminal-147x300.jpg

In the next part we will be dealing with interrupt based buffered communications.

11   Programming STM32 USART using GCC tools: Part 2

  1. Juni 2012

URL: http://www.keytosmart.com/programming-stm32-usart-gcc-tools-part-2/

In previous part of tutorial we have covered simple USART routines that sends data directly to USART peripheral. This is OK to use such approach when project isn't time critical and processing resources are far from limits. But most often we stuck with these limiting factors especially when RTOS is used or when we perform critical real time data processing. And having USART routines with while loop based wait isn't a good idea - it simply steals processing power only to send a data.

As you may guessed - next step is to employ interrupts.

img/stm32_usart_interrupt_events-300x146.jpg

As you can see there are many sources to trigger interrupts and each of them are used for different purpose. In order to use one or another interrupt first it has to be enabled in USART control register (USART_CR1, USART_CR2 or USART_CR3). Then NVIC USART_IRQn channel has to be enabled in order to map interrupt to its service routine. Because NVIC has only one vector for all USART interrupt triggers, service routine has to figure out which of interrupts has triggered an event. This is done by checking flags in USART status register (USART_SR).

Another important thing about using interrupt based transmission is buffering. Using an interrupts is a "set and forget" method and it is a bit hard predict when it will occur especially when system is complex with multiple different priority interrupt. It can be situation when higher priority preempts USART interrupt during transfer and tries to send data via same USART channel. Without buffering this might become impossible.

img/usart_fifo-300x144.png

As you can see we can continue stacking data FIFO buffer and once USART interrupt routine gets it's control it will transmit accumulated data. Then only problem here that may occur is a buffer overflow - so make sure that FIFO is large enough to deal worth case scenario when buffer gets overfilled and no more data can be accepted that leads to loss of bytes until USART interrupt routine gets chance to sip few bytes off.

In following example we are going to create s simple FIFO implementation that allows us to create send and transmit buffers. For this we create another two files in project - buffer.c and buffer.h. In header file we define FIFO_TypeDef type:

typedef struct{
    uint8_t in;
    uint8_t out;
    uint8_t count;
    uint8_t buff[USARTBUFFSIZE];
} FIFO_TypeDef;

Members of this structure are as follows:

in - indicates input data location. It points to last written data to buffer;

out - indicates next data byte to be sent via USART;

count - indicates the number of bytes currently stored in FIFO;

buff[] - array storing data;

USARTBUFFSIZE - is the size of FIFO.

Having all this we can create a simple circular FIFO or so called ring buffer:

img/circular_fifo-300x108.png

First of all when FIFO is created we have to initialize it by setting initial values of indexes and count:

void BufferInit(__IO FIFO_TypeDef *buffer)
{
   buffer->count = 0;//0 bytes in buffer
   buffer->in = 0;//index points to start
   buffer->out = 0;//index points to start
}

This will give us an empty buffer.

Next follows writing byte to buffer:

ErrorStatus BufferPut(__IO FIFO_TypeDef *buffer, uint8_t ch)
{
if(buffer->count==USARTBUFFSIZE)
    return ERROR;//buffer full
buffer->buff[buffer->in++]=ch;
buffer->count++;
if(buffer->in==USARTBUFFSIZE)
    buffer->in=0;//start from beginning
return SUCCESS;
}

As you can see before writing to buffer we have to make sure its not full. If its full we simply return error - in program this would mean loss of data (if no special care is taken). Otherwise if buffer isn't full, then it adds byte to the end (tail) of queue and increases element count. And since this is circular buffer once index reaches end of array it cycles to the beginning. Similar situation is with reading from buffer:

ErrorStatus BufferGet(__IO FIFO_TypeDef *buffer, uint8_t *ch)
{
if(buffer->count==0)
    return ERROR; // buffer empty
*ch=buffer->buff[buffer->out++];
buffer->count--;
if(buffer->out==USARTBUFFSIZE)
    buffer->out=0; // start from beginning
return SUCCESS;
}
ErrorStatus BufferIsEmpty(__IO FIFO_TypeDef buffer)
{
    if(buffer.count==0)
        return SUCCESS; // buffer full
    return ERROR;
}

Here we first check if there is data in buffer and return error if its empty. If data is present in FIFO then we take first byte from beginning (head) of FIFO and decrease count of data in it and update index.

Continuing with our example we are going to update the code that does same task, but using buffers and interrupts.

First of all we implement buffers - one for receiving data and another for transmitting:

// initialize buffers
volatile FIFO_TypeDef U1Rx, U1Tx;

and in USART1Init function we add couple lines where these buffers are initialized:

BufferInit(&U1Rx);

BufferInit(&U1Tx);

Since we are going to use interrupt based USART communication we also have to enable NVIC channel for USART1 by toUSART1Init() following:

//configure NVIC
NVIC_InitTypeDef NVIC_InitStructure;
//select NVIC channel to configure
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
//set priority to lowest
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
//set subpriority to lowest
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
//enable IRQ channel
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//update NVIC registers
NVIC_Init(&NVIC_InitStructure);
//disable Transmit Data Register empty interrupt
USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
//enable Receive Data register not empty interrupt
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

We simply enable USART1_IRQn channel with 0 priority and 0 sub-priority. Next thing is to decide which interrupt sources will be used to trigger transmission and reception. For reception it is obvious that Received Data Ready to be Read (RXNE) interrupt will rise once data from receive shift register is transferred to USART_DR data register. So during this interrupt we simply need to read out the value from it. A bit different situation is with transmitting. For this we are going to use Transmit Data Register Empty (TXE) interrupt source to trigger transfer. This interrupt raises every time contents of data register is transferred to output shift register meaning readiness for another byte to transfer. Initially we disable this interrupt because we don’t need to trigger it since we don’t have anything to send.

Now we can modify byte send function:

void Usart1Put(uint8_t ch)
{
    //put char to the buffer
    BufferPut(&U1Tx, ch);
    //enable Transmit Data Register empty interrupt
    USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
}

It simply adds byte to buffer and then enables TXE interrupt so it could be triggered to transmit. Similar situation is with reading byte function:

uint8_t Usart1Get(void){
    uint8_t ch;
    //check if buffer is empty
    while (BufferIsEmpty(U1Rx) == SUCCESS);
    BufferGet(&U1Rx, &ch);
    return ch;
}

There we need to check if there is data in buffer. A simple helper function BufferIsEmpty() checks if FIFO is empty by reading buffer count value and returns SUCCESS if its empty. If its empty we simply wait for data to be received. Once its here it is passed to variable and returned.

And the last thing we have to do is to write our interrupt handler where all magic happens:

void USART1_IRQHandler(void)
{
    uint8_t ch;
    //if Receive interrupt
    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        ch=(uint8_t)USART_ReceiveData(USART1);
            //put char to the buffer
            BufferPut(&U1Rx, ch);
    }
    if (USART_GetITStatus(USART1, USART_IT_TXE) != RESET)
    {
            if (BufferGet(&U1Tx, &ch) == SUCCESS)//if buffer read
            {
                USART_SendData(USART1, ch);
            }
            else//if buffer empty
            {
                //disable Transmit Data Register empty interrupt
                USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
            }
    }
}

As I mentioned before first of all we need to clear out which event triggered an interrupt. This is done by checking flags in status register. This is done with

USART_GetITStatus(USART1, USART_IT_RXNE)

function which simply checks selected flag and returns logical ’1′ if particular flag is set. Then conditional code is executed. So if we get receive (RXNE) interrupt then we simply read data value from USART data register and place it in to receive buffer.

If we find that this is transmit interrupt (TXE), then we take data byte from buffer and place in to USART data register to send. And once transmit buffer is empty (TXE) interrupt is disabled to avoid chain interrupt triggering. And this practically it. Same example from part1 works fine. I modified code so that it works either in buffered and in non buffered without interrupts mode. All you need to comment or comment out

#define BUFFERED

in usart.h file. If you need more info about print format check out any external source like this.

Download CodeSourcery + Eclipse project files here to give it a try:

demo/STM32DiscoveryUsart.zip

printf Referenz: http://www.cplusplus.com/reference/cstdio/printf/

12   OpenOCD and NGX USB ARM JTAG

img/NGX_ARM_USB-JTAG_OpenOCD_Debugger-300x225.jpg

This post describes the steps needed to make NGX’s USB ARM JTAG to work with OpenOCD in windows 7. This JTAG is compatible with colink JTAG and works with IAR Workbench and Keil uVision. To use with these IDEs there is a well defined methods/plug-ins available in the product page and in internet. However to use this JTAG with OpenOCD there is scarce resource in the internet.

OpenOCD can be used to low level debugging, source level debugging (through GDB) and can be used for flashing. OpenOCD exposes a command line interface which can be accessed through telnet. It also provides remote GDB server which also can be reached through TCP connection.

Steps needed for Windows:

  1. Plug-In the JTAG to a available USB connector
  2. Download libusb-win32 from http://sourceforge.net/projects/libusb-win32/files/
  3. Extract libusb-win32 to a folder and run “inf-wizard.exe”
  4. Select “USB Serial Converter A” and install driver
  5. Download and install openocd from http://www.freddiechopin.info/index.php/en/download/category/4-openocd
  6. Attach the JTAG probe to your target ARM board and poweron the target board
  7. Create a openocd configurations file (see at the end)
  8. Run openocd.exe –f
  9. Run putty or telnet and connect to port localhost:4444

After this the target board will respond to JTAG commands which can be issued through the telnet session.

For GDB debugging, you need a cross compiled GDB which can be downloaded from http://www.codesourcery.com/sgpp/lite/arm/portal/subscription?@template=lite. After launching arm-none-eabi-gdb.exe run “target remote localhost:3333″ to start remote debugging. You can execute low level JTAG commands from GDB by using “monitor ” command.

Flashing can be done using the following commands:

reset
halt
sleep 200
wait_halt
flash probe 0
flash info 0
flash write_image erase unlock
sleep 200
reset run

Open OCD configuration file:

# openocd configurations telnet_port 4444

# gdb configuration gdb_port 3333

# cpu configuration source [find target/lpc1768.cfg]

# interface configuration interface ft2232 ft2232_vid_pid 0x0403 0x6010 ft2232_device_desc "NGX JTAG" ft2232_layout "oocdlink" ft2232_latency 2