Rust ESP FAQ Печать
Добавил(а) microsin   

Описание решения различных вопросов, касающихся программирования микроконтроллеров Espressif на языке Rust.

Следующий код выдает предупреждение на неиспользуемую переменную i (warning: unused variable: `i`):

for i in 0..256
{
let rawadc = adc.read_oneshot(&mut voltage_pin).await;
info!("Результат АЦП {}", rawadc); }

Переменная i на самом деле не используется. Есть несколько способов устранить это предупреждение:

Способ 1: использовать префикс нижнего подчеркивания (conventional-конструкция Rust).

for _i in 0..256
{
let rawadc = adc.read_oneshot(&mut voltage_pin).await;
info!("Результат АЦП {}", rawadc); }

Способ 2: использовать вместо имени переменной символ подчеркивания (наиболее часто используемый метод для таких случаев).

for _ in 0..256
{
let rawadc = adc.read_oneshot(&mut voltage_pin).await;
info!("Результат АЦП {}", rawadc); }

Способ 3: использовать итератор range (функциональный стиль).

(0..256).for_each(|_|
{
let rawadc = adc.read_oneshot(&mut voltage_pin).await;
info!("Результат АЦП {}", rawadc); });

Рекомендация: используйте Способ 2 (просто _), поскольку это самый идиоматический метод Rust, когда вам в теле цикла не нужна переменная счетчика. Нижнее подчеркивание говорит компилятору, что вы намеренно не используете эту переменную, что подавит вывод соответствующего предупреждения.

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

warning: unused `Result` that must be used
   --> src/bin/main.rs:201:5
    |
137 |     spawner.spawn(adc_task(peripherals.GPIO3, peripherals.ADC1));
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled
    = note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
    |
137 |     let _ = spawner.spawn(adc_task(peripherals.GPIO3, peripherals.ADC1));
    |     +++++++

В этом предупреждении сразу дается совет, как его устранить: с помощью оператора let выполните присвоение возвращаемого результата функции временной переменной _:

let _ = spawner.spawn(adc_task(peripherals.GPIO3, peripherals.ADC1));

Разделение кода приложения на отдельные файлы (модули кода) помогает в логической организации функционала вашего приложения, делая код понятнее и удобнее для поддержки. Это становится особенно важным по мере разрастания проекта.

Пошаговое руководство по добавлению модуля кода в приложение Rus:

[Шаг 1: создание файловой структуры для модуля]

Вариант A: модуль с одним файлом

your_project/
├── src/
│   ├── main.rs
│   └── your_module.rs    # файл нового модуля
└── Cargo.toml

Вариант B: модуль из нескольких файлов (директория)

your_project/
├── src/
│   ├── main.rs
│   └── your_module/      # директория модуля
│       ├── mod.rs        # главный файл модуля
│       └── submodule.rs  # дополнительные (не обязательные) субмодули
└── Cargo.toml

[Шаг 2: написание кода модуля]

Для одиночного модуля (src/your_module.rs):

// Публичные функции (доступные снаружи модуля):
pub struct MyStruct {
pub value: i32, }

impl MyStruct {
pub fn new(value: i32) -> Self {
Self { value }
}
pub fn double(&self) -> i32 {
self.value * 2
} }

pub fn utility_function() -> String {
"Привет из модуля!".to_string() }

// Приватная функция (она доступна только в пределах этого модуля):
fn private_helper() {
println!("Это приватная функция"); }

Для модуля директории (src/your_module/mod.rs):

// src/your_module/mod.rs
pub mod submodule; // Декларация субмодулей

pub struct MyStruct {
pub value: i32, }

impl MyStruct {
pub fn new(value: i32) -> Self {
Self { value }
} }

[Шаг 3: декларация модуля в основном файле приложения]

В файле src/main.rs:

// Декларация модуля (говорит для Rust искать модуль в your_module.rs
// или в your_module/mod.rs):
mod your_module;

// Импорт элементов из вашего модуля:
use your_module::{MyStruct, utility_function};

fn main() {
// Использование функционала вашего модуля:
let my_instance = MyStruct::new(42);
println!("Значение: {}", my_instance.value);
println!("Удвоенное значение: {}", my_instance.double());

let message = utility_function();
println!("{}", message);

// Вы также можете использовать полный путь для вызова функций модуля:
let another_instance = your_module::MyStruct::new(100);
println!("Другое значение: {}", another_instance.value); }

[Шаг 4, опциональный: создание субмодулей]

Создайте файл src/your_module/submodule.rs:

pub fn submodule_function() -> &'static str {
"Я в субмодуле!" }

Обновите src/your_module/mod.rs для экспорта субмодуля:

// src/your_module/mod.rs
pub mod submodule;

pub struct MyStruct {
pub value: i32, }

// Повторный экспорт элементов субмодуля при необходимости:
pub use submodule::submodule_function;

impl MyStruct {
pub fn new(value: i32) -> Self {
Self { value } } }

[Шаг 5: использование субмодулей в основной программе]

В src/main.rs:

mod your_module;

// Импорт из основного модуля и субмодуля:
use your_module::{MyStruct, submodule_function, submodule};

fn main() {
let my_instance = MyStruct::new(42);
println!("Значение: {}", my_instance.value);

// Использование повторно экспортированной функции:
println!("{}", submodule_function());

// Или доступ через путь субмодуля:
println!("{}", your_module::submodule::submodule_function()); }

[Шаг 6: организация нескольких модулей]

Для нескольких модулей:

your_project/
├── src/
│   ├── main.rs
│   ├── math/           # модуль математики math
│   │   ├── mod.rs
│   │   ├── calculus.rs
│   │   └── algebra.rs
│   ├── utils/          # модуль утилит utils
│   │   ├── mod.rs
│   │   └── helpers.rs
│   └── models/         # модуль моделей данных models
│       ├── mod.rs
│       └── user.rs
└── Cargo.toml

В главном файле приложения src/main.rs:

// Декларация всех модулей:
mod math;
mod utils;
mod models;

// Импорт определенных элементов модулей:
use math::algebra::add;
use utils::helpers::format_message;
use models::user::User;

fn main() {
let result = add(5, 3);
let message = format_message("Привет");
let user = User::new("Светлана");

println!("Результат: {}", result);
println!("Сообщение: {}", message);
println!("Пользователь: {}", user.name); }

[Ключевые концепции]

1. mod module_name; - декларирует модуль (Rust ищет module_name.rs или module_name/mod.rs).

2. pub - делает элементы модуля публичными, т. е. доступными вне модуля.

3. use - импорт элементов в текущую область видимости (current scope) кода.

4. Именование файлов - имена модуля должны совпадать с именами файлов.

5. mod.rs - специальный файл, который определяет содержимое модуля, когда для модуля используется директория.

[Общие шаблоны]

Ре-экспорт:

// В вашем your_module/mod.rs:
pub mod submodule;
pub use submodule::important_function; // реэкспорт для упрощения доступа

Приватные модули:

// Модули без 'pub' являются приватными для своего родительского модуля:
mod private_module; // доступен только из этого файла

Для устранения этой ошибки добавьте в модуль директиву компилятора #![no_main]:

#![no_std]
#![no_main]

use embassy_sync::channel::Channel;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_executor::task;
use esp_hal::gpio::Output;

...

Ошибка компиляции в модуле Rust "error: #[panic_handler] function required, but not found" возникает обычно в ситуации, когда выполняется сборка проекта Rust, линкуемого без использования стандартной библиотеки (например это no_std проект). В таких условиях обработчик паники (panic handler) стандартной библиотеки недоступен, вы должны предоставить свой собственный обработчик.

В случае no_std рабочего окружения, когда вы собираете приложение микроконтроллера (embedded system), ядро операционной системы (operating system kernel) или работаете с другим окружением, где стандартная библиотека Rust недоступна, вы используете атрибут #![no_std] в своем файле main.rs или lib.rs.

Panic Handling. На языке Rust термин "panic" (паника) означает невосстановимую ошибку, обозначающую серьезную проблему в состоянии программы. Когда возникает паника в no_std окружении, компилятор Rust нуждается в специально выделенной функции для обработки этой ситуации, поскольку panic handler стандартной библиотеки отсутствует.

#[panic_handler]

Этот атрибут помечает функцию как выделенный panic handler вашей no_std-программы.

Чтобы устранить ошибку "`#[panic_handler]` function required, but not found", вам нужно определить функцию с атрибутом #[panic_handler] в крейте, где применен атрибут #![no_std]. Обычная минимальная реализация запускает пустой бесконечный цикл, попадание в который эффективно подвешивает выполнение при возникновении паники. Более сложные реализации подразумевают вывод в лог осмысленных сообщений, описывающих ошибку.

Пример (в main.rs или lib.rs):

use core::panic::PanicInfo;

/// Эта функция вызывается при панике.
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
// Сюда вы можете добавить свою логику, наподобие вывода в лог
// информации panic (например в последовательный порт), или
// активировать системный сброс.
loop {} // вход в бесконечный цикл, останавливающий выполнение приложения }

[Дополнительные замечания]

1. panic = "abort": для приложений no_std часто рекомендуется конфигурировать ваш файл Cargo.toml для настройки стратегии паники "abort". Это говорит компилятору генерировать код, который обрывает обработку при возникновении паники вместо того, чтобы пытаться отмотать стек (unwind), что может не поддерживаться или не является желательным для вашего целевого рабочего окружения.

# В файле Cargo.toml
[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

Custom Panic Logic: хотя простой цикл loop {} допускается как базовый panic handler, вы можете реализовать более продвинутую логику, в которой функция паники обработает ситуацию сбоя с учетом специфики вашего приложения (например выведет в лог сообщения об ошибке, показывающие информацию диагностики, или инициирует system reset).

Предоставлением функции #[panic_handler] и потенциальным конфигурированием panic = "abort" в файле Cargo.toml вы обеспечиваете реализацию требования для panic handler в no_std-проектах Rust и устраните ошибку "function required, but not found".

Это предупреждение указывает, что функция была определена, но нигде не используется, что соответствует "мертвому коду" (dead code). Такое поведение задается lint-атрибутом #[warn(dead_code)], который по умолчанию применяется ко всем функциям, переменным или полям структур.

Атрибут #[warn(dead_code)] помогает идентифицировать неиспользуемый код, что потенциально способствует устранению разрастания кода и связанных с этим потенциальных ошибок. Атрибут #[allow(dead_code)] обладает противоположным действием: он отменит это предупреждение. Пример:

#[allow(dead_code)]
pub async fn function_name() {
set_gpio8(GpioCommand::Toggle).await; }

Прошивка скомпилированной программы Rust ESP выбирается опцией runner секции [target.riscv32imc-unknown-none-elf] файла настроек .cargo/config.toml:

[target.riscv32imc-unknown-none-elf]
runner = "espflash flash --monitor --chip esp32c3 --log-format defmt"

Здесь указано, что прошивка осуществляется утилитой espflash с запуском монитора после перепрошивки. Обычно утилита espflash сама найдет последовательный порт для передачи скомпилированного двоичного кода, или предложит выбрать, через какой порт прошивать. Но иногда бывают случаи, когда это необходимо указывать вручную. Это можно сделать передачей опции -p или --port:

espflash flash -p /dev/ttyACM0  # Пример для Linux/macOS example
espflash flash -p COM3          # Пример для Windows

[Ссылки]

1. rustup + espup: тулчейн для Rust на платформе ESP32.
2. Rust ESP32: руководство новичка.