ESP are cool. Let s bend them to our needs.
ESP
Meshtastic
Heltec v3
Linux setup
First you ll need Python and Pip installed. You ll figure it out.
Then install esptool
yay -S esptool
Clone the repo :
git clone git@github.com:meshtastic/firmware.git
cd firmware
git switch tags/v2.5.19.f9876cf # which is the latest tag at the moment, adapt
Da build
in order to build and especially upload, we ll use Platformio. I tried to install it system wide, it did compile the firmware, but during the upload process, it tried to pip-install some modules and failed. Therefore I would recommend the following :
# Setting up the env
uv venv .venv
source .venv/bin/activate
uv pip install platformio
# Now We can play with pio
pio system info
pio pkg list
# List all available envs
pio project config | grep env: [ | grep heltec ]
# Teh build
pio run -e heltec-v3
# Teh upload
pio run -e heltec-v3 -t upload --upload-port /dev/ttyUSB0
Bonus
Change the name on the splash screen.
Edit the following file : src/graphics/Screen.cpp.
Around line 168, look for ‘const char *title = “meshtastic.org”;’ and replace it by whatever name you want.
Side note :
The line before is : “#ifdef USERPREFS_SPLASH_TITLE”, so I am pretty sure there is better way to make a custom splashscreen
T114
The process is pretty similar. I had an issue while trying to upload the firmware using the USB port, as it seemed that none was found (plug on the same port as before, using the same cable). By removing the –upload-port, it worked, using /dev/ttyACM0. I am not sure why one is discovered on the USB port and the other on the ACM port.
pio run -e heltec-mesh-node-t114 -t upload
Edit : This difference would come from the different chip used on the board. Heltec v3 is using a USB-UART converter and appears on USB, whereas the t114 uses native USB (CDC-ACM) and appears on ACM.
ESP-32
It follows pretty much the same principles, except… We are going to build the firmware.
We can use different framework to do so. We ll first explore the Arduino style, then the ESP-IDF style and finally we ll use Rust.
Arduino style
Step 1
mkdir arduino
cd arduino
# setup the project
uv venv .venv
source .venv/bin/activate
uv pip install platformio
Step 2
# first try !
pio init --board esp32dev # TODO : add board list
This will create the directory structure and download the necessary lib. For the sake of the example lets copy this code inside the src folder :
#include <Arduino.h>
#define LED_PIN 2
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500); // 500ms
digitalWrite(LED_PIN, LOW);
delay(500);
}
And finally build + upload
pio run -t upload
ESP-IDF C-style
Step 1
Same same
Step 2
# first try !
pio init --board esp32dev --project-option="framework=espidf"
And now copy this code inside of src/main.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#define LED_PIN GPIO_NUM_2
void app_main(void)
{
gpio_reset_pin(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(LED_PIN, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
gpio_set_level(LED_PIN, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
Compile and upload
pio run
pio run -t upload
Rust-IDF style
Setup the ESP ecosystem
You do have Rust installed, right ?
# Install tools
cargo install espup cargo-generate
# Install the ESP toolchain
espup install
read carefully the output, it will tell you where is the script you should run to load the env. You can add it to your .zshrc conf
espup is …
cargo-generate is…
espup installs the toolchains like xtensa which is…
and generate a script…
Create the ready-to-go Rust ESP32 project
cargo generate \
--git https://github.com/esp-rs/esp-idf-template.git \
--name esp32-rs
Template : cargo
MCU : esp32
cd esp32-rs
Build
Lets try to build that
cargo build
if the build crashes because of a lib it might be because of a recent change of name, try to installlibxml12-legacy (and possibly ldproxy)
And finally flash
espflash flash --speed 460800 /dev/ttyUSB0 target/xtensa-esp32-espidf/debug/esp32-rs
Upload
cargo install espflash
espflash flash -p /dev/ttyUSB0 target/xtensa-esp32-espidf/debug/esp32-rs
Note that platformio can also be used to upload
Coding
Just to get started, let s make it blink. Yes it was a pain… and yes it can be better. But not tonight. src/main.rs :
use esp_idf_hal::prelude::*;
use esp_idf_hal::gpio::{Gpio2, PinDriver}; // On utilise PinDriver
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::sys::link_patches;
use std::thread;
use std::time::Duration;
use anyhow::Error;
fn main() -> Result<(), Error> {
// Necessary runtime patch call
link_patches();
// Initialize the logger
EspLogger::initialize_default();
// Set up the GPIO2 pin (typically the on-board LED)
let peripherals = Peripherals::take().unwrap();
let mut gpio2 = PinDriver::output(peripherals.pins.gpio2)?; // Utilisation de PinDriver::output()
// Blink the LED
loop {
gpio2.set_high()?; // Turn the LED on
log::info!("LED ON");
thread::sleep(Duration::from_secs(1)); // Wait for 1 second
gpio2.set_low()?; // Turn the LED off
log::info!("LED OFF");
thread::sleep(Duration::from_secs(1)); // Wait for 1 second
}
}
Cargo.toml :
[package]
name = "esp32-rs"
version = "0.1.0"
authors = ["cristalcorp <cristal@cristalcorp.com>"]
edition = "2021"
resolver = "2"
rust-version = "1.77"
[[bin]]
name = "esp32-rs"
harness = false # do not use the built-in cargo test harness -> resolve rust-analyzer errors
[profile.release]
opt-level = "s"
[profile.dev]
debug = true # Symbols are nice, and they don't increase the size on Flash
opt-level = "z"
[features]
default = []
[dependencies]
log = "0.4"
esp-idf-sys = "*"
esp-idf-hal = "*"
esp-idf-svc = "*"
anyhow = "*"
# --- Optional Embassy Integration ---
# esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync"] }
# If you enable embassy-time-driver, you MUST also add one of:
# a) Standalone Embassy libs ( embassy-time, embassy-sync etc.) with a foreign async runtime:
# embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } # NOTE: any generic-queue variant will work
# b) With embassy-executor:
# embassy-executor = { version = "0.7", features = ["executor-thread", "arch-std"] }
# NOTE: if you use embassy-time with embassy-executor you don't need the generic-queue-8 feature
# --- Temporary workaround for embassy-executor < 0.8 ---
# esp-idf-svc = { version = "0.51", features = ["embassy-time-driver", "embassy-sync"] }
# critical-section = { version = "1.1", features = ["std"], default-features = false }
[build-dependencies]
embuild = "0.33"