- PVSM.RU - https://www.pvsm.ru -
Для того, чтобы подружить между собой указанные в заголовке технологии нам понадобятся:
Идея состоит в том, чтобы с скомпилировать написанную на Rust программу в библиотеку, которую можно будет слинковать с помощью тулчейна для ARM.
В итоге мы сможем даже вполне комфортно дебажить смешанный код на Rust и С.
Воспользуемся для этого утилитой STM32CubeMX. Для демо-проекта нам понадобится:
Проверим настройки тактирования. Тут при желании можем указать тактирование от внешнего кварца и его частоту.
Сгенерируем проект. Назовем его “HwApi”, т.к. этот слой кода у нас будет представлять собой абстракцию над железом, который мы будем использовать при написании кода на Rust. В качестве IDE выбираем SW4STM32.
Если Workbench установлен, то можем открыть сгенерированный проект и проверить, что он успешно компилируется.
Хоть System Workbench и основан на Eclipse, нам придется создать новый проект в свежей мажорной версии Eclipse (Neon), т.к. RustDT несовместим с той версией Eclipse.
Также нам понадобится шаблон проекта, который устанавливается вместе с GNU ARM Eclipse Plugin.
Для того, чтобы успешно слинковать либу, сгенерированную rust компилятором, нам понадобится заранее установленная свежая версия GNU ARM Embedded Toolchain.
Начинаем процесс переноса проекта из System Workbench в Eclipse CDT. В интернете можно найти скрипты, которые этот процесс автоматизируют, но я буду это делать вручную, т.к. собираюсь переиспользовать HwApiLib в других проектах, изменяя только написанную на Rust часть кода.
Копируем следующие папки/файлы в новый проект:
Если Workbench установлен, то разворачиваем два окна настроек проектов (из старого и нового Eclipse) так, чтобы было удобно копировать значения из одного окна в другое. Окна немного отличаются, поэтому при копировании ориентируемся на флаги, которые указаны в скобках.
Если Workbench не установлен, можно просто скопировать настройки со скриншотов, приложенных ниже.
Копируем Defined Symbols:
Пути к папкам, содержащие *.h файлы:
На вкладке “Optimization” можно включить оптимизацию Optimize size(-Os).
Далее указываем, что нам нужны все предупреждения компилятора:
Указываем путь к скрипту линкера + отмечаем чекбокс для удаления из результата линковки неиспользуемых в коде секций:
На следующей вкладке важно отметить чекбокс “Use newlib-nano” и вручную указать флаг -specs=nosys.specs
:
Указываем пути к папкам с файлами для компиляции:
Нажимаем Ок. После чего меняем расширение startup файла на заглавную .S, чтобы файл успешно подхватился компилятором. Проверяем, что проект компилируется.
Теперь нужно настроить дебаггер (Run — Debug Configurations — GDB OpenOCD Debugging). Создаем файл для OpenOCD с описанием железа, в котором будет запускаться программа (в моем случае файл называется STM32F103C8x_SWD.cfg):
source [find interface/stlink-v2.cfg]
set WORKAREASIZE 0x5000
transport select "hla_swd"
set CHIPNAME STM32F103C8Tx
source [find target/stm32f1x.cfg]
# use hardware reset, connect under reset
reset_config none
Если у вас используется другой микроконтроллер или другой способ подключения к нему, то корректный файл для OpenOCD можно сгенерировать в Workbench (с помощью Debugging options — Ac6).
В Config options указываем флаг -f и путь к созданному в предыдущем шаге файлу.
Жмем Debug. Проверяем, что дебаггер успешно залил код в микроконтроллер и началась отладка.
Пришло время создавать Rust проект.
Т.к. нам понадобятся инструкции компилятора, которые не поддерживаются в stable версии, нам нужно будет переключиться нам nightly версию компилятора, запустив в cmd следующие команды:
rustup update
rustup default nightly
Далее нужно получить текущую версию компилятора:
rustc -v --version
Затем склонировать себе исходники rust и переключится на коммит, который использовался для сборки этого компилятора (указан в commit-hash).
git clone git@github.com:rust-lang/rust.git
cd rust
git checkout cab4bff3de1a61472f3c2e7752ef54b87344d1c9
Следующим шагом скомпилируем необходимые нам библиотеки под ARM.
mkdir libs-arm
rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/libcore/lib.rs --out-dir libs-arm --emit obj,link
rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/liballoc/lib.rs --out-dir libs-arm -L libs-arm --emit obj,link
rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/libstd_unicode/lib.rs --out-dir libs-arm -L libs-arm --emit obj,link
rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/libcollections/lib.rs --out-dir libs-arm -L libs-arm --emit obj,link
В будущем, при каждом обновлении компилятора (rustup update) нужно будет переключаться на актуальную версию исходников и перекомпилировать библиотеки для ARM, иначе потеряется возможность дебажить код на rust.
Наконец-то можно приступить к создают Rust-проекта в eclipse.
Eclipse просит указать путь к компилятору, исходникам и утилитам для работы с rust-кодом.
Обычно эти компоненты можно найти в C:Users%username%.cargo.
Rust src — путь к папке src в исходниках, которые мы скачали ранее.
Теперь основной код:
lib.rs
#![feature(macro_reexport)]
#![feature(unboxed_closures)]
#![feature(lang_items, asm)]
#![no_std]
#![feature(alloc, collections)]
#![allow(dead_code)]
#![allow(non_snake_case)]
extern crate alloc;
pub mod runtime_support;
pub mod api;
#[macro_reexport(vec, format)]
pub extern crate collections;
use api::*;
#[no_mangle]
pub extern fn demo_main_loop() -> ! {
let usart2 = Stm32Usart::new(Stm32UsartDevice::Usart2);
loop {
let u2_byte = usart2.try_read_byte();
match u2_byte {
Some(v) => {
let c = v as char;
match c {
'r' => { toggle_led(Stm32Led::Red); }
'g' => { toggle_led(Stm32Led::Green); }
'b' => { toggle_led(Stm32Led::Blue); }
_ => { usart2.print("cmd not found"); }
}
}
_ => {}
}
delay(1);
}
}
api.rs — прослойка для интеграции между собой Rust и C кода
use collections::Vec;
extern {
fn stm32_delay(millis: u32);
fn usart2_send_string(str: *const u8, len: u16);
fn usart2_send_byte(byte: u8);
fn usart2_try_get_byte() -> i16;
fn stm32_toggle_led(led: u8);
fn stm32_enable_led(led: u8);
fn stm32_disable_led(led: u8);
}
pub fn delay(millis: u32) {
unsafe {
stm32_delay(millis);
}
}
#[derive(Copy, Clone)]
pub enum Stm32UsartDevice {
Usart2
}
#[derive(Copy, Clone)]
pub struct Stm32Usart {
device: Stm32UsartDevice
}
impl Stm32Usart {
pub fn new(device: Stm32UsartDevice) -> Stm32Usart {
Stm32Usart {
device: device
}
}
pub fn print(&self, str: &str) {
let bytes = str.bytes().collect::<Vec<u8>>();
self.print_bytes(bytes.as_slice());
}
pub fn print_bytes(&self, bytes: &[u8]) {
unsafe {
match self.device {
Stm32UsartDevice::Usart2 => usart2_send_string(bytes.as_ptr(), bytes.len() as u16)
}
}
}
pub fn println(&self, str: &str) {
self.print(str);
self.print("rn");
}
pub fn send_byte(&self, byte: u8) {
unsafe {
match self.device {
Stm32UsartDevice::Usart2 => usart2_send_byte(byte)
}
}
}
pub fn try_read_byte(&self) -> Option<u8> {
unsafe {
let r = usart2_try_get_byte();
if r == -1 { return None; }
return Some(r as u8);
}
}
}
pub enum Stm32Led {
Red,
Green,
Blue,
Orange
}
impl Stm32Led {
fn to_api(&self) -> u8 {
match *self {
Stm32Led::Green => 2,
Stm32Led::Blue => 3,
Stm32Led::Red => 1,
Stm32Led::Orange => 0
}
}
}
pub fn toggle_led(led: Stm32Led) {
unsafe {
stm32_toggle_led(led.to_api());
}
}
pub fn enable_led(led: Stm32Led) {
unsafe {
stm32_enable_led(led.to_api());
}
}
pub fn disable_led(led: Stm32Led) {
unsafe {
stm32_disable_led(led.to_api());
}
}
runtime_support.rs — для поддержки низкоуровневых функций Rust
extern crate core;
/// Call the debugger and halts execution.
#[no_mangle]
pub extern "C" fn abort() -> ! {
loop {}
}
#[cfg(not(test))]
#[inline(always)]
/// NOP instruction
pub fn nop() {
unsafe {
asm!("nop" :::: "volatile");
}
}
#[cfg(test)]
/// NOP instruction (mock)
pub fn nop() {}
#[cfg(not(test))]
#[inline(always)]
/// WFI instruction
pub fn wfi() {
unsafe {
asm!("wfi" :::: "volatile");
}
}
#[cfg(test)]
/// WFI instruction (mock)
pub fn wfi() {}
#[lang = "panic_fmt"]
fn panic_fmt(_: core::fmt::Arguments, _: &(&'static str, usize)) -> ! {
loop {}
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
// Memory allocator support, via C's stdlib
#[repr(u8)]
#[allow(non_camel_case_types)]
pub enum c_void {
__variant1,
__variant2,
}
extern "C" {
pub fn malloc(size: u32) -> *mut c_void;
pub fn realloc(p: *mut c_void, size: u32) -> *mut c_void;
pub fn free(p: *mut c_void);
}
#[no_mangle]
#[allow(unused_variables)]
pub unsafe extern "C" fn __rust_allocate(size: usize, align: usize) -> *mut u8 {
malloc(size as u32) as *mut u8
}
#[no_mangle]
#[allow(unused_variables)]
pub unsafe extern "C" fn __rust_deallocate(ptr: *mut u8, old_size: usize, align: usize) {
free(ptr as *mut c_void);
}
#[no_mangle]
#[allow(unused_variables)]
pub unsafe extern "C" fn __rust_reallocate(ptr: *mut u8,
old_size: usize,
size: usize,
align: usize)
-> *mut u8 {
realloc(ptr as *mut c_void, size as u32) as *mut u8
}
Также в корне проекта необходимо создать файл конфигурации целевой платформы grossws [8] подсказал, что теперь этот файл включен в компилятор и можно его не создавать.
thumbv7m-none-eabi.json
{
"arch": "arm",
"cpu": "cortex-m3",
"data-layout": "e-m:e-p:32:32-i1:8:32-i8:8:32-i16:16:32-i64:64-v128:64:128-a:0:32-n32-S64",
"disable-redzone": true,
"executables": true,
"llvm-target": "thumbv7m-none-eabi",
"morestack": false,
"os": "none",
"relocation-model": "static",
"target-endian": "little",
"target-pointer-width": "32"
}
Копируем в папку Rust проекта папку libs-arm содержащую скомпилированные для работы под ARM компоненты из стандартной библиотеки Rust.
Изменяем Debug target, так чтобы он запускал компиляцию с нужными нам параметрами
rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g --crate-type lib -L libs-arm src/lib.rs --emit obj,link
Компилируем Rust-проект. В результате в папке проекта появится файл lib.o.
Теперь в С-проекте создаем файлы api.h/api.c, в которых объявляем и реализуем функции, которые используются в api.rs.
api.h
#ifndef SERIAL_DEMO_API_H_
#define SERIAL_DEMO_API_H_
#include "stm32f1xx_hal.h"
void stm32_delay(uint32_t milli);
void usart2_send_string(uint8_t* str, uint16_t len);
void usart2_send_byte(uint8_t byte);
int16_t usart2_try_get_byte(void);
void stm32_toggle_led(uint8_t led);
void stm32_enable_led(uint8_t led);
void stm32_disable_led(uint8_t led);
#endif
api.c
#include "api.h"
#include "stm32f1xx_hal.h"
#include "stm32f1xx_hal_uart.h"
#include "main.h"
void stm32_delay(uint32_t milli) {
HAL_Delay(milli);
}
extern UART_HandleTypeDef huart2;
void usart2_send_string(uint8_t* str, uint16_t len) {
HAL_UART_Transmit(&huart2, str, len, 1000);
}
void usart2_send_byte(uint8_t byte) {
while (!(USART2->SR & UART_FLAG_TXE));
USART2->DR = (byte & 0xFF);
}
int16_t usart2_try_get_byte(void) {
volatile unsigned int vsr;
vsr = USART2->SR;
if (vsr & UART_FLAG_RXNE) {
USART2->SR &= ~(UART_FLAG_RXNE);
return (USART2->DR & 0x1FF);
}
return -1;
}
uint16_t stm32_led_to_pin(uint8_t led);
void stm32_toggle_led(uint8_t led) {
HAL_GPIO_TogglePin(LED_R_GPIO_Port, stm32_led_to_pin(led));
}
void stm32_enable_led(uint8_t led) {
HAL_GPIO_WritePin(LED_R_GPIO_Port, stm32_led_to_pin(led), GPIO_PIN_SET);
}
void stm32_disable_led(uint8_t led) {
HAL_GPIO_WritePin(LED_R_GPIO_Port, stm32_led_to_pin(led), GPIO_PIN_RESET);
}
uint16_t stm32_led_to_pin(uint8_t led) {
switch (led) {
case 1:
return LED_R_Pin;
case 2:
return LED_G_Pin;
case 3:
return LED_B_Pin;
default:
return LED_B_Pin;
}
}
Добавляем вызов demo_main_loop() внутри функции main.
main.c
...
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
demo_main_loop();
}
/* USER CODE END 3 */
...
Осталось всё слинковать. Для этого открываем свойства проекта на C и укажем линковщику где взять недостающие obj файлы.
Компилируем. Бинарник сильно прибавил в весе, но все еще умещается в STM32F103C8.
Запускаем Debug и видим, что Eclipse без проблем переходит из C-кода в Rust.
В завершении статьи хочу выразить благодарность авторам следующих постов, без них я бы не осилил этот процесс:
www.hashmismatch.net/pragmatic-bare-metal-rust [9]
spin.atomicobject.com/2015/02/20/rust-language-c-embedded [10]
github.com/japaric/rust-cross [11]
Статью писал с надеждой на то, что это послужит дополнительным шагом в появлении комьюнити разработчиков использующих Rust для программирования под микроконтроллеры, т.к. это действительно удобный и современный язык, несмотря на то, что у него довольно высокий порог вхождения.
Автор: build_your_web
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/stm32/250545
Ссылки в тексте:
[1] GNU ARM Embedded Toolchain: https://launchpad.net/gcc-arm-embedded
[2] System Workbench for STM32: http://www.st.com/en/development-tools/sw4stm32.html
[3] Свежий Eclipse CDT: https://eclipse.org/cdt/
[4] GNU ARM Eclipse Plugin: http://gnuarmeclipse.github.io/
[5] Rust: https://www.rust-lang.org/en-US/install.html
[6] RustDT: https://marketplace.eclipse.org/content/rustdt
[7] Racer, Rainicorn и rustfmt: https://github.com/RustDT/RustDT/blob/master/documentation/UserGuide.md
[8] grossws: https://habrahabr.ru/users/grossws/
[9] www.hashmismatch.net/pragmatic-bare-metal-rust: http://www.hashmismatch.net/pragmatic-bare-metal-rust/
[10] spin.atomicobject.com/2015/02/20/rust-language-c-embedded: https://spin.atomicobject.com/2015/02/20/rust-language-c-embedded/
[11] github.com/japaric/rust-cross: https://github.com/japaric/rust-cross
[12] Источник: https://habrahabr.ru/post/324646/
Нажмите здесь для печати.