Skip to content

Instantly share code, notes, and snippets.

@adamgreig
Last active August 12, 2022 08:37
Show Gist options
  • Save adamgreig/b47fe9159e721e368601 to your computer and use it in GitHub Desktop.
Save adamgreig/b47fe9159e721e368601 to your computer and use it in GitHub Desktop.
Run embedded Rust code on your STM32F4

Embedded Rust on STM32F4

My notes from implementing Job Vranish's excellent guide.

Follow along with the guide above, getting rustc from rustup or similar:

rustc 1.0.0-nightly (dcaeb6aa2 2015-01-18 11:28:53 +0000)
binary: rustc
commit-hash: dcaeb6aa23ecba2dc2af870668a9239136d20fa3
commit-date: 2015-01-18 11:28:53 +0000
host: x86_64-unknown-linux-gnu
release: 1.0.0-nightly

(I have a nightly just around alpha2).

Then we want a target spec. The two linked examples work but for hard float support (which my C tends to get built with) we need to swap the llvm-target to thumbv7em-none-eabihf, as in thumbv7em-none-eabihf in this gist.

Create a folder to work in, grab Rust, build libcore:

$ mkdir embedded-rust
$ cd embedded-rust
$ git clone [email protected]:rust-lang/rust.git
$ cd rust
$ git checkout dcaeb6aa23ecba2dc2af870668a9239136d20fa3
$ cd ..
$ vi thumbv7em-none-eabihf # Get thumbv7-none-eabihf from this gist
$ mkdir libcore-thumbv7em-none-eabihf
$ rustc -C opt-level=2 -Z no-landing-pads --target thumbv7em-none-eabihf -g rust/src/libcore/lib.rs --out-dir libcore-thumbv7em-none-eabihf

Now create a new STM32 project. Here I'll use libopencm3.

$ mkdir example
$ cd example
$ git clone https://github.com/libopencm3/libopencm3.git
$ cd libopencm3
$ make -j8
$ cd ..

Finally we write some C code (with our main function, though main could just as happily go in the Rust code), a sample Rust file with a single function we'll call from the C, and a Makefile (from the libopencm3 project). The Makefile is modified slightly to support rust code compilation (see Makefile-notable). Then we build it and try it out!

$ vi example.c # From this gist
$ vi test_rust.rs # From this gist
$ vi Makefile # From this gist
$ cp ../thumbv7em-none-eabihf . # There's probably a better way to reference this
$ make
$ make debug

(gdb) break main
Breakpoint 1 at 0x80001ae: file example.c, line 9.
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/adam/Projects/embedded-rust/example/example.elf 
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, main () at example.c:9
9	    volatile uint32_t a = 5;
(gdb) s
10	    volatile uint32_t b = 6;
(gdb) 
11	    volatile uint32_t c = test_add(a, b); 
(gdb) 
test_rust::test_add (a=5, b=6) at test_rust.rs:36
36	    return a + b;
(gdb) 
37	}
(gdb) 
main () at example.c:12
12	    (void)c;
(gdb) p c
$1 = 11
#include <stdint.h>
// Defined in Rust
uint32_t test_add(uint32_t a, uint32_t b);
int main(void);
int main() {
volatile uint32_t a = 5;
volatile uint32_t b = 6;
volatile uint32_t c = test_add(a, b);
(void)c;
for(;;);
return 0;
}
MEMORY
{
rom (rx) : ORIGIN = 0x08000000, LENGTH = 1M
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
INCLUDE libopencm3_stm32f4.ld
BINARY = example
##
## This file is part of the libopencm3 project.
##
## Copyright (C) 2009 Uwe Hermann <[email protected]>
## Copyright (C) 2010 Piotr Esden-Tempski <[email protected]>
## Copyright (C) 2011 Fergus Noble <[email protected]>
##
## This library is free software: you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## This library is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this library. If not, see <http://www.gnu.org/licenses/>.
##
PREFIX ?= arm-none-eabi
CC = $(PREFIX)-gcc
LD = $(PREFIX)-gcc
OBJCOPY = $(PREFIX)-objcopy
OBJDUMP = $(PREFIX)-objdump
GDB = $(PREFIX)-gdb
FLASH = $(shell which st-flash)
TOOLCHAIN_DIR = ./libopencm3
CFLAGS += -Os -g \
-Wall -Wextra -Wimplicit-function-declaration \
-Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes \
-Wundef -Wshadow \
-I$(TOOLCHAIN_DIR)/include \
-fno-common -mcpu=cortex-m4 -mthumb \
-mfloat-abi=hard -mfpu=fpv4-sp-d16 -MD -DSTM32F4
LDSCRIPT ?= $(BINARY).ld
LDFLAGS += --static -lc -lnosys -L$(TOOLCHAIN_DIR)/lib \
-L$(TOOLCHAIN_DIR)/lib/stm32/f4 \
-T$(LDSCRIPT) -nostartfiles -Wl,--gc-sections \
-mthumb -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16
OBJS = $(sort \
$(patsubst %.c,%.o,$(wildcard *.c)) \
$(patsubst %.s,%.o,$(wildcard *.s)) \
$(patsubst %.rs,%.o,$(wildcard *.rs)))
BMP_PORT ?= /dev/ttyACM0
# Be silent per default, but 'make V=1' will show all compiler calls.
ifneq ($(V),1)
Q := @
NULL := 2>/dev/null
else
LDFLAGS += -Wl,--print-gc-sections
endif
RUSTC = rustc
RUSTCFLAGS = -C opt-level=2 -Z no-landing-pads --target thumbv7em-none-eabihf \
-g --emit obj -L ../libcore-thumbv7em-none-eabihf
.SUFFIXES: .elf .bin .hex .srec .list .images
.SECONDEXPANSION:
.SECONDARY:
all: images
images: $(BINARY).images test_rust
flash: $(BINARY).flash
%.images: %.bin %.hex %.srec %.list
@#printf "*** $* images generated ***\n"
%.bin: %.elf
@#printf " OBJCOPY $(*).bin\n"
$(Q)$(OBJCOPY) -Obinary $(*).elf $(*).bin
%.hex: %.elf
@#printf " OBJCOPY $(*).hex\n"
$(Q)$(OBJCOPY) -Oihex $(*).elf $(*).hex
%.srec: %.elf
@#printf " OBJCOPY $(*).srec\n"
$(Q)$(OBJCOPY) -Osrec $(*).elf $(*).srec
%.list: %.elf
@#printf " OBJDUMP $(*).list\n"
$(Q)$(OBJDUMP) -S $(*).elf > $(*).list
%.elf: $(OBJS) $(LDSCRIPT) $(TOOLCHAIN_DIR)/lib/libopencm3_stm32f4.a
@#printf " LD $(subst $(shell pwd)/,,$(@))\n"
$(Q)$(LD) -o $(*).elf $(OBJS) -lopencm3_stm32f4 $(LDFLAGS)
%.o: %.c Makefile
@#printf " CC $(subst $(shell pwd)/,,$(@))\n"
$(Q)$(CC) $(CFLAGS) -o $@ -c $<
%.o: %.rs Makefile
@#printf " RUSTC $(subst $(shell pwd)/,,$(@))\n"
$(Q)$(RUSTC) $(RUSTCFLAGS) -o $@ $<
clean:
$(Q)rm -f *.o
$(Q)rm -f *.d
$(Q)rm -f *.elf
$(Q)rm -f *.bin
$(Q)rm -f *.hex
$(Q)rm -f *.srec
$(Q)rm -f *.list
%.stlink-flash: %.bin
@printf " FLASH $<\n"
$(Q)$(FLASH) write $(*).bin 0x8000000
ifeq ($(BMP_PORT),)
ifeq ($(OOCD_SERIAL),)
%.flash: %.hex
@printf " FLASH $<\n"
@# IMPORTANT: Don't use "resume", only "reset" will work correctly!
$(Q)$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \
-f board/$(OOCD_BOARD).cfg \
-c "init" -c "reset init" \
-c "stm32x mass_erase 0" \
-c "flash write_image $(*).hex" \
-c "reset" \
-c "shutdown" $(NULL)
else
%.flash: %.hex
@printf " FLASH $<\n"
@# IMPORTANT: Don't use "resume", only "reset" will work correctly!
$(Q)$(OOCD) -f interface/$(OOCD_INTERFACE).cfg \
-f board/$(OOCD_BOARD).cfg \
-c "ft2232_serial $(OOCD_SERIAL)" \
-c "init" -c "reset init" \
-c "stm32x mass_erase 0" \
-c "flash write_image $(*).hex" \
-c "reset" \
-c "shutdown" $(NULL)
endif
else
%.flash: %.elf
@printf " GDB $(*).elf (flash)\n"
$(Q)$(GDB) --batch \
-ex 'target extended-remote $(BMP_PORT)' \
-x $(TOOLCHAIN_DIR)/scripts/black_magic_probe_flash.scr \
$(*).elf
endif
.PHONY: images clean
-include $(OBJS:.o=.d)
##############################################################################
# Black Magic Probe flashing via GDB
#
flash: $(BINARY).elf
arm-none-eabi-gdb --batch \
-ex 'target extended-remote /dev/ttyACM0' \
-ex 'monitor version' \
-ex 'monitor swdp_scan' \
-ex 'attach 1' \
-ex 'load' $(BINARY).elf
debug: $(BINARY).elf
arm-none-eabi-gdb -ex 'target extended-remote /dev/ttyACM0' \
-ex 'monitor swdp_scan' \
-ex 'attach 1' \
-ex "file $(BINARY).elf"
#
# End BMP flashing
##############################################################################
RUSTC = rustc
RUSTCFLAGS = -C opt-level=2 -Z no-landing-pads --target thumbv7em-none-eabihf \
-g --emit obj -L ../libcore-thumbv7em-none-eabihf
OBJS = $(sort \
$(patsubst %.c,%.o,$(wildcard *.c)) \
$(patsubst %.s,%.o,$(wildcard *.s)) \
$(patsubst %.rs,%.o,$(wildcard *.rs)))
%.o: %.rs Makefile
@#printf " RUSTC $(subst $(shell pwd)/,,$(@))\n"
$(Q)$(RUSTC) $(RUSTCFLAGS) -o $@ $<
// From http://spin.atomicobject.com/2015/02/20/rust-language-c-embedded/
#![no_std]
#![crate_type="staticlib"]
#![allow(unstable)]
// **************************************
// These are here just to make the linker happy
// These functions are just used for critical error handling so for now we just
// loop forever For more information see:
// https://github.com/rust-lang/rust/blob/master/src/doc/trpl/unsafe.md
#![feature(lang_items)]
extern crate core;
#[lang="stack_exhausted"] extern fn stack_exhausted() {}
#[lang="eh_personality"] extern fn eh_personality() {}
#[lang="panic_fmt"]
pub fn panic_fmt(_fmt: &core::fmt::Arguments, _file_line: &(&'static str, usize)) -> ! {
loop { }
}
#[no_mangle]
pub unsafe fn __aeabi_unwind_cpp_pr0() -> () {
loop {}
}
// **************************************
// **************************************
// And now we can write some Rust!
#[no_mangle]
pub fn test_add(a: u32, b: u32) -> u32 {
return a + b;
}
{
"arch": "arm",
"cpu": "cortex-m4",
"data-layout": "e-m:e-p:32:32-i1:8:32-i8:8:32-i16:16:32-i64:64-v128:64:128-a:0:32-n32-S64",
"disable-redzone": true,
"executables": true,
"llvm-target": "thumbv7em-none-eabihf",
"morestack": false,
"os": "none",
"relocation-model": "static",
"target-endian": "little",
"target-word-size": "32"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment