4. Programming the STM32

4.1. Memory Map

In Fig. 4.1.1 you see the different FLASH and SRAM sections of the STM32L4. Also have a look at the “starter” source code:



Fig. 4.1.1 The memory sections are defined in the linker script STM32L476RG_FLASH.ld.

  • The first two entries in the vector table are important for the right program start:

    Entry 0: initial stack pointer (sp). This vector table entry is the only vector which is not a “real vector”, which means, it is not a pointer to executable code.

    Entry 1: initial program counter (pc)

    You can print these two values with a GDB command:

    (arm-gdb) x/2xw 0x08000000

4.2. Levels of abstraction

The following items are arranged from low to high abstraction level.

  1. Standalone C, C++ or assembler program without dependencies to other libraries. This is the stronger definition of bare-metal programming. A more general definition is programming without operating system, but with additional libraries.

  2. CMSIS: Abstraction of the Cortex M3 core and core peripherals. In addition a vendor specific part abstracts the microcontroller peripherals. CMSIS consists of a bunch of C header files.

  3. STM32 Cube: Software framework with “hardware abstraction layer” (HAL) and “low layer” (LL). Cube also include the CMSIS layer.

  4. libc: Standard C library (or libc++)

  5. Middleware Libraries for graphical user interfaces, TCP/IP networking, file systems, realtime operating systems and so on.

  6. High-level interpreters - for example JavaScript or Python.

The layers are often used in combinations:


Fig. 4.2.1 Levels of programming.

An overview of different programming techniques gives [YIUWAYS]. It covers Java, MatLab/SimuLink, Labview, Arduino, mbed, Finite State Machines and other programming languages like Ada, Pascal, BASIC.

Higher-level interpreted programming languages are more and more ported to microcontrollers. A few examples are JerryScript (http://jerryscript.net), eLua (http://www.eluaproject.net), Espruino (http://www.espruino.com) and Micropython (http://micropython.org).

4.3. CMSIS

Cortex Microcontroller Software Interface Standard

CMSIS block diagram

Fig. 4.3.1 CMSIS Blockdiagramm (from https://developer.arm.com/embedded/cmsis)

  • Versioning: CMSIS v3, v4, v5.

  • Cube L4 1.8.0 contains CMSIS Version 4.30.

    • CMSIS/Include/core_cm4.h

      Definitions for APSR, IPSR, xPSR, CONTROL, NCIV, SCB, SCnSCB, SysTick, ITM, DWT, TPI, MPU, FPU, CoreDebug.

    • CMSIS/Device/ST/STM32L4xx/Include/stm32l476xx.h


    • Templates for startup code. The standard startup file in CMSIS is written in assembler (startup_stm32l476xx.s). It could also be written in C. The startup code calls SystemInit which is defined in system_stm32l4xx.c.

    • Documentation in Cube Library


  • Files

    CMSIS files

    Fig. 4.3.2 Essential CMSIS files of version 4.30.

    You find the CMSIS file tree in one of the CMSIS projects ending with -cmsis, e.g. starter-cmsis or gpio-intr-cmsis.

  • Literature

    • Homepage of the CMSIS project: https://developer.arm.com/embedded/cmsis

    • [CM4PM] contains the most important CMSIS definitions.

    • Trevor Martin, The Designer’s Guide to the Cortex M3, chapter 4 “Cortex Microcontroller Software Interface Standard”, pp. 109 - 139. Chapter 6 “Developing with CMSIS RTOS”, pp. 165 - 216, [MARTIN].

    • Doulos CMSIS Tutorial


  1. Look at project starter-cmsis. In directory src you find the CMSIS files startup_stm32l476xx.s and system_stm32l4xx.c. Find out, what these files do during startup from RESET.

4.4. Cube L4

STM32Cube Overview

Fig. 4.4.1 The diagram comes from Release Notes for STM32CubeL4 Firmware Package, which is contained in the Cube package.

Documentation within Cube

  • View “Release Notes” in Web-Browser:

    firefox STM32Cube_FW_L4_V1.8.0/Release_Notes.html

  • Documentation/STM32CubeL4GettingStarted.pdf (UM1860, 4/2017, 29 pages)

  • BSP

    • Drivers/BSP/STM32L476G_EVAL/STM32L476G_EVAL_BSP_User_Manual.chm

    • Drivers/BSP/STM32L4xx_Nucleo_32/STM32L4xx_Nucleo_32_BSP_User_Manual.chm

    • Drivers/BSP/B-L475E-IOT01/B-L475E-IOT01_BSP_User_Manual.chm

    • Drivers/BSP/STM32L4xx_Nucleo/STM32L4xx_Nucleo_BSP_User_Manual.chm

    • Drivers/BSP/STM32L476G-Discovery/STM32L476G-Discovery_BSP_User_Manual.chm

  • HAL

    • Drivers/STM32L4xx_HAL_Driver/STM32L486xx_User_Manual.chm

    • Drivers/STM32L4xx_HAL_Driver/STM32L462xx_User_Manual.chm

    • Drivers/STM32L4xx_HAL_Driver/STM32L443xx_User_Manual.chm

    • Drivers/STM32L4xx_HAL_Driver/STM32L4A6xx_User_Manual.chm


    • Drivers/CMSIS/Documentation/General/html/index.html

Literature for various application areas

  • UM1860: Getting started with STM32CubeL4 for STM32L4 Series

  • UM1884: Description of STM32L4 HAL drivers

  • UM1734: STM32Cube USB device library

  • UM1720: STM32Cube USB host library

  • UM1721: Developing Applications on STM32Cube with FatFs

  • UM1722: Developing Applications on STM32Cube with RTOS

  • UM1916: STM32CubeL4 Nucleo demonstration firmware

  • UM1919: STM32CubeL4 demonstration firmware for 32L476GDISCOVERY discovery kit

  • UM1937: STM32CubeL4 demonstration firmware for STM32L476G-EVAL board

  • UM2145: STM32CubeL4 demonstration firmware for 32L496GDISCOVERY discovery kit

4.5. Standard C library

4.6. Debugging with GDB

Fig. 4.6.1 shows the basic debug setup. The GNU Debugger for ARM connects to the OpenOCD via a local network connection on port number 3333. The OpenOCD server drives the debug adaptor.

OpenOCD also offers a telnet port as a command interface. Run the telnet command as telnet localhost 4444 and see the telnet prompt >. Type help to see all the available commands.


Fig. 4.6.1 Simplified block diagram of the debug setup.

The OpenOCD homepage is http://openocd.org.

OpenOCD User’s Guide http://openocd.org/doc-release/html/index.html.


  • Each project makefile has a target gdb. This target loads the GDB initialisation file .estool/gdbinit.

  • GDB shows the source code after the file has been flashed into the microcontroller (or transferred into the RAM). Your can run the command estool -f main.bin outside of GDB or run load within GDB.

  • If the GDB command window is blocked, the program runs on the target. You can stop the program by pressing Ctrl-C. Now you can enter GDB commands after the prompt (arm-gdb).

  • GDB has tab completion.

  • GDB and OpenOCD work only as long as the Nucleo board is connected to the USB. After plugging the board off and on, you must restart the OpenOCD and GDB.

4.6.1. GDB Commands

  1. Get help with:

    (arm-gdb) help ...
  2. Stop a running program with Ctrl-C.

  3. Run program from start (also see GDB function debug-program in .estool/gdbinit):

    (arm-gdb) monitor reset halt
    (arm-gdb) continue
  4. List breakpoints. Shows a number for each breakpoint.

    (arm-gdb) info br
  5. Set breakpoint

    (arm-gdb) b main
    (arm-gdb) b RESET_Handler
  6. Set breakpoint with command list

    (arm-gdb) b RESET_Handler
    (arm-gdb) commands
    (arm-gdb) print $sp
    (arm-gdb) continue
    (arm-gdb) end
  7. Delete breakpoint

    (arm-gdb) del <nr>

    Similar command: clear

  8. Breakpoint with condition

    (arm-gdb) br handler if count == 10

    It is also possible to use the condition command.

  9. Step

    Step one line or one instruction (si). Steps into functions.

    (arm-gdb) s
    (arm-gdb) si
  10. Next

    Next line or next instruction (ni). Does not step into funtions.

    (arm-gdb) n
    (arm-gdb) ni
  11. Continue

    (arm-gdb) cont
  12. Finish to the end of a function

    (arm-gdb) fini
  13. Print

    (arm-gdb) p <expr>
    (arm-gdb) p var1
    (arm-gdb) p GPIOA_ODR
    (arm-gdb) p EXTI->PR1
    (arm-gdb) p &g_bss          # to find addresses also see arm-none-eabi-nm
    (arm-gdb) p main::loopcnt   # local var
  14. Dump memory

    (arm-gdb) x <addr>
    (arm-gdb) x/4xw 0
    (arm-gdb) x/8xw $sp
  15. Display

    (arm-gdb) disp $sp

    Use undisplay to remove displays.

  16. Set variables

    (arm-gdb) set var counter = 21
  17. Write value at an address

    (arm-gdb) set *(uint32_t*)0x20002000 = 0x12345678
  18. Switch windows: repeat C-x 2

    • Cmd, Src und Asm

    • Cmd, Src und Reg

    • Cmd, Asm und Reg

Set SP and PC

The following commands set stack pointer (SP) and program counter (PC). Then the program is startet (continue).

(arm-gdb) set $sp = 0x...
(arm-gdb) set $pc = 0x..
(arm-gdb) continue

The following commands are sometimes useful:

  1. Erase Flash memory:

    OpenOCD Telnet prompt:

    > reset halt
    > stm32l4x mass_erase 0

    GDB console:

    (arm-gdb) monitor reset halt
    (arm-gdb) monitor stm32l4x mass_erase 0
  2. Read and set the VTOR register in SCB (Vector Table Offset Register). This register is set to 0x00000000 after RESET.

    (arm-gdb) x/1xw 0xE000ED08    # print VTOR register
    (arm-gdb) monitor mww 0xE000ED08 0x20000000   # set VTOR to RAM (OpenOCD syntax)
    (arm-gdb) set {int}0xE000ED08 = 0x20000000    # set VTOR to RAM (GDB syntax)

Print out the status register ``xPSR`` in a more readable form

You can define the following function in the .estool/gdbinit file:

define flags
printf "N=%d Z=%d C=%d V=%d Q=%d GE=%1x ICI/IT=0x%x Exc#=%d\n", \
($xPSR & 0x80000000)?1:0, \
($xPSR & 0x40000000)?1:0, \
($xPSR & 0x20000000)?1:0, \
($xPSR & 0x10000000)?1:0, \
($xPSR & 0x08000000)?1:0, \
($xPSR & 0x000F0000) >> 16, \
($xPSR & 0x0000FC00) >> 10, \
($xPSR & 0x000001FF)

document flags
Print out the xPSR register in a readable form

With this function you will have a nice printout of the flags in xPSR:

(arm-gdb) flags                                                                 │
N=0 Z=0 C=1 V=0 Q=0 GE=0 ICI/IT=0x0 Exc#=56

The p/x $xPSR will just print out:

(arm-gdb) p/x $xPSR                                                             │
$2 = 0x21000038