Actions

EmSys

Programming STM32 using it's firmware library

From EdWiki

Objective

The objective of this documentation is to help embedded systems beginners get jump started with programming the STM32 family. Software development for the STM32 family can be extremely challenging for the uninitiated with a vast array of documentation and software libraries to wade through. For example, the reference manual for ultra low-power STM32 L1 devices - RM0038, is 809 pages and does not even cover the Cortex-M3 processor core ! Fortunately, it is not necessary to read this documentation to get started with developing software for the STM32, although it is an important reference.

In addition, a beginner is faced with many tool-chain (A tool-chain includes a compiler, assembler, linker, debugger, and various tools for processing binary files) choices. The development software used in this documentation is all open-source. Our primary resource is the GNU software development tool-chain including gcc, gas, objcopy, objdump, and the debugger gdb. One should not assume that open-source means lower quality – many commercial tool-chains for embedded systems utilize GNU software and a significant fraction of commercial software development is accomplished with GNU software.

Finally, virtually every embedded processor is supported by the GNU software tool-chain. Learning to use this toolchain on one processor literally opens wide the doors to embedded software development.

Firmware development differs significantly from application development because it is often exceedingly difficult to determine what is actually happening in code that interacts with a hardware peripheral simply through examining program state. Furthermore, in many situations it is impractical to halt program execution (e.g., through a debugger) because doing so would invalidate real-time behaviour.

Required Hardware

A list of the hardware required for this tutorial is provided in the following Figure. The component list is organized by categories corresponding to the various interfaces covered in this article followed by the required prototyping materials and test equipment. In the remainder of this section, we describe each of these components and, where some options exist, key properties that must be satisfied. A few of these components require header pins to be soldered on.

Category
Component
I/O board
Processor
STM32L-Discovery
STM32L Discovery Board
Asynchronous Serial
Analog I/O
Analog IO Baord
I2C
I2C Interface

Magneto & Accelerometer

I2C Iinterface Baord

Magneto-Accel IO Board

SPI
Nokia LCD

Storage

NOKIA LCD Board

Storage Board

Analog
Audio
Audio IO Baord
















STM32L-Discovery Board

The key component used in this tutorial is the STM32L-Discovery board produced by STMicroelectronics (ST) and available from many electronics distributors for approximately Rs.976.57. The STM32L-DISCOVERY board helps you to discover the STM32L ultralow power features and to develop and share your applications. It is based on an STM32L152RBT6 and includes an ST-Link/V2 embedded debugging tool interface, an LCD (24 segments, 4 commons), LEDs, pushbuttons, a linear touch sensor or touchkeys.

Software Installation

Please follow this link to install necessary software development tools.

STM32L1xx Template Project

Stm32l1xx stdPeriph template directory.png

While the firmware provided by STMicroelectronics provides a solid foundation for software development with the STM32 family, it can be difficult to get started. Unfortunately, the examples distributed with the STM32L1xx Standard Peripheral Library are deeply interwoven with the commercial windows-based IDEs available for STM32 code development and are challenging to extract and use in an Eclipse environment.

The layout of STM32l1xx Template Project directory is illustrated in the Figure.

STM32 Configuration

The STM32 processors are complex systems with many peripherals. Before any of these peripherals can be used they must be configured. Some of this configuration is generic – for example clock distribution and pin configuration – while the rest is peripheral specific.

The fundamental initialization steps required to utilize any of the STM32 peripherals are:

  1. Enable clocks to the peripheral
  2. Configure pins required by the peripheral
  3. Configure peripheral hardware

The STM32 processors, as members of the Cortex-M3 family, all have a core system timer which can be used to provide a regular timing tick. We utilize this timer to provide a constant blink rate for our example. The overall structure of this program is illustrated in the following code-fragment.

#include "stm32l1xx.h"
#include <stm32l1xx_rcc.h>
#include <stm32l1xx_gpio.h>
 
void TimingDelay_Decrement(void);  // used in SysTick_Handler function
void Delay(__IO uint32_t nTime);
 
int main(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
 
    // Enable Peripheral Clocks
    ... (1) ...
    // Configure Pins
    ... (2) ...
    // Configure SysTick Timer
    ... (3) ...
    while(1) {
        // toggle led
        ... (4) ...
        Delay (100); // wait for 1-Second
    }
}
 
// Delay code
... (5) ...
 
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t* file , uint32_t line)
{
/* Infinite loop */
/* Use GDB to find out why we're here */
while (1);
}
#endif

This template program requires support from two library modules (stm32l1xx_gpio.c, stm32l1xx_rcc.c). The STM32-Discovery board has an LED driven by I/O pin PB6 & PB7. In order to configure these pins, clocks must first be enabled for GPIO port B with the followingg library command.

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);    // (1)

After enabling the clocks, it is necessary to configure any required pins. In this case, PB6 and PB7 must be configured as an output.

/*    (2)        */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

Once configuration is complete, the pin(s) can be set or reset with the following code:

/*     (3)         */
GPIO_WriteBit(GPIOB, GPIO_Pin_6, Bit_RESET);
GPIO_WriteBit(GPIOB, GPIO_Pin_7, Bit_RESET);

The template demo program also utilizes a timer tick to measure the passage of time. While this timer tick utilizes interrupts, which we will not be discussing here, the actual use here can be treated simply as an idiom. The Cortex-M3 core used in the STM32 processors has a dedicated timer for this function. The timer as a multiple of the system clock (which is defined as ticks/second) – here we configure it for 1 msec interrupts (the constant SystemCoreClock is defined in the firmware library to be the number of system clock cycles per second):

/*    (4)         */
SysTick_Config(SystemCoreClock / 1000);

Every 1 msec, the timer triggers a call to the SysTick_Handler. For the template program, we simply decrement a shared counter – declared as __IO to ensure that the compiler doesn’t perform undesired optimization.

/*    (5)         */
static __IO uint32_t TimingDelay;
void TimingDelay_Decrement(void)  { 
    if( TimingDelay != 0x00 ) {
        TimingDelay--;
     }
}
void Delay(__IO uint32_t nTime) {
    TimingDelay = nTime;
    while(TimingDelay != 0)
       ;
}

Linker Script & Startup Code

While downloading and executing a binary is comparatively easy, the process of building a binary is not. The compilation tools require non-obvious options, the STM32 firmware libraries require various definitions, and the generation of an executable from binaries requires a dedicated linker script. Furthermore, main.c is not in itself a complete program – there are always steps necessary to initialize variables and set up the execution environment. In the linux world, every C program is linked with crt0.o to perform this initialization. In the embedded world, additional initialization is necessary to set up hardware environment.

Linker Script

The memory of the STM32 processors consists of two major areas – flash memory (effectively read-only) begins at address 0x08000000 while static ram (read/write) memory begins at address 0x20000000. The size of these areas is processor specific. When a program is executing, the machine code (generally) resides in flash variables and the run-time stack resides in static ram (SRAM). In addition, the first portion of flash memory, starting at 0x08000000, contains a vector table consisting of pointers to the various exception handlers. The most important of these are the address of the reset handler - stored at 0x8000004- which is executed whenever the processor is reset, and the initial stack pointer value - stored at 0x80000000.

This memory structure is reflected in the linker script fragment is as follows.

 ENTRY(Reset_Handler)
 
 MEMORY
 {
     RAM (rwx) : ORIGIN = 0x20000000 , LENGTH = 8K
     FLASH (rx) : ORIGIN = 0x08000000 , LENGTH = 128K
 }
 
 SECTIONS
 {
     .text :
 {
 
 KEEP (*(. isr_vector)) /* vector table */
     *(. text) /* code */
     *(. text .*) /* remaining code */
     *(. rodata) /* read -only data (constants) */
     ...
 } >FLASH
 
 ...
 
 _etext = .;
 _sidata = _etext;
 
 /* Init data goes in RAM , but is copied after code as well */
 .data : AT ( _sidata )
 {
     ...
     _sdata = .;
     *(. data)
     ...
     _edata = . ; /* used to init data section */
 } >RAM
 
 .bss :
 {
    ...
    _sbss = .; /* used to init bss */
    __bss_start__ = _sbss;
    *(. bss)
    ...
    . = ALIGN (4);
    _ebss = . ; /* used to init bss */
    __bss_end__ = _ebss;
 } >RAM


The script begins by defining the code entry point - Reset_Handler - and the two memory regions – flash and ram. It then places the named sections from the object files being linked into appropriate locations in these two memory regions. From the perspective of an executable, there are three relevant sections – .text which is always placed in flash, .data' and .bss which are always allocated space in the ram region. The constants required to initialize .data at runtime are placed in flash as well for the startup code to copy. Notice further that the linker script defines key labels _etext, _sidata, ... that are referenced by the startup code in order to initialize the ram.

The GNU linker is instructed to place the data section in FLASH – specifically at the location of _sidata, but links data references to locations in RAM by the following code fragment:

.data : AT ( _sidata )
{
...
} >RAM

The key idea is that the GNU linker distinguishes between virtual (VMA) and load addresses (LMA). The VMA is the address a section has when the program is executed, the LMA is the address at which the section is loaded. For data, our linker script places the LMA of the data section within FLASH and the VMA within RAM – notice that _sidata = _etext.

The first portion of the .text section is loaded with the exception vectors - .isr_vector - which are later defined in the startup code. These exception vectors start at 0x08000000, as is required when the STM32 boots from flash.

Startup Code

The linker script and the startup code collaborate to create a meaningful executable environment. The linker script is responsible for ensuring that the various portions of the executable (e.g. the exception vectors) are in their proper place and for associating meaningful labels with specific regions of memory used by the start up code. At reset, the reset handler is called. The reset handler (Reset_Handler) - defined in startup_stm32l1xx_md.S, copies the initial values for variables from flash (where the linker places them) to SRAM and zeros the so-called uninitialized data portion of SRAM.

These steps are necessary whenever the processor resets in order to initialize the C environment. The reset handler then calls SystemInit() function (defined in system_stm32l1xx.c from the firmware library) which initializes the clocking system, disables and clears interrupts. The compile time symbol STM32L1XX_MD is crucial to this code because the clock initialization code is processor specific. Finally, the reset handler (Reset_Handler) calls the main function defined in user code. The external variables required by the reset handler to initialize memory (e.g. _sidata, _sdata...) are defined by the linker script.

Another important function of the startup code is to define the default interrupt vector table as illustrated in the following code fragment:

// Linker supplied pointers
extern unsigned long _sidata;
extern unsigned long _sdata;
extern unsigned long _edata;
extern unsigned long _sbss;
extern unsigned long _ebss;
 
extern int main(void);
 
void Reset_Handler(void) {
 
    unsigned long *src , *dst;
 
    src = &_sidata;
    dst = &_sdata;
 
    // Copy data initializers
    while( dst < &_edata )
        *(dst ++) = *(src ++);
 
    // Zero bss
    dst = &_sbss;
    while( dst < &_ebss )
        *(dst ++) = 0;
 
    SystemInit();
 
    __libc_init_array();
 
    main();
 
    while( 1 ) {}
        ;
}


In order to allow application code to conveniently redefine the various interrupt handler, every required interrupt vector is assigned an overideable (weak) alias to a default hander (which loops forever). To create a custom interrupt handler in application code, it is sufficient to define a procedure with the appropriate handler name. One word of caution – you must be careful to use exactly the names defined in the vector table for your handler or else it will not be linked into the loaded vector table!

static void default_handler(void)
{ 
    while(1)
        ; 
}
void NMI_Handler (void) __attribute__ ((weak, alias, !(``default_handler``)));
void HardFault_Handler (void) __attribute__ ((weak, alias, !(``default_handler``)));
void MemMange_Handler (void) __attribute__ ((weak, alias, !(``default_handler``)));
void BusFault_Handler (void) __attribute__ ((weak, alias, !(``default_handler``)));
...
__attribute__ (( section(``.isr_vector``)))
void (* const g_pfnVectors [])(void) = {
        _estack,
        Reset_Handler,
        NMI_Handler,
        HardFault_Handler,
         ...
}

Building & Debugging

In this section we discuss the process of creating, compiling, loading, executing, and debugging a program with the STM32L-Discovery board and Sourcery tools.

The process of compiling a program for embedded processors such as the STM32 can involve quite a few details such as processor specific compilation flags, paths to the compilation tools, etc. Generally the best approach is to build a make script to guide the process. Rather than diving in at this level, we will Import an existing Project from the file system.

Download STM32L-Discovery Eclipse Project and extract to your home folder., which contains the necessary compiler flags, paths to the tools, etc. In order to build this example on your system, you will need to IMPORT STM32L-Discovery Eclipse Project into the Eclipse workspace.

Toolchain Prefix and Path

To make sure that the toolchain Prefix and Path are correct, Select Project > C/C++ Build > Settings > Cross Settings. The Prefix field is set to arm-none-eabi- and modify the Path field to the folder where you have installed the CodeSourcery toolchain.

/home/USERNAME/CodeSourcery/Sourcery_CodeBench_Lite_for_ARM_EABI

if you have accepted the default installation folder.

Setting Compiler, Assembler and Linker Options

Since we have imported a pre-configured project, various options for compiler, assembler, and linker for Debug Configuration are already set. So, let's build the project.

Building Project

To Build the Project on demand, click on the Project menu and un-select Build Automatically.

Un-Select Build Automatically














From the main menu, Select Project > Build Project.

Build Progress Bar








If everything goes well, you will see in the console window something like the following:

Build Console Output












Build will be completed with some Warning Messages. For the time being we can ignore these warning messages.



<

GDB, OpenOCD and Eclipse

To use OpenOCD with our projects, we need to do more than just connecting the STM32L-Discovery board to PC through USB cable and then starting the OpenOCD server. We need to configure OpenOCD server so that it knows about the adapter(Dongle) and the board(CPU). We also need to configure OpenOCD such that it communicates to GDB using Eclipse GUI for flashing the code and debugging.


OpenOCD complies with the remote gdbserver protocol, and as such can be used to debug remote targets. Setting up GDB with OpenOCD involves several components:

  1. The OpenOCD server support for GDB needs to be configured.
  2. GDB's support for OpenOCD needs configurations.
  3. The Eclipse GUI environment will also needs to be configured such that both GDB and OpenOCD are integraed with in the IDE.

OpenOCD Project Setup

It is assumed that OpenOCD 0.7.0 is available in your home folder such as:

/home/USERNAME/openocd-0.7.0-bin

with all the necessary configuration files.

To configure OpenOCD, select Run > External Tools > External Tools Configurations... from the main menu.

External Tools Configurations...















In the External Tools Configurations window, enter (browse to the) openocd executable as

Configuring OpenOCD


  • OpenOCD in the Name,
  • /home/USERNAME/openocd-0.7.0-bin/openocd in the Location,
  • /home/USERNAME/openocd-0.7.0-bin in the Working Directory, and
  • -f board/stm32ldiscovery.cfg in the Arguments fields.



Next, Select Common tab in the same External Tools Configurations window and Select Externals Tools to Display in favourite menu as shown in the following screen.

Configuring OpenOCD







Make sure that Launch in background option is checked.





Next, select Apply followed by Close button.

Accessing OpenOCD without Sudo

On Linux, OpenOCD requires superuser privileges to communicate with your USB dongles. Please, follow the link Accessing Devices without Sudo to allow OpenOCD to access USB dongles with out superuser privileges.





Configuring the Debugger

To configure the Debugger, select Run > Debug Configurations... from the main menu.

Debug Configurations Menu
















In the Debug Configurations window, select GDB Hardware Debugging from the left pane, and make sure that C/C++ Application, Project and Build Configuration fields are selected properly from the main tab as shown in the following screen.

Debug Configurations












In the same Debug Configurations window, select Debugger tab. In this window tab, specify :

Debugger Configurations
  • /home/USERNAME/CodeSourcery/Sourcery_G++_Lite/bin/arm-none-eabi-gdb as GDB Command,
  • Check Use remote target option
  • Select Generic TCP/IP for JTAG Device,
  • localhost for Host Name and
  • 3333 for Port Number as shown in the screen.






Next, Select the Startup tab and enter the following Commands in the text window:

Startup Commands
monitor reset init
monitor halt
monitor flash probe 0
monitor sleep 500
monitor poll on








Next, Select the Common tab and check Debug to Display in favourites menu as shown in the following screen.

Favourites menu


















That’s it. We have completed the Debug Configurations. Click on Apply followed by Close button to complete the Debug Configurations.











STM32L-Discovery Board

  1. Check jumper position on the board, JP1 ON, CN3 ON
  2. Connect the STM32L-Discovery Board to a PC with a Mini-USB cable to power the board. RED LED LD2 (PWR) then lights up.

Running OpenOCD

We have already configured OpenOCD, just select OpenOCD from the External Tools Tool Bar.

Run OpenOCD








OpenOCD Output







Note: OpenOCD will be running in the background, don’t terminate.








Launching the Debugger

To launch the debugger, select the Debug icon from the tool bar.

Invoking Debugger










When we launch the Debugger for the first time, eclipse tries to switch the Perspective from C/C++ to debug, select Remember my decision and click YES.

Eclipse Perspective Switch












The Debugger should break at C main function as we have configured it.

Debug Window




















Now you can use all the debugging commands like, single step, examine registers, examine data etc.

Resources

  1. STM32L-Discovery Eclipse Project
  2. STM32L-Discovery Codelight Project
  3. STM32L1xx Standard Peripherals Library

You can also clone a codelite STM32L-Discovery Blinky git repository

git clone http://10.114.15.15/stm32l_discovery/blinky.git