How to start with Rust and an ESP32 board ?
In this blog post I will explain you how to get started with an ESP32 and Rust.
One big question you may be asking yourself: what model does support Rust ?
The supported board versions seem to be:
-
esp32
-
esp32c2
-
esp32c3
-
esp32c6
-
esp32h2
-
esp32s2
-
esp32s3
And maybe ESP32-P4
according to the README.
What device should I buy ?
I did buy the Hailege ESP32 Wireless Stick Lora+WiFi+BLE Development Board SX1262 Module LoRa WiFi Bluetooth Arduino LoRaWAN 863/928MHz with 0.49 inch OLED Display from Amazon but you should maybe buy it at heltec.org.
It has support for:
- LoRa/LoRaWAN
- WiFi
- Bluetooth
It comes with the OLED 64*32 (0.49 inch) screen nfp1315-99a
.
The screen uses the chip SSD1315.
It seems to be ESP32-S3FN8
according to the Heltec doc that says: MCU is changed from ESP32-D0WDQ6 to [
ESP32-S3FN8`](https://products.espressif.com/#/product-comparison?names=ESP32-S3FN8&type=SoC) for V3.
The Amazon pinout picture is garbage, see heltec.org for the real one. High resolution picture: heltec.cn (HTIT-WS_V3.png)
High resolution schematic diagram: heltec.cn (HTIT-WS_V3_Schematic_Diagram.pdf)
The OLED pins are advertised as Heltec source code: GPIO 4 -> OLED SDA GPIO 15 -> OLED SCL GPIO 16 -> OLED reset
static const uint8_t Vext = 36; static const uint8_t LED = 35; static const uint8_t RST_OLED = 21; static const uint8_t SCL_OLED = 18; static const uint8_t SDA_OLED = 17;
// Slave address bit (SA0) SSD1306 has to recognize the slave address before transmitting or receiving any information by the I 2 C-bus.
// SDA acts as a communication channel between the transmitter and the receiver. The data and the acknowledgement are sent through the SDA
// I 2 C-bus clock signal (SCL) The transmission of information in the I2 C-bus is following a clock signal, SCL. Each transmission of data bit is taken place during a single clock period of SCL
// https://www.smart-prototyping.com/image/data/9_Modules/101855%200.66%20OLED%206448%20IICSPI/SSD1306.pdf
Tooling install
Obviously you will have to install Rust. My current version is: rustc 1.88.0 (6b00bc388 2025-06-23)
.
If there is a some build failure related to Python you may need to install this:
sudo apt install python3.11-venv
cargo install espup
cargo install espflash
cargo install ldproxy
cargo install cargo-generate
If you do not install ldproxy
the cargo run
will fail:
error: linker `ldproxy` not found
|
= note: No such file or directory (os error 2)
Device info
espflash board-info
[2025-10-09T12:02:26Z INFO ] Serial port: '/dev/ttyUSB0'
[2025-10-09T12:02:26Z INFO ] Connecting...
[2025-10-09T12:02:26Z INFO ] Using flash stub
Chip type: esp32s3 (revision v0.2)
Crystal frequency: 40 MHz
Flash size: 8MB
Features: WiFi, BLE, Embedded Flash
MAC address: 48:27:e2:e4:8a:6c
Security Information:
=====================
Flags: 0x00000000 (0)
Key Purposes: [0, 0, 0, 0, 0, 0, 12]
Chip ID: 9
API Version: 0
Secure Boot: Disabled
Flash Encryption: Disabled
SPI Boot Crypt Count (SPI_BOOT_CRYPT_CNT): 0x0
This may throw an error:
$ espflash board-info
× Failed to open serial port /dev/ttyUSB0
├─▶ Error while connecting to device
├─▶ IO error while using serial port: Permission denied
╰─▶ Permission denied
Add your user to the group of the file:
$ ls -lah /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 0 Dec 21 16:37 /dev/ttyUSB0
# Print current user groups
id
sudo usermod -a -G dialout $USER
# Add the group without re-login
newgrp dialout
# Print current user groups
id
Setup the project
cargo generate esp-rs/esp-idf-template cargo
⚠️ Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
🤷 Project Name: esp32-lcd
🔧 Destination: <workdir>/esp32-lcd ...
🔧 project-name: esp32-lcd ...
🔧 Generating template ...
✔ 🤷 Which MCU to target? · esp32s3
✔ 🤷 Configure advanced template options? · true
✔ 🤷 ESP-IDF version (master = UNSTABLE) · v5.3
✔ 🤷 Configure project to use Dev Containers (VS Code and GitHub Codespaces)? · false
✔ 🤷 Configure project to support Wokwi simulation with Wokwi VS Code extension? · false
✔ 🤷 Add CI files for GitHub Action? · false
[ 1/11] Done: .cargo/config.toml
[ 2/11] Done: .cargo
[ 3/11] Done: .gitignore
[ 4/11] Done: .vscode
[ 5/11] Done: Cargo.toml
[ 6/11] Done: build.rs
[ 7/11] Ignored: pre-script.rhai
[ 8/11] Done: rust-toolchain.toml
[ 9/11] Done: sdkconfig.defaults
[10/11] Done: src/main.rs
[11/11] Done: src
🔧 Moving generated files into: `<workdir>/esp32-lcd`...
🔧 Initializing a fresh Git repository
✨ Done! New project created <workdir>/esp32-lcd
Run it
cd esp32-lcd
cargo run
After the build you will see this output:
[2025-10-09T12:16:10Z INFO ] Serial port: '/dev/ttyUSB0'
[2025-10-09T12:16:10Z INFO ] Connecting...
[2025-10-09T12:16:11Z INFO ] Using flash stub
Chip type: esp32s3 (revision v0.2)
Crystal frequency: 40 MHz
Flash size: 8MB
Features: WiFi, BLE, Embedded Flash
MAC address: 48:27:e2:e4:8a:6c
App/part. size: 517,568/8,323,072 bytes, 6.22%
[00:00:01] [========================================] 14/14 0x0 Verifying... OK!
[00:00:00] [========================================] 1/1 0x8000 Verifying... OK!
[00:00:27] [========================================] 261/261 0x10000 Verifying... OK!
[2025-10-09T12:16:41Z INFO ] Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x9 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fce2810,len:0x15a0
load:0x403c8700,len:0x4
load:0x403c8704,len:0xd24
load:0x403cb700,len:0x2f04
entry 0x403c8928
I (30) boot: ESP-IDF v5.4.1-426-g3ad36321ea 2nd stage bootloader
I (30) boot: compile time Apr 24 2025 15:55:11
I (30) boot: Multicore bootloader
I (32) boot: chip revision: v0.2
I (34) boot: efuse block revision: v1.3
I (38) boot.esp32s3: Boot SPI Speed : 40MHz
I (42) boot.esp32s3: SPI Mode : DIO
I (46) boot.esp32s3: SPI Flash Size : 8MB
I (49) boot: Enabling RNG early entropy source...
I (54) boot: Partition Table:
I (56) boot: ## Label Usage Type ST Offset Length
I (63) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (69) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (76) boot: 2 factory factory app 00 00 00010000 007f0000
I (82) boot: End of partition table
I (86) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=20308h (131848) map
I (125) esp_image: segment 1: paddr=00030330 vaddr=3fc90e00 size=0296ch ( 10604) load
I (129) esp_image: segment 2: paddr=00032ca4 vaddr=40374000 size=0cd48h ( 52552) load
I (144) esp_image: segment 3: paddr=0003f9f4 vaddr=00000000 size=00624h ( 1572)
I (145) esp_image: segment 4: paddr=00040020 vaddr=42000020 size=4e570h (320880) map
I (231) boot: Loaded app from partition at offset 0x10000
I (232) boot: Disabling RNG early entropy source...
I (242) cpu_start: Multicore app
I (252) cpu_start: Pro cpu start user code
I (252) cpu_start: cpu freq: 160000000 Hz
I (252) app_init: Application information:
I (255) app_init: Project name: libespidf
I (260) app_init: App version: 1
I (264) app_init: Compile time: Oct 9 2025 14:15:30
I (270) app_init: ELF file SHA256: 000000000...
I (275) app_init: ESP-IDF: v5.3.3
I (280) efuse_init: Min chip rev: v0.0
I (285) efuse_init: Max chip rev: v0.99
I (290) efuse_init: Chip rev: v0.2
I (295) heap_init: Initializing. RAM available for dynamic allocation:
I (302) heap_init: At 3FC940C8 len 00055648 (341 KiB): RAM
I (308) heap_init: At 3FCE9710 len 00005724 (21 KiB): RAM
I (314) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (320) heap_init: At 600FE000 len 00001FE8 (7 KiB): RTCRAM
I (328) spi_flash: detected chip: gd
I (331) spi_flash: flash io: dio
W (335) pcnt(legacy): legacy driver is deprecated, please migrate to `driver/pulse_cnt.h`
W (343) i2c: This driver is an old driver, please migrate your application code to adapt `driver/i2c_master.h`
W (354) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (363) sleep: Configure to isolate all GPIO pins in sleep state
I (370) sleep: Enable automatic switching of GPIO sleep configuration
I (377) main_task: Started on CPU0
I (387) main_task: Calling app_main()
I (387) esp32_lcd: Hello, world!
I (387) main_task: Returned from app_main()
Success !
Writing some code
Testing the board with Arduino
Using Arduino IDE and as explained on Heltec ESP32's GitHub I verified I could get the screen working.
#include <Wire.h>
#include "HT_SSD1306Wire.h"
static SSD1306Wire display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED); // addr , freq , i2c group , resolution , rst
void drawLines() {
for (int16_t i=0; i<display.getWidth(); i+=4) {
display.drawLine(0, 0, i, display.getHeight()-1);
display.display();
delay(10);
}
}
void LedON(void)
{
pinMode(LED,OUTPUT);
digitalWrite(LED, HIGH);
}
void VextON(void)
{
pinMode(Vext,OUTPUT);
digitalWrite(Vext, LOW);
}
void VextOFF(void) //Vext default OFF
{
pinMode(Vext,OUTPUT);
digitalWrite(Vext, HIGH);
}
void setup() {
LedON();
VextON();
delay(100);
display.init();
drawLines();
}
void loop() { }
Writing Rust !
Now that we know the pins and addresses are correct, let's Rust !
The example to light the LED (GPIO 35) works, just change the pin number in the code.
To be done, the OLED display !
// https://github.com/ivmarkov/rust-esp32-std-demo/issues/158 // http://cholla.mmto.org/electronics/displays/oled/