Programando em Rust para o ESP32();

Neste tutorial, apresento um passo a passo completo para configurar o ambiente de desenvolvimento em Rust e criar um primeiro programa para o ESP32. A proposta é partir do zero, instalando e preparando o toolchain específico para a família ESP32, gerar um projeto utilizando os templates oficiais e, por fim, compilar, gravar e executar um exemplo simples que imprime “Hello, World!” na porta serial e faz o LED de teste piscar. O foco é oferecer um guia para quem está dando os primeiros passos na programação embarcada com Rust para o ESP32.

O módulo ESP32

Este experimento foi feito utilizando um módulo ESP-WROOM-32. Embora esse seja um dos modelos mais simples da família ESP32, esse tutorial pode ser aplicado a modelos mais novos também.

Os módulos ESP32 são fabricados pela Espressif. Esse modelo especificamente, tem as seguintes características:

  • CPU Xtensa LX6 que opera até aproximadamente 240 MHz, 32 bits e arquitetura Harvard (usa regiões de memória separadas para instruções e dados);
  • 4 MB de memória flash;
  • 520 KB de memória RAM (divididos em várias regiões);
  • Wi-Fi 802.11 b/g/n;
  • Bluetooth Classic e Bluetooth Low Energy (BLE);
  • GPIOs configuráveis (digitais e analógicos);
  • ADC (Conversores Analógico-Digitais);
  • DAC (saídas analógicas nativas);
  • PWM (saídas moduladas);
  • I²C, I²S, SPI, UART;
  • Timers, RTC, sensores de toque capacitivos;
  • Suporte a modo deep sleep com consumo extremamente reduzido.


Enfim, ele possui várias características interessantes para aplicações embarcadas e IoT. 

Módulo ESP-WROOM-32.
O projeto

O projeto aqui proposto é bem simples, ideal para quem quer dar os primeiros passos na programação Rust para essa plataforma. O programa de exemplo deste tutorial exibe a mensagem “Hello, World!” no terminal, usando a saída serial/USB do módulo, além de fazer piscar o LED azul de teste.

Mensagem emitida por um programa em Rust no ESP32 e recebida via serial no terminal.
LED de teste do módulo piscando por meio de um programa em Rust.
Tutorial

Segue o passo a passo do projeto:

[
01
]
Compartilhando a porta USB do Windows com o Ubuntu WSL

Esse passo é necessário para quem estiver programando no Linux do WSL. Vamos usar a porta USB/serial para que as ferramentas de desenvolvimento façam o upload do programa para dentro do módulo. Além disso, nesse programa de exemplo, o módulo irá se comunicar com o terminal também via USB/serial. 

O problema é que o Linux do WSL não “enxerga” as portas do PC por padrão, como no Windows. É necessário configurar o Windows para que ele compartilhe as portas USB com o Linux do WSL. Para fazer isso, instale no Windows o utilitário usbipd, digitando no PowerShell:

				
					winget install --interactive --exact dorssel.usbipd-win
				
			

Com o usbipd instalado e o módulo ESP32 conectado a uma porta USB, verifique em qual barramento USB o módulo está conectado através do comando:

				
					usbipd list
				
			
Dispositivos USB conectados, listados pelo utilitário usbipd.

Observe em qual barramento USB está conectado o módulo ESP32 (qual dispositivo “novo” apareceu no seu PC). No meu caso, está conectado ao barramento 1-5 (USB-Enhanced-SERIAL CH9102). Agora compartilhe essa porta com o Linux no WSL através dos comandos abaixo, substituindo 1-5 pelo seu barramento onde está conectado o módulo ESP32.

Obs: É provável que você precise abrir o PowerShell como usuário administrador para fazer isso.

				
					usbipd bind --busid 1-5

usbipd attach --wsl --busid 1-5
				
			
[
02
]
Atualizando o Ubuntu e verificando as portas USB

Este projeto foi desenvolvido em um ambiente WSL (Ubuntu 24.04.1 no Windows) em um PC de 64 bits. O primeiro passo é certificar-se de que o sistema operacional está atualizado. Abra o terminal e digite:

				
					sudo apt update

sudo apt upgrade -y
				
			

Agora verifique se o Linux reconhece o dispositivo USB compartilhado pelo Windows. Para fazer isso, instale o pacote usbutils e use o comando lsusb, como mostrado abaixo:

				
					sudo apt install usbutils -y

lsusb
				
			
Dispositivos USB conectados no Linux, listados pelo comando lsusb.

O dispositivo pode ter aparecido com nome diferente, como é o caso mostrado na figura acima. Sem problemas quanto a isso. Normalmente, ao usar lsusb no Linux sem ter feito o compartilhamento antes, a lista de dispositivos é vazia.

[
03
]
Instalando Rust

As linhas de comando abaixo instalam o compilador Rust e seu conjunto de ferramentas:

				
					curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y

. "$HOME/.cargo/env" -y
				
			

Se quiser verificar se Rust foi instalado corretamente, basta digitar:

				
					cargo --version
				
			

Cargo é o gerenciador de pacotes e de compilação do ecossistema Rust. Esse comando deverá mostrar a versão instalada do Cargo. No meu caso, 1.91.1.

[
04
]
Instalando as dependências

Como estamos compilando para outra plataforma, o compilador Rust ainda precisa de ferramentas externas para completar a “linkagem” do binário executável. Ele até consegue fazer o primeiro estágio de compilação, gerando os arquivos “objeto”, mas quem cria o binário final é o “linker” – normalmente o ccgcc ou clang. Para instalar essas dependências, digite:

				
					sudo apt install build-essential -y
				
			
[
05
]
Instalando a toolchain de Rust para o ESP32

Os comandos abaixo instalam o conjunto de ferramentas e templates de projeto necessários para compilarmos um programa em Rust para os módulos ESP32:
(substituindo SEU_USUARIO pelo seu username no Linux)

				
					cargo install espup

espup install

. /home/SEU_USUARIO/export-esp.sh

cargo install espflash

cargo install esp-config --features=tui

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh | sh

cargo install esp-generate
				
			
[
06
]
Criando o projeto

Com as ferramentas e templates instalados, digite o comando abaixo para criar o projeto:

				
					esp-generate
				
			
Opções de módulos mostradas pelo programa esp-generate.

Primeiramente, o esp-generate questionará para qual módulo deverá ser o projeto. No meu caso, por se tratar de um modelo ESP-WROOM-32, trata-se da opção “esp32”. Verifique qual é o seu modelo de ESP32 e escolha a opção adequada. Usando as setas para cima e para baixo do teclado, selecione o módulo correspondente e pressione Enter. 

Depois disso, o esp-generate perguntará qual é o nome do projeto. Nesse caso, “hello_blink“.

Digitando o nome do projeto.

Finalmente, o esp-generate permitirá escolher quais recursos (“features”) devem estar automaticamente disponíveis no projeto. Como esse tutorial é apenas de primeiros passos, vamos deixar o padrão. Para fazer isso, basta salvar as opções pressionando “s“.

Escolhendo os recursos do projeto ("features").

Isso conclui a criação do projeto usando o template. 

Tela mostrando que o projeto foi criado com sucesso pelo esp-generate.

O que vamos fazer agora é acessar o projeto recém-criado e adicionar o crate esp-println ao projeto. Isso vai fazer com que possamos usar a macro println!, redirecionando sua saída para a porta serial do ESP32.

				
					cd hello_blink

cargo add esp-println --features esp32
				
			

O arquivo principal do binário é src/bin/main.rs. Altere-o para que ele tenha o conteúdo mostrado abaixo:

				
					#![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."
)]

use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::main;
use esp_hal::time::{Duration, Instant};
use esp_println::println;

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

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);

    // Configura o LED no GPIO2 (LED embutido do ESP32)
    let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());

    // Imprime mensagem na serial
    println!("Hello, World!");
    
    loop {
        // Troca o estado do led
        led.toggle();

        // Aguarda 250ms
        let delay_start = Instant::now();
        while delay_start.elapsed() < Duration::from_millis(250) {}
    }
}
				
			
[
07
]
Executando o programa

Para compilar e executar o projeto, digite (com o ESP32 conectado à porta USB):

				
					cargo run
				
			

Conforme mostrado no início do tutorial, o resultado deve ser “Hello, World!” impresso no terminal e o LED azul do módulo ESP32 piscando:

Mensagem emitida por um programa em Rust no ESP32 e recebida via serial no terminal.
LED de teste do módulo piscando por meio de um programa em Rust.
Problemas comuns
- Linker não encontrado

Se der a mensagem de erro “error: linker `xtensa-esp32-elf-gcc` not found, basta digitar novamente:

(substituindo SEU_USUARIO pelo seu username no Linux)

				
					. /home/SEU_USUARIO/export-esp.sh
				
			
- Falha ao abrir porta serial

Se for exibida a mensagem de erro ao se conectar com a porta serial, normalmente é falta de permissão. Basta observar na mensagem de erro qual é o dispositivo (/dev/…) e adicionar a permissão. No caso da mensagem acima, houve um erro ao acessar o dispositivo /dev/ttyACM0. O comando para conceder permissão então é:

				
					 sudo chmod 666 /dev/ttyACM0