原文来自:http://pandafruits.com/stm32_primer/stm32_primer_minimal.php
In this section I'll show how to get the STM32F103RB running with a minimal "hello world" example - a flashing LED. This example involves the following five files:
- an application source file - "main.c" file
- a liker script - "stm32_minimal.ld"
- a makefile - "Makefile"
- an OpenOcd configratrion file - "openocd.cfg"
- a small bash script - "write_bin.sh"
Technically, only the first two files are needed for a true "minimal" example; but the other three files greatly simplify the process of building and programming. First we look at the C code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
/******************************* * stm32 minimal example main.c *******************************/ /* memory and peripheral start addresses */ #define FLASH_BASE 0x08000000 #define SRAM_BASE 0x20000000 #define PERIPH_BASE 0x40000000 /* work out end of RAM address as initial stack pointer */ #define SRAM_SIZE 20*1024 // STM32F103RB has 20 Kbye of RAM #define SRAM_END (SRAM_BASE + SRAM_SIZE) /* LED connected to PIN 8 of GPIOA */ #define LED_PIN 8 #define OUTPUT_MODE (0x10|0x03) // output mode: push-pull + 50MHz /* RCC peripheral addresses applicable to GPIOA */ #define RCC_BASE (PERIPH_BASE + 0x21000) #define RCC_APB2ENR (*(volatile unsigned long*)(RCC_BASE + 0x18)) /* GPIOA peripheral addresses */ #define GPIOA_BASE (PERIPH_BASE + 0x10800) #define GPIOA_CRL (*(volatile unsigned long*)(GPIOA_BASE + 0x00)) #define GPIOA_CRH (*(volatile unsigned long*)(GPIOA_BASE + 0x04)) #define GPIOA_BSRR (*(volatile unsigned long*)(GPIOA_BASE + 0x10)) #define GPIOA_BRR (*(volatile unsigned long*)(GPIOA_BASE + 0x14)) /* user functions */ int main( void ); void delay(unsigned long count); /* vector table */ unsigned long *vector_table[] __attribute__((section( ".vector_table" ))) = { (unsigned long *)SRAM_END, // initial stack pointer (unsigned long *)main // main as Reset_Handler }; int main() { /* enable clock on GPIOA peripheral */ RCC_APB2ENR = 0x04; /* set LED pin output mode */ //GPIOA_CRL |= OUTPUT_MODE << ((LED_PIN) << 2); // if pins 0 to 7 GPIOA_CRH |= OUTPUT_MODE << ((LED_PIN-8) << 2); // if pins 8 to 15 while (1) { GPIOA_BSRR = 1<<LED_PIN; // set LED pin high delay(200000); GPIOA_BRR = 1<<LED_PIN; // set LED pin low delay(200000); } } void delay(unsigned long count) { while (count--); } |
The code is pretty much self-explanatory. Most of it are actually macros difining memory and peripheral addresses. ST supplies a library with all these done for us in header files, we'll use that in the next section in a moment. One important thing to note, though, is the vector table. The first two entries must be the top-of-stack address and the address of the reset handler. Next we look at the linker script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/***************************************** * stm32 minimal example stm32_minimal.ld *****************************************/ /* memory layout for an STM32F103RB */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K SRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K } /* output sections */ SECTIONS { /* program code into FLASH */ .text : { *(.vector_table) /* Vector table */ *(.text) /* Program code */ } >FLASH /* uninitialized global and static variables (which we don't have any in this example) into SRAM */ .data : { *(.data) } >SRAM } |
Again, here we see that the vector table needs to be place before any application code in Flash. Also note that, because there is no global or static variables in the application code, the '.data' section is actually not needed. Now we take a look at the Makefile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
################################## # stm32 minimal example Makefile ################################## CC = arm-none-eabi-gcc LD = arm-none-eabi-ld CP = arm-none-eabi-objcopy LKR_SCRIPT = stm32_minimal.ld CFLAGS = -c -fno-common -O0 -g -mcpu=cortex-m3 -mthumb LFLAGS = -nostartfiles -T $(LKR_SCRIPT) CPFLAGS = -Obinary all: main.bin main.o: main.c $(CC) $(CFLAGS) -o main.o main.c main.elf: main.o $(LD) $(LFLAGS) -o main.elf main.o main.bin: main.elf $(CP) $(CPFLAGS) main.elf main.bin clean : rm -rf *.o *.elf *.bin write: ./write_bin.sh openocd.cfg main.elf |
Noticed the command used to invoke the compiler - 'arm-none-eabi-gcc', the linker - 'arm-none-eabi-ld' and the object copy binary utility - 'arm-none-eabi-objcopy'? We installed these in the previous section. The last line './write_bin.sh openocd.cfg main.elf' runs a bash script that takes two arguments - an OpenOcd configuration file and a binary image to be programmed into the micro. We have seen the contents of 'openocd.cfg' in the previous section:
1
2
3
4
5
6
7
|
############################################## # stm32 minimal example openocd.cfg # config file for J-link used with stm32f1x ############################################## source [ find interface /jlink .cfg] source [ find target /stm32f1x .cfg] |
And the bash script has the following contents:
1
2
3
4
5
6
7
8
9
10
11
|
#!/bin/bash ################################################################### # stm32 minimal example write_bin.sh # openocd commands to program the micro, invoked in the Makefile ################################################################### OPENOCD_CFG=$1 BIN_IMAGE=$2 killall -s 9 openocd openocd -f ${OPENOCD_CFG} -c init -c "reset halt" -c "flash write_image erase ${BIN_IMAGE}" -c "verify_image ${BIN_IMAGE}" -c reset -c shutdown |
All it does is first kill any OpenOcd process already running; then start a new one to read the config file sent as the first argument, find and write the binary image as specified in the second argument into the micro with multiple commands. Like what's in the Makefile, we could've typed all those commands one by one in the shell but that would be much more time-consuming.
Now let's make the project:
me@pandafruits:~$ make arm-none-eabi-gcc -c -fno-common -O0 -g -mcpu=cortex-m3 -mthumb -o main.o main.c arm-none-eabi-ld -nostartfiles -Tstm32_minimal.ld -o main.elf main.o arm-none-eabi-objcopy -Obinary main.elf main.bin me@pandafruits:~$
There should be three more files in the directory: 'main.o', 'main.elf' and 'main.bin'. Let's JTAG the micro:
me@pandafruits:~$ make write ./write_bin.sh openocd.cfg main.elf openocd: no process found Open On-Chip Debugger 0.5.0 (2011-12-03-08:57) Licensed under GNU GPL v2 For bug reports, read http://openocd.berlios.de/doc/doxygen/bugs.html Warn : Adapter driver 'jlink' did not declare which transports it allows; assuming legacy JTAG-only Info : only one transport option; autoselect 'jtag' 1000 kHz adapter_nsrst_delay: 100 jtag_ntrst_delay: 100 cortex_m3 reset_config sysresetreq Info : J-Link initialization started / target CPU reset initiated Info : J-Link ARM V8 compiled Jan 12 2012 20:43:19 Info : J-Link caps 0xb9ff7bbf Info : J-Link hw version 80000 Info : J-Link hw type J-Link Info : J-Link max mem block 9440 Info : J-Link configuration Info : USB-Address: 0x0 Info : Kickstart power on JTAG-pin 19: 0x0 Info : Vref = 3.313 TCK = 1 TDI = 0 TDO = 1 TMS = 0 SRST = 0 TRST = 0 Info : J-Link JTAG Interface ready Info : clock speed 1000 kHz Info : JTAG tap: stm32.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3) Info : JTAG tap: stm32.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1) Info : stm32.cpu: hardware has 6 breakpoints, 4 watchpoints Info : JTAG tap: stm32.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3) Info : JTAG tap: stm32.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1) target state: halted target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000008 msp: 0x20005000 auto erase enabled Info : device id = 0x20036410 Info : flash size = 128kbytes wrote 1024 bytes from file main.elf in 0.316950s (3.155 KiB/s) verified 148 bytes in 0.311988s (0.463 KiB/s) Info : JTAG tap: stm32.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3) Info : JTAG tap: stm32.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1) shutdown command invoked
The LED should be flashing now. Finally let's see if we could debug with GDB. First, do:
me@pandafruits:~$ openocd Open On-Chip Debugger 0.5.0 (2011-12-03-08:57) Licensed under GNU GPL v2 For bug reports, read http://openocd.berlios.de/doc/doxygen/bugs.html Warn : Adapter driver 'jlink' did not declare which transports it allows; assuming legacy JTAG-only Info : only one transport option; autoselect 'jtag' 1000 kHz adapter_nsrst_delay: 100 jtag_ntrst_delay: 100 cortex_m3 reset_config sysresetreq Info : J-Link initialization started / target CPU reset initiated Info : J-Link ARM V8 compiled Jan 12 2012 20:43:19 Info : J-Link caps 0xb9ff7bbf Info : J-Link hw version 80000 Info : J-Link hw type J-Link Info : J-Link max mem block 9440 Info : J-Link configuration Info : USB-Address: 0x0 Info : Kickstart power on JTAG-pin 19: 0x0 Info : Vref = 3.313 TCK = 1 TDI = 0 TDO = 1 TMS = 0 SRST = 0 TRST = 0 Info : J-Link JTAG Interface ready Info : clock speed 1000 kHz Info : JTAG tap: stm32.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3) Info : JTAG tap: stm32.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1) Info : stm32.cpu: hardware has 6 breakpoints, 4 watchpoints
With the OpenOcd server running, open a new terminal and connect to the server using telnet by doing:
me@pandafruits:~$ telnet local host 4444 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Open On-Chip Debugger >
Enter 'reset halt' to halt the target:
> reset halt JTAG tap: stm32.cpu tap/device found: 0x3ba00477 (mfg: 0x23b, part: 0xba00, ver: 0x3) JTAG tap: stm32.bs tap/device found: 0x16410041 (mfg: 0x020, part: 0x6410, ver: 0x1) target state: halted target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000008 msp: 0x20005000 >
Now, open a third terminal and do:
me@pandafruits:~$ arm-none-eabi-gdbtui --eval-command="target remote localhost:3333" main.elf
You should see something similar to this:
Let's try setting a couple of breakpoints so that we can turn the LED on and off from GDB. In my case the two lines in the code that turns the LED on and off are line 51 and line 53. So at the GDB command prompt I do:
(gdb) break main.c:51 Breakpoint 1 at 0x8000032: file main.c, line 51 (gbd) break main.c:53 Breakpoint 2 at 0x800004c: file main.c, line 53 (gbd)
Note the addresses of these breakpoints are pretty close to the beginning of the Flash, which is expected for such a small program. I can now step between the two breakpoints to turn the LED on and off by typing 'c' (for continue) in GDB:
(gdb) c Continuing. Note: automatically using hardware breakpoints for read-only addresses. Breakpoint 1, main () at main.c:51 (gdb) c Continuing. Breakpoint 2, main () at main.c:53 (gdb)
When done, type 'q' to quit GDB.
Great, in this section I've tried building, programming and debugging a STM32 micro with a minimal project. But there are some serious limitations. Among them are:
- The linker script is not really complete, it doesn't accommodate data such as initialized global variables.
- The stack size is not defined.
- The vector table is not fully populated, which makes it hard to use interrupts.
- All the peripheral register addresses were defined in 'main.c'.
In the next section I'll show a more complete linker script, use a startup file and bring the STM32 peripheral library into the picture.