Posted on Mon 16 December 2019
In Общие вопросы.
tags: embedded rust STM32 openocd gdb nucleo
Rust programming language is a great tool for embedded development, at least due to its memory ownership mechanism. I believe it's the future of the embedded world (well... not the present though :) ). To make the future a bit closer, I decided to use the Rust language for my next projects. To make it happen, it is necassery to create a template. Let's do it!
I am using Ubuntu 16.04 at the moment.
The evaluation board I use is NUCLEO-F030R8, and it's a bit harder to work with the hardware not described in the official manuals (but it's not a rocket science too).
My preferrable IDE for such kind of projects is Microsoft Visual Studio Code.
The sources I used are:
Let's prepare the environment.
First, we need Rust itself:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
After the installation process is done, we can check it:
rustc -V
We should see something like this:
rustc 1.38.0 (625451e37 2019-09-23)
Then (we know the type of the microcontroller we are going to use, but let this manual be as universal as possible) we have to install the toolchains:
rustup target add thumbv6m-none-eabi
rustup target add thumbv7m-none-eabi
rustup target add thumbv7em-none-eabi
rustup target add thumbv7em-none-eabih
For the Cortex-M0, M0+, and M1 (ARMv6-M architecture), Cortex-M3 (ARMv7-M architecture), Cortex-M4 and M7 without hardware floating point (ARMv7E-M architecture) and Cortex-M4F and M7F with hardware floating point (ARMv7E-M architecture) respectively. You can choose only one you are going to use.
Also we need cargo-binutils
cargo install cargo-binutils
and LLVM compiler
rustup component add llvm-tools-preview
and itmdump
cargo install itm
We will need gdb (for flashing firmware into the microcontroller and debugging), openocd (to connect to the microcontroller), screen to interact through an UART port (though official guide recommends minicom), and qemu as an emulator. Let's install it:
sudo apt install gdb-arm-none-eabi openocd screen qemu-system-arm
If you are using Ubuntu 18.04, you would need the following command:
sudo apt install gdb-multiarch openocd screen qemu-system-arm
And to make the life easier, I created the file /etc/udev/rules.d/70-st-link.rules
:
nano /etc/udev/rules.d/70-st-link.rules
with the following content:
# STM32F3DISCOVERY rev A/B - ST-LINK/V2
ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", TAG+="uaccess"
# STM32F3DISCOVERY rev C+ - ST-LINK/V2-1
ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", TAG+="uaccess"
and realoaded the rules
sudo udevadm control --reload-rules
so it would take effect.
Now it's time to check if the software sees the board. Connect the board, start the terminal and run
openocd -f interface/stlink-v2-1.cfg -f target/stm32f0x.cfg
It would work for the NUCLEO-F030R8 board, but there can be some differences: you can work with Blue Pill (STM32F103) board. As it is 100th serie, the command would be
openocd -f interface/stlink-v2-1.cfg -f target/stm32f1x.cfg
If you know the chip serie, you can choose the right configuration file taking
a look at /usr/share/openocd/scripts/target
.
It also may happen that the flash programmer you use may be different (if your
board is not a NUCLEO, or you use an external ST-LINK flash programmer).
The version of the programmer can be different, so it could be stlink-v2.cfg
(different configuration files lie in the /usr/share/openocd/scripts/interface
dir, check it for yours).
For example, if you are going to use Blue Pill with an old ST-LINK v2, the command would be
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
Anyway, if you have chosen the right parameters, you'll see something like that:
Open On-Chip Debugger 0.9.0 (2018-01-24-01:05)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v23 API v2 SWIM v9 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.273594
Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints
It worked!
Now it's time to go to the next part, which is
To have an opportunity to create a project from a template, we have to install a
crate called cargo-generate
:
cargo install cargo-generate
Get to the folder where you are going to store your projects, start the terminal and run the command:
cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart
During the process you will be asked to provide the project name. Choose the one
you like, and we will use the name app
here, just as in the official manual.
The project based on the https://github.com/rust-embedded/cortex-m-quickstart
repository would be generated, thanks to the cortex team.
Now you can run the vs code IDE, and open the newly generated folder app
.
In the IDE open the file config
in the .cargo
directory and uncomment the target
line that corresponds your microcontroller (I use target = "thumbv6m-none-eabi"
here as my NUCLEO board holds STM32F030R8T6 MCU, which belongs to the Cortex-M0
family).
Now we have to tune the project so it describes our MCU.
Go to the memory.x
file and change the lines
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 8K
the way they describe your microcontroller. The lines above describe STM32F030R8T6, and necessary information can be found at the corresponding datasheet, the RAM origin in the 2.2.2 Memory map and register boundary addresses topic, and FLASH origin in the 3.2.1 Flash memory organization topic.
And finally we can edit the file hello.rs
in the examples
folder,
commenting out the
debug::exit(debug::EXIT_SUCCESS);
line.
Now we have to
Open the terminal in the VS Code and run
cargo build --example hello
Yep, that simple!
You'll see:
Compiling semver-parser v0.7.0
Compiling typenum v1.11.2
Compiling proc-macro2 v1.0.6
Compiling unicode-xid v0.2.0
Compiling syn v1.0.11
Compiling stable_deref_trait v1.1.1
Compiling vcell v0.1.2
Compiling cortex-m v0.6.1
Compiling cortex-m-rt v0.6.11
Compiling cortex-m-semihosting v0.3.5
Compiling r0 v0.2.2
Compiling app v0.1.0 (/home/aleksandr/Projects/Rust/app)
Compiling panic-halt v0.2.0
Compiling semver v0.9.0
Compiling volatile-register v0.2.0
Compiling rustc_version v0.2.3
Compiling quote v1.0.2
Compiling bare-metal v0.2.5
Compiling generic-array v0.13.2
Compiling generic-array v0.12.3
Compiling as-slice v0.1.2
Compiling aligned v0.3.2
Compiling cortex-m-rt-macros v0.1.7
warning: unused import: `debug`
--> examples/hello.rs:9:28
|
9 | use cortex_m_semihosting::{debug, hprintln};
| ^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Finished dev [unoptimized + debuginfo] target(s) in 35.32s
Don't mention the warning, it's quite ok. If you want it to disappear, you can
remove the debug
element from the
use cortex_m_semihosting::{debug, hprintln};
line, so it would look like that
use cortex_m_semihosting::hprintln;
Let's flash it into the microcontroller and check if it works!
Let's adjsut some configuration files here.
Switch to the openocd.cfg
file in VS Code IDE and adjust the lines
source [find interface/stlink-v2-1.cfg]
source [find target/stm32f0x.cfg]
Do you remeber you started openocd? Change the strings in the openocd.cfg
accordingly (here are mines, for my NUCLEO board).
Now return to the .cargo/config
file, and uncomment the line
runner = "arm-none-eabi-gdb -q -x openocd.gdb"
Note that it's for my system, Ubuntu 16.04. If you use Ubuntu 18.04, you have to uncomment another one,
runner = "gdb-multiarch -q -x openocd.gdb"
That would allow you to flash and run your microcontroller with a single cargo run command.
In the build
section the line that corresponds your MCU has to be
uncommented too, for me (for my NUCLEO-F030) it's the
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+
line.
Now follow ~the white rabbit~ to the project dir (which is app
in our case),
run there two terminals, in the first one start the command
openocd
(it would use openocd.cfg
file automatically), and in the other terminal
window the following one
arm-none-eabi-gdb -q target/thumbv6m-none-eabi/debug/examples/hello
Remember, the arm-none-eabi-gdb
part is for Ubuntu 16.04 (and it would be
gdb-multiarch
for the 18.04 system), and the path target/thumbv6m-none-eabi/debug/examples/hello
depends on the microcontroller you use (for example, the path would be
target/thumbv7em-none-eabihf/debug/examples/hello
for the Cortex-M4 with FPU).
The line like
Reading symbols from target/thumbv6m-none-eabi/debug/examples/hello...done.
appears if the path is correct and now you can connect to the openocd by entering the command
target remote :3333
in the gdb terminal window. It tells the gdb to connect to the remote (not quite
in this case, but anyway, to speak with it through the network interface) openocd
instance (on the same computer, without IP address, on 3333 port, :3333
).
It will show
Remote debugging using :3333
0x00000000 in ?? ()
in the gdb window and
Info : accepting 'gdb' connection on tcp/3333
Info : device id = 0x20006440
Info : flash size = 64kbytes
in the openocd window.
By the way, you may see the
undefined debug reason 7 - target needs reset
message. In that case, enter the
monitor reset halt
command in the gdb window. You'll see the message
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x08001060 msp: 0x20002000
It's quite ok.
Now you can flash your newly created program into the microcontroller by printing
load
in the gdb window.
Now you have to see something like that:
Loading section .vector_table, size 0xc0 lma 0x8000000
Loading section .text, size 0x1414 lma 0x80000c0
Loading section .rodata, size 0x5c0 lma 0x80014e0
Start address 0x8001060, load size 6804
Transfer rate: 14 KB/sec, 2268 bytes/write.
in the gdb window.
Now enter the next command to enable the semihosting
monitor arm semihosting enable
and now (finally!) you can run the program by printing the command
continue
in the gdb window.
Immediately you'll see the message
Hello, world!
in the openocd window.
If you return to the gdb window and press Ctrl-C, you'll see
^C
Program received signal SIGINT, Interrupt.
0x08000156 in hello::__cortex_m_rt_main::h4f811533e300e6b9 ()
at examples/hello.rs:19
19 loop {}
message, which tells us that the program was interrupted during the performing the loop procedure at line 19.
Let's make the life a bit easier: we can use openocd.gdb file prepared by the cortex team for us (thanks again).
Do you remember the monitor reset halt
command we used earlier? Let's add it
to the openocd.gdb file so we do not need to enter it manually. Add
monitor halt reset
line to the openocd.gdb
file before load
command.
Open two different terminal windows in the project directory, and perform commands
openocd
and
arm-none-eabi-gdb -x openocd.gdb target/thumbv6m-none-eabi/debug/examples/hello
You'll see a lot of information in gdb window:
GNU gdb (7.10-1ubuntu3+9) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from target/thumbv6m-none-eabi/debug/examples/hello...done.
DefaultPreInit ()
at /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs:571
571 pub unsafe extern "C" fn DefaultPreInit() {}
Breakpoint 1 at 0x800109a: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs, line 562.
Breakpoint 2 at 0x80014c0: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs, line 552.
Breakpoint 3 at 0x8000ebc: file /home/aleksandr/.cargo/registry/src/github.com-1---Type <return> to continue, or q <return> to quit---
Press enter, and program will be loaded to the microcontroller.
You'll see:
ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs, line 32.
Breakpoint 4 at 0x8000130: file examples/hello.rs, line 11.
semihosting is enabled
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x08001060 msp: 0x20002000, semihosting
Loading section .vector_table, size 0xc0 lma 0x8000000
Loading section .text, size 0x1414 lma 0x80000c0
Loading section .rodata, size 0x5c0 lma 0x80014e0
Start address 0x8001060, load size 6804
Transfer rate: 14 KB/sec, 2268 bytes/write.
Note: automatically using hardware breakpoints for read-only addresses.
DefaultPreInit ()
at /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs:571
571 pub unsafe extern "C" fn DefaultPreInit() {}
Enter the command continue
in the gdb window twice and see the Hello, world!
message in the openocd window.
Pressing Ctrl-C in the gdb window will interrupt the program in the microcontroler, and you would be able to quit the gdb, if you want.
Hooray!
It all finally worked.
Close the gdb window by entering the quit
command, and interrupt the openocd
session by pressing Ctrl-C in its window.
Now you can close both terminals.
And finally, there' another option, the most easy one :)
Return back to .cargo/config
file, make sure the line
runner = "arm-none-eabi-gdb -x openocd.gdb"
is uncommented (runner = "gdb-multiarch -x openocd.gdb"
) for Ubuntu 18.04.
Make sure the line
monitor reset halt
is added to the openocd.gdb
file.
If everything is ok, you can load the program to the microcontroller and start debugging by entering command
openocd
in a terminal window in the project directory and command
cargo run --example hello
in the VS Code terminal.
You'll see something like that:
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `arm-none-eabi-gdb -q -x openocd.gdb target/thumbv6m-none-eabi/debug/examples/hello`
Reading symbols from target/thumbv6m-none-eabi/debug/examples/hello...done.
0x08000156 in hello::__cortex_m_rt_main::h4f811533e300e6b9 () at examples/hello.rs:19
19 loop {}
Breakpoint 1 at 0x800109a: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs, line 562.
Breakpoint 2 at 0x80014c0: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs, line 552.
Breakpoint 3 at 0x8000ebc: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs, line 32.
Breakpoint 4 at 0x8000130: file examples/hello.rs, line 11.
semihosting is enabled
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x08001060 msp: 0x20002000, semihosting
Loading section .vector_table, size 0xc0 lma 0x8000000
Loading section .text, size 0x1414 lma 0x80000c0
Loading section .rodata, size 0x5c0 lma 0x80014e0
Start address 0x8001060, load size 6804
Transfer rate: 14 KB/sec, 2268 bytes/write.
Note: automatically using hardware breakpoints for read-only addresses.
DefaultPreInit () at /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs:571
571 pub unsafe extern "C" fn DefaultPreInit() {}
(gdb)
Enter the command continue
twice, and you'll see message Hello, world!
in
the openocd window.
That's all for today :)
Stay tuned for the next part!