Программирование ARM Rust ESP32: руководство новичка Thu, August 28 2025  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


Rust ESP32: руководство новичка Печать
Добавил(а) microsin   

В этой статье (перевод [1]) приведено простое руководство по аппаратуре и программированию на платформе чипов ESP32 с использованием языка программирования Rust.

В этом руководстве мы сфокусируемся на ESP32, недорогом и мощном микроконтроллере, который отлично подойдет как для новичков, так и для экспертов. Вы можете купить микроконтроллер ESP32 как часть какого-нибудь стартер-кита, на плате которого могут быть установлены различные датчики, такие как датчики температуры или освещения.

[Программное обеспечение]

Для ESP32 написано большое количество хорошо документированных библиотек, которые помогут вам быстро освоить программирование Rust, см. документацию по ESP-RS [3].

Мы рассмотрим шаги для настройки ESP32-программирования на Rust, от установки необходимых утилит до написания вашей первой программы. Также будут описаны решения возможных проблем, которые не всегда можно найти в официальной документации.

Шаг 1: std против no_std

На языке Rust существует два различных подхода к программированию: использование полностью стандартной библиотеки (опция std) или возможность использовать нестандартных библиотек (опция no_std). Понимание разницы между этими вариантами имеет решающее значение при работе со встроенными системами, где ресурсы (память и время процессора) часто ограничены.

std

• Библиотека std предоставляет множество функций "из коробки", такие как коллекции (например Vec), работа с файлами, сетевыми протоколами и потоками. Это ускоряет и упрощает разработку.
• Многие библиотеки Rust имеют зависимость от std, поэтому использование std позволяет воспользоваться широким спектром имеющихся ресурсов.
• Если вы разрабатывали программы Rust для десктопа PC или сервера, то многое в программировании ESP32 на Rust покажется вам знакомым и удобным.

no_std

• no_std разработана для систем с очень ограниченными ресурсами, что идеально подходит для встраиваемых устройств.
• Вы можете запускать no_std-программы непосредственно на аппаратуре без OS, что экономит ресурсы микроконтроллера.
• Программы получаются меньше по размеру, что лучше всего работает на устройствах, где объем памяти ограничен.

При разработке на Rust встраиваемых устройств, когда микроконтроллеры работают в условиях очень ограниченной оперативной памяти и без наличия операционной системы, лучшим выбором будет no_std. Этот вариант удалит функции, которые потребляют большое количество системных ресурсов, сосредоточив внимание на основном функционале, необходимом для низкоуровневого программирования.

В этом руководстве мы будем использовать no_std для непосредственной работы с аппаратурой микроконтроллера ESP32.

Шаг 2: установка

Перед тем, как можно будет начать программировать, нам понадобится установить рабочее окружение, основанное на архитектуре вашего программируемого устройства. Чипы Espressif SoC (System on Chip, системы на чипе, другое название микроконтроллера) могут использовать различные архитектуры, такие как RISC-V и Xtensa, и процесс установки зависит от того, чип какой архитектуры вы используете.

Идентификация архитектуры. Таким образом, необходимо определить, на какой архитектуре работает ваш чип ESP32.

Xtensa: на этой архитектуре работают чипы ESP32 (например, ESP32-WROOM, ESP32-Ethernet-Kit и другие).

RISC-V: это устройства наподобие чипа ESP32-C3 (платы ESP32-C3 SuperMini, LUATOS.COM/T/ESP32C3, LUATOS.COM/T/ESP32C3 b и другие).

Чтобы узнать, к какой архитектуре относится ваше устройство, обратитесь к его документации, обычно доступной в соответствующем даташите или на сайте производителя платы.

Установка тулчейна. Чтобы разрабатывать Rust-приложения на чипах Espressif SoC, нам нужно установить подходящий программный инструментарий (тулчейн). Это то место, где вступает в игру espup [2] — эта программа поможет установить и обслуживать утилиты тулчейна для вашей используемой архитектуры.

Чтобы установить espup, запустите в терминале следующую команду:

$ cargo install espup

Затем установите необходимые тулчейны:

$ espup install

После завершения установки вы увидите сообщение наподобие следующего, где вам советуют установить некоторые переменные окружения:

[info]: Installation successfully completed!
To get started, you need to set up some environment variables by running: '. $HOME/export-esp.sh' This step must be done every time you open a new terminal. See other methods for setting the environment in < https://esp-rs.github.io/book/installation/riscv-and-xtensa.html#3-set- up-the-environment-variables>

Если вы столкнетесь с ошибкой:

Caused by:
  process didn't exit successfully: `/tmp/cargo-installlfnEMgk/release/build
  /libudev-sys-678fd6d3ec7ef9ed/build-script-build` (exit status: 101)
...
  --- stderr
  thread 'main' panicked at '/home/danielez/.cargo/registry/src/index.crates
  .io-6f17d22bba15001f/libudev-sys-0.1.4/build.rs:38:41:
  called `Result::unwrap()` on an `Err` value: "pkg-config exited with
  status code 1
PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config
 --libs --cflags libudev
The system library `libudev` required by crate `libudev-sys` was not found.
The pkg-config utility tells us that `libudev.pc` needs to be installed and the PKG_CONFIG_PATH environment variable must contain its parent directory.
NOTE: if you have installed the library, you may need to add PKG_CONFIG_PATH to the directory containing `libudev.pc`. "

.. то можете её исправить запуском команды (см. https://github.com/esp-rs/espflash/issues/108):

$ sudo apt-get install libudev-dev

[Создание программы на основе шаблона]

Следующий шаг - создание проекта с помощью шаблона.

Установите утилиту cargo-generate, которая позволит вам создать новый проект на основе существующего шаблона:

$ cargo install cargo-generate

Затем сгенерируйте проект с использованием шаблона esp-template:

$ cargo generate esp-rs/esp-template

Когда проект сгенерируется, откройте его (например, с помощью любой вашей среды IDE наподобие VScode, или просто перейдя в директорию проекта в терминале), и проверьте код внутри файла src/main.rs. Этот код будет выглядеть примерно так:

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_hal::{ clock::ClockControl, delay::Delay, peripherals::Peripherals, prelude::*,
system::SystemControl, };

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);

let clocks = ClockControl::max(system.clock_control).freeze();
let delay = Delay::new(&clocks);

esp_println::logger::init_logger_from_env();

loop {
log::info!("Hello world!");
delay.delay(500.millis());
} }

Давайте разберемся что тут происходит.

#![no_std]

Эта директива говорит Rust не использовать стандартную библиотеку, что часто применяется для программирования встраиваемых систем.

#![no_main]

Это показывает, что обычная точка входа main в программе не используется. Вместо неё предоставляется указываемая пользователем точка входа (custom entry point), что является одним из типовых решений для встраиваемых систем.

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

Это определяет точку входа в программу с помощью функции main, из которой никогда не происходит возврат (-> ! указывает на дивергентную функцию).

Вы также можете заметить, что строчкой use esp_hal::{ импортируется HAL. Аббревиатура HAL означает Hardware Abstraction Layer, библиотека абстракции аппаратуры, что часто используется для встраиваемых систем.

let peripherals = Peripherals::take();

Эта строка получает доступ к аппаратным периферийным устройствам микроконтроллера.

let system = SystemControl::new(peripherals.SYSTEM);

Эта строка активирует системный контроллер, используемый системными утилитами микроконтроллера.

let clocks = ClockControl::max(system.clock_control).freeze();

Это запустит системное тактирование на самой высокой частоте и блокирует это состояние. Это гарантирует, что микроконтроллер будет работать с самой высокой возможной производительностью. Блокировка (.freeze) этой настройки гарантирует, что скорость остается постоянной во время работы, поэтому не должно быть неожиданных замедлений или изменений тактирования.

[Выполнение программы - Hello World]

Подключите вашу платку через USB и запустите команду:

$ cargo run

При первом запуске на Linux вы можете увидеть следующую ошибку:

WARNING: use --release
  We *strongly* recommend using release profile when building esp-hal.
  The dev profile can potentially be one or more orders of magnitude
  slower than release, and may cause issues with timing-senstive
  peripherals and/or devices.
Finished `dev` profile [optimized + debuginfo] target(s) in 0.09s Running `espflash flash --monitor target/xtensa-esp32-none-elf/debug/rust-test-esp32` [2024-08-01T21:27:30Z INFO ] Serial port: '/dev/ttyUSB0' [2024-08-01T21:27:30Z INFO ] Connecting... Error: espflash::serial_error
× Failed to open serial port /dev/ttyUSB0 ├─▶ Error while connecting to device ├─▶ IO error while using serial port: Permission denied ╰─▶ Permission denied

Эта ошибка связана с отсутствием прав доступа текущего пользователя к последовательным устройствам, что исправляется командой:

$ sudo usermod -a -G dialout $USER

После того, как ваш пользователь был добавлен в группу dialout, вам нужно перелогиниться, чтобы изменения вступили в силу. Альтернативно вы можете перезагрузить свой компьютер.

Ваше членство в группе dialout можно проверить командой:

$ groups

Будет выведен список групп, в которые входит текущий пользователь, и в этом списке должна присутствовать группа dialout.

Теперь снова попробуйте запустить команду:

$ cargo run

Вы должны увидеть следующий вывод:

I (31) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (31) boot: compile time Jun 7 2023 07:48:23
I (33) boot: Multicore bootloader
I (37) boot: chip revision: v3.1
I (41) boot.esp32: SPI Speed : 40MHz
I (46) boot.esp32: SPI Mode : DIO
I (50) boot.esp32: SPI Flash Size : 4MB
I (55) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
I (64) boot: ## Label Usage Type ST Offset Length
I (71) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (79) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (86) boot: 2 factory factory app 00 00 00010000 003f0000
I (94) boot: End of partition table
I (98) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=04168h ( 16744) map
I (112) esp_image: segment 1: paddr=00014190 vaddr=3ffb0000 size=0000ch ( 12) load
I (115) esp_image: segment 2: paddr=000141a4 vaddr=40080000 size=01028h ( 4136) load
I (125) esp_image: segment 3: paddr=000151d4 vaddr=00000000 size=0ae44h ( 44612)
I (148) esp_image: segment 4: paddr=00020020 vaddr=400d0020 size=05d24h ( 23844) map
I (157) boot: Loaded app from partition at offset 0x10000
I (157) boot: Disabling RNG early entropy source...
INFO - Hello world!
INFO - Hello world!
INFO - Hello world!
INFO - Hello world!
INFO - Hello world!
INFO - Hello world!

Строка вывода в лог log::info!("Hello world!"); обычно посылает текстовое сообщение в последовательный терминал через интерфейс UART ESP32. Это сообщение в результате передается в подключенное устройство (наподобие компьютера), что дает возможность увидеть его с помощью специального программного обеспечения.

[Программа, мигающая светодиодом]

Большинство плат с микроконтроллером ESP32 оборудованы светодиодом (LED). Давайте попробуем им помигать. В примере, предоставленном ESP-RS, вы можете найти следующий код:

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
delay::Delay,
gpio::{Io, Level, Output},
peripherals::Peripherals,
prelude::*,
system::SystemControl, };

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let system = SystemControl::new(peripherals.SYSTEM);
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

// GPIO7 настроен как выход, в начальном состоянии лог. 1.
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let mut led = Output::new(io.pins.gpio7, Level::Low);
led.set_high();

// Инициализация класса задержки Delay и его использование
// для переключения состояния светодиода в цикле.
let delay = Delay::new(&clocks);

loop {
led.toggle();
delay.delay_millis(500u32);
} }

Здесь есть две новые строки, которые поначалу могут быть непонятны:

let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);

Эта строка инициализирует аппаратуру периферийного устройства GPIO (General Purpose Input/Output), которая управляет вводом и выводом через ножки портов микроконтроллера. IO MUX (Input/Output multiplexer) позволяет микроконтроллеру перенаправлять различные внутренние сигналы на физические выводы корпуса чипа.

let mut led = Output::new(io.pins.gpio7, Level::Low);

Эта строка настраивает на вывод ножку порта GPIO7, к которой может быть подключен светодиод. Светодиод может быть подключен и к другому порту, например GPIO2, проконсультируйтесь с принципиальной схемой вашей платы.

После запуска команды cargo run на плате начет мигать светодиод с периодом 1 секунда, это обеспечивает бесконечный цикл:

loop {
led.toggle();
delay.delay_millis(500u32); }

[Подключение кнопки]

Теперь мы сделаем наш проект интерактивным, добавив опрос состояния кнопки. По нажатиям на кнопку мы будем зажигать и гасить светодиод.

Для этого нам понадобятся:

• ESP32 DEVKIT V1
• 5 mm LED
• 220 Ом резистор
• Кнопка
• 10k кОм резистор
• Макетная плата (Breadboard)
• Провода для перемычек

Rust ESP32 fig01

Схема соединений:

Rust ESP32 fig02

Код Rust:

#![no_std]
#![no_main]

use esp_backtrace as _;
use esp_hal::{
gpio::{Input, Io, Level, Output, Pull},
peripherals::Peripherals,
prelude::*,
system::SystemControl, };

#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let _system = SystemControl::new(peripherals.SYSTEM);

let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let mut red_led = Output::new(io.pins.gpio5, Level::Low);
let button = Input::new(io.pins.gpio4, Pull::Up);

loop {
if button.is_low() {
// Кнопка нажата
red_led.set_high();
} else {
// Кнопка отпущена
red_led.set_low();
}
} }

Мы использовали порты GPIO4 и GPIO5 для подключения светодиода (LED) и кнопки (button) соответственно. Настройка порта для опроса кнопки отражена следующей строчкой кода:

let button = Input::new(io.pins.gpio4, Pull::Up);

Кнопка подключена к GPIO4, и сконфигурирована как вход с верхним подтягивающим резистором (Pull::Up). Подтяжка такого типа гарантирует, что уровень порта кнопки считывается как лог. 1, если кнопка не нажата. В нажатом состоянии кнопки ножка порта замыкается на землю, и её низкий потенциал относительно GND считывается как уровень лог. 0.

let mut red_led = Output::new(io.pins.gpio5, Level::Low);

Порт для подключения светодиода GPIO5 сконфигурирован как выход. По умолчанию светодиод должен быть погашен, поэтому в соответствии с его подключением мы инициализируем GPIO5 в состоянии лог. 0 (Level::Low).

Опрос кнопки и включение и выключение светодиода обеспечивается телом бесконечного цикла:

    loop {
if button.is_low() {
// Кнопка нажата
red_led.set_high();
} else {
// Кнопка отпущена
red_led.set_low();
}
}

[Ссылки]

1. Introduction to Embedded Systems with Rust: A Beginner's Guide Using ESP32 site:rust-dd.com.
2. rustup + espup: тулчейн для Rust на платформе ESP32.
3. The Rust on ESP Book site:docs.espressif.com.

 

Добавить комментарий


Защитный код
Обновить

Top of Page