Пару слів про Rustilka

rustilka original TM R logo

Вихідний код на Gitlab

Rustilka не тільки бібліотека

Rustilka це мініпроєкт підтримки мови Rust для Лілки, який має аналогічну мету як і сама консоль - "Полегшити", але якщо девайс полегшує навчання в embedded, то ця бібліотека полегшує тільки розробку. Чому? На думку автора rustilka облегшення навчання вже не має сенсу, адже якщо ви знаєте Rust, то ви вже знаэте багато, так теоритично можна почати вивчати embedded з мови Rust(адже для цієї сфери вийшло багато фреймворків і необхідних бібліотек та утиліт), але все одно, сама лалка позиціюватися для новачків(які на мою думку як мінімум знають Arduino), Rust сам по собі не для новачків, а її embedded частина потребує більше часу.

Також цей проєкт чисто технічно незалежний від оригінального SDK, при наявності можливостей ми могли б мати IDF версію, але на думку автора це не має сенсу. Адже для того що б виконати головну мету Лілки було прийняти рішення взяти bare-metal HAL який не має ніяких зв'язків з IDF, і також сама rustilka(в якості бібліотеки) як і казалося раніше повністю незалежна в плані коду, вона має свою імплементацію, ідеологію(частково), і дизайн бібліотеки. Це було зроблено через бажання переписати все на Rust йти за звичаями ком'юніті расту, тобто без біндів, своя реалізація і т.д.

Яка мета ось цього вашого "мініпроєкту"

Доволі проста! Спростити розробку. Вона в собі має генератор який спрощує вам налаштування проєкту для створення прошивки, адже деяка функціональність ще потребує окремі версії, як то, наприклад nightly для async, чи флаги лінтів для wireless протоколів. Також ця документація є частиною мініпроєкту.

Для кого розрахована бібліотека?

Для людей які як мінімум прочитали Rustbook і попрактикували мову, і як мінімум спробували написати просту програму для МК на расті. Також було б непогано мати знання фізики 8-9 клас розділу "Електроніка" і поверхневі знання embedded по типу arduino і мікроконтроллерів які часто використовують для DIY.

Чи має він підтримку "версій лілки"?

Ні! Офіційно можна вважати V1 застарілою:

No more V1

Тому на цей час пишеться підтримка тільки для V2.

Чому проєкт має назву "Rustilka"

Все доволі просто й очевидно, поєднання Rust і лілка.

Раніше був варіант "Кіра". Чому? В офіційній документації в розділі поширених запитань говориться що проєкт назвали на честь кота Андерсона, а якщо вже пішла така тенденція, то я б міг назвати в честь своєї кішки Кіри.

Що таке лілка?

Лілка

Взагалі про лілку в подробицях є в її особистій документації.

Але якщо сказати коротко: Це DIY консоль яку можна відносно легко зібрати з готових модулів, і дешево(в районі 500-600 гривень). Її мета це навчання у світі embedded(я зрозумів автор ліби rustilka) для навчання з отриманням максимум можливостей.

Сам пристрій базується на мікроконтроллері ESP32S3 що має хорошу потужність разом з другим LP ядром і бездротові протоколи Bluetooth і WiFi.

В основному прошивка пишеться на Arduino фреймворці й спеціальній однойменній бібліотеці, для всіх подробиць є вищезгадана офіційна документація.

Все ж таки яка різниця між оригінальним SDK та rustilka bsp?

1. Різні мови програмування та різні імплементації.

SDK для Лілки розробляється саме на Arduino фреймворці який є модифікований і спрощений C++, бібліотека підтримки повністю написана на Rust, навіть всі бібліотеки написані які використані в проєкті з нуля написана на Rust, тобто ніяких "біндів" не повинно бути.

2. Присутність та відсутність IDF.

Все ж таки rustilka немає IDF, з одної сторони всі функції написані тільки на Rust, менше займає пам'яті, та (мабуть) працює швидше без зайвих викликів, адже бібліотека дає прямий доступ до периферії. З іншої сторони все трохи складніше.

3. Все зайве чи потрібне?

Ориганільний SDK має окрім стандартної ініціалізації заліза на платі для маніпуляцій з периферією, а ще й деякі функції по типу математичних функцій та UI. А rustilka йде так само як esp-hal і різні "хали", тобто все що є на платі та потрібне, а різну функціональність пропонується написати самому для навчання(для чого і була створена лілка).

4. RTOS чи async?

Частіше за все використовують різні RTOS по типу FreeRTOS. В бібліотеці вже вбудована проста async функціональність через проєкт embassy. Перевага тут в, тому що асинхронність займає менше постійної пам'яті та/або асинхронно виконувати окремі функції.

5. Мінімалізм чи простота для користувача?

Як і говорилося бібліотека має тільки підтримку периферії та модулів на платі, але все ж таки деякі на даний момень модулі мають raw ініціалізацію які потребують детальнішої роботи в коді. Комусь це здається ускладненням, але все ж таки на думку автора це як раз і потрібно для low level програмування та навчання embedded.

6. Перевага мов

Сама Rust має дуже багато переваг, так звісно вона не замінить повністю той же C++ і навіть богоподібний C, але все ж таки, безпека пам'яті дуже болюча тема на сьогодні, адже контроль над ними в C/C++ виконують самі розробники, Rust своєю чергою має свої правила безпеки пам'яті, і перевіряє їх перед компіляцією, цей етап називається borrow checker.

Не все так просто піде (((

Існує проблема, таргет xtensa немає в офіційному переліку таргетів для компіляції, і тепер для цього треба використовувати зовнішні інструменти.

Що б встановити все це діло нам треба espup.

cargo install espup

І далі треба встановити сам тулчейн:

espup install

Також під кінець інсталяції ви побачите те що вам треба КОЖЕН РАЗ ВИКОНУВАТИ КОЛИ ВИ ВІДКРИВАЄТЕ НОВИЙ ТЕРМІНАЛ І ХОЧЕТЕ ЗКОМПІЛЮВАТИ

. $HOME/export-esp.sh

Детальніше можете прочитати на самій сторінці Github espup.

Створення проєкту.

Також тут свої нюанси, для нормальної компіляції потребується детальніше налаштування проєкт. Тому що б спростити створення та конфігурацію є можливість згенерувати проєкт за допомогою cargo-generate. Для початку встановіть його:

cargo install cargo-generate

Далі вже ви можете почати:

cargo generate https://gitlab.com/imbiruss/rustilka.git template

Вибори

Перед початком звісно у вас запитають назву проєкт, думаю тут все зрозуміло:

Назва для проекту

Алокація

Аллокація потребується для різних колекцій і типів даних що розширюються динамічно, наприклад Vec, HashMaps, BTree і т. д. ЧИ треба вам та алокація?

Логування та println!

Логування в esp-hal йдеться через usb-serial-jtag чи usb-serial адаптер на багатьох платах, тому тут є й опціональний вибір чи треба вам логування. Згодом буде ясно як моніторити ці логи та прінти. Також це опціоеально через те що самі розробники повідомили про те що esp-backtrace може сповільнувати роботу програми, але на моєму досвіді такого не було, тому тут на ваш розсуд.

Довбаний println! відсутній в  no_std >:(

ВІФІ та синій зуб

Незважайте на те що пункт має тільки wifi в тексті, також підтримується протокол синього зуба(Bluetooth), просто сама бібліотека для підтримки бездротових протоколів називається esp-wifi, хоч і має підтримку Bluetooth.

Опа, віфі і блютузіу

Вітаю, у вас все готово!

Тепер у вас згенерований проєкт, заходьте в редактор та робіть мигалку з асінком по блютузу і логуванням :)

фіналочка

Все ж таки як зкомпілювати?

Взагалі можна просто зкомпілювати, потім зконвертувати в .bin за допомогою binutils, а там вже різними утилітами завантажити прошивку, НУДНО! Для полегшення цього процесу можна встановити утиліту espflash:

cargo install cargo-espflash

Відтепер достатньо однієї команди для компіляції та прошивання:

cargo espflash flash --monitor

(аргумент --monitor лише для того що б дивитись різні логи які надсилаються через println! чи крейт log)

Але якщо вам ліньки навіть таке прописувати чи ви звикли до cargo run то можна прописати таке в .cargo/config.toml:

[target.xtensa-esp32s3-none-elf]
runner = "espflash flash --monitor"

Налаштування та початок розробки.

Після того як ви створили заготовку за допомогою cargo generate ви тепер можете почати розробляти. Як бачите, вже ініціалізований об'єкт lilka

#![allow(unused)]
fn main() {
let mut lilka = Lilka::new(Configuration::default()).unwrap();
}

Рядок Configuration::default() дає дефолтні значення, а саме:

  • Частота чіпа 160MHz
  • Частота SPI 60MHz
  • Порядок оновлення "Vertical Refresh Order - Top To Bottom" і "Horizontal Refresh Order - Left To Right"
  • Орієнтація екранцу в градусах 270. Міняти тільки тоді якщо знаєте що робите
  • Тірінг дисплея вимкнений

Якщо хочете налаштувати під себе то можете це зробити через new():

#![allow(unused)]
fn main() {
//умовно у вас імпортовано prelude
//use rustilka::prelude::*;

let cfg = Configuration::new(50.MHz, ClockFreq::Clock80MHz, RefreshOrder::default(), Rotation::Deg270, TearingEffect::HorizontalAndVertical)
let lilka = Lilka::new(cfg).unwrap();
}

З самого початку вже ініціалізований embassy Executor, що дасть вам змогу писати async таски і усіляке інше, детальніше про async тут.

Також вам не треба ініціалізувати peripherals та систему, все вже зроблено і знаходитья в об'єкту lilka

#![allow(unused)]
fn main() {
let rng_periph = lilka.peripherals.RNG; //це тільки об'єкт периферії для створення rng, як такий об'єкт він не буде мати функціональності
}

Якщо ж ви хочете використовувати всі можливості плати, то можете активувати радості у всі штани включити в features full_fun:

rustilka = {version = "0.0.1-alpha", features = ["full-fun"]}

Відтепер вам доступна вся периферія на платі, тому розважайтесь!

TODO Контроллер

TODO Дисплей

TODO SdCard

TODO Вбудований UART

TODO Отримання рівня батареї

TODO Керування бузером

TODO I2S

Написання асинхронних задач за допомогою embassy executor

Коли ви згенерували проект за допомогою template, ви побачите свій стартовий код.

І як ми бачимо тут вже ініціалізований executor

#![allow(unused)]
fn main() {
let syst = SystemTimer::new(lilka.peripherals.SYSTIMER); //Ініціалізація таймера для executor
rustilka::hal::embassy::init(&lilka.clock, syst); //Визначаємо цей таймер для роботи
let executor = EXECUTOR.init(embassy::executor::Executor::new());//Ініціалізуємо сам executor
}

Відтепер вже ви можете писати різні таски.

Давайте, наприклад напишемо два таски для перемикання світлодіодів асинхронно.

По перше треба ініціалізувати піни для світлодіодів.

#![allow(unused)]
fn main() {
let io = IO::new(lilka.peripherals.GPIO, lilka.peripherals.IO_MUX);//ініціалізуємо GPIO
let firstled = io.pins.gpio47.into_push_pull_output();//Перший 
let secondled = io.pins.gpio14.into_push_pull_output();//Другий
}

Оскільки у вас ініціалізований в Cargo.toml embassy_executor embassy_time, нам треба за допомогою макросів вказати таск.

#![allow(unused)]
fn main() {
#[embassy_executor::task]//обов'язково над функцією(також async обов'язково прописати)
async fn first_led(mut led: GpioPin<Output<PushPull>, 47>) {
    loop {
    embassy_time::Timer::after_millis(100u64).await;//чекаємо 100 мілісекунд
    led.set_high();//вмикаємо
    embassy_time::Timer::after_millis(100u64).await;
    led.set_low();//вимикаємо

    }
}
}

Далі напишемо інший аналогічний таск, але з іншим піном

#![allow(unused)]
fn main() {
#[embassy_executor::task]
async fn second_led(mut led: GpioPin<Output<PushPull>, 14>) {//зверніть увагу на інший номер піна в типі
    loop {
    embassy_time::Timer::after_millis(1000u64).await;//чекаємо секунду
    led.set_high();
    embassy_time::Timer::after_millis(1000u64).await;
    led.set_low();

    }
}
}

Таски готові, далі нам треба вже заспавнити таски для виконання, методом та внутрішнім замиканням ми вже зможемо їх спавнити

#![allow(unused)]
fn main() {
executor.run(|spawner| {//створюємо замикання і спавнимо в цьому блоці
        spawner.spawn(first_led(firstled)).expect("failed to run first led");//Спавнимо перший світлодіод
        spawner.spawn(second_led(secondled)).expect("failed to run second led");//Спавнимо другий світлодіод
    });
}

Чому rustilka використовує esp-hal замість esp-idf-hal?

По-перше, це через ідеологію мови Rust, адже бінди в Rust небезпечні з точки зору програмування, а esp-idf-hal має прив'язку до фреймворку IDF, так це доволі не легко не маючи бібліотеки які полегшують розробку, але це дає переваги у вазі та швидкості. По-друге, по звичці Rust))). Точніше все повинно бути переписано та імплементовано з нуля, esp-hal має прямий доступ до периферії через PAC.

Який сенс мені взагалі використовувати якщо є C/C++ і т.д.?

Не знаю, чесно... Ваше право на чому писати, автор же писав проєкт що б популяризувати Rust та її embedded сфера яка росте на очах. Та й варіацію захотілось :).

Чому крейт embedded-graphics не прописаний в template Cargo.toml?

На всяк випадок, адже доволі довго не було різних бібліотек абстракцій для графіки, але відносно нещодавно з'явився slint, універсальний GUI фреймворк не тільки для desktop, а і для мікроконтролерів.

Чому вся периферія розділена по своїм features?

Причина проста, хочется гнучкості! А чому би й ні? Це все ж таки непогано!

ДЕ RTOS? ЧОМУ ЇХ НЕМАЄ?

Доволі банальна причина, а який сенс якщо є embassy? Так на Rust є різні RTOS, їх навіть декілька, і навіть в esp-idf-hal є вбудований freertos, але це вже забирає багато ресурсів, embassy ж навіть не ОС, а тільки легкий та простий фреймворк для асинхронних тасків який працює завдяки використанню таймера.

В якому сенсі незалежний від оригінального проєкт "Лілка"?

Тут треба було звісно примітити що незалежний в плані софта і рішень, код ліби на Rust на момент написання цього питання писав тільки ImbirWithoutSugar, і в цілому всі рішення для цього мініпроєкту підтримки вирішувалися тільки ним. Єдине від чого залежить, так це від домену, адже що домен lilka.dev, що rust.lilka.dev належить Андерсону. І до речі що до домену, дякую Андерсону за те що дав сабдомен для хостингу цієї книги/сайту мініпроєкту Rustilka.

Є якісь приклади коду для rustilka/esp32s3?

Так звісно, їх звісно немає в монорепо rustilka, але все ж таки там можна розібрати як адаптувати цей код з бібліотекою. Приклади тут

Які бібліотеки можуть бути корисними чи в пригоді?

Наразі в crates.io є навіть окремий каталог бібліотек для #[no_std] середовищ, що дуже добре для нашого випадку, але їх куча, складно знайти цікаві та необхідні, тому ця сторінка і для цього!

embedded-graphics та різні додатки

Цей крейт стане вашим рендером, хоч і базова бібліотека має тільки примітивні лінії, фігури й різні прості малювання, але цього достатньо, для того що б зробити що завгодно. З іншими додатковими крейтами різноманіття збільшується.

embedded-graphics

Список деяких корисних крейтів

Драйвера для дисплеїв

USB класи та різна функціональність.

Продовжуючи тему навчання embedded, то бібліотека не надає просте керування USB, дається тільки low-level доступ до периферії, і написати логіку повинні самотужки, наразі по підтримці класів USB все добре.

usb-device головний дескриптор usb

Дескриптор для HID(клавіатури, мишка і т.д.)

Інша реалізація HID

Serial usb

DFU

USB-Ethernet

Switch_hal для зручної маніпуляції з Controller: тут крейт.