Hello world in morse code
Table of Contents
Introduction
I do love working with Rust. I deliberately create excuses to work on mini projects that ultimately involves coding in Rust.
Recently, I have bought a blue pill which is a little board that costs about 1$. Although, I have zero experience in embedded programming I always wanted to give it a try with Rust which, in my case, boils down to blinking a LED on the board.
I know there are excellent materials about embedded programming with Rust out there, but things do change very rapidly. Materials become outdated, tools change. So, this is basically my experience in 2019.
First thing I have done was to read the excellent The Embedded Rust Book. It helped me to get familiar with the concepts and tools which I believe are very well-known in the embedded development world but were quite new to me.
I never had to use GDB from the command line before, but at least I get to see what commands to use to load and step through the application.
OpenOCD (Open On-Chip-Debugger) used to sound quite scary at first judging by just the name but after reading the book, I had a better understanding of what it does and how it does it.
Unfortunately, the hardware examples of the book are for STM32 Discovery Kit which is quite different than the one I had. All I knew about the board I had was that it is not an STM32 Discovery and it contains an ARM Cortex-M3 processor which means I can cross compile for with Rust.
Get Started
I have cloned the cortex-m quick start repository. I had already configured and connected my blue pill to my computer by using an ST-Link cable.
As described in the Embedded Rust Book, I have updated the values in the memory.x
file by changing the FLASH base address value to 0x08000000
and updating the length values of FLASH and RAM to be 64K and 20K, respectively.
$ cat memory.x
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
And give it a go.
$ openocd
Open On-Chip Debugger 0.10.0
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>'.
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
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
Error: open failed
in procedure 'init'
in procedure 'ocd_bouncer'
Ok, obviously it couldn’t open a connection to the board. OpenOCD is using the openocd.cfg
file that comes with the quick start repository.
$ cat openocd.cfg
# Sample OpenOCD configuration for the STM32F3DISCOVERY development board
# Depending on the hardware revision you got you'll have to pick ONE of these
# interfaces. At any time only one interface should be commented out.
# Revision C (newer revision)
source [find interface/stlink-v2-1.cfg]
# Revision A and B (older revisions)
# source [find interface/stlink-v2.cfg]
source [find target/stm32f3x.cfg]
The first comment line clearly states that this configuration is for STM32F3DISCOVERY.
After a little bit of googling, I had enough evidence to believe that the blue pill I had was using an STM32F103 microprocessor.
So, I guess I just need to change the target/stm32f3x.cfg
to target/stm32f1x.cfg
. I have no idea about the ST-Link interface version so I will leave as it is for now.
Try again.
$ openocd
..snip...
Info : clock speed 950 kHz
Error: open failed
in procedure 'init'
Same, maybe my ST-Link interface is version 2
instead of 2.1
.
Change and try again.
$ openocd
...snip...
Info : STLINK v2 JTAG v31 API v2 SWIM v7 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.182058
Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477
in procedure 'init'
in procedure 'ocd_bouncer'
Whoa! That’s a different error which means progress in software development world!
Ok, so my ST-Link interface is definitely version 2. The error says ‘UNEXPECTED idcode’ so I guess target/stm32f1x.cfg
is not quite right.
I will keep trying. Maybe a stm32f2x
?
$ openocd
..snip...
Info : STLINK v2 JTAG v31 API v2 SWIM v7 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.188390
Info : stm32f2x.cpu: hardware has 6 breakpoints, 4 watchpoints
Wow! We are connected! I don’t why stm32f2
worked instead of stm32f1
. But, I am not going to complain and will jump to running the hello world example that comes with the quick start repository.
$ cargo build --example hello
..snip..
Finished dev [unoptimized + debuginfo] target(s) in 18.91s
$ arm-none-eabi-gdb -x openocd.gdb target/thumbv7m-none-eabi/debug/examples/hello
GNU gdb (GDB) 8.2.1
Copyright (C) 2018 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-pc-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/thumbv7m-none-eabi/debug/examples/hello...done.
openocd.gdb:1: Error in sourced command file:
Remote connection closed
Ok, it did start and lost the connection immediately. OpenOCD also printed the following to the terminal.
...snip...
Info : device id = 0x20036410
Warn : Cannot identify target as a STM32 family.
Error: auto_probe failed
Error: Connect failed. Consider setting up a gdb-attach event for the target to prepare target for GDB connect, or use 'gdb_memory_map disable'.
Error: attempted 'gdb' connection rejected
Interesting. The message says Cannot identify target as a STM32 family. I think the target configuration of openocd is still not correct.
After many trial and error I manage to get the following output with target/stm32f3x.cfg
.
$ arm-none-eabi-gdb -x openocd.gdb target/thumbv7m-none-eabi/debug/examples/hello
...snip...
Reading symbols from target/thumbv7m-none-eabi/debug/examples/hello...done.
main () at examples/hello.rs:13
13 hprintln!("Hello, world!").unwrap();
Breakpoint 1 at 0x8001464: file /home/idursun/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.8/src/lib.rs, line 540.
Breakpoint 2 at 0x800167e: file /home/idursun/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.8/src/lib.rs, line 530.
Breakpoint 3 at 0x80012da: file /home/idursun/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs, line 33.
Breakpoint 4 at 0x8000548: file examples/hello.rs, line 13.
semihosting is enabled
Loading section .vector_table, size 0x400 lma 0x8000000
Loading section .text, size 0x128e lma 0x8000400
Loading section .rodata, size 0x278 lma 0x8001690
Start address 0x800141e, load size 6406
Transfer rate: 35 KB/sec, 2135 bytes/write.
Note: automatically using hardware breakpoints for read-only addresses.
DefaultPreInit () at /home/idursun/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.8/src/lib.rs:546
546 pub unsafe extern "C" fn DefaultPreInit() {}
Wow! That’s something!
Keep executing the commands!
(gdb) next
Reset () at /home/idursun/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.8/src/lib.rs:488
488 r0::zero_bss(&mut __sbss, &mut __ebss);
(gdb) next
489 r0::init_data(&mut __sdata, &mut __edata, &__sidata);
(gdb) next
493 () => main(),
(gdb) next
Breakpoint 4, main () at examples/hello.rs:13
13 hprintln!("Hello, world!").unwrap();
Success! At this point, I managed to execute the hello world example app in the blue pill.
Make it blink
Next step is to make the LED blink. As I don’t know much about the low-level details of the board, I am going to modify one of the samples in stm32-rs/stm32f1xx-hal repository.
#![no_std]
#![no_main]
extern crate panic_halt;
use cortex_m_rt::entry;
use stm32f1xx_hal::{pac, prelude::*};
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
loop {
cortex_m::asm::delay(1 * 1000000);
led.set_high();
cortex_m::asm::delay(1 * 1000000);
led.set_low();
}
}
which successfully makes the LED blink every second.
To make things a little bit more interesting, I have changed the code to make it blink hello world in morse code.
I found an online tool to convert the text to the morse code and changed my program to use the code to drive the blinking.
#[entry]
fn main() -> ! {
let dp = pac::Peripherals::take().unwrap();
let mut rcc = dp.RCC.constrain();
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
let code = ".... . .-.. .-.. --- .-- --- .-. .-.. -.."; // hello world in morse code
loop {
for c in code.bytes() {
let (secs, do_blink) = match c {
b'.' => (1, true),
b'-' => (2, true),
_ => (2, false),
};
cortex_m::asm::delay(secs * 1000000);
if !do_blink {
continue;
}
led.set_high();
cortex_m::asm::delay(secs * 1000000);
led.set_low();
}
}
}
This is how it looks now:
{{< tweet 1120835944003846144 >}}
Conclusion
There are great deal of resources online out there which does a pretty decent job of explaining concepts to hobbyists like me. I hope I managed to show that what you can do with a couple of hours of effort.
I have already ordered LCD modules and looking forward to playing with embedded devices even more in the future.
Cheers!