DEV Community

loading...
Cover image for Rust para Embebidos?

Rust para Embebidos?

Iddar Olivares
software and hardware developer for more than 10 years, maker, juggler and travel
・5 min read

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 un Hello world en el archivo main.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 de nrf-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ón main 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 como push_pull (te recomiendo ver el esquema de los pines si tienes dudas sobre como funciona esto.) este nos regresa una instancia Pin que usaremos para configurar el estado del mismo. En este ejemplo el pin que utilizamos es el 24 del puerto 0

  • Timer::new(board.TIMER0) para poder generar un retraso para poder parpadear el led usaremos un instancia del Timer 0.

  • loop {} este es un bucle infinito equivalente a cuando usamos el metodo loop en un sketch de arduino.

  • led1.set_low() y led1.set_high() son métodos utilitarios para cambiar el estado de los pines.

  • timer.delay(1_000_000); utilizando la instancia del Timer 0 podemos ejecutar su método delay 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.

Discussion (0)