Программируем Arduino Uno на Rust: настраиваем среду и моргаем светодиодом

76e973286cebafb614770570ff36f91b

Кто-то из вас наверняка задавался вопросом:, а нельзя ли программировать Arduino на чём-то более современном и удобном? Вот и я задавался. И нашёл Rust (не то, чтобы я о нём не знал). И на нём можно программировать микроконтроллеры AVR и платы Arduino, построенные на них. И здесь я расскажу о том, как настроить среду разработчика на Rust в Linux, GNU Emacs и Visual Studio Code и как запрограммировать Arduino Uno на моргание светодиодом.

#![feature(llvm_asm)]

#![no_std]
#![no_main]

use ruduino::Pin;
use ruduino::cores::current::{port};

#[no_mangle]
pub extern fn main() {
    port::B5::set_output();

    loop {
        port::B5::set_high();
        ruduino::delay::delay_ms(1000);
        port::B5::set_low();
        ruduino::delay::delay_ms(1000);
    }
}

Итак, у нас есть Arduino Uno, компьютер с Ubuntu Linux, GNU Emacs (и/или Visual Studio Code). И мы хотим написать код на Rust, который будет моргать встроенным в плату светодиодом (LED Blink). Но сначала нужно настроить среду разработки. Нам потребуется установить инструментарий Rust, LSP-сервер, настроить GNU Emacs. Также посмотрим как с поддержкой Rust в VS Code.


Устанавливаем Rust

Точкой входа в мир Rust служит страница на сайте языка https://www.rust-lang.org/learn/get-started Здесь мы узнаем, как установить Rust, используя утилиту rustup. Здесь же рассказывается, как создать проект и запустить приложение. Установщик создаёт в домашнем каталоге две директории: .cargo для инструментария Rust и .rustup для служебной информации rustup. Для изучения Rust отдадим предпочтение стабильной версии компилятора, но для установки также доступна ночная сборка.

После установки нам доступны следующие программы:


  • cargo: менеджер пакетов и проектов на языке Rust
  • cargo-clippy: инструмент проверки кода на ошибки (линтер)
  • cargo-fmt: форматирует файлы проекта на Rust, используя rustfmt
  • cargo-miri: интерпретатор промежуточного кода
  • clippy-driver: линтер clippy
  • rls: сервер языка Rust
  • rust-gdb: отладчик на основе GDB (нужно отдельно установить gdb)
  • rust-lldb: отладчик на основе LLDB (нужно отдельно установить lldb с llvm)
  • rustc: компилятор языка Rust
  • rustdoc: генератор документации проекта на Rust
  • rustfmt: форматер кода на Rust
  • rustup: установщик и средство управления инструментами языка Rust

Предупреждение: коллеги в комментариях подсказывают, что RLS официально объявлен устаревшим, и вместо него должен использоваться rust-analyzer.


Настраиваем GNU Emacs

В качестве основной среды разработки я использую GNU Emacs. Команда Rust разрабатывает официальный пакет для Emacs rust-mode. Но есть и аналог интегрированный среды Rustic. Этот пакет не требует никакой настройки, и из коробки предоставляет массу удобностей. С него и начнём.

Ставим rustic обычным образом из репозитория MELPA и прописываем его инициализацию при запуске Emacs в ~/.emacs.d/init.el:


  • если используем пакет use-package:
    (use-package rustic)
  • если используем стандартный метод подключения пакетов:
    (require 'rustic)

Для создания проекта вызываем команду rustic-cargo-init, которая запросит у нас, где создать проект (поэтому сначала заготовьте для него новую пустую директорию). (Команда rustic-cargo-new, которая по идее должна также запросить название проекта и создать для него директорию, не сработала.)

При попытке открыть файл на Rust ./hello_rust/src/main.rs получим ошибку запуска LSP-сервера rls. Для более подробной информации заглядывем в буфер *rls::stderr* и видим сообщение о том, что rls не установлен (хотя команда такая есть). Проверяем в командной строке:

rls --version

Действительно, та же самая ошибка:

error: 'rls' is not installed for the toolchain 'stable-x86_64-unknown-linux-gnu'
To install, run `rustup component add rls`

Так и поступим:

rustup component add rls

Повторяем проверку версии rls и теперь получаем то, что нужно:

rls 1.41.0 (bf88026 2021-09-07)

Закрываем буфер с main.rs и снова его открываем: теперь всё хорошо, LSP-сервер запустился, интерфейс редактора изменился.

Для запуска программы вызываем команду rustic-cargo-run и ничего не видим: почему-то консольный вывод нашей программы не отображается…

Но можно запустить напрямую в консоли. Для этого откроем её прямо в Emacs: запускаем встроенную оболочку eshell и вызываем в открывшейся консоли команду

cargo run

Теперь наш Hello, world! на экране.


Управление пакетами Cargo в GNU Emacs

Проекты на Rust управляются утилитой Cargo, которая конфигурируется файлами в формате TOML Для работы LSP-клиента GNU Emacs нам потребуется LSP-сервер для TOML taplo. Установим его:

cargo install taplo-lsp

Сборка прервалась с ошибкой: не найдена библиотека openssl. Установим её:

sudo apt install libssl-dev

и запустим сборку заново.

И снова ошибка, теперь уже в коде на Rust пакета taplo-lsp (все зависимости собрались без вопросов):

error[E0599]: no method named `about` found for struct `Arg` in the current scope

На это уже был заведён ишью. Опытные товарищи подсказывают там, что сборку нужно запускать с опцией --locked. И это сработало, сервер установился.

cargo install --locked taplo-lsp

Команда cargo install описывается здесь. Опция --locked требует, чтобы cargo не обращался к репозиторию пакетов за свежими версиями. Без этой опции cargo будет обновлять Cargo.lock.

После установки LSP-сервера и перезапуска GNU Emacs ничего видимо не изменилось: сообщение о запуске LSP-сервера не появляется, ни в статусе, ни в буфере *lsp-log*; никаких новых возможностей к базовой поддержке не добавилось. Непонятно.


Настраиваем Visual Studio Code

Visual Studio Code последние несколько лет очень популярен среди программистов как легковестная и настраиваемая альтернатива интегрированным средам разработки (IDE). Когда-то и я был его постоянным пользователем, но последние года два я им не пользовался, полностью перейдя на GNU Emacs. Так как мои коллеги и студенты часто отдают ему предпочтение, то буквально совсем недавно я его снова поставил в свой Ubuntu Linux, чтобы разговаривать на общем языке, так сказать. Поэтому сегодня посмотрим, что нам приготовила команда Rust как пользователям Code. Собственно, это расширение с коротким названием Rust. Как и пакет для GNU Emacs расширение для Code базируется на rls или rust-analyzer.

С отладкой и запуском программы у меня здесь не заладилось. При нажатии на F5 или Ctrl-F5 выскакивает сообщение, что расширение для отладки не установлено. И такового, как я понял, нет. В общем, интереса личного у меня к работе в Code нет, поэтому дальше копать, что да как, я не намерен, по крайней мере пока.

Спустя некоторое время, после того как я пытался настроить VS Code, на Хабре вышел пост о том, как настроить VS Code для Rust. Отладка в нём таки доступна, но нужно вместо родного расширения установить базирующийся на rust-analyzer. Возможно есть смысл использовать оный и в GNU Emacs.

По итогу изучения материала поста вышло следующие:


  • rust-analyzer устанавливать не стал, ни сервер, ни расширение для Code. Как я понял он ещё в разработке, в стабильную поставку ещё не включён. Подождём. После выпуска статьи выяснилось, что RSL объявлен устаревшим, и теперь всё же нужно переходить на rust-analyzer.


  • Оказалось, что для отладки кода на Rust rust-analyzer и не нужен. Всё работает и со стандартным RLS, нужно только:



  1. установить в систему LLDB:

    sudo apt install lldb

  2. установить в Code расширение CodeLLDB


  3. и добавить конфигурацию запуска в Code: воспользовавшись генератором из Code или вручную, создав в директории проекта файл .vscode/launch.json со следующим содержанием:

    {
      "configurations": [
          {
          "type": "lldb",
          "request": "launch",
          "name": "Launch",
          "program": "${workspaceFolder}/target/debug/hello_rust",
          "args": [],
          "cwd": "${workspaceFolder}"
          }
      ]
    }

  4. установить точку останова в нужном месте кода на Rust и нажать F5 — отладка запущена.
    Дальше всё как обычно.


Стоит заметить, что так мы можем отлаживать код, работающий в среде Linux. Код же запущенный на Arduino так не отладить. Здесь можно посмотреть в сторону Qemu с поддержкой AVR, но там используется gdb.

Ну и, как я писал ранее, для работы с файлами TOML нужно установить LSP-сервер Taplo.


Настраиваем инструментарий для программирования Arduino Uno

Для программирования контроллеров на базе AVR был создан специальный проект AVR-Rust, одной из задач которого является разработка поддержки AVR в Rust. Также в рамках данного проекта разрабатывается поддержка Arduino и ведётся список библиотек и проектов.

Начать изучение AVR-Rust лучше всего с официального руководства. В разделе «Installing the compiler» описывается, как установить компилятор Rust с поддержкой AVR. Но перед тем как это сделать нужно установить стороннее ПО, которое потребуется для работы.

В Ubuntu Linux нам потребуется установить пакеты binutils, gcc-avr, avr-libc и avrdude:

sudo apt-get install binutils gcc-avr avr-libc avrdude

Далее нужно воспользоваться rustup и поставить с его помощью ночную сборку инструментария и исходники Rust:

rustup toolchain install nightly
rustup component add rust-src --toolchain nightly


Моргаем светодиодом на Arduino Uno

Наконец разоберёмся с примером моргания встроенным светодиодом Arduino Uno на Rust.
Собственно, пример кода можно найти здесь:

#![feature(llvm_asm)]

#![no_std]
#![no_main]

use ruduino::Pin;
use ruduino::cores::current::{port};

#[no_mangle]
pub extern fn main() {
    port::B5::set_output();

    loop {
        port::B5::set_high();
        ruduino::delay::delay_ms(1000);
        port::B5::set_low();
        ruduino::delay::delay_ms(1000);
    }
}

Действуем строго по инструкции:

git clone https://github.com/avr-rust/blink.git
cd blink
rustup override set nightly
export AVR_CPU_FREQUENCY_HZ=16000000
cargo build -Z build-std=core --target avr-atmega328p.json --release

И получаем ошибку компиляции, которая описана вот в этих ишью:
https://github.com/avr-rust/blink/issues/37 и https://github.com/avr-rust/delay/issues/10
Проблема решается установкой ночной сборки компилятора годичной давности:

rustup toolchain install nightly-2021-01-05
rustup override set nightly-2021-01-05

Неайс, конечно: будет установлена версия Rust 1.51.0.

Теперь повторим шаг:

cargo build -Z build-std=core --target avr-atmega328p.json --release

И снова получим ошибку, только другую и с рекомендацией установить rust-src. Давайте поставим:

rustup component add rust-src

И повторим попытку сборки. Кажется прошло успешно: target/avr-atmega328p/release/blink.elf создан. Его размер примерно 9 Кб. Многовато, конечно, при 32 Кб доступной Flash-памяти.

Руководство по прожигу чипа Arduino Uno смотрим здесь. Будем использовать раннее установленную нами утилиту avrdude,
только сначала узнаем порт для опции -P:


  • убеждаемся, что устройство успешно подключено:
lsusb


  • смотрим номер порта:
sudo dmesg | tail

Запускаем прожиг:

avrdude -patmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:target/avr-atmega328p/release/blink.elf:e

Фух! Всё прошло благополучно, светодиод мигает, только как-то быстро. Во flash-память было записано примерно 2 Кб.

В опции -c указывается название программатора. Мы используем встроенный в Arduino на базе USB.

Наша Arduino Uno слишком быстро моргает своим светодиодом. И на это уже есть своя ишью. В комментариях к ней рекомендуют добавить опцию сборки:

[profile.release]
lto = true

Проверяем: работает, моргает в ожидаемом ритме. При этом размер прошивки уменьшился до менее чем 1 Кб. Недурно.

Что делает опция lto? Название расшифровывается как Link Time Optimization.
Это технология LLVM https://llvm.org/docs/LinkTimeOptimization.html, которая оптимизирует результирующий код в процессе сборки. Как я понял, при включении этой опции, линкер удаляет из кода неиспользуемые части, за счёт чего наша прошивка и похудела. Вот только непонятно, почему эта опция влияет на правильность работы нашего кода? Ведь этого не должно происходить.

Попробуем другие значения для опции lto:


  • false: образ «жирный», светодиод моргает слишком часто.
  • "thin": эффект как при true (она же "fat"): размер и ритм такие же.
  • "off": при отключённой оптимизации при сборке такой же эффект, как при false.

Вот собственно и всё, что я хотел рассказать. Надеюсь, это руководство кому-то поможет стартануть с Rust на Arduino.


Что ещё почитать

Интересный пост про программирование на Rust микроконтроллеров семейства PIC32.

© 2022 Симоненко Евгений

© Habrahabr.ru