Hello, world!
HEADS UP The "solder bridge" SB10 (see back of the board) on the STM32F3DISCOVERY, which is required to use the ITM and the
iprint!
macros shown below, is not soldered by default (see page 21 of the User Manual). (To be more accurate: this actually depends on the board revision. If you have an old version of the board as the old User Manual said, the SB10 was soldered. Check your board to decide whether you need to fix it.)
TL;DR You have two options to fix this: Either solder the solder bridge SB10 or connect a female to female jumper wire between SWO and PB3 as shown in the picture below.
Just a little more of helpful magic before we start doing low level stuff.
Blinking an LED is like the "Hello, world" of the embedded world.
But in this section, we'll run a proper "Hello, world" program that prints stuff to your computer console.
Go to the 06-hello-world
directory. There's some starter code in it:
#![deny(unsafe_code)] #![no_main] #![no_std] #[allow(unused_imports)] use aux6::{entry, iprint, iprintln}; #[entry] fn main() -> ! { let mut itm = aux6::init(); iprintln!(&mut itm.stim[0], "Hello, world!"); loop {} }
The iprintln
macro will format messages and output them to the microcontroller's ITM. ITM stands
for Instrumentation Trace Macrocell and it's a communication protocol on top of SWD (Serial Wire
Debug) which can be used to send messages from the microcontroller to the debugging host. This
communication is only one way: the debugging host can't send data to the microcontroller.
OpenOCD, which is managing the debug session, can receive data sent through this ITM channel and redirect it to a file.
The ITM protocol works with frames (you can think of them as Ethernet frames). Each frame has a
header and a variable length payload. OpenOCD will receive these frames and write them directly to a
file without parsing them. So, if the microntroller sends the string "Hello, world!" using the
iprintln
macro, OpenOCD's output file won't exactly contain that string.
To retrieve the original string, OpenOCD's output file will have to be parsed. We'll use the
itmdump
program to perform the parsing as new data arrives.
You should have already installed the itmdump
program during the installation chapter.
In a new terminal, run this command inside the /tmp
directory, if you are using a *nix OS, or from
within the %TEMP%
directory, if you are running Windows. This should be the same directory from
where you are running OpenOCD.
NOTE It's very important that both
itmdump
andopenocd
are running from the same directory!
$ # itmdump terminal
$ # *nix
$ cd /tmp && touch itm.txt
$ # Windows
$ cd %TEMP% && type nul >> itm.txt
$ # both
$ itmdump -F -f itm.txt
This command will block as itmdump
is now watching the itm.txt
file. Leave this terminal open.
Make sure that the STM32F3DISCOVERY board is connected to your computer. Open another terminal
from /tmp
directory (on Windows %TEMP%
) to launch OpenOCD similar as described in chapter 3.
Alright. Now, let's build the starter code and flash it into the microcontroller.
We will now build and run the application, cargo run
. And step through it using next
.
Since openocd.gdb
contains the monitor
commands in openocd.gdb
OpenOCD will redirect
the ITM output to itm.txt and itmdump
will write it to its terminal window. Also, it setup
break points and stepped through the trampoline we are at the first executable
statement in fn main()
:
~/embedded-discovery/src/06-hello-world
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `arm-none-eabi-gdb -q -x ../openocd.gdb ~/embedded-discovery/target/thumbv7em-none-eabihf/debug/hello-world`
Reading symbols from ~/embedded-discovery/target/thumbv7em-none-eabihf/debug/hello-world...
hello_world::__cortex_m_rt_main () at ~/embedded-discovery/src/06-hello-world/src/main.rs:14
14 loop {}
Loading section .vector_table, size 0x194 lma 0x8000000
Loading section .text, size 0x2828 lma 0x8000194
Loading section .rodata, size 0x638 lma 0x80029bc
Start address 0x08000194, load size 12276
Transfer rate: 18 KB/sec, 4092 bytes/write.
Breakpoint 1 at 0x80001f0: file ~/embedded-discovery/src/06-hello-world/src/main.rs, line 8.
Note: automatically using hardware breakpoints for read-only addresses.
Breakpoint 2 at 0x800092a: file /home/wink/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs, line 570.
Breakpoint 3 at 0x80029a8: file /home/wink/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs, line 560.
Breakpoint 1, hello_world::__cortex_m_rt_main_trampoline () at ~/embedded-discovery/src/06-hello-world/src/main.rs:8
8 #[entry]
hello_world::__cortex_m_rt_main () at ~/embedded-discovery/src/06-hello-world/src/main.rs:10
10 let mut itm = aux6::init();
(gdb)
Now issue a next
command which will execute aux6::init()
and
stop at the next executable statement in main.rs
, which
positions us at line 12:
(gdb) next
12 iprintln!(&mut itm.stim[0], "Hello, world!");
Then issue another next
command which will execute
line 12, executing the iprintln
and stop at line 14:
(gdb) next
14 loop {}
Now since iprintln
has been executed the output on the itmdump
terminal window should be the Hello, world!
string:
$ itmdump -F -f itm.txt
(...)
Hello, world!
Awesome, right? Feel free to use iprintln
as a logging tool in the coming sections.
Next: That's not all! The iprint!
macros are not the only thing that uses the ITM. :-)