Перейти до змісту

Що є в проєкті

Звичайний Rust-проєкт починається з Cargo.toml і src/main.rs. Тут до них додається ще кілька файлів. Не тому що embedded любить зайві ритуали, а тому що компілятору треба знати, куди покласти код та як його запустити без операційної системи.

Cargo.toml

Тут описуються залежності, features та профілі збірки. Назва пакета визначає назву ELF після компіляції: у basics це target/xtensa-esp32s3-none-elf/release/basics від кореня workspace.

Залежність на SDK не замінює всі інші. Якщо ваш код використовує embedded_graphics, він має бути у ваших dependencies. Якщо макрос задачі генерує звернення до embassy_executor, цей крейт теж потрібен напряму.

У репозиторії examples/* входять до workspace. Cargo.lock, target/ та release-профіль спільні. Це не означає, що всі прошивки треба збирати одночасно: features одного SDK об'єднаються, і blocking разом з async не зберуться.

rust-toolchain.toml

Обирає тулчейн esp, встановлений через espup. Стандартний stable потрібен для звичайних програм на комп'ютері, але Xtensa-прошивка Lilka збирається портом Rust під ESP32-S3.

Команда cargo +esp ... вказує цей вибір явно. Якщо esp не знайдено, повертайтесь до встановлення середовища, а не замінюйте target на схожу назву.

.cargo/config.toml

У перевірених прикладах він такий:

[target.xtensa-esp32s3-none-elf]
runner = "probe-rs run --chip=esp32s3 --preverify --always-print-stacktrace --no-location --catch-hardfault"

[env]
DEFMT_LOG="info"

[build]
rustflags = [
  "-C", "link-arg=-nostartfiles",
  "-Z", "stack-protector=all",
]

target = "xtensa-esp32s3-none-elf"

[unstable]
build-std = ["alloc", "core"]

target задає чіпову архітектуру. build-std збирає core та alloc для неї. Наявність alloc у цьому списку не створює heap автоматично — це лише збірка самої бібліотеки.

runner визначає, що зробить cargo run. У нашому випадку це probe-rs, тому команда вже записує код на пристрій. cargo build runner не запускає.

build.rs

fn main() {
    println!("cargo:rustc-link-arg=-Tdefmt.x");
    println!("cargo:rustc-link-arg=-Tlinkall.x");
}

Цей файл виконується на комп'ютері під час збірки. Він передає лінкеру скрипти розташування секцій та підтримки defmt. Це не код, який потім виконує Lilka.

src/main.rs

#![no_std] вимикає стандартну бібліотеку, а #![no_main] — звичайний запуск програми через системний main. Точку входу створює #[esp_hal::main] або #[esp_rtos::main], залежно від режиму.

esp_bootloader_esp_idf::esp_app_desc!() додає опис застосунку для ESP-завантажувача. Саме використання цього крейта не означає, що програма працює на всьому ESP-IDF: SDK залишається на esp-hal.

#[panic_handler] потрібен, бо звичайної реалізації зі std немає. У прикладах він друкує причину й зупиняє виконання. Для власної програми вже вирішуйте, чи потрібне перезавантаження, чи очікування підключення налагоджувача.

Як розділяти свій код

Не обов'язково тримати все у main.rs. Нехай точка входу створює периферію, а окремі модулі займаються станом, малюванням та даними. Звичайний модуль з mod ui; не потребує нового Cargo-пакета.

Логіку, яка не знає про GPIO та SPI, простіше перевіряти на комп'ютері. Але успішний host-тест не перевіряє дисплей або карту: апаратну частину все одно треба запускати на Lilka.