Rust - stm32 mikrovezérlőn éles bevetésben

Eddig jónéhány apróbb toolt, célfeladatot ellátó démont írtam Linux fölé Rust-ban, főleg SBC-kre (Beaglebone, Odroid, Raspberry).
Az élet úgy hozta, hogy lett egy mikrovezérlős melóm, ahol választhattam STM32 mikrovezérlőt.

Kedvem szottyant a Rust-hoz, megnézni hogy amit pár éve implementáltam Rust-ban "Hello World"-öt (értsd: LED villogtatást), szóval azon túllépve mennyire nehéz boldogulni vele.

C után furcsa, viszont némi guglizás után gyorsan összetákoltam a feladatot. Picit olyan érzésem van, mint a natív C-beli mikrovezérlő programozásból az Arduino LIB-re váltás esetén, amikor a sok-sok általános funkció (pl. ADC kezelés, I2C kezelés, ...) mind ott van készen a keretrendszerben, csak felparaméterezve meg kell hívni és működik.

Feladat egyébként nem bonyolult, ezért is gondoltam hogy itt az ideje kipróbálnom a Rust-ot mikrovezérlőn is:
   - UART
   - 2 ADC
   - GPIO lábak kezelése
   - időzítés
   - némi logika
Viszont tényleg szépen összepattintható a stable fordítóval és unsafe nélkül.

Hozzászólások

Na, erdekes a dolog! C-n (meg asm-en) kivul meg nem tettem ilyesmi MCU-kra programot. Irhatnal kicsit reszletesebben is, ilyesmikrol, hogy:

  •  Mi maga a toolchain? A szokasos C-s valtozatbol kiindulva a *.rs => *.o => *.elf => *.hex, majd...ooo... profit?
  •  Ezen belul hol mondjuk meg hogy mi az architektura? Pl. a rustc honnan tudja hogy (mondjuk) armv6-m-re kell forditatni? 
  •  Szinten ezen belul, hol mondjuk meg hogy mi a linker script, hogy osszuk fel a memoriat, ilyesmi?
  •  Mi ez a ``keretrendszer'', amiben mar minden (is) keszen van? Ez egy HAL (hardware abstraction layer)-re epulo rust library? 

Felrakod userként a $HOME-ba a Rust fordítót: https://www.rust-lang.org/tools/install
... kiszáll-beszáll ... a .profile-ban a PATH módosul (de ezt kézzel is bedobhatod)
Ez eddig a normál Rust fordító (stable), amivel a host rendszerre fordítanál.

Adjuk hozzá az ARM Cortex M3-hoz szükséges kiegészítést:
$ rustup target add thumbv7m-none-eabi

Létrehozod az alábbi példa projektet:
$ cargo new projekted
Ebben a "projekted" mappában (ahol a Cargo.toml is van) alá létrehozol egy .cargo mappát, abba egy config fájlt. Itt mondod meg hogy nem a host rendszerre fogsz fordítani:

[build]
target = "thumbv7m-none-eabi"

rustflags = [
  "-C", "link-arg=-Tlink.x",
]

Egyúttal az utóbbi link-arg részben írt link.x fogja a linkelésnél követelni a projekt gyökerében a memory.x állományt, amit kérdeztél. Ez minimum ennyi:

MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

Cargo.toml [dependencies] részébe pár csomag (alább) és forráskód innentől
$ cargo build --release  # és lefordul

$ sudo apt install binutils-arm-none-eabi  # bin előállításához
$ arm-none-eabi-objcopy -O binary target/thumbv7m-none-eabi/release/projekted   projekted.bin  # csak erre kell

$ sudo apt install stm32flash                           # USB-TTL átalakítón vagy Raspberry UART-ján keresztül áttölti a "projekted.bin" kódot.
$ stm32flash -w  projekted.bin /dev/ttyUSB0   # tartalmaznak az STM32 mikrovezérlők egy kinyírhatatlan UART-os bootloadert, ehhez a BOOT0 lábat kell felhúzni + reset és ekkor ez a bootloader rész fut.

A stm32f1xx-hal csomagban van az igazi varázslat.

use stm32f1xx_hal::{adc, pac, prelude::*, serial, timer};

Ami még fontos: minimál forráskód (main.rs), amely bár még nem csinál semmit, de az a minimum ami már lefordul.
Látszik, hogy a no_std és no_main miatt erősen eltér attól, amit például Linux alatt lazán "fn main() { ... }" -ként megszoktunk.

#![no_std]
#![no_main]

extern crate panic_halt; // ez kell!
use cortex_m_rt::entry;

use stm32f1xx_hal;

#[entry]
fn main() -> ! {
    // ...
    loop {
        // ...
    }
}

A Cargo.toml-ben is van furcsaság. Itt adod meg azt is, hogy melyik mikrovezérlő legyen.

[dependencies]
panic-halt = "0.2"
cortex-m-rt = "0.6"
stm32f1xx-hal = {version = "0.7", features = ["stm32f103", "rt"]}

Először én is rustc + Makefile módon kezdtem az ismerkedést. Aztán rá kellett jönnöm, hogy rustc -t közvetlenül ritkán fogsz használni. Kb. akkor, amikor csak

use std::akármi;

van használatban és semmi fordításkor importált külső csomagod nincs.
A probléma vele, hogy ekkor a külső csomag menedzsmentet neked kell végigszkriptelned, amit a cargo automatikusan megcsinál. Miért nehezítenéd meg az életedet?

A cargo és a Cargo.toml txt fájl ugyanis sokkal több a make és "Makefile" képességénél.

Lásd még $ cargo --help

Tetszik! Visszatalálós hsz. :-)