esp-rs

Activity

Stars and mentions · Last 30 days, Oct 6 – Nov 4

GitHub stars0
Mentions3
01234Oct 6Oct 20Nov 4
UTC days · repositories esp-rs owns0 repositories

Top repositories

esp-rs’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside esp-rs’s account where esp-rs opened or reviewed pull requests in the past 365 days.

Who noticed esp-rs

Everyone who starred or forked esp-rs’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

3posts
Nov 50 posts
  1. Rust で ESP32 を動かす はじめに これまで、マイコンは Raspberry Pi Pico 2 W を使ってきましたが、別のマイコンも試してみたくなり、ESP32 を触ってみることにしました。 また、普段は MicroPython で書いていますが、Rust に結構興味があるので、今回は Rust で動かしてみます。 ボード上の LED で L チカするところまでやってみます。 ESP32 を開発している Espressif が Rust での book を公開しているので、それに沿ってやっていきます。 docs.espressif.com 環境 - 開発ボード: Freenove ESP32 WROOM Board - Freenove/Freenove_ESP32_WROOM_Board - documentation.espressif.com - amazon.co.jp - Amazon で安かったので、これにしました - ホストPC: Mac - Rust でクロスコンパイルできるので、たぶんなんでも大丈夫です build target について ESP32 シリーズは、チップによって CPU のアーキテクチャが違います。 > - Xtensa: The ESP32 and ESP32-S series are based on the Xtensa architecture. > - RISC-V: The ESP32-C and ESP32-H series are based on the RISC-V architecture. docs.espressif.com 今回使う ESP32-WROOM-32E は、データシートに > Built around ESP32 series of SoCs, Xtensa® dual-core 32-bit LX6 microprocessor とあるので、中身は無印の ESP32(Xtensa)だとわかります。 RISC-V のチップであれば、 $ rustup target add riscv32imc-unknown-none-elf # ESP32-C2, ESP32-C3 $ rustup target add riscv32imac-unknown-none-elf # ESP32-C6, ESP32-H2 のように、通常の Rust のツールチェーンに target を追加するだけでビルドできます。 どちらも Tier 2 の target になっています。 doc.rust-lang.org Xtensa の場合は Rust(というよりもバックエンドの LLVM)で、まだ十分にサポートされていないので、Espressif が fork した Rust のコンパイラを使う必要があります。 これをインストールするためのツールとして、espup が用意されています。 esp-rs/espup espup によって、 - Xtensa に対応した、Espressif の Rust の fork - esp-rs/rust - Xtensa に対応した、LLVM の fork - espressif/llvm-project - リンクのための GCC - espressif/crosstool-NG がインストールされます。 やってみる ツールチェーンのインストール まずは、上で説明した通り Xtensa に対応するために espup をインストールします。 docs.espressif.com $ cargo install espup --locked $ espup install --targets esp32 [info]: Installing the Espressif Rust ecosystem [info]: Checking Rust installation [info]: Installing Xtensa Rust 1.97.0.0 toolchain [info]: Installing GCC (xtensa-esp-elf) [info]: Installing Xtensa LLVM --targets を指定しない場合は、すべてのチップ向けのツールチェーンがインストールされるので、今回は esp32 だけにしています。 インストールが終わると、esp という名前のツールチェーンが追加されます。 $ rustup toolchain list stable-aarch64-apple-darwin (active, default) esp また、espup install を実行すると、ホームディレクトリに export-esp.sh が作られます。 export LIBCLANG_PATH="/Users/tsuzuki-takaaki/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-21.1.3_20260408/esp-clang/lib" export PATH="/Users/tsuzuki-takaaki/.rustup/toolchains/esp/xtensa-esp-elf/esp-16.1.0_20260609/xtensa-esp-elf/bin:$PATH" rustc や cargo は、rustup が rust-toolchain.toml を見て esp ツールチェーンのものを呼び出してくれますが、リンクに使う GCC に関しては、rustup が切り替えてくれないので、このスクリプトを読んで、PATH を通す必要があります。 このファイルは、ターミナルを開くたびに読み込む必要があります。 ツールのインストール Rust での ESP32 の開発が簡単になるように、ツールが用意されているので、それをインストールします。 - esp-generate - プロジェクトのテンプレート生成 - docs.espressif.com - espflash - ビルドしたプログラムの書き込み - docs.espressif.com $ cargo install esp-generate --locked $ cargo install espflash --locked プロジェクトの作成 esp-generate を使ってプロジェクトを作ります。 docs.espressif.com $ esp-generate --headless -o esp32 -o esp32-wroom-32e blinky esp-generate は、何も指定しないと TUI でオプションを選ぶ形になりますが、今回は --headless で直接指定しています。 -o esp32 でチップを、-o esp32-wroom-32e でモジュールを指定しています。 オプションの一覧は、 $ esp-generate list-options でみることができます。 blinky % tree . ├── build.rs ├── Cargo.toml ├── rust-toolchain.toml └── src ├── bin │   └── main.rs └── lib.rs 3 directories, 5 files rust-toolchain.toml で、esp ツールチェーンを使うように指定されています。 [toolchain] channel = "esp" そのため、このディレクトリの中では esp ツールチェーンが使われます。 $ rustup toolchain list stable-aarch64-apple-darwin (default) esp (active) .cargo/config.toml では、build target と、cargo run したときに実行するコマンドが指定されています。 # Keep compilation settings in this file and user/runtime settings in `esp-config.toml`. include = ["esp-config.toml"] [target.xtensa-esp32-none-elf] runner = "espflash flash --monitor --chip esp32" [build] rustflags = [ "-C", "link-arg=-nostartfiles", ] target = "xtensa-esp32-none-elf" [unstable] build-std = ["core"] runner に espflash flash --monitor が指定されているので、cargo run でビルドから書き込みまででき、書き込んだあとはそのままシリアルモニタになります。 L チカする esp-generate によって、以下のファイルが生成されます。 #![no_std] #![no_main] #![deny( clippy::mem_forget, reason = "mem::forget is generally not safe to do with esp_hal types, especially those \ holding buffers for the duration of a data transfer." )] #![deny(clippy::large_stack_frames)] use esp_hal::clock::CpuClock; use esp_hal::main; use esp_hal::time::{Duration, Instant}; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description> esp_bootloader_esp_idf::esp_app_desc!(); #[allow( clippy::large_stack_frames, reason = "it's not unusual to allocate larger buffers etc. in main" )] #[main] fn main() -> ! { // generator version: 2.0.0 // generator parameters: -o esp32 -o esp32-wroom-32e let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); // The following pins are used to bootstrap the chip. They are available // for use, but check the datasheet of the module for more information on them. // - GPIO0 // - GPIO2 // - GPIO5 // - GPIO12 // - GPIO15 // These GPIO pins are in use by some feature of the module and should not be used. let _gpio6 = peripherals.GPIO6; let _gpio7 = peripherals.GPIO7; let _gpio8 = peripherals.GPIO8; let _gpio9 = peripherals.GPIO9; let _gpio10 = peripherals.GPIO10; let _gpio11 = peripherals.GPIO11; let _gpio16 = peripherals.GPIO16; let _gpio20 = peripherals.GPIO20; loop { let delay_start = Instant::now(); while delay_start.elapsed() < Duration::from_millis(500) {} } // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.2.2/examples } ピンの配置図を見ると、LED は、 - LED TX - LED RX - LED IO2 の 3 つが用意されていることがわかります。 github.com/Freenove/Freenove_ESP32_WROOM_Board/bl… 今回は、LED IO2 を光らせます。 esp-hal でだいぶ抽象化されているので、Output とピンを指定するだけで LED を光らせることができます。 github.com/esp-rs/esp-hal/blob/41b881629e88acdf00… #![no_std] #![no_main] use esp_hal::clock::CpuClock; use esp_hal::delay::Delay; use esp_hal::gpio::{Level, Output, OutputConfig}; use esp_hal::main; #[panic_handler] fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } // This creates a default app-descriptor required by the esp-idf bootloader. // For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description> esp_bootloader_esp_idf::esp_app_desc!(); #[main] fn main() -> ! { let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max()); let peripherals = esp_hal::init(config); let mut led_io2 = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default()); let delay = Delay::new(); loop { led_io2.toggle(); delay.delay_millis(500); } } $ cargo run

    1 like
  2. Все уходят на Rust: в ML c Python, в бэкенде с Java, в микроконтроллерах c С/С++ Привет, Хабр! Сегодня заглянем на онлайн-митап от MWS. Посмотрим, как в трех разных кейсах переходили на Rust. При работе с ML перешли с Python на Rust для синтеза и распознавания речи, ускорив работу сервисов. На другом проекте перешли с Java на Rust — в итоге стало меньше инцидентов, стала быстрее выкатка фич и разгрузили железо. И изюминкой будет рассказ про использование Rust вместо C/C++ в микроконтроллерах с игрой в динозаврика из Google Chrome в реальности. Кейс № 1. Rust для ML вместо Python Кейс № 2. Rust на проекте вместо Java Кейс № 3. На микроконтроллере крутится Rust MWS Meetup — серия технических встреч для разработчиков и инженеров, которые проходят онлайн и офлайн. На мероприятиях специалисты компании и приглашенные эксперты разбирают практические задачи из разработки, инфраструктуры и облачных технологий. До этого митапа не знал, что у MWS есть свои открытые онлайн-митапы. Это был 10-й митап, и в нем разбирали кейсы с Rust. Как раз этот язык начал изучать в этом году, поэтому пришел послушать — мне было интересно, как он применяется в реальных проектах. После митапа решил, что новый проект буду делать на Rust, отказавшись от Python и Go. А еще захотелось купить микросхемы, радиодетали и попрограммировать свои устройства. Кейс № 1. Rust для ML вместо Python Артур Казарян выступил с докладом «Ускоряем инференс ML с Rust». Он рассказал про платформу синтеза и распознавания речи, в которой используется интерференс-сервер Triton, работающий на видеокартах NVidia Triton поддерживает разные бэкенд-серверы, и только один Python-бэкенд, который поддерживает предобработку и постобработку. На других языках нет такого решения. Рассмотрим ансамблевую модель ONNX для классификации текста. Модель работает только с цифрами, поэтому нужна предобработка, превращающая текст или картинку в цифры, и постобработка, преобразующая цифры в текст. В этой схеме можно упереться в производительность модели или в производительность Python на предобработке и постобработке. Во втором случае и понадобилось кастомное решение на Rust. Для бесшовной работы пришлось сделать так, чтобы никто из работающих с сервером не догадывался, что он написан на Rust, а не на Python. Чтобы этого добиться, подключились к header-файлам Triton, написанными на C++. На Rust есть библиотека bindgen, которая генерирует файл bindings.rs, с которыми уже можно работать на Rust. После компиляции получается один .so-файл, который Triton ищет в трех местах: /mnt/model/1/libtriton_model.so (зависимые модели, входящие в линковку) /mnt/model/libtriton_model.so (исходный код конкретной модели) /opt/tritonserver/backends/model/libtriton_model.so (bindings.rs, сгенерированный bindgen) И этот файл отдаем на интеграцию — все очень просто. Теперь переключимся на то, как устроен Rust-бэкенд: В качестве примера — создание модели на Rust. В настройках прописываем путь к rust-бэкенду, а файл model.rs — главный файл модели. При переходе от Python к Rust важно, чтобы все типы данных совпадали для обеспечения совместимости, этому уделили отдельное внимание: Далее нам показали в сравнении три основных функции — initialize, execute, finalize. На Python initialize() выглядел так: def initialize(self, args): self.tokenizer = ( AutoTokenizer.from_pretrained( “/models/…/bert_tokenizer” ) )sourcecraft.dev На Rust теперь выглядит так: fn initialize(config: HashMap<&str, &str>) -> Result<Self, Error> { let tokenizer = Tokenizer::form_file( repo.join(“/models/…/bert_tokenizer/tokenizer.json”) )?; Ok(Self { tokenizer }) }sourcecraft.dev Код основной логики на Python выглядел так: decoded = self.tokenizer.batch_decode(input_) response = np.array(decoded, dtype=np.object_) output_tensor_transcr = pb_utils.Tensor(“OUTPUT”, response) interence_response = pb_utils.InterenceResponse( output_tensors=[output_tensor_transcr] ) responses.append(inference_response)sourcecraft.dev Тот же код на Rust стал выглядеть так: match self.tokenizer.decode_batch(&batch_slices, true) { Ok(decoded) => { let mut output_data: Vec<u8> = vec![]; for row in decoded.iter() { let encoded: Vec<u8> = rust_backend::utils::encode_string(&row); output_data.extend(encoded); } response.set_output( “OUTPUT”, &output_data, vec![batch_size, 1], TritonserverDataTipe::Bytes, TritonserverDataTipe::Cpu )?; }sourcecraft.dev И напоследок выгрузка модели, в которой ничего не происходит На Python: def finalize(self): passsourcecraft.dev На Rust: fn finalize(&self) -> Result<(), Error> { Ok(()) }sourcecraft.dev Теперь переходим к сравнению производительности Python и Rust. Нужно сравнить производительность предобработки и постобработки, поэтому исключим саму модель и получился 10-кратный рост производительности. Но после добавления ONYX-модели разница уже не так заметна: порядка 20%. В этом случае нужно уже думать об оптимизации интерференс-модели. Далее перешли к конкретным кейсам, в котором оптимизировали саму модель: После показа кейсов перешли к тому, после чего вообще принимать решение о необходимости перехода с Python на Rust? Для этого смотрим состав ML-моделей на сервере, спамим запросы к ним, чтобы выяснить, на что сколько времени уходит. Если работа ML-модели занимает больше 90% времени, то нет смысла оптимизировать предобработку и постобработку, так как прирост будет несущественным. Затем нужно смотреть на уровень выше: в первую очередь оптимизировать медленные сервера. Плюсы в сохранении архитектуры, экономии ресурсов, неизменности API. Минусы в том, что ускорение не гарантировано, экосистема и компетенций меньше. Кейс № 2. Rust на проекте вместо Java Следующим выступал типлид Дмитрий Кузьмин с презентацией «Как мы снизили стоимость продукта после миграции части стека на Rust» на системе медиации и предбиллинга. Пришли к необходимости переписывания legacy на другой язык, чтобы снизить постоянные затраты и негатив клиентов. И все это в критической системе с цепной реакцией при падении и невозможностью отката. Прямые затраты — это 3–8 часов в неделю на разбор инцидентов, пятничные падения с ночными дежурствами и работой в выходные. Чем дольше идет разбор — тем больше тратится денег клиентов и больше их недовольство. Команда из пяти разработчиков, 20+ инфраструктурных сервисов с нагрузкой в 200 000 RPS по всей системе и 10–20 Тб трафика в сутки. Стек до изменений был в основном Java, а также Shell, Perl, PHP, Go. Рассматривали несколько вариантов: оставить как есть, языки с Garbage Collector, Rust. При анализе оказалось, что часть базовых вещей Rust решают часть эксплуатационных задач и задач по разработке. Система критическая, поэтому делали постепенную миграцию в рамках параллельно запущенного пилота в течение четырех недель с затратами в 130–160 человеко-дней. Что по расчетам приводило к окупаемости меньше чем за месяц. При этом старое решение продолжало работать, и при возникновении проблем на проде могли сразу переключиться на него. Также в новом решение добавили инструменты, которые отсутствовали в старом решении: инструменты межсервисного взаимодействия, трейсинг, расширенного логинования. Использовали: clippy + rustimt + sonarqube: ревью дешевле, стиль единый, контроль качества и сложности кода cargo test: тесты как часть пайплайна, cargo-audit, cargo-deny Наблюдаемость: OpenSerarch — логи, Sentry — трейсинг, Telegraf — метрики Из сложностей: пришлось дописывания тесты для старых систем для возможности сравнения старой и новой системы. На legacy системе о багах узнавали в runtime, а также были утечки и проблемы на GC. В новой системе о багах узнавали при сборке, нет GC и память заранее определена. В итоге инциденты и время на их разбор сократились в несколько раз. Неожиданно сократилась потребляемая память, а также уменьшилось время на онбординг новых сотрудников и реализацию фич. Пришли к тому, что нужно сильнее покрывать тестами и повышать наблюдаемость через трейсинг и межсервисное взаимодействие по логам. Также лучше не трогать то, что не создает инцидентов сейчас. И конечно, нужно все измерять без домыслов. Дмитрий рекомендует начинать с пилота, который решает большую часть проблем. Важно всех обучать и проводить внутренние встречи. Также надо продумывать и проверять взаимодействие со старыми системами заранее и переписывать по частям. В конце доклада спикеру задали несколько вопросов. Решение о переходе было общекомандным. RTS не изменился, потому что количество клиентов не менялось. При переходе не использовали ИИ, переписывали вручную. Рынок Rust идет с задержкой, но появляются вакансии. Интересно, что Rust-разработчики с плюсовиками друг друга понимают, и плюсовики сами уже переходят на Rust. Важно выбирать тот язык, который решает вашу задачу. Сложности изучения Rust — стандартные: непонимание Borrow Checker и понимать что говорит Cargo. Делали профилирование — у Java было больше потребление input/output по дискам, а у Rust значительно ниже. В остальном не было видно значительных отличий. Кейс № 3. На микроконтроллере крутится Rust Финальный доклад — от Марка Вартаняна «Прошить железо c Rust: ESP32 за 20 минут». Марк обычно пишет на Go в облаке, но расскажет об опыте программирования на Rust. Основное преимущество Rust — это работа с памятью и владение ресурсами. А это как раз важно для микроконтроллеров — в показанном Марком микросхеме всего 100 МГц и 100 Кбайт оперативной памяти.  С микроконтроллерами Марк впервые пересекся на проекте телемедицины, в котором он писал бэкенд для электронной таблетницы: может светить огнями, кричать динамиками, крутить моторы, отсыпать таблетки и отправлять на сервер сообщение о пропуске — однажды это спасло жизнь человеку. Когда Марк занимался проектом, этот девайс стоял на столе и управлялся из консоли. В итоге программирование выходит в настоящий мир — и это вызывает восторг. Проект, который сделал Марк: к микроконтроллеру подключен акселерометр и маленький экранчик. Прыгая с этим устройством, управляешь динозавром в игре, которая показывается на экране: На микроконтроллере ESP32, он по всем показателям на порядок лучше Arduino. При этом она меньше — 5 × 5 миллиметров: Также есть на рынке и другие решения.  STM32 — мощные и отличные решения, но на них нет Wi-Fi. Wi-Fi есть на Raspberry Pi Zero, но это уже другая лига со стоимостью чипа от 5к и гигагерцовым процессором. Забавно, что у Arduino есть модель с поддержкой WiFi, но эта поддержка сделана отдельным модулем ESP32. При выборе чипов от ESP важно понимать, что ESP8266 — древний чип, на котором Rust не запустишь. ESP32 — тоже старый, для запуска Rust на нем нужно патчить компилятор. Поэтому надо сразу брать ESP32-C. Другие варианты без Wi-Fi: ESP32-P — для высокого перформанса, ESP32-E — энергоэффективный. После выбора чипа можно выбрать вариант поставки этого чипа — он может быть и с двумя USB, и с OLED-экраном, и в небольшой плате. Сам микроконтроллер — это черная микросхема в виде ромба. Купить можно на Озоне или другом маркетплейсе, они очень дешевые. Важно не взять случайно ESP32 без суффикса. И важно, чтобы архитектура была RISK-V (можно проверить на сайте производителя) — это то, на чем можно Rust запустить. Для начала работы потребуются монтажные провода DuPort, монтажная плата, набор светодиодов и резисторов. Чтобы познакомиться с самим микроконтроллером, можно почитать документацию на 600 страниц, но проще найти в интернете схемы распиновки подобного вида: Первое, что мы тут видим, — это USB, от которого приходит питание 5 вольт, которое разведено на первые две ноги. Также есть пониженное напряжение 3,3 вольта, которое используется микроконтроллером. На самой плате внизу красная C3 — это маленькая Wi-Fi-антенна и рядом с ним белый кварц для поддержания правильной частоты работы Wi-Fi. Над кварцем — микросхема, которая понижает напряжение с 5 до 3,3 вольта. На плате есть светодиод, подключенный к 8-й ноге. Есть две кнопки: левая кнопка программируемая, подключена к 9-й ноге. Правая — непрограммируемая: она для сброса и перезапуска микроконтроллера. Все полезные ноги помечены фиолетовым — это GPIO (General Purpose Input/Output). Они могут работать на вход и на выход, где 1 — это HIGH, 0 — это LOW. Ноги помеченные коричневым позволяют работать с аналоговым сигналом по ADC до 3,3 вольта. Прямое управление через ноги требует ресурсы процессора, поэтому есть отдельные интерфейсы, по которым можно отправлять сразу наборы байт по определенному протоколу, это разгружает процессор. Рассмотрим шину I2C — к ней можно подключать несколько устройств параллельно. В данном проекте к ней подключен акселерометр и экран. Можно давать команды — например: «дай мне ускорение» или «выведи пиксель» — и получать ответы. У этой шины есть режимы 1 Гбит и 5 Гбит, что позволит подключать достаточно мощные компоненты. Кроме шины I2C есть UART, которая позволяет подключить консоль, к которой подключаешься как к COM-порту. Можно слать текстовые команды и получать текстовые ответы. Оно дуплексное, то есть можно одновременно отправлять и получать. На картинке подключен GPS-модем, и именно эту модель не рекомендует Марк. Можно, например, запросить уровень сигнала (29 — это максимальный) или уведомить, что поступил звонок или пришло СМС. Обмен идет по двум проводам, но можно повесить только одно устройство, так как это не шина. Но как эти все режимы работают? У каждой ноги есть внутри переключатель GPIO Mux, который переключается в зависимости от присланного в определенный регистр. Также при работе с микропроцессором столкнешься с подобными схемами, в которых описаны возможности микропроцессора и вся поддерживаемая периферия. Есть даже отдельные блоки аппаратного шифрования. Почему именно ESP32? Потому что у них на GitHub (EspressIf) и у них есть официальная поддержка Rust. esp-pucs — это структуры чипа, при записи в которые получаешь определенное поведение чипа. esp-generic — подготавливает проект. espFlash — для прошивки и ее записи на сам чип. Все это нужно для работы поставить на компьютер, но лучше это поставить в докер-контейнер. Чтобы можно было работать с разными микросхемами и не приходилось каждый раз удалять ненужное и ставить нужное. Итого в докер нужно поставить rust, две архитектуры, cargo-binstall — чтобы устанавливать сразу бинарники, чтобы установка не шла по полчаса, а также поставить официальные программы для прошивки чипа. FROM ubuntu: latest SHELL ["/bin/bash", "-0" "-o", "pipefail", "-c"] RUN apt-get update && apt-get install --no-install-recommends -y \ build-essential g++ gcc clang make pkgconf pkg-config \ ca-certificates libssl-dev musl-tools llvm-dev libclang-dev libudev-dev libuv1-dev \ rustup esptool Ildb Rust, ESP32 RISC-V targets, tooling RUN rustup toolchain install stable --component rust-src RUN rustup add riscv32imc-unknown-none-elf # ESP32-C2, C3 RUN rustup add riscv32imac-unknown-none-elf # ESP32-C6, H2 Cargo install. Binstall is a LOT faster. RUN cargo install cargo-binstall RUN cargo binstall espflash cargo-espflash \ esp-generate cargo-generate \ probe-rs-tools ldproxy --lockedsourcecraft.dev Докер-файл можно через расширение devcontainer подключить к среде разработки. В нем подключаем образ, монтируем исходники в workspace, с которыми уже можем работать напрямую. { "name": "rust-esp32", "image": "kolypto/rust-esp32-devcontainer", "remoteUser": "ubuntu" "runArgs": [ "--privileged" ], "mounts": [ "source=/dev/,target=/dev/,type=bind", ]. "workspaceMount": "source=${localWorkspaceFolder}, target=workspace, type=bind,consistency=cached" "workspaceFolder": "/workspace" }sourcecraft.dev При первом запуске нужно будет запустить генерацию проекта через esp-generate. Она спросит? какой у нас чип (например C3), там же можно включить некоторые фичи. Фичи, помеченные как unstable, не являются нестабильными или плохо работающими, лишь их интерфейс еще не зафиксирован до стабильного состояния. Сгенерированный проект выглядит так. В главном конфиге указан чип и архитектура. С микроконтроллером можно работать напрямую. Но это неинтересно, поэтому для тестов собрал небольшую схему со светодиодом и резистором, чтобы не сжечь чип. Более мощные нагрузки подключаются через транзистор: Пишем простую программу на Rust. Сразу отключаем привычное std — это значит нет аллокатора, функций по работе со строками, привычных типов и прочего удобного, точка входа в программу другая и нам нужно работать на самом низком уровне с памятью вручную. Главная функция никогда не завершается — об этом позже. Настраиваем чип на максимальную частоту (минимальная потребуется при энергосбережения). Затем подключаем периферию для доступа к оборудованию. Настраиваем два пина: 8 — в output для включения светодиода, 9 — в input для кнопки. #![no_std] #![no_main] use esp_hal::{...}; #[main] in main() → ! { let config = esp_hal:: Config:: default().with_cpu_clock(CpuClock:: max)); let peripherals = esp_hal:: init (config); let mut led = Output:: new (peripherals. GPI08, Level:: High, OutputConfig:: default()); let mut button = Input:: new (peripherals.GPI09, InputConfig:: default()-with_pull(Pull::Up)); led. set_high();sourcecraft.dev Main никогда не завершается, потому что в нем крутится бесконечный цикл. До входа в цикл выведем сообщение, чтобы видеть в консоли, что мы стартовали. В цикле проверяем нажатость кнопки и в зависимости от этого включаем/выключаем светодиод. Затем устройство засыпает на 500 миллисекунд, дожидаясь следующей проверки. Но так лучше не делать, так как при Delay процессор постоянно работает. Лучше для нажимания кнопок использовать прерывания, но это сильно усложнит код. println!("Started!"); loop { if button.is_high() { led.set_high(); } else { led.set_low(); } // Busy wait let delay = Delay::new(); delay.delay_millis(500); } }sourcecraft.dev Когда код программы написан — собираем ее через cargo build. Затем запускаем через cargo run. Под капотом запускается espFlash, который прошивает микроконтроллер: подключается к устройству, считывает его параметры, записывает программу в память — и чип готов к работе, даже есть некоторое управление прямо из консоли (CTRL+R — Reset chip, CTRL+C — Exit). Затем в консоли выводятся все параметры чипа — и тут же выводятся сообщения из нашей программы. Есть симулятор Wokwi, на котором можно поиграться без микроконтроллера: А теперь про то, как такие вещи делаются «по-взрослому». Обычно на таких проектах нужно параллельно работать с несколькими компонентами и учитывать физические процессы. На рисунке ниже — проект Wi-Fi-кнопки, при нажатии на которую отправляется сообщение в Telegram. Нужно сначала подключить фреймворк embassy с созданием async-задачи для него. У функции есть цикл, но благодаря async она умеет спать, разгружая процессор. В цикле ждем, когда кнопка будет нажата, точнее — ждем момента, когда кнопку отпустили. Затем ждем 20 миллисекунд, за которые проходят все физические процессы с замыканием и искрением контакта. Если кнопка нажата, отправляем сообщение в канал, сделанный ниже через CriticalSectionRawMutex /// Task: listen to button clicks #[embassy_executor::task()] pub async fn task_button_clicks(mut button: gpio::Input< 'static>) { loop { // Wait for press (high→low transition) button.wait_for_falling_edge().await; // Debounce. embassy_time:: Timer:: after_millis(20).await; if button.is_low() { // Send ONE event defmt::debug!("Button clicked"); let _ = BUTTON_CLICKS.try_send(()): // Non-blocking // Wait for it to be released. Don't send any more events. button.wait_for_high() .await; } } } /// Channel: button clicks. static BUTTON_CLICKS: Channel<CriticalSectionRawMutex, (), 1> = Channel::new();sourcecraft.dev Читаем в одном канале, что кнопка нажата и отправляем сообщения в другой канал, через который будет отправляться сообщение в Telegram. // Task: main logic // — Read button clicks // — Send them as Telegram messages #[embassy_executor::task()] pub async fn task_main() { loop { button::BUTTON_CLICKS.receive().await; telegram::send_telegram_message (MESSAGE_CONTENT); } } /// Send a message pub fn send_telegram_message(msg: &str) { // We only got a reference. To take ownership, we need a copy. let owned: String<32> = String::try_from(msg).unwrap() ; match MESSAGES_QUEUE.try_send(owned) { Ok (()) => (), Err (_) => defmt::error!("Queue full: cannot send message"), } } /// Messages queue static MESSAGES_QUEUE: Channel<CriticalSectionRawMutex, String::<32>, 8> = Channel:: new();sourcecraft.dev Третий сервис уже работает с Telegram. Для начала нужно прочитать восемь случайных байт с датчика случайных чисел, который находится в метастабильном состоянии и от любого шума и тепла его колбасит от 0 до 1, поэтому с него можно считать случайные байты. Дальше мы инициализируем буфер для TLS, и это целые 16 Кб, и это очень много для микроконтроллера, у которого всего 400 Кб, часть которых уже занята. И в конце создаем буфер по 4 Кб для чтения и записи. Итого 40 Кб, только чтобы отправить один запрос, поэтому для связи с внешним миром обычно используют отдельные чипы. // Send a message async fn telegram_send_message (stack: embassy_net::Stack<'_>, send_to: i64, message: &str) -> Result<(), TelegramSendMessageError> { // TLS needs a random value let rnd = Rng::new(); let tls_seed = { let mut bytes = [0; 8]; rnd.read(&mut bytes); u64::from_le_bytes(bytes) }; // Init TLS. let (mut rx_buffer, mut tx_buffer) = ([0; 16640], [0; 16640]); let tls = TlsConfig::new( tls_seed, &mut rx_buffer, &mut tx buffer, reqwless::client::TlsVerify::None, ); let tcp_state = TcClientState::<1, 4096, 4096>::new(); let tcp = TcpClient::new(stack, &tcp_state); let dns = DnsSocket::new(stack); let mut client = HttpClient::new_with_tls(&tcp, &dns, tls);sourcecraft.dev Конструируем сообщение. Затем сериализуем JSON, для этого опять нужна 256 байт памяти. Отправляем запрос и читаем ответ и тоже нужно 4 Кб памяти. // Data let mut url: String<128> = String::new(); use core::fmt::Write; write!(url, "api.telegram.org", BOT_TOKEN).unwrap(); let msg = TelegramMessageInput { chat_id: send_to, text: message, }; let mut body_buf = [0u8; 256]; let _body_len = serde_json_core::to_slice(&msg, &mut body_buf)?; // Request let mut buf = [0; 4096]; let mut req = client.request(reqwless::request::Method::POST, url.as_str()) .await? .content_type(ContentType::ApplicationJson) .body(body_buf.as_slice()); let resp = req.send (&mut buf).await?;sourcecraft.dev Наконец читаем ответ до конца, и благодаря await можно уснуть, чтобы другое устройство могло перехватить управление, пока мы спим. Дешифруем UTF, если требуется. И вместо парсинга JSON мы просто ищем в нем подстроку “ok”:true — чтобы сэкономить память. При обращении к Telegram, наверное, не лучшая идея, так как они могут добавить пробел или изменить формат, а при обращении к своим серверам это отличный вариант. // Read response let response = resp.body().read_to_end().await?; let resp_text = core::str::from_utf8(&response) .map_err(|_| TelegramSendMessageError::ResponseError)?; // Check for success if !resp_text.contains(r#" "ok":true"#) { defmt::error!("Telegram failed: {3", resp_text); return Err(TelegramSendMessageError::ResponseError) } // Ok return Ok(()) }sourcecraft.dev На реальных больших проектах это пишут сложнее и на С++. Например, используя операционную систему FreeRTOS в мозгах машины Tesla. Создают очередь, чтобы писать туда события нажатых кнопок. Затем создается задача с бесконечным циклом внутри. И если кнопка нажата или отпущена, то в очередь отправляется сообщение. Далее уведомляют планировщик, что функция может вздремнуть и пока можно отдать управление другим. Эта операционная система FreeRTOS оперирует не процессами, а функциями, у которых есть стек, приоритет и она может спать. void gpio_input_task_init(void) { button_events_queue = xQueueCreate(8, sizeof(button_event_t)); xTaskCreate(gpio_input_task, "gpio_input", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); } static void gpio_input_task(void *argument) { (void) argument; for (;;) { for (int ii = 0; i < BUTTON_COUNT; i++) { button_t *button = &buttons[i]; bool pressed = read_button(button); if (pressed != button->is_pressed) { button->is_pressed = pressed; button_event_t event = {.button = (button_id_t)i, .pressed = pressed}; xQueueSend(button_events_queue, &event, 0); } } vTaskDelay(pdMS_TO_TICKS(BUTTON_POLL_PERIOD_MS)); } } static QueueHandle_t button_events_queue; static bool read_button(const button_t *button) { return HAL_GPIO_ReadPin(button->port, button->pin) == GPIO_PIN_RESET; }sourcecraft.dev Код под микроконтроллеры можно писать и на MicroPython — он сам управляет памятью. Тут также читаем два пина, и если кнопка нажата — включаем лампочку и дальше спим. Это очень просто, но мы тут памятью не управляем, поэтому это для домашних проектов и прототипов. from machine import Pin from time import sleep_ms led = Pin(8, Pin. OUT) btn = Pin(9, Pin.IN, Pin.PULL_UP) # button to GND, pressed = 0 while True: pressed = btn.value() == 0 led.value(1 if pressed else 0) sleep_ms(10)sourcecraft.dev Есть аналогичное и для Go, называется TinyGo. В этом примере идет подключение к Wi-Fi, запускает на микроконтроллере веб-сервер, на котором даже есть микространица с сообщением hello. func main() { link := &link.Esplink{} netdev.UseNetdev(link) println("Connecting to WiFi...") failIfErr ("connect", link.NetConnect (&nl. ConnectParams{ Ssid: ssid, Passphrase: password, })) println("Connected to WiFi.") var http httphi.MuxSlice http.Handle("/", func(exch *httphi.Exchange) { exch.RespondString(200, "application/json", '{"message": "hello"}`) }) var router httphi.Router cfg := httphi.DefaultRouterConfig(4, 2048, http.MaxPathValues()) failIfErr("configuring Router", router.Configure(&http, cfg)) err := link.ListenAndServe(&router, port) failIfErr("Esplink.ListenAndServe", err) }sourcecraft.dev Помимо микроконтроллера, есть много компонентов для умного дома ESPHome. Например, в примере ниже микроволновый радар, который определяет наличие человека дома. И обычным yaml-файлом без программирования можно настроить работу этого устройства. Марк ремомендует посмотреть 20 уроков и примеры Rust ESP32: esp32.implrust.com github.com/esp-rs/esp-hal/tree/main/examples github.com/kolypto/learning-and-playground/tree/m… Все переходим на Rust Послушав три доклада, решил, что новый проект буду писать на Rust.  А после последнего доклада очень захотелось купить микропроцессор и поиграться с ним.. Книга «Паттерны проектирования Rust», лежащая у меня на столе, дождалась наконец — пришло ее время.  Резюмируя все доклады… Плюсы от использования Rust: Сокращение используемой памяти. Совместимость с другими системами на Python, Java и C/C++. Отсутствие Garbage Collector и связанных с ним проблем. Выявление проблем на этапе сборки, а не в runtime. Возможность работать с ограниченными ресурсами и даже на микропроцессорах. Минусы: Нужно время на изучение после другого языка. Непривычный поначалу Borrow Checker и то, что пишет cargo. Пока мало вакансий разработчиков Rust в РФ. Явно плюсы перевешивают. А каков был ваш опыт перехода на Rust и делали ли проекты на микроконтроллерах? Продолжим в комментариях.

  3. The watchface itself right now is very basic, it only displays the time and the battery, but I think all the pieces that are needed to make a replacement for the C++ firmware are done! The clock, accelerometer, and display drivers are working. The main part that I haven't tested yet is wi-fi and BLE, though I think it should only be a matter of wiring up esp-radio. But yeah, as for functionality the firmware is still very much a WIP :) You can find it here.

    2 points

Achievements

Awards earned by the repositories esp-rs owns. Select an award to embed its badge.

No awards yet

Repositories earn awards by reaching the top three in a completed trending day or week.

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.