easimonenko Evgeny Simonenko

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

01 Sep 2022 |  Tutorial  |  Rust   GNU Emacs   Visual Studio Code   Arduino  

Полгода назад я вёл в Телеграме канал о языке Rust. Канал я закрыл, и сделал выжимку из материалов, опубликованных в нём. Здесь я расскажу о том, как настроить среду разработчика на Rust в Linux, GNU Emacs и Visual Studio Code и как запрограммировать Arduino Uno на моргание светодиодом.

#![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 отдадим предпочтение стабильной версии компилятора, но для установки также доступна ночная сборка.

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

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

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

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

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

(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.

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

  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. Собственно, пример кода можно найти здесь:

#![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, которая оптимизирует результирующий код в процессе сборки. Как я понял, при включении этой опции, линкер удаляет из кода неиспользуемые части, за счёт чего наша прошивка и похудела. Вот только непонятно, почему эта опция влияет на правильность работы нашего кода? Ведь этого не должно происходить.

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

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

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

(c) 2022 Симоненко Евгений