Skeleton Program. STMicroelectronics STM32 F1
Advertentie
Advertentie
Chapter 3
Skeleton Program
In this chapter I discuss the process of creating, compiling, loading, executing, and debugging a program with the STM32 VL Discovery board and Sourcery tools. For desktop machines, the standard first example is the
“hello world” program:
# include <stdio .h> main () { printf (" hello world \n");
} which can be compiled and executed in a single step
$ gcc -o hello hello .c ; ./ hello hello world
This simple program hides an enormous amount of complexity ranging from the automatic inclusion of the standard libraries, to linking in startup code, to interacting with the world through the shell. In the embedded world, much of that complexity is visible to the programmer and hence it is necessary to understand quite a bit more about the execution environment for even the simplest program (and “hello world” is not a simple program).
In the embedded world, the simplest C program is one which does not require any standard libraries and does not interact with the world: main {
}
Revision: 1396a85 (2013-01-07)
47
CHAPTER 3. SKELETON PROGRAM
However, this is a little too pared down for our purposes. Instead, we structure this chapter around a program that has some data and which runs forever: int i; int off = 5; void inc(void){ i += off;
} int main(void){ while (1) { inc ();
}
}
While we cannot directly observe this program when it executes, we can attach a debugger and control its execution through breakpoints and watchpoints. Notice that this program has two global variables (i and off) one of which is initialized to zero and the other has a non-zero initizializer. Furthermore the program has a single procedure other than main and repeatedly calls this procedure.
Before we can execute the program there are a number of hurdles we must overcome. First, we must compile the program into a binary format suitable for loading onto the discovery board. Second, we must load this binary into the flash memory. Finally, in order to observe the program, we must intereact with the discovery board through a debugger (GDB). While we use GDB as a loader as well as a debugger, in general the last two steps may involve separate tools.
Demo Program
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, you should download the code template as described in Section 1.2 which
contains the necessary scripts. The layout of this directory is illustrated in
In order to build this example on your system, you will need to modify two constants in the file Makefile.common – TOOLROOT which should point
48
Revision: 1396a85 (2013-01-07)
to the bin directory of your Sourcery installation and LIBROOT which should point to your installation of the STM32 standard periperal library.
To compile this program, change directories to the Demo directory and execute “make”. This should create a file called Demo.ELF which contains the compiled binary.
To download and execute this binary we will need two programs – gdb
(arm-none-eabi-gdb), which is part of the Sourcery distribution, and st-util, which provides a gdb server that communicates with the stlink debugging stub on the discovery board through a USB connection. We described how to
install st-util in Section 1.2. I will assume you have installed and tested the
connection. You should open two terminal windows. In one, execute: st -util -1
Note: earlier versions of st-util need a different startup sequence st-util 4242 /dev/stlink which starts a gdb server listening at port 4242. You should see an output such as the following:
Chip ID is 00000420 , Core ID is 1 ba01477 .
KARL - should read back as 0x03 , not 60 02 00 00
Listening at *:4242...
In the other terminal window, execute (lines starting “(gdb)” are within the debugger): arm -none -eabi -gdb Demo.elf
(gdb) target extended - remote :4242
(gdb) load
(gdb) break main
(gdb) break inc
(gdb) continue
The “target” command should connect to the gdb server at port 4242; the load command downloads the executable to the STM32 flash memory.
The next two commands set breakpoints at main and inc procedures, and the continue command executes until the next breakpoint occurs. You can then repeatedly execute and examine the value of i:
Revision: 1396a85 (2013-01-07)
49
CHAPTER 3. SKELETON PROGRAM
(gdb) print i
(gdb) continue
...
Exercise 3.1
GDB on STM32
Experiment with GDB to test various commands such as the following:
1. Print current register values (e.g. print /x $sp displays the stack pointer in hex.
2. Try setting a watchpoint on i.
3. Try using a breakpoint command that prints i and continues just before main calls inc
Make Scripts
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 Unix 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. In this section I discuss the build process and in the next, the function performed by the STM32 startup code (which is included with the firmware libraries).
The make files included with the demo program are split into two parts
– Makefile.common does the heavy lifting and is reusable for other projects while Demo/Makefile is project specific. Indeed the only function of the project specific makefile is to define the required object files and their de-
pendencies. The Makefile for the demo project is illustrated in Listing 3.1.
This can be modified for other projects by adding additional objects and modifying compilation flags. The variable TEMPLATEROOT should be modified to point to the template directory.
50
Revision: 1396a85 (2013-01-07)
TEMPLATEROOT = ..
# compilation flags for gdb
CFLAGS = -O1 -g
ASFLAGS = -g
# object files
OBJS= $( STARTUP ) main.o
# include common make file include $( TEMPLATEROOT )/ Makefile . common
Listing 3.1: Demo Makefile
Most of Makefile.common defines paths to the tools and libraries. The only notable parts of this file are the processor specific defines and the compilation flags. The processor specific definitions include the linker script LDSCRIPT that informs the linker of the correct linking script to use – we will discuss this file briefly in the next section. A processor type define PTYPE that controls conditional compilation of the firmware libraries, and two startup files
– one generic (system_stm32f10x.o) and one specific to the STM32 Value
Line processors (startup_stm32f10x.o). The STM32 libraries are designed to support multiple members of the STM32 family by requiring various compile time switches. The processor on the STM32 VL Discovery board is
“medium density value line” part – this is reflected in the compile-time definition STM32F10X_MD_VL.
PTYPE = STM32F10X_MD_VL
LDSCRIPT = $( TEMPLATEROOT )/ stm32f100 .ld
STARTUP = startup_stm32f10x .o system_stm32f10x .o
# Compilation Flags
FULLASSERT = -DUSE_FULL_ASSERT
LDFLAGS += -T$( LDSCRIPT ) -mthumb -mcpu=cortex -m3
CFLAGS += -mcpu=cortex -m3 -mthumb
CFLAGS += -I$( TEMPLATEROOT ) -I$( DEVICE ) -I$(CORE) -I$( PERIPH )/inc -I.
CFLAGS += -D$( PTYPE ) -DUSE_STDPERIPH_DRIVER $( FULLASSERT )
The flags -mcpu=cortex-m3 -mthumb inform gcc about the core proces-
Revision: 1396a85 (2013-01-07)
51
CHAPTER 3. SKELETON PROGRAM sor. -DUSE_STDPERIPH_DRIVER -DUSE_FULL_ASSERT affect the compilation of firmware library code.
STM32 Memory Model and Boot Sequence
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 and the mutable state (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 il-
lustrated in Figure 3. 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.
52
Revision: 1396a85 (2013-01-07)
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 .*)
*(. rodata )
/* remaining code */
/* 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
Figure 3.1: Linker Script Fragment
The first portion of the .text section is loaded with the exception vectors (.isr_vector) which are later defined in the startup code. These excep-
Revision: 1396a85 (2013-01-07)
53
CHAPTER 3. SKELETON PROGRAM tion vectors start at 0x08000000, as is required when the STM32 boots from flash.
There are a number of details elided that ensure the creation of labels assumed by the compiler and standard libraries as well as handling debugging sections of the binary.
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 (defined in startup_stm32f10x.c) 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. (see Listing 3.2).
These steps are necessary whenever the processor resets in order to initialize the “C” environment. The reset handler then calls SystemInit (defined in system_stm32f10x.c from the firmware library) which initializes the clocking system, disables and clears interrupts. The compile flag STM32F10X_MD_VL defined in our makefile is crucial to this code because the clock initialization code is processor specific. Finally, the 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 (Listing 3.3). 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 then names defined in the vector
!
table for your handler or else it will not be linked into the loaded vector table
54
Revision: 1396a85 (2013-01-07)
// 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) {}
Listing 3.2: Reset Handler in startup_stm32f10x.c
Revision: 1396a85 (2013-01-07)
55
CHAPTER 3. SKELETON PROGRAM 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 ,
...
Listing 3.3: Interrupt Vectors
56
Revision: 1396a85 (2013-01-07)
Downloaden
Advertentie