Rust embedded.

Rust embedded. Stopwatch.
Rust embedded. Spi и embedded-graphics.
Rust embedded. Gpio.
Rust embedded. Сиквел.
Rust embedded.

Rust – это язык системного программирования, предназначенный для обеспечения безопасности, скорости и параллелизма. Rust имеет множество функций времени и безопасности во время компиляции, чтобы избежать сбоев данных и общих ошибок, все с минимальными издержками до нуля. Язык сфокусирован на безопасной работе с памятью, обеспечивает автоматическое управление памятью и предоставляет средства для достижения высокого параллелизма выполнения заданий, при этом обходясь без использования сборщика мусора и runtime. Автоматическое управление памятью в Rust избавляет разработчика от манипулирования указателями и защищает от проблем, возникающих из-за низкоуровневой работы с памятью, таких как обращение к области памяти после её освобождения, разыменование нулевых указателей, выход за границы буфера и т.п. Для распространения библиотек, обеспечения сборки и управления зависимостями проектом развивается пакетный менеджер Cargo, позволяющий получить нужные для программы библиотеки в один клик.

Rustup – установщик Rust и инструмент для управления версиями. Основным способом установки Rust, который используют разработчики, является Rustup – инструмент для установки и управления версиями Rust.  Для загрузки Rustup и установки Rust на Linux, запустите следующее в вашем терминале и следуйте инструкциям (у меня Linux).

curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh

Cargo: Менеджер пакетов и инструмент сборки для Rust. При установке через Rustup, вы получаете последнюю стабильную версию пакетного менеджера и средства сборки Rust, известного, как Cargo. Cargo делает многие вещи:

  • собирает ваш проект с cargo build
  • запускает ваш проект с cargo run
  • тестирует ваш проект с cargo test
  • собирает документацию для вашего проекта с cargo doc
  • публикует библиотеку на crates.io с cargo publish
    Чтобы удостовериться, что Rust и Cargo установлены, вы можете запустить в терминале следующую команду:
  • cargo –version

Cargo – это инструмент, который позволяет указывать необходимые зависимости для проектов на языке Rust и убедиться, что вы получите воспроизводимые сборки.

Для достижения этих целей, Cargo выполняет следующие действия:

  • Создает два файла с некоторой необходимой информацией о проекте.
  • Получает и собирает зависимости вашего проекта.
  • Запускает rustc или другие инструменты сборки со всеми необходимыми параметрами для правильной сборки вашего проекта.
  • Предоставляет все необходимые условия, чтобы работа с проектами на Rust стала проще.

Для тех у кого zsh, добавьте следующие строки в файл zshrc.

gedit ~/.zshrc 
export PATH="$HOME/.cargo/bin:$PATH"

Для программирования на Rust хороший редактор VS Code.

Определите платформу кросс-компиляции для необходимого аппратного обеспечения.

  • Использовать thumbv6m-none-eabi для ARM Cortex-M0 and Cortex-M0+
  • Использовать thumbv7m-none-eabi для ARM Cortex-M3
  • Использовать thumbv7em-none-eabi для ARM Cortex-M4 и Cortex-M7 (без FPU )
  • Использовать thumbv7em-none-eabihf для ARM Cortex-M4F и Cortex-M7F (с FPU )

Установите компонент rust-std для вашей аппаратной платформы

$ rustup target add thumbv7em-none-eabihf

Минимально достаточный проект для Cortex M7 (у меня devkit stm32f746g-disco)

Rust экосистема для embedded микроконтроллеров.

Cargo.toml

Синтаксис TOML основан на парах ключ = “значение”, [разделах] и # комментариях. Этот файл называется манифестом и содержит в себе все метаданные, которые необходимы Cargo, чтобы скомпилировать ваш проект.

[package]
name = "empty"
version = "0.1.0"
authors = ["Roman Shulenkov postmaster@mcu.by"]
edition = "2018"

[dependencies]
cortex-m = "0.6.2"
cortex-m-log="0.6.1"

[dependencies.stm32f7]
version = "0.10.0"
features = ["stm32f7x6", "rt"]

[dependencies.cortex-m-rt]
version = "0.6.12"
features = ["device"]

.cargo/config

В этом файле мы обозначаем цель компиляции (аппаратная платформа) , а так же флаги компиляции. Все конфигурационные файлы хранятся в TOML формате (как манифесты), в простом формате ключ-значение, которые хранятся внутри секций (таблиц), а потом будут объединены. Ключи для значений, которые указывают на определенную программу, могут быть в формате абсолютного пути, относительного, а также можно просто указать название программы. Абсолютные пути и название программ используются как есть. Относительные пути используются исходя из родительской директории, в которой расположена директория .cargo, в которой находится конфигурационный файл.

# Массив путей к локальным репозиториям, которые будут переопределены в качестве
# зависимостей. Для более подробной информации смотрите документ Specifying Dependencies.
paths = ["/path/to/override"]

[cargo-new]
# Настройки для имени/email, которые будут помещены в блок `authors` в новых Cargo.toml
# Если эти параметры не указаны, будут взяты параметры из конфигурации `git`. А, если и их нет
# запишутся `$USER` и `$EMAIL`.
name = "..."
email = "..."

# По умолчанию команда `cargo new` создан новый Git репозиторий. Это значение может быть
# изменено на `hg`, тогда будет создан Mercurial репозиторий, или `none`, чтобы отключить
# данный функционал.
vcs = "none"

# Для следующего раздела, $triple относится к любой возможной целевой платформой,
# не к строкову литералу "$triple", и будет применяться каждый раз, когда будет сборка
# для целевой платформы.
[target]
# Для сборок Cargo, для которых не указан параметр --target, будет использован компоновщик
# переданный в rustc (с помощью `-C linker=`). По умолчанию этот флаг не передан
# как параметр компилятора.
linker = ".."

[target.$triple]
# Этот раздел похож на раздел, который был описан выше, но тут указывается конкретная
# целевая платформа, которая будет скомпилирована.
linker = ".."
# пользовательские настройки будут переданы в компилятор, каждый раз когда будет $triple
# вызвана компиляция для целевой платформы.
# этот параметр переопределит build.rustflags, если он указан
rustflags = ["..", ".."]

# Настройки для реестра
[registry]
index = "..."   # Ссылка для индекса реестра (по умолчанию - центральный репозиторий)
token = "..."   # Ключ доступа (можно найти на сайте центрального репозитория)

[http]
proxy = "..."       # HTTP прокси. Используется для HTTP запросов (по умолчанию не указан)
timeout = 60000     # Таймаут для каждого HTTP запроса, в миллисекундах
cainfo = "cert.pem" # Путь до ключа Центра Сертификации (опционально)

[build]
jobs = 1                  # количество параллельно выполняемых заданий, по умолчанию - 
                          # количество ЦП
rustc = "rustc"           # компилятор rust
rustdoc = "rustdoc"       # инструмент генерации документации
target = "triple"         # build for the target triple
target-dir = "target"     # путь к директории, в которой будет скомпилированный проект
rustflags = ["..", ".."]  # настройки, которые будут переданы компилятору

[term]
verbose = false        # предоставлять ли cargo развернутый вывод
color = 'auto'         # предоставлять ли cargo цветной вывод

# Конфигурация сети
[net]
retry = 2 # сколько раз будет вызвана попытка повторной отправки сигнала

# Псевдонимы для команд Cargo. Первые 3 псевдонима встроены.
# Если вы хотите передать параметры в псевдоним, в которых есть пробелы, то используйте список.
[alias]
b = "build"
t = "test"
r = "run"
rr = "run --release"
space_example = ["run", "--release", "--", "\"command list\""]
[target.thumbv7em-none-eabihf]
rustflags = ["-C", "link-arg=-Tlink.x"]

[build]
target = "thumbv7em-none-eabihf"

memory.x

Пакет cortex-m-rt требует от нас создать файл “memory.x” в корневом каталоге проекта. В нём указывается распределение адресного пространства микроконтроллера:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 320K
}

/* This is where the call stack will be allocated. */
/* The stack is of the full descending type. */
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
_stack_start = ORIGIN(RAM) + LENGTH(RAM);

.vscode/tasks.json

VS Code содержит понятие “сборки проекта”. Редактор можно настроить таким образом, чтобы сборка Rust-проекта происходила необходимым нам способом.

Добавьте task (задание) в файл tasks.json в директории .vscode в корне вашего проекта:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Cargo build",
            "type": "shell",
            "command": "cargo",
            "args": ["build"],
            "problemMatcher": [
                "$rustc"
            ],
            "group": "build"
        },
        {
            "label": "Build binary",
            "type": "shell",
            "command": "arm-none-eabi-objcopy",
            "args": [
                "--output-target", "binary",
                "./target/thumbv7em-none-eabihf/debug/empty",
                "./target/thumbv7em-none-eabihf/debug/empty.bin"],
            "problemMatcher": [
                "$rustc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "dependsOn": "Cargo build"
        }

    ]
}

.vscode/launch.json

В каталоге проекта создаётся  папка .vscode, в которой создаётся файл с конфигурациями отладки launch.json.

{
    "version": "0.2.0",
    "cortex-debug.armToolchainPath": "/home/mcuby/Downloads/gcc-arm-none-eabi-8-2019-q3-update/bin",
    "configurations": [
        {            
            "name": "Debug empty",
            "request": "launch",
            "type": "cortex-debug",
            "cwd": "${workspaceRoot}",
            "executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/empty",
            "svdFile": "${workspaceFolder}/STM32F7x6.svd",
            "servertype": "openocd",
            "configFiles": ["STM32F7x6.cfg"],
            "preLaunchTask": "Build binary",         

          
            "preLaunchCommands": [
                "monitor init",
                "monitor reset init",
                "monitor halt",
                "monitor flash write_image erase ./target/thumbv7em-none-eabihf/build/empty.bin 0x08000000"
            ],
            "postLaunchCommands": ["monitor reset halt"] 
        }
    ]    
}

src/main.rs

Самая простая программа для микроконтроллера пустой цикл с nop.

#![no_main]
#![no_std]
extern crate cortex_m;
extern crate cortex_m_rt as runtime;
extern crate stm32f7;

use core::panic::PanicInfo;
use cortex_m::asm;

#[no_mangle]
fn main() -> ! {    
    loop {
        for _i in 0..100000 {
             asm::nop()
        }
    }
}

#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
    loop {}
}

С помощью cortex_m_log нам доступен механизм отладки. В пакете предусмотрены следующие пункты назначения для записи: Dummy – пункт назначения noop, который не выполняет запись. Полезный режим Itm – использует Cortex-M Itm для отправки вывода. Обратите внимание, что он доступен только на ARMv7-M и более новых версиях ядра Cortex,  Semihosting – использует Cortex-M Semihosting для отправки вывода.

#![no_main]
#![no_std]

extern crate cortex_m;
extern crate cortex_m_rt as runtime;
extern crate stm32f7;

use core::panic::PanicInfo;
use cortex_m::asm;

use cortex_m_log::{print, println, d_print, d_println};
use cortex_m_log::printer::Dummy;

#[no_mangle]
fn main() -> ! {   

    let mut log = Dummy::new();
    println!(log, "Some print with newline!");
    //Debug version of print that resolves into nothing in release mode
    //Note that you must import print macro for it to work
    d_print!(log, "Print stuff: {}", "stuff");
    //Note that you must import println macro for it to work
    d_println!(log, "Print stuff: {} and also newline", "stuff");

    loop {

        for _i in 0..1000000 {
             asm::nop()
        }
    }
}

#[panic_handler]
fn panic(_panic: &PanicInfo<'_>) -> ! {
    loop {}
}

Необходимые пакеты для VS Code (все кроме C/C++).

Исходники демо проекта на https://github.com/mcuby/rust-embedded-example