Rust para Embebidos?
Iddar Olivares
Posted on July 20, 2020
Durante los días pasados estuve leyendo sobre el tema para preparar este post, es mucha información y que lo mas seguro termine siendo toda una serie.
Quiero agradecer tanto al equipo de Electronic cats como al equipo de de nrf-rs que estuvieron ayudándome a resolver dudas sobre el hardware y el funcionamiento de las bibliotecas.
Material
Para la practica de hoy requerimos una placa con nrf52840
como lo es la Bast BLE
y un debuger, yo usare el Black magic Probe que vimos como programar en post anteriores.
Software
En tu sistema debes contar con el toolchain de ARM instalado y agregado al path, Rust para este post se usa la versión 1.45.0
para el proyecto final usare GNU Make para cargar el firmware.
Estructura de un proyecto.
En este ejemplo quiero platicar como iniciar un proyecto de Rust para embebidos desde cero. Por lo cual debo comentar varias partes previas a código de nuestro uC.
$ mkdir nfr-blik
$ cd nrf-blink
$ cargo init
Creamos la carpeta para nuestro proyecto y dentro ejecutamos cargo init
, cargo es un el manejador de paquetes de Rust y con el comando init
le indicamos que cree un proyecto nuevo. El resultado de este comando se debe ver algo así:
.
├── Cargo.toml
└── src
└── main.rs
Esta es la estructura base de cualquier proyecto en Rust:
-
src
: este directorio almacena todos el codigo de nuestro proyecto, si vemos incluye por defecto unHello world
en el archivomain.rs
-
Cargo.toml
: este archivo incluye la descripción de nuestro proyecto y las dependencias del mismo.
Dependencias
Para nuestro proyecto requerimos tres dependencias, las cuales nos facilitan el trabajo. Abre el archivo Cargo.toml
en tu editor favorito y agrega las dependencias justo debajo de[dependancies]
ver el ejemplo a continuación:
[dependencies]
nrf52840-hal = "0.11.0"
cortex-m-rt = "0.6.12"
panic-halt = "0.2.0"
cortex-m-rt
: esta el la biblioteca base para trabajar con ARM Cortex-M incluye las configuraciones base del sistema manejo de memoria, registro etc, que son comunes en toda la familia microcontroladores.panic-halt
: Siempre debemos indicar un handler en caso de que algo salga mal, este paquete nos ahorra este paso dotándonos de una función genérica para esto.nrf52840-hal
: esta es una capa de abstracción creada por el equipo denrf-rs
la cual tiene mapeados todos los registro del uC al igual de dotarnos de algunas funciones y utilerias que veremos mas adelante.
Target
Debemos indicar al compilador de Rust cual es el target para nuestro binario, en este caso la plataforma es ARM; para esto crearemos un carpeta .cargo
en la raiz de nuestro proyecto y dentro un archivo de nombre config
en el colocaremos las derectiva, aprovecharemos para agregar el soporte para arm con el comando rustup
.
$ mkdir .cargo # el nombre de la carpeta inicia con un punto
$ touch .cargo/confg # con este comando creamos el archivo
$ rustup target add thumbv7em-none-eabihf
Al archivo .cargo/confg
agregamos las siguientes lineas
[build]
target = "thumbv7em-none-eabihf"
Hora del código
El momento esperado llego veamos como podemos controlar nuestro uC. Colocare el código y luego explicare cada parte
#![no_std]
#![no_main]
extern crate panic_halt;
extern crate nrf52840_hal;
use cortex_m_rt::entry;
use nrf52840_hal::prelude::*;
use nrf52840_hal::pac::Peripherals;
use nrf52840_hal::gpio::*;
use nrf52840_hal::Timer;
#[entry]
fn main() -> ! {
let board = Peripherals::take().unwrap();
let p0 = p0::Parts::new(board.P0);
let mut led1 = p0.p0_24
.into_push_pull_output(Level::High);
let mut timer = Timer::new(board.TIMER0);
loop {
led1.set_low().unwrap();
timer.delay(1_000_000);
led1.set_high().unwrap();
timer.delay(1_000_000);
}
}
Como puedes ver son muy pocas lineas las que se requieren para esta tarea. Veamos cada una a detalle:
#![no_std]
y#![no_main]
la primera de ellas le dice al compilador que el programa debe compilarse sin la biblioteca entandar ya que no corre sobre un sistema operativo (no tenemos files ni sockets), la segunda que no tiene un punto de entrada definido.Luego tenemos la sección de dependencias que ya hablamos antes.
#[entry]
justo antes de la definición de nuestra funciónmain
vemos esta instrucción que lo que le indica al compilador es que esta sera principal de nuestro programa.fn main() -> ! {}
: si ya habías programado en rust antes seguro esto te llama la atención; el tipo de retorno!
indica que esta función nunca terminara, esto porque dentro tenemos un bucle infinito.Peripherals::take()
aquí creamos una instancia de nuestro uC esta es la forma en la que funcionan todos los Cortex-M en rust y desde aquí podemos interactuar e inicializar los periféricos.p0::Parts::new(board.P0)
aquí inicializamos el reloj de puerto 0, veremos mas sobre como funciona los relojes y los periféricos en ARM en aproximas entregas.p0.p0_24.into_push_pull_output(Level::High)
con figuramos el pin comopush_pull
(te recomiendo ver el esquema de los pines si tienes dudas sobre como funciona esto.) este nos regresa una instanciaPin
que usaremos para configurar el estado del mismo. En este ejemplo el pin que utilizamos es el24
del puerto0
Timer::new(board.TIMER0)
para poder generar un retraso para poder parpadear el led usaremos un instancia del Timer0
.loop {}
este es un bucle infinito equivalente a cuando usamos el metodo loop en un sketch de arduino.led1.set_low()
yled1.set_high()
son métodos utilitarios para cambiar el estado de los pines.timer.delay(1_000_000);
utilizando la instancia del Timer0
podemos ejecutar su métododelay
el cual genera un retardo.
Cargar el programa
Antes de grabar el programa debemos generar el binario para esto nos ayudamos de cargo
como vemos a continuación:
$ cargo build --release
Esto nos genera un archivo en binario en la ruta
target/thumbv7em-none-eabihf/release/nrf-blink
Este archivo lo usaremos para grabar nuestro micro. Tal como vimos en articulos anteriores conectamos nuestra placa al depurador y ejecutamos arm-none-eabi-gdb
$ arm-none-eabi-gdb
# Con esto entramos a la consola para debug
GNU gdb (GNU Arm Embedded Toolchain)
For help, type "help".
(gdb)
(gdb) target extended-remote /dev/ttyACM0
(gdb) monitor swdp_scan
> Target voltage: 2.9V
> Available Targets:
> No. Att Driver
> 1 Nordic nRF52 M4
> 2 Nordic nRF52 Access Port
(gdb) attach 1
(gdb) load target/thumbv7em-none-eabihf/release/nrf-blink
(gdb) compare-sections
(gdb) detach
# ctrl + d para salir
Listo tenemos nuestro Blink compilado y cargado en nuestro ARM felicidades.
En los próximos artículos hablaremos sobre los puertos seriales y otros periféricos.
Posted on July 20, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.