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 printsiand continues just before
main callsinc
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 mod- ifying compilation flags. The variable TEMPLATEROOT should be modified to point to the template directory.
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.commondefines paths to the tools and libraries. The only notable parts of this file are the processor specific defines and the compila- tion flags. The processor specific definitions include the linker scriptLDSCRIPT
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 con- trols 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 com- pile time switches. The processor on the STM32 VL Discovery board is “medium density value line” part – this is reflected in the compile-time defi- nition 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 )
sor. -DUSE_STDPERIPH_DRIVER -DUSE_FULL_ASSERTaffect 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 0x08000004) which is executed whenever the processor is reset, and the initial stack pointer value (stored at 0x08000000).
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.
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
Figure 3.1: Linker Script Fragment
The first portion of the .textsection is loaded with the exception vec- tors (.isr_vector) which are later defined in the startup code. These excep-
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 mean- ingful 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 re- gions 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 ini- tial 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 clock- ing system, disables and clears interrupts. The compile flagSTM32F10X_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 suffi- cient 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 !
// 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) {} }
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 ,
...
Chapter 4
STM32 Configuration
The STM32 processors are complex systems with many peripherals. Be- fore any of these peripherals can be used they must be configured. Some of this configuration is generic – for example clock distribution and pin config- uration – while the rest is peripheral specific. Throughout this chapter, we utilize a simple “blinking lights” program as a guiding example.
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 Figure 4. The program begins by including the relevant firmware library headers – in this case for clock and pin configuration. The main routine follows the initialization steps described above and then enters a loop in which it toggles an LED and waits for 250ms. Procedure main is followed by code implementing the delay function which utilizes the system timer. Finally, a helper function is provided to handle assertion violations in the firmware library (required if USE_FULL_ASSERT is defined when compiling firmware library modules). While the assert_failed handler does nothing, it is very useful when debugging new projects as the
firmware library will perform extensive parameter checking. In the event of an assertion violation, GDB can be used to examine the parameters of this routine to determine the point of failure.
# include <stm32f10x .h> # include <stm32f10x_rcc .h> # include <stm32f10x_gpio .h>
void Delay( uint32_t nTime);
int main(void) {
GPIO_InitTypeDef GPIO_InitStructure ;
// Enable Peripheral Clocks
... (1) ...
// Configure Pins
... (2) ...
// Configure SysTick Timer
... (3) ... while (1) {
static int ledval = 0;
// toggle led ... (4) ... Delay (250) ; // wait 250 ms } } // Timer 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
Figure 4.1: Blinking Lights
The STM32 VL discovery board has an LED driven by I/O pin PC9. [14] In order to configure this pin, clocks must first be enabled for GPIO
Port C with the following library command (described in greater detail in Section 4.1).
RCC_APB2PeriphClockCmd ( RCC_APB2Periph_GPIOC , ENABLE ); // (1)
After enabling the clocks, it is necessary to configure any required pins. In this case, a single pin (PC9) must be configured as an output (described in greater detail in Section 4.2).
/* (2) */
GPIO_StructInit (& GPIO_InitStructure ); GPIO_InitStructure . GPIO_Pin = GPIO_Pin_9 ;
GPIO_InitStructure . GPIO_Mode = GPIO_Mode_Out_PP ; GPIO_InitStructure . GPIO_Speed = GPIO_Speed_2MHz ; GPIO_Init (GPIOC , & GPIO_InitStructure );
Once configuration is complete, the pin can be set or reset with the following code:
/* (4) */
GPIO_WriteBit (GPIOC , GPIO_Pin_9 , ( ledval ) ? Bit_SET : Bit_RESET ); ledval = 1 - ledval ;
The blinking light demo also utilizes a “timer tick” to measure the passage of time. While this timer tick utilizes interrupts, which we will not be discussing until Chapter 11, 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 in ticks/second) – here we configure it for 1 msec interrupts (the constantSystemCoreClockis defined in the firmware library to be the number of system clock cycles per second):
/* (3) */
if ( SysTick_Config ( SystemCoreClock / 1000)) while (1);
Every 1 msec, the timer triggers a call to the SysTick_Handler. For the blinking light demo, we simply decrement a shared counter – declared as
/* (5) */
static __IO uint32_t TimingDelay ;
void Delay( uint32_t nTime){ TimingDelay = nTime; while ( TimingDelay != 0); }
void SysTick_Handler (void){ if ( TimingDelay != 0x00)
TimingDelay --; }
This simple blinking lights program requires support from two library modules (stm32_gpio.c, stm32_rcc.c). To include these in the project, we have to slightly modify the Makefile provided with the demo project.
TEMPLATEROOT = ../../ stm32vl_template
# additional compilation flags
CFLAGS += -O0 -g ASFLAGS += -g
# project files
OBJS= $( STARTUP ) main.o
OBJS += stm32f10x_gpio .o stm32f10x_rcc .o
# include common make file
include $( TEMPLATEROOT )/ Makefile . common
In the remainder of this chapter we examine clock and pin configuration in greater detail.