Posted on Tue 07 January 2020
In Общие вопросы.
tags: embedded rust STM32 openocd gdb nucleo
It is the second part of the quick start tutorial for embedded Rust first part here.
Today we are going to blink a led (who would imagine :) ).
Actually, there are at least 3 ways to blink a led in embedded Rust:
We won't use the first approach because... why we need Rust then?
The HAL isn't implemented yet for all STM32 line-up, but for the start it's a good option. You can check which families the approach can be used with here (look for HAL repositories).
The sources I am going to use this time are:
Using HAL approach allows us to call simple functions to do things we need.
To use that functions we have to tell the building system (i.e. cargo) to use corresponding crate.
We have to insert
stm32f0xx-hal = {version = "0.14.1", features = ["stm32f030x8"]}
command into the Cargo.toml file into the [dependencies] section, as crate description says :)
Do not forget to check which feature you use, cause stm32f030x8 is appropriate for the NUCLEO board I use, but it may be inappropriate for your board.
Actually, you don't have to change the cortex-m and cortex-m-rt dependencies versions. I use
cortex-m = "0.6.0"
cortex-m-rt = "0.6.10"
versios, and it works just great.
Now we can add the code to the main program. Go to the main.rs file, and add the line
use stm32f0xx_hal::{prelude::*, stm32, delay::Delay};
in the beginning, where the other use commands are. I know, I know, using delay is a bad practice, but we'll change it in future, and it's good enough for the start.
Also let's throw away the crate we won't use: cortex_m::asm. Just delete the line. Also we have to remove the asm asm::nop(); command in the main function.
The main program now has to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Now you can build the program to be sure we didn't brake anything.
Just perform the command
cargo build
in the VS Code terminal. You'll see something like this:
Compiling typenum v1.11.2
Compiling semver-parser v0.7.0
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 cortex-m v0.6.1
Compiling vcell v0.1.2
Compiling cortex-m-rt v0.6.11
Compiling stm32f0 v0.7.1
Compiling void v1.0.2
Compiling cortex-m-semihosting v0.3.5
Compiling nb v0.1.2
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 embedded-hal v0.2.3
Compiling rustc_version v0.2.3
Compiling quote v1.0.2
Compiling bare-metal v0.2.5
Compiling cast v0.2.3
Compiling generic-array v0.12.3
Compiling generic-array v0.13.2
Compiling as-slice v0.1.2
Compiling aligned v0.3.2
Compiling cortex-m-rt-macros v0.1.7
Compiling stm32f0xx-hal v0.14.1
warning: unused imports: `delay::Delay`, `prelude::*`, `stm32`
--> src/main.rs:11:21
|
11 | use stm32f0xx_hal::{prelude::*, stm32, delay::Delay};
| ^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
Finished dev [unoptimized + debuginfo] target(s) in 59.25s
The warning is quite ok at the moment cause we didn't use any of the elements the crate provides. We'll fix it soon :)
First of all we need to know which pin the led is connected to.
The documentation says that LD2, the user green led, is connected to PA5 port on NUCLEO-F030R8 board, and that the high level turns the led on. Great.
The STM32F has a quite complicated internal structure, and you cannot just set the high logical level for the pin, it just wouldn't work. First of all, you need to turn on the clock, both for the MCU itself and for the peripheral block the pin is connected to. Lucky us, the HAL library would do it for us.
Ok, now to the program.
First, let's get the access to the MCU hardware.
Insert the following lines into the main function:
let cm_peripherals = cortex_m::peripheral::Peripherals::take().unwrap();
let mut f030_peripherals = stm32::Peripherals::take().unwrap();
Here we create the objects to access the hardware. It's two different variables because the first one, cm_peripherals provides access to the common hardware of Cortex-M MCU, while f030_peripherals gives access to the specific hardware of the STM32F030 MCU.
A bit of additional information: the take function aim is quite obvious, but the unwrap function does an important task too. In fact, the take function returns a wrapper around the result of the operation, which can tell if the operation was successful or not. The unwrap function extracts the result of the take function, and panics if it is impossible. What happens if the panic begins is defined by external crate panic which you can choose in the beginning of the program.
Now, let's rock! :)
First, we need to start the clock. Insert the new portion of a code:
let mut rcc = f030_peripherals
.RCC
.configure()
.sysclk(48.mhz())
.pclk(24.mhz())
.freeze(&mut f030_peripherals.FLASH);
The clocking structure is specific for different MCUs, so we use the f030_peripherals variable.
The freeze function freezes the RCC configuration. The rest of the functions are obvious: configure, set the main clock to 48 MHz, and pclk clock to 24 Mhz.
Well, we are going to use delay function, because the algorithm is simple:
It's not a good way to do what we are going to do, and we will do it the right way... but a bit later, let's get things done in the wrong way :)
So we need the delay function, let's create it:
let mut delay = Delay::new(cm_peripherals.SYST, &rcc);
We know that our led is connected to the PA5 pin, so we need access to the GPIOA pin bank:
let gpioa = f030_peripherals.GPIOA.split(&mut rcc);
And finally, we need led itself:
let mut led = cortex_m::interrupt::free(|cs| gpioa.pa5.into_push_pull_output(cs));
Well, the gpioa.pa5.into_push_pull_output part is self-explaining, I believe, but what about cortex_m::interrupt::free part?
The MCU library provides very useful mechanism: we can do some part of a code with interrupts turned off. This part of the code does exactly what we expect - turns the interrupts off, sets the push-pull mode for the PA5 pin, and then turns the interrupts back on.
Phew!
Let's get to the last part: the blinking.
Insert the following code into the loop:
led.toggle();
delay.delay_ms(1_000_u16);
The algorithm is obvious: toggle the led's state and wait for one second.
The whole code now looks like
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 |
|
Now you can check if it compiles:
cargo build
After compilation you get warning:
warning: use of deprecated item 'embedded_hal::digital::v1::ToggleableOutputPin::toggle': Deprecated because the methods cannot return errors. Users should use the traits in digital::v2.
--> src/main.rs:32:13
|
32 | led.toggle();
| ^^^^^^
|
= note: `#[warn(deprecated)]` on by default
Finished dev [unoptimized + debuginfo] target(s) in 0.33s
The warning is simple, but we just skip it and flash the firmware into the board.
Connect the board, start the terminal and proceed to the project directory, and start the openocd there:
openocd
As the openocd.cfg file is present in the project directory, the program connects to the board
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.269266
Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints
Now switch back to VS Code, and enter the command cargo run in the VS Code terminal. It will build the firmware (if you didn't do it before), burn it into the MCU, and start the debugging process.
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `arm-none-eabi-gdb -q -x openocd.gdb target/thumbv6m-none-eabi/debug/app`
Reading symbols from target/thumbv6m-none-eabi/debug/app...done.
0x00000000 in ?? ()
Breakpoint 1 at 0x8002a4e: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs, line 562.
Breakpoint 2 at 0x8002b48: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.11/src/lib.rs, line 552.
Breakpoint 3 at 0x80027f4: file /home/aleksandr/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs, line 32.
Breakpoint 4 at 0x8000272: file src/main.rs, line 13.
semihosting is enabled
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xc1000000 pc: 0x08002a14 msp: 0x20002000, semihosting
Loading section .vector_table, size 0xc0 lma 0x8000000
Loading section .text, size 0x2a9c lma 0x80000c0
Loading section .rodata, size 0xba4 lma 0x8002b60
Start address 0x8002a14, load size 14080
Transfer rate: 16 KB/sec, 4693 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 continue command twice, and you'll see the blinking green led.
Hooray! We did it!
Now you can interrupt the debugging process by pressing Ctrl-C, and then enter the quit command, and being asked, answer y.
Now you can detach the board from the usb port and reconnect it again.
You'll see the blinking led, cause the firmware starts automatically as the power is on.
The next time we will implement blinking the right way, and will do something more useful, like I2C interacting :)
Stay tuned!
Posted on Mon 16 December 2019
In Общие вопросы.
tags: embedded rust STM32 openocd gdb nucleo
The first part of an embedded Rust for NUCLEO tutorial
Read More