Программируем Arduino Uno на Rust: настраиваем среду и моргаем светодиодом
Кто-то из вас наверняка задавался вопросом:, а нельзя ли программировать 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
: менеджер пакетов и проектов на языке Rustcargo-clippy
: инструмент проверки кода на ошибки (линтер)cargo-fmt
: форматирует файлы проекта на Rust, используя rustfmtcargo-miri
: интерпретатор промежуточного кодаclippy-driver
: линтер clippyrls
: сервер языка Rustrust-gdb
: отладчик на основе GDB (нужно отдельно установить gdb)rust-lldb
: отладчик на основе LLDB (нужно отдельно установить lldb с llvm)rustc
: компилятор языка Rustrustdoc
: генератор документации проекта на Rustrustfmt
: форматер кода на Rustrustup
: установщик и средство управления инструментами языка 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, нужно только:
установить в систему LLDB:
sudo apt install lldb
установить в Code расширение CodeLLDB
и добавить конфигурацию запуска в Code: воспользовавшись генератором из Code или вручную, создав в директории проекта файл
.vscode/launch.json
со следующим содержанием:{ "configurations": [ { "type": "lldb", "request": "launch", "name": "Launch", "program": "${workspaceFolder}/target/debug/hello_rust", "args": [], "cwd": "${workspaceFolder}" } ] }
установить точку останова в нужном месте кода на 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 Симоненко Евгений