Rusty STM32

Introduction

So I’ve recently being have a play with building software in Rust for my Nucleo F7 dev board. As I didn’t really fancy writing USB drivers and wanted to have a play with ST’s STM32Cube drivers I thought the best course of action was to mix C and Rust together in one project.

Setup

For this project I decided that I would use the rust build system (Cargo) and link my C code via a static library. The Embedded rust book (https://docs.rust-embedded.org/book/) gives a good walkthrough on what is needed to build and link a project and get setup with something simple that can print ‘hello world’ via semihosting on the debugger and I did this before adding any of my C code.

As I’m using Visual Studio Code for development currently Cortex-Debug VS Code extension (https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug). This enables debugging using OpenOCD via ST-Link SWD board on the Nucleo development board. For this kind of project having a debugging capability makes life much easier.

Creating the C setup and drivers

The first thing that can make life a lot easier is to use the STMCubeMX program from ST to build an initial setup C code project that will initialise the clocks on the CPU so they are all running at the correct frequency. This utility has some templates for the Nucleo series boards that would also be a good starting point if you are using something else like the STM32F4 Black pill board or your own custom CPU.

I used this to build a initial project with a serial port and several integrated peripherals then renamed the main method to an init method (and made it return) that can be called from the main rust code.

I did find one gotcha in this in that it looks like the rust init routines do not clear the processors ram before running. This gave the symptom that the serial port would work when the ST-Link windows utility was run but not at any other time so it’s important to manually clear the gState vairable (and probably others) before you initialise the HAL drivers. I’m currently initialising each HAL driver from the C code but this could also be done in Rust (and in that case all initialisation has to be done explicitly).

Creating the Rust code

The Embedded Rust book (https://docs.rust-embedded.org/book/start/qemu.html) provides a good overview on how to setup the Rust side of things. I followed this and got a simple program to output ‘Hello World’ via Semihosting on the Debugger. One thing to note is that the debugger will hit a breakpoint when outputting each character if you don’t enable semihosting on OCD. Using hprintln is also recommended over itm as the later won’t work if you don’t have the SWO pin connected.

In order to link your C code to your rust code you will need to modify the build.rs I added the following lines to mine:

    println!("cargo:rustc-link-lib=static=<stm32-c-library>");
    println!("cargo:rustc-link-search=native=<src-dir>");

This will tell Cargo to link your C code with the rust code when building. For the above example I placed the C library .a file in the source root directory, though for less experminental projects it would be better to create a lib directory. You can then use an extern “C” directive to tell the rust compiler about your C functions. Here is a simple one below:

extern "C" {
pub fn c_main();
pub fn HAL_IncTick();
pub fn SystemInit();
}

As the Rust cortex libraries handle the Interrupt table it is very important that you define an interrupt handler for the SysTick interrupt. Not doing this will cause the board to get stuck in Rust’s DefaultHandler which will just loop forever. The SysTick interrupt is also very important for the STM32 Hal for tasks such as the delay function so the SysTick timer should at least have the following:

#[exception]
fn SysTick() {
    unsafe { HAL_IncTick();};
    //hprint!(".").unwrap();
}

My next steps

Now I have something that works I want to have a go at getting Futures working and changing over the STM32F4 black pill for the bulk of my coding. These STM32F4 boards are much cheaper and should have plenty of ram to run most the things I’m interested in.

Adding a custom USB socket to the STM32 NUCLEO-F746

I’m currently building a design on the STM32F7 which I want to later port to a F4-NUCLEO board and eventually my own design so I want to test the USB host without the supplied electronics.

In order to do this I built a small USB breakout adapter and tried directly connecting this to 5V (for power) and PA11/PA12 (for USB).  The breakout was made by simply soldering some header pins to a USB socket.

I then attached the USB socket to CN12 as shown below.

The complete pinout for CN12 can be found here https://developer.mbed.org/platforms/ST-Nucleo-F746ZG/.

You will need to connect the pins on the usb connector as shown below:

  1. VBUS (+5v)
  2. PA11
  3. PA12
  4. GND

GRE tunneling for fun and profit.

I recently subscribed to Netflix and being in the UK I found that they have loads more available in the US to watch.  To get around this in a way that would also allow me to stream programmes to my chromecast is actually quite complicated.  As I have small Linux box on my network to provide IPv6 via a tunnel I thought I would allow this to also allow access via a GRE tunnel to a VPS running in the US.

The first thing you need to do is setup the machine in the US so it can do NAT just like your home router can do with the following:
#!/bin/sh
echo 1 > /proc/sys/net/ipv4/ip_forward

iptables -F FORWARD

iptables -A FORWARD -m state –state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -s 192.168.0.0/16 -j ACCEPT
iptables -A FORWARD -j REJECT
iptables -t nat -A POSTROUTING -s 192.168.0.0/16 -o eth0 -j MASQUERADE
iptables -A INPUT -i tun+ -j ACCEPT
iptables -A FORWARD -i tun+ -j ACCEPT
iptables -A INPUT -i tap+ -j ACCEPT
iptables -A FORWARD -i tap+ -j ACCEPT

iptables -A INPUT -i us-gre -j ACCEPT
iptables -A FORWARD -i us-gre -j ACCEPT
iptables -A INPUT -i us-gre -j ACCEPT
iptables -A FORWARD -i us-gre -j ACCEPT

Your then need to setup a GRE tunnel between your VM running in another country and your home network.  One thing to be aware of is that GRE tunnels use a specific IP protocol number rather than TCP or UDP.  This means that your need to either activate an option which enables this or setup the machine doing the routing on your home network as the DMZ host.

I used the following to setup a GRE tunnel on the VPS which will forward all data to my home network over the tunnel (my home network has addresses in the 192.168.x.x range).


ip tunnel del us-gre
ip tunnel add us-gre mode gre remote local ttl 255

ip link set us-gre up
ip addr add 192.168.X.1/24 dev us-gre

echo add routes

ip route add 192.168.0.0/16 via 192.168.X.10 dev us-gre
#ip route add 192.168.0.0/16 dev us-gre

Once you have everything setup on the VPS VM then your need to do the same on your home network with the following:


# VPN hosts.
ip rule add from 192.168.0.x table vpn

# Add default routes for vpn table.
ip route add default via 10.9.0.1 dev tun0 table vpn

ip route flush cache

ip tunnel del us-gre
ip tunnel add us-gre mode gre remote local ttl 255

ip link set us-gre up
ip addr add 192.168.8.10/24 dev us-gre

echo add default route

ip route add 0.0.0.0/1 via 192.168.8.1 dev us-gre table vpn

You will also need to run the following to add a table so you can have different routing destinations for different hosts which route via this machine.

echo 200 vpn >> /etc/iproute2/rt_tables

In order to make a machine use the tunnel your need to adjust your DHCP settings so the machine you want uses the machine with the tunnel as it’s default route. Once this is done your use:

ip rule add from ip route flush cache

table vpn on the machine with the tunnel on it. This creates a rule which makes the requested machine use a different routing table to all other traffic.

Disaster recovery

So the last 24hrs has been spent mostly recovering from disasters. The electricity was switched off recently at my flat which managed to take down my little arm server. For some reason it didn’t come back up when the power was restored so had to be manually rebooted last night. This turned out to be an exercise in futility as for some reason icedove (debian version of thunderbird) wasn’t showing any mail folders on my mail server. I ended up having to delete the server resync everything and recreate my mail filters which wasn’t fun.
After this I’m kind of toying with the idea of building a backup power system for it. It should be relatively simple as I can just float charge a SLA battery and use switch mode regulators to provide 12V and 5V outputs for my NAS and home server. I can also use the unregulated 24V from the battery to supply power for my next version of my tube time displaying clock next to my bed.
Of course this wasn’t the only fun to be had as I also had a flat tyre on the Brompton coming into Kings X. I ended up sitting on the floor by the doors on the train with a dismantled back wheel of a bike. The back wheel on my bike is especially bad as to remove it you have to take off the chain tensioner and disconnect the hub gear cable (which then needs to be readjusted or you can’t change gears properly). I did better though then last time I had to change the tire and my tirelever combined with spanner thingy saved the day again as I only needed one tool and it’s a metal tire lever so doesn’t snap (though it is coated in plastic).