..with focus on Embassy
Getting started with ESP32 Rust development, using macOS host (for IDE) and a Multipass VM (other tooling).
Note: This repo builds upon ESP32 on WSL (Windows and WSL; esp-idf; C/C++)
- Minimal software installs on the host (Mac)
- Ability to flash the device from within Multipass VM
Focus
-
RISC V only
If you want Xtensa-based chips covered, please sign up as a co-author. This one doesn't own the chips. :)
-
Embassy async development
Embassy is a generational shift in embedded programming. Its support for
async
without a separate RTOS makes concurrent programming linear to write. This is very similar to what Node.js did. -
Only stable Rust
ESP32 (RISC V variants) Rust development is possible using the stable Rust (Jun'24).
- Mac with:
- Multipass installed
**Alternative A. Connecting devices via Windows**
- Windows computer (Win10 Home is enough)
usbipd-win
4.2.0 (or later) installedCP210x universal Windows driver
(11.3.0) installed from CP210x USB to UART Bridge VCP Drivers (SiLabs)
**Alternative B. Connecting devices via Raspberry Pi**
**Alternative C. Connecting devices directly to Mac**
Not recommended.
There's no good open source USB/IP server for macOS and Multipass doesn't have USB device management, unlike other VM solutions.
If you want to do this, also consider that attaching solderable electronics to a computer always carries risks. The author sees it beneficial to have an air gap between the USB/IP server and the development machine! ⚡️
The intention is to support as many common ESP32 (RISC V) development boards as possible. If you have access to boards not listed below, please consider giving a PR and/or becoming a co-author to the repo.
Warning!!
When powering the ESP32 boards for the first time, BEWARE OF THE STRONG LED LIGHT!! The author used a tape on top, until he reflashed the device. 😎🩹
board | hw version[1] |
chip revision[2]``[3] |
status | notes |
---|---|---|---|---|
ESP32-C3-DevKitC-02 | "1.1" | 0.4 | works with espflash |
|
ESP32-C3-DevKit-RUST-1 | "1.2a (04/2022)" | 0.4 | works with espflash |
|
ESP32-C6-DevKitM-1 | "1.0" | 0.0 | works with espflash |
[2]
: revision listed when running espflash board-info
or restarting a board. This matters to some functionality, e.g. for C3 boards JTAG functionality (with added cable) is available for chip revisions >= 0.4.
[3]
: Earlier documentation and bootloaders (from the ESP-IDF 4 era; up till Dec'22) mention chip revisions "3" and "4", they mean 0.3 and 0.4.
The boards have USB connectors, but whether these are standard "USB+UART" (serial port) or connected to an integrated JTAG debugging circuitry varies.
VID:PID | flashing | comms | debug | |
---|---|---|---|---|
UART | 10c4:ea60 |
✓ | ✓ | - |
JTAG | 303a:1001 |
✓ | ✓ | ✓ |
You can use either port for programming, but need JTAG for interactive debugging. UART is the default for esp-hal
. Below, we will cover both.
VID:PID
are the device identifiers you'll see in USB access.
ESP32-C3-DevKitC-02
The board has just one Micro-USB port. To gain access to JTAG, you need to solder a USB cable to the board. Details here
pin | USB | wire color |
---|---|---|
GPIO18 | D- | ⬜️ |
GPIO19 | D+ | 🟩 |
5V | V_BUS | 🟥 |
GND | Ground | ⬛️ |
ESP32-C3-DevKit-RUST-1
Has only JTAG connection (USB-C).
ESP32-C6-DevKitM-01
The dev board has two USB ports:
For now, connect the cable to the "left" (uart
) port. This way, the default settings of esp-hal
(we'll get there soon) will allow println
messages to be see on the VM terminal.
-
Set up USB/IP sharing according to instructions in the ESP32-WSL (ignore the WSL parts of it).
You should have:
usbipd bind
exposing the dev board to the network- IP of the computer doing the sharing
We use a Multipass VM to keep the Rust development toolchain away from your main account. This is similar to using a dockerized workflow for development.
-
Study the
mp
repo in detail!- Create the
rust-emb
instance within it.
- Create the
We presume you have the USB/IP server running, and know the IP of the machine (192.168.1.29
below - replace with yours).
$ multipass shell rust-emb
The following commands are to be given in the
rust-emb
VM.
-
Check that you can see the dev board (optional)
$ usbip list -r 192.168.1.29
**ESP32-C3-DevKitC-02**
Exportable USB devices ====================== - 192.168.1.29 3-1: Silicon Labs : CP210x UART Bridge (10c4:ea60) : USB\VID_10C4&PID_EA60\BC2F214F809DED11AAFA5F84E259FB3E : (Defined at Interface level) (00/00/00) : 0 - Vendor Specific Class / unknown subclass / unknown protocol (ff/00/00)
**ESP32-C3-DevKit-RUST-1**
Exportable USB devices ====================== - 192.168.1.29 3-1: unknown vendor : unknown product (303a:1001) : USB\VID_303A&PID_1001\40:4C:CA:8D:9B:44 : Miscellaneous Device / ? / Interface Association (ef/02/01) : 0 - Communications / Abstract (modem) / None (02/02/00) : 1 - CDC Data / unknown subclass / unknown protocol (0a/02/00) : 2 - Vendor Specific Class / Vendor Specific Subclass / unknown protocol (ff/ff/01)
**ESP32-C6-DevKitM-1 (`uart`)**
Exportable USB devices ====================== - 192.168.1.29 3-1: Silicon Labs : CP210x UART Bridge (10c4:ea60) : USB\VID_10C4&PID_EA60\3086768A3759ED11B797B7301D62BC44 : (Defined at Interface level) (00/00/00) : 0 - Vendor Specific Class / unknown subclass / unknown protocol (ff/00/00)
**ESP32-C6-DevKitM-1 (`JTAG-serial`)**
Exportable USB devices ====================== - 192.168.1.29 3-1: unknown vendor : unknown product (303a:1001) : USB\VID_303A&PID_1001\54:32:04:07:15:10 : Miscellaneous Device / ? / Interface Association (ef/02/01) : 0 - Communications / Abstract (modem) / None (02/02/00) : 1 - CDC Data / unknown subclass / unknown protocol (0a/02/00) : 2 - Vendor Specific Class / Vendor Specific Subclass / unknown protocol (ff/ff/01)
-
Attach the dev board to your VM
$ sudo usbip attach -r 192.168.1.29 -b 3-1
-
Now you should see it in the device tree:
$ lsusb
**ESP32-C3-DevKitC-02**
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 009: ID 10c4:ea60 Silicon Labs CP210x UART Bridge Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
**ESP32-C6-DevKitM-1 (`uart`)**
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 007: ID 10c4:ea60 Silicon Labs CP210x UART Bridge Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
**ESP32-C6-DevKitM-1 (`JTAG-serial`)**
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 003: ID 303a:1001 Espressif USB JTAG/serial debug unit Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
-
Check with
espflash
$ espflash board-info
**ESP32-C3-DevKitC-02**
[2024-06-03T11:27:27Z INFO ] Serial port: '/dev/ttyUSB0' [2024-06-03T11:27:27Z INFO ] Connecting... [2024-06-03T11:27:28Z INFO ] Using flash stub Chip type: esp32c3 (revision v0.4) Crystal frequency: 40 MHz Flash size: 4MB Features: WiFi, BLE MAC address: 54:32:04:41:7d:60
**ESP32-C6-DevKitM-1 (`uart`)**
[2024-06-03T11:00:18Z INFO ] Serial port: '/dev/ttyUSB0' [2024-06-03T11:00:18Z INFO ] Connecting... [2024-06-03T11:00:18Z INFO ] Using flash stub Chip type: esp32c6 (revision v0.0) Crystal frequency: 40 MHz Flash size: 4MB Features: WiFi 6, BT 5 MAC address: 54:32:04:07:15:10
**ESP32-C6-DevKitM-1 (`JTAG-serial`)**
[2024-06-03T11:25:36Z INFO ] Serial port: '/dev/ttyACM0' [2024-06-03T11:25:36Z INFO ] Connecting... [2024-06-03T11:25:36Z INFO ] Using flash stub Chip type: esp32c6 (revision v0.0) Crystal frequency: 40 MHz Flash size: 4MB Features: WiFi 6, BT 5 MAC address: 54:32:04:07:15:10
Info:
espflash
is one of the commands you can use for interacting with the device (flashing and seeing its info). -
Check with
probe-rs
$ probe-rs list
**ESP32-C3-DevKitC-02**
No debug probes were found.
**ESP32-C6-DevKitM-1 (`uart`)**
No debug probes were found.
**ESP32-C6-DevKitM-1 (`JTAG-serial`)**
The following debug probes were found: [0]: ESP JTAG -- 303a:1001:54:32:04:07:15:10 (EspJtag)
Background: The esp-hal repo (Hardware Abstraction Layer) has code that allows using the ESP32 hardware from Rust. It also includes an
examples
folder that we'll look into.
Let's clone the esp-hal
repo to your host (Mac):
$ git clone --depth 1 [email protected]:esp-rs/esp-hal.git
--depth 1
excludes history; we don't need it and same some disk space
$ multipass stop rust-emb
$ multipass mount --type=native esp-hal rust-emb:/home/ubuntu/esp-hal
Note: We don't need to enter the
esp-hal
folder on the host side. Just sharing it with the VM.
Now you have esp-hal
available within the VM.
Note: Unfortunately, using
--type=native
requires the VM to be shut down when adding/removing mounts. On the other side, it promises better performance than default Multipass mounts. The folder is huge (ca. 1.9GB) so we like to have the performance!!
$ multipass shell rust-emb
Within the VM, re-attach the development kit. It was lost when we restarted the VM.
$ sudo usbip attach -r 192.168.1.29 -b 3-1
Within the VM:
$ cd esp-hal
Study the
esp-hal/examples/
folder.This is a treasure trove of dealing with different sensors. To begin with, we just build (and run) the
hello_world
example.
Within the esp-hal
folder in the VM:
$ cargo xtask run-example esp-hal esp32c6 hello_world
Output:
**ESP32-C3-DevKitC-02**
...
Hello world!
Hello world!
**ESP32-C6-DevKitM-1 (`uart`)**
...
Hello world!
Hello world!
**ESP32-C6-DevKitM-1 (`JTAG-serial`)**
...
I (112) esp_image: segment 3: paddr=00017614 vaddr=40800020 size=00eech ( 3820) load
I (120) boot: Loaded app from partition at offset 0x10000
(no output)
See the devkit specific appendix for an explanation.
Since console output only goes to uart
, it's probably best to move to using that port, for further examples.
To see which samples are suitable for e.g. esp32-c6
:
$ git grep -P "(?<=% CHIPS:).*esp32c6" examples/src/
That gives 82 (65 for C3) matches! 😃
The esp-hal
and embassy
repos seem to be arranged so (for ESP32, that is) that we don't need to switch repos. The examples for Embassy are within esp-hal/examples/src/bin/
embassy_*.rs
(18 of them).
This is good.
What's special about Embassy? It harnesses the
async
/await
mechanism, making creating concurrent embedded code easy to write. This is brilliance - see Embassy.dev for more details or just hang on and let's give it a try! ☀️
$ cargo xtask run-example esp-hal esp32c6 embassy_hello_world
**ESP32-C3-DevKitC-02**
Init!
Bing!
Hello world from embassy using esp-hal-async!
Hello world from embassy using esp-hal-async!
**ESP32-C6-DevKitM-1 (`uart`)**
...
Init!
Bing!
Hello world from embassy using esp-hal-async!
Hello world from embassy using esp-hal-async!
**ESP32-C6-DevKitM-1 (`JTAG-serial`)**
...
Init!
Bing!
Hello world from embassy using esp-hal-async!
Hello world from embassy using esp-hal-async!
About JTAG-serial
and defmt
logging
The above example was able to be run also on C6 JTAG-serial
, with a one line configuration change in examples/Cargo.toml
:
-esp-println = { path = "../esp-println", features = ["log"] }
+esp-println = { path = "../esp-println", default-features=false, features = ["log", "jtag-serial", "defmt-espflash"] }
The default (that works for C3 and C6 uart
) is targeting uart
. Here, we prevented default features (default-features=false
) and manually chose output to go to jtag-serial
.
In addition - and this may be useful for any target - defmt-espflash
was enabled. You can read online more about defmt
; in short it makes logging easier on the embedded side, causing smaller binary sizes. It's a Good Thing but needs support from the development toolchain. Which we seem to have.
All this works because the embassy_hello_world
uses esp_println::println!
for its output.
Now is a good time to check through the other 24 *embassy_*.rs
samples (note that wifi ones start with esp_wifi_embassy
).
It just helps you code embedded in the right and efficient way. If all your code are await
ing something, it means the Embassy scheduler has placed the CPU to sleep mode. These are details you just don't need to be concerned of.
This author sees a bright future for the Embassy project, and now You can be part of it!
We set out to create a development toolchain with the following aims:
- Minimal software installs on the host (Mac)
- Ability to flash the device from within Multipass VM
This was reached. :) You can now extend the reach by studying and modifying the other Embassy examples, and bringing your electronics and motor skills to the game!
This is probably a good place to stop the repo. It is doing its service if it made it easy for you to step in. Please spread the word and if you find any mistakes - or have other ESP32 boards you would like to be featured - chime in!
The plans of the author can be seen in the repo's Issues section.
Happy Riscing! 🎉
Note the
303a:1001
in e.g. thelsusb
output. These are the "vendor id" and "product id" of your board.
They match a line in the udev
rules file used by probe-rs
:
$ cat /etc/udev/rules.d/69-probe-rs.rules | grep 303a | grep 1001
ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", MODE="660", GROUP="plugdev", TAG+="uaccess"
You can increase the number of cores Multipass is allowed to use by:
multipass stop rust-emb
multipass set local.rust-emb.cpus={X}
Then multipass shell rust-emb
to get back in action.
The device has a single USB port. From chip revisions 0.3 onwards, it does also have JTAG circuitry, but enabling this needs soldering a USB cable to the device.
The ESP32-C3 Devkit C doesn't expose the JTAG interface over USB by default, see the ESP32-C3 debugging docs to configure the board for debugging or consider using the esp32c3-rust-board instead. source
Tried this, and it works. However, what benefits JTAG really brings over UART needs to be studied..
Be aware before purchasing:
This board contains some peripherals to get started easier. Having Rust in its name seems enticing. It's intended for educational purposes.
However:
- its availability may be worse than the other two
- it comes without pins soldered so you must add those before using on a breadboard
- anecdotally, the author's board cost 50 euros (about 5x more than the others!)
- ..and he needed to fill all kinds of export forms that weren't needed by the other, computationally similar boards.
This is the kind of logistics friction we can do without. The shopping and using experience with the other two boards was smooth, in comparison!
As already mentioned, the kit has two USB ports, uart
and JTAG-serial
. Programming works on both, but passing the console (println
) output is by default sent to the uart
port.
-
See above on how to steer the output to
uart
vs.jtag-serial
, based on your needs. -
What are the benefits of such dedicated JTAG connection? (debugging; try and tell) tbd.