# ESP32

# Getting Started

While exploring my local network, I stumbled upon several devices named `espressif`, and after some searching online I found the [ESP32](https://www.espressif.com/en/products/socs/esp32) was the likely culprit. I've always been interested in hardware and have experience working with electrical circuits from 120-480 volts, but I have never identified a resistor or even considered purchasing a breadboard.

So, I bought a board on Amazon because it was fast shipping and I had a long weekend coming up. I won't share an Amazon link here for several reasons, but the exact model that I purchased was the [ESP32-DevKitC V4](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-devkitc/user_guide.html#overview) which comes with the [ESP32-WROOM](https://www.espressif.com/sites/default/files/documentation/esp32-wroom-32e_esp32-wroom-32ue_datasheet_en.pdf) chip soldered on.

I love smart devices, that's how the ESP32 ended up on my local network. I tracked the `espressif` devices down to my cat's water and food bowls from [Petlibro](https://petlibro.com/). So it's only natural that I'd like to use the ESP32 for some wifi controlled 'smart' task.

When the board first arrived, I tried out the Arduino IDE. It was fine for learning purposes, but it felt like the IDE was doing a lot for me without having the chance to really understand everything. Personally I (barely) prefer CLion, and I wanted to get back to the IDE that was familiar to me. More importantly, I wanted to work on the ESP32 without tethering myself to _any_ IDE. I wanted the project to be completely stand-alone, and to document and install any dependencies manually. Using Sketches in the Arduino IDE wasn't going to get me there.

Enter the [Espressif IoT Development Framework](https://github.com/espressif/esp-idf), or the ESP-IDF for short. This looks slightly intimidating at first, but if you're at all familiar with C programming you'll get along fine. Before we dive into the ESP-IDF, lets take a step back and gain our bearings.

# Ecosystems

Typically the ESP32 examples recommended for beginners will be using the [arduino-esp32 APIs](https://docs.espressif.com/projects/arduino-esp32/en/latest/getting_started.html) - Espressif provides one such example called [WiFiClient](https://github.com/espressif/arduino-esp32/tree/master/libraries/WiFi/examples/WiFiClient). These examples are also made available in the Arduino IDE's menus where you can create a new project (AKA a new sketch) based on this WiFiClient example. This example uses higher level C++ APIs than what you'll find in the [ESP-IDF examples](https://github.com/espressif/esp-idf/tree/master/examples).

Why would Espressif, creators of the ESP32 and _not_ the [Arduino](https://www.arduino.cc/), want to support [Arduino APIs](https://docs.arduino.cc/language-reference/en/functions/wifi/client/)? Simply because Arduinos are a popular point of entry for learning programming on microcontrollers, and supporting these APIs lowers the barrier of entry for ESP32 devices.

The ESP-IDF build system supports using those same Arduino APIs via an IDF component configuration marking `arduino-esp32` as a dependency. See the [Espressif GitHub example here](https://github.com/espressif/arduino-esp32/tree/master/idf_component_examples/hello_world), paired with the [IDF component documentation from espressif](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html). 

# Development Methods

There are different ways to organize ESP32 projects, the sections below outline the formats I've explored.

## Arduino IDE

The Arduino IDE can be used to load sketches easily and flash to your ESP32.

This is easiest for a beginner, but you will have to use the Arudino IDE.

[Download Arduino IDE here](https://www.arduino.cc/en/software)

```bash
unzip arduino-ide_2.3.4_Linux_64bit.zip -d ./arduino-ide_2.3.4
cd arduino-ide_2.3.4/
sudo chown root:root /home/shaun/Downloads/arduino-ide_2.3.4/chrome-sandbox
# Maybe
sudo chmod 4755 /home/shaun/Downloads/arduino-ide_2.3.4/chrome-sandbox

# For permissions to access USB devices
newgrp uucp
newgrp dialout

# Start IDE
./arduino-ide
```

## ESP-IDF

In the sections below, we will use the simpler arduino-esp32 APIs and examples to get familiar with deploying to the ESP32 using the ESP-IDF. This example isn't going to do very much interesting other than validate our build system. If we are successful, it will simply write some output to the serial monitor. No lights, buttons, sensors, wifi, etc.

In a future post, I'll look at drawing to an LCD display using the I2C communication protocol with the lower-level ESP APIs instead, and drop the dependency for arduino-esp32 entirely.

[![](https://knoats.com/uploads/images/gallery/2025-03/image-1741743996218.gif)](https://knoats.com/uploads/images/gallery/2025-03/image-1741743996218.gif)

### Installing

First we need to install the ESP-IDF following [GitHub instructions](https://github.com/espressif/esp-idf?tab=readme-ov-file#setup-build-environment) on ubuntu 24.04

**WARNING**: If you are using arduino-esp32 APIs mentioned above, pay attention to [arduino-esp32 releases](https://github.com/espressif/arduino-esp32/releases) for the latest supported ESP-IDF version. The [latest ESP-IDF version](https://github.com/espressif/esp-idf/releases/latest) is likely *not* currently supported by arduino-esp32. At the time of this writing, the [latest arduino-esp32 release v3.1.1](https://github.com/espressif/arduino-esp32/releases/tag/3.1.1) is `based on ESP-IDF 5.3.2`, so below we checkout the `v5.3.2` branch of `github.com/espressif/esp-idf.git`.

Install the [prerequisites required for the ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-macos-setup.html#step-1-install-prerequisites)

```bash
sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
```

Clone the ESP-IDF using the `v5.3.2` branch and install. There will be a lot of output produced from these commands that is not shown below.

```bash
git clone -b v5.3.2 git@github.com:espressif/esp-idf.git
cd esp-idf
./install.sh
. ./export.sh
```

#### Switching Versions

To switch versions after previously installing the ESP-IDF

```bash
cd /path/to/esp-idf
git checkout v5.3.2
git submodule update --init --recursive
./install.sh
. ./export.sh
```

If everything worked correctly, you should end up with this final output in your terminal - 

```
Done! You can now compile ESP-IDF projects.
Go to the project directory and run:

  idf.py build
```

### Project Example

First set up your environment to use ESP-IDF tools and to have the correct exports required for building with CMake. Unfortunately the way this is done is by sourcing a script in the ESP-IDF repository, after you've installed the ESP-IDf.

```bash
source /path/to/esp-idf/export.sh
```

You should now have the `idf.py` tool available in your shell.

```bash
idf.py

Usage: idf.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

  ESP-IDF CLI build management tool. For commands that are not known to idf.py an attempt to execute
  it as a build system target will be made. Selected target: None
```

To make initializing my environment for using the ESP-IDF simpler, I threw an alias in my `.bash_aliases` file. I prefix all my alias commands with a comma. It looks strange, but it clearly separates an alias from a typical command and I think that's pretty cool.

```bash
echo "alias ,idf='source $HOME/Code/Clones/esp-idf/export.sh'" >> ~/.bash_aliases
```

So next time when I want to work on ESP things I can just run `,idf` in a terminal to initialize my environment. From here we can use CMake to create an ESP-IDF project, and even flash to our device or open a serial monitor.


#### CMake

Setting up the project in CMake is fairly simple. For more information about build system see [ESP-IDF - Build System](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html)

```bash
mkdir esp-example
cd esp-example
vim CMakeLists.txt
```

```cmake
cmake_minimum_required(VERSION 3.26)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)

project(
    #[[NAME]]   esp-idf-arduino
    VERSION     0.1
    DESCRIPTION "Example ESP-IDF cmake project"
    LANGUAGES   CXX
)

# For writing pure cmake components, see the documentation
# https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/build-system.html#writing-pure-cmake-components
idf_build_set_property(COMPILE_OPTIONS "-Wno-error" APPEND)
```

And then create our `main/` directory where we'll store our source code and write a quick CMakeLists.txt

```bash
mkdir main
vim main/CMakeLists.txt
```

```cmake
idf_component_register(
    SRCS "main.cpp"
    INCLUDE_DIRS "."
)
```

The project structure now looks like the following file tree. We'll configure and build with cmake later, once we set up our dependencies in the ESP-IDF.

```bash
esp-example/
├── CMakeLists.txt
└── main
    └── CMakeLists.txt
```


#### Source Code

Now we write the source code for our application. I hope you're prepared, because it's a lot.

```bash
cd esp-example
vim main/main.cpp
```

```c++
#include "Arduino.h"

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println("Hello world!");
  delay(1000);
}
```

This code simply attaches to the serial monitor at 115200 baud and prints `Hello world!` every second. That's it, the project is completed - we have our cmake lists set up and our source code in place. Next we will configure the project with cmake using the `idf.py` tool.

#### Dependencies

Now that we have the tools to configure and build ESP-IDF projects with cmake, we can look at creating our first project. The example code that I'm going to build here is available at [git.shaunreed.com/shaunrd0/klips](https://git.shaunreed.com/shaunrd0/klips/src/branch/master/esp/cpp/04_esp-idf-arduino). The commands below will not clone this repository, but instead set up the project as I did when creating it.

First set the build target to the ESP32 device. This tells the ESP-IDF which microcontroller we are working with. 

```bash
idf.py set-target esp32
```

Notice that the `idf.py` command created the `sdkconfig` file in our current directory, and produced a `build/` directory -

```bash
esp-example/
├── build/
├── CMakeLists.txt
├── main
│   ├── CMakeLists.txt
│   └── main.cpp
└── sdkconfig
```

Next we need to add a dependency for the arduino-esp32 APIs. These simpler APIs will be a nice way to test our build system and get familiar with the tools.

**IMPORTANT:** We set the `3.1.1` version because the release is compatible with ESP-IDF `v5.3.2` we installed in the first step.

```bash
# https://github.com/espressif/arduino-esp32/releases/tag/3.1.1
idf.py add-dependency "espressif/arduino-esp32^3.1.1"

Executing action: add-dependency
NOTICE: Created "/home/shaun/Code/esp-example/main/idf_component.yml"
NOTICE: Successfully added dependency "espressif/arduino-esp32": "^3.1.1" to component "main"
NOTICE: If you want to make additional changes to the manifest file at path /home/shaun/Code/esp-example/main/idf_component.yml manually, please refer to the documentation: https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html
```

After the `idf.py` commands above, we have a new `idf_component.yml` file -

```bash
esp-example/
├── build/
├── CMakeLists.txt
├── main
│   ├── CMakeLists.txt
│   ├── idf_component.yml
│   └── main.cpp
└── sdkconfig
```

The contents should look like this, note the `arduino-esp32` dependency is listed on the last line

```yaml
## IDF Component Manager Manifest File
dependencies:
  ## Required IDF version
  idf:
    version: '>=4.1.0'
  # # Put list of dependencies here
  # # For components maintained by Espressif:
  # component: "~1.0.0"
  # # For 3rd party components:
  # username/component: ">=1.0.0,<2.0.0"
  # username2/component2:
  #   version: "~1.0.0"
  #   # For transient dependencies `public` flag can be set.
  #   # `public` flag doesn't have an effect dependencies of the `main` component.
  #   # All dependencies of `main` are public by default.
  #   public: true
  espressif/arduino-esp32: ^3.1.1
```

Now we're ready to configure the project and prepare to flash to our device. These commands will automatically pull in the arduino-esp32 dependency into our project directory so it may take some time to complete.

```bash
cmake -B build
```

If you're lucky after some output from cmake you'll get the following error

```bash
-- Project sdkconfig file /home/shaun/Code/test-esp/sdkconfig
-- Compiler supported targets: xtensa-esp-elf
-- App "esp-idf-arduino" version: 0.1
-- Adding linker script /home/shaun/Code/test-esp/build/esp-idf/esp_system/ld/memory.ld
-- Adding linker script /home/shaun/Code/test-esp/build/esp-idf/esp_system/ld/sections.ld.in
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/esp_rom/esp32/ld/esp32.rom.ld
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/esp_rom/esp32/ld/esp32.rom.api.ld
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/esp_rom/esp32/ld/esp32.rom.syscalls.ld
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
-- Adding linker script /home/shaun/Code/Clones/esp-idf/components/soc/esp32/ld/esp32.peripherals.ld
-- DEBUG: Use esp-modbus component folder: /home/shaun/Code/test-esp/managed_components/espressif__esp-modbus.
-- git rev-parse returned 'fatal: not a git repository (or any of the parent directories): .git'
ESP Insights Project commit: HEAD-HASH-NOTFOUND
-- git rev-parse returned 'fatal: not a git repository (or any of the parent directories): .git'
ESP RainMaker Project commit: HEAD-HASH-NOTFOUND

CMake Error at managed_components/espressif__arduino-esp32/CMakeLists.txt:371 (message):
  esp32-arduino requires CONFIG_FREERTOS_HZ=1000 (currently 100)
```

Run the following `sed` command to fix it. This just modifies a value that we could have probably tracked down in the `idf.py menuconfig` interface, but that doesn't sound like a good time.

```bash
sed -i -e 's/CONFIG_FREERTOS_HZ=100/CONFIG_FREERTOS_HZ=1000/' sdkconfig
```

Now rerun the cmake command to configure the project and it should complete normally. 

```bash
cmake -B build
```

Finally, we can build the project

```bash
cmake --build build
```

We'll hit this error, which was confusing at first for me because I wasn't familiar with the differences between `app_main()` and `loop()` / `setup()`

```bash
/home/shaun/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin/../lib/gcc/xtensa-esp-elf/13.2.0/../../../../xtensa-esp-elf/bin/ld: esp-idf/freertos/libfreertos.a(app_startup.c.obj):(.literal.main_task+0x24): undefined reference to `app_main'
/home/shaun/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin/../lib/gcc/xtensa-esp-elf/13.2.0/../../../../xtensa-esp-elf/bin/ld: esp-idf/freertos/libfreertos.a(app_startup.c.obj): in function `main_task':
/home/shaun/Code/Clones/esp-idf/components/freertos/app_startup.c:199:(.text.main_task+0x99): undefined reference to `app_main'
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
```

The build failed because we are using the ESP-IDF which expects the `app_main()` convention. We can use the Arudino style by setting some configurations in the ESP-IDF.

We need to [automatically start Arduino under the ESP-IDF](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html#configuration) so that we can make use of the Arduino `loop()` and `setup()` functions in our example. You can also use the ESP-IDF `app_main()` function if preferred, see the examples below for differences between the two.

[ESP-IDF example using app_main()](https://github.com/espressif/esp-idf/blob/v5.4/examples/get-started/hello_world/main/hello_world_main.c) \
[ESP-IDF example using Arduino loop() and setup()](https://github.com/espressif/arduino-esp32/blob/master/idf_component_examples/hello_world/main/main.cpp)

You can alternatively do this in the TUI tool `idf.py menuconfig`, but here is a bash command to append the same configuration to the `sdkconfig` in your project directory that was generated by the ESP-IDF. 

```bash
echo "CONFIG_AUTOSTART_ARDUINO=y" >> sdkconfig
```

We can once again build the project, only this this time it won't fail to build.

```bash
cmake --build build
```

If everything completed normally, the build output will end with 

```
Project build complete. To flash, run:
 idf.py flash
or
 idf.py -p PORT flash
or
 python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/main.bin
or from the "/home/shaun/Code/klips/esp/cpp/04_esp-idf-arduino/build" directory
 python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash "@flash_args"
```

In the next and final section, we'll look at flashing to the ESP32 and validating the program is behaving correctly using the serial monitor.


#### Flashing


Find the device for the ESP32. Mine is `/dev/ttyUSB0` in the output below.

```bash
$ ls /dev/tty*
/dev/tty    /dev/tty16  /dev/tty24  /dev/tty32  /dev/tty40  /dev/tty49  /dev/tty57  /dev/tty8       /dev/ttyS14  /dev/ttyS22  /dev/ttyS30
/dev/tty0   /dev/tty17  /dev/tty25  /dev/tty33  /dev/tty41  /dev/tty5   /dev/tty58  /dev/tty9       /dev/ttyS15  /dev/ttyS23  /dev/ttyS31
/dev/tty1   /dev/tty18  /dev/tty26  /dev/tty34  /dev/tty42  /dev/tty50  /dev/tty59  /dev/ttyprintk  /dev/ttyS16  /dev/ttyS24  /dev/ttyS4
/dev/tty10  /dev/tty19  /dev/tty27  /dev/tty35  /dev/tty43  /dev/tty51  /dev/tty6   /dev/ttyS0      /dev/ttyS17  /dev/ttyS25  /dev/ttyS5
/dev/tty11  /dev/tty2   /dev/tty28  /dev/tty36  /dev/tty44  /dev/tty52  /dev/tty60  /dev/ttyS1      /dev/ttyS18  /dev/ttyS26  /dev/ttyS6
/dev/tty12  /dev/tty20  /dev/tty29  /dev/tty37  /dev/tty45  /dev/tty53  /dev/tty61  /dev/ttyS10     /dev/ttyS19  /dev/ttyS27  /dev/ttyS7
/dev/tty13  /dev/tty21  /dev/tty3   /dev/tty38  /dev/tty46  /dev/tty54  /dev/tty62  /dev/ttyS11     /dev/ttyS2   /dev/ttyS28  /dev/ttyS8
/dev/tty14  /dev/tty22  /dev/tty30  /dev/tty39  /dev/tty47  /dev/tty55  /dev/tty63  /dev/ttyS12     /dev/ttyS20  /dev/ttyS29  /dev/ttyS9
/dev/tty15  /dev/tty23  /dev/tty31  /dev/tty4   /dev/tty48  /dev/tty56  /dev/tty7   /dev/ttyS13     /dev/ttyS21  /dev/ttyS3   /dev/ttyUSB0
```

To flash run the following commands.

```bash
# Manually selecting port with above output
# Baud rate matters: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/establish-serial-connection.html#connect-esp32-to-pc
idf.py -p /dev/ttyUSB0 -b 115200 flash

# Let esp-idf automatically detect port and baud rate
idf.py flash
```

To open serial monitor via the commandline -

```bash
idf.py monitor -b 115200
```

Or in CLion the [Serial Monitor](https://plugins.jetbrains.com/plugin/8031-serial-port-monitor) extension works well.

If everything is working correctly, you should see `Hello world!` printed to the serial monitor repeatedly.

[See the full source code for this project on gitea](https://git.shaunreed.com/shaunrd0/klips/src/branch/master/esp/cpp/04_esp-idf-arduino)

# Other Links

* [lafvintech/Basic-Starter-Kit-for-ESP32-S3-WROOM](https://github.com/lafvintech/Basic-Starter-Kit-for-ESP32-S3-WROOM)
* [espressif/esp-idf](https://github.com/espressif/esp-idf)
* [espressif/arduino-esp32](https://github.com/espressif/arduino-esp32)
* [basic-starter-kit-for-esp32-s3-wroom.readthedocs](https://basic-starter-kit-for-esp32-s3-wroom.readthedocs.io/en/latest/1.C_Tutorial/1_led.html)
* [esp32 forum](https://www.esp32.com/viewforum.php?f=23&sid=0f7fb222c257676b49e71d8231048a74)
* [esp32 datasheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
* [Migrating from API 2.X to 3.0](https://docs.espressif.com/projects/arduino-esp32/en/latest/migration_guides/2.x_to_3.0.html)
* [lafvintech tutorial starter kits (paid)](https://lafvintech.com/pages/tutorials)
* [this board](https://www.amazon.com/dp/B0BVZBTP8V?ref=ppx_yo2ov_dt_b_fed_asin_title)
* [tutorials specific to this board are here](https://www.dropbox.com/scl/fo/6znlij3eb23ih4jxcpv2w/AKvB1t9CCUgoVRVtGen8Yrw?rlkey=z84anl0hs940qf9fpl7l8q8q2&e=1&dl=0)

# Rust

[Rust ESP Book](https://docs.espressif.com/projects/rust/book/) was referenced for most of this writing.

Some context was addded for deeper understanding of high-level requirements of a rust esp project.

## Project Types

There are two project types to consider when creating esp32 rust projects. It mostly depends on your hardware or application requirements, but there may be other factors to consider as well. 

### Using std

These projects include Rust's standard library and depend on the ESP-IDF framework as an underlying OS.
They support features like heap allocation, file systems, networking, and standard error handling.
Because of the range of features and implementations available in `std`, this is better for applications that have more complex logic and more resources available to run.
While probably not technically required in all applications, you could expect an application like this to require AC power connection to run.

### Using no std

no_std projects exclude the standard library and operate without an operating system.  
They use only the Rust core library and sometimes the alloc crate for heap-like behavior.  
This approach is ideal for low-level, resource-constrained, or timing-critical applications where full control over the hardware is required.
For example this would be best for an application that is going to run on battery power with tight resources.

## Dependencies

There are a few things we'll need to install before we can work on ESP projects with rust.
The following sections will cover each required step.

### Installing espup

This section was in [RISCV and XTensa](https://docs.espressif.com/projects/rust/book/installation/riscv-and-xtensa.html?highlight=espup#1-install-espup) requirements in the docs, but from my attempts locally I could not proceed even with ESP32 without it.

This tool will install the needed libraries for working with ESP32 microcontrollers with Rust.

```bash
# This is required if you are working with esp-idf std projects. If using no std this is not required.
cargo install ldproxy

# This is required for all applications.
cargo install espup --locked
```

Running `espup` will give us this output that is important to read.

```bash
espup install
[info]: Installing the Espressif Rust ecosystem
[info]: Checking Rust installation
[info]: Installing Xtensa Rust 1.87.0.0 toolchain
[info]: Installing RISC-V Rust targets ('riscv32imc-unknown-none-elf', 'riscv32imac-unknown-none-elf' and 'riscv32imafc-unknown-none-elf') for 'stable' toolchain
[info]: Installing Xtensa LLVM
[info]: Installing GCC (xtensa-esp-elf)
[info]: Creating symlink between '/home/shaun/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-19.1.2_20250225/esp-clang/lib' and '/home/shaun/.espup/esp-clang'
[info]: All downloads complete
[info]: Installing 'rust' component for Xtensa Rust toolchain
[info]: Installing 'rust-src' component for Xtensa Rust toolchain
[info]: Installation successfully completed!

        To get started, you need to set up some environment variables by running: '. /home/shaun/export-esp.sh'
        This step must be done every time you open a new terminal.
            See other methods for setting the environment in https://esp-rs.github.io/book/installation/riscv-and-xtensa.html#3-set-up-the-environment-variables
```

So every time we want to work on esp-idf things with rust we need to run this script to set some variables.

```bash
. ~/export-esp.sh
```

### Project Generators

This section covers using generators to create a new ESP32 rust project. There are two options, so the two sections that follow will describe each one. You only need to pick one.

#### Installing cargo-generate

Installed with

```bash
cargo install cargo-generate
```

This is more configurable and can be used with `cargo-generate.toml` configurations stored on a remote git repository. This is not just for ESP project but can be used with a range of rust projects. For example to use a cargo generate project template for esp-idf (using `std`) we can run this command

```bash
cargo generate --git https://github.com/esp-rs/esp-idf-template.git --name esp-idf-rust-std -d mcu=esp32 -d std=true
```

To generate a project with no `std`

```bash
cargo generate --git https://github.com/esp-rs/esp-idf-template.git --name esp-idf-rust-no-std -d mcu=esp32 -d std=false
```

#### Installing esp-generate

[Understanding esp-generate](https://docs.esp-rs.org/book/writing-your-own-application/generate-project/esp-generate.html)

Installed with

```bash
cargo install esp-generate
```

To generate a ESP32 rust project using `std` run the following command

```bash
esp-generate new esp-idf-rust-std --mcu esp32 --std
```

To generate a project with no `std`

```bash
esp-generate new esp-idf-rust-no-std --mcu esp32 --no-std
```

### Build

Regardless of the method you chose for generating your project, your build steps will be the same.

Don't forget to source the script `espup` told us we needed when we were installing dependencies.

```bash
cd esp-rust-project

# Export the script that espup informed us we needed to build esp rust projects before you build
. ~/export-esp.sh
cargo build
```

Now we've built the project, and we just need to flash it to an ESP32 device to test it.

### Flash

Install `espflash` and configure your user to the required `dialout` group.

```bash
cargo install espflash
sudo usermod -aG dialout $USER
newgrp dialout
```

Pay careful attention to the path passed to `espflash` below. If you are using a different microcontroller or project name it will need to be changed to match.

```bash
espflash flash target/xtensa-esp32-espidf/debug/esp-idf-rust-std
```

You can also just use cargo to flash to the device automatically. If you have more than one device this may not work.

```bash
cargo run
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.07s
     Running `espflash flash --monitor target/xtensa-esp32-espidf/debug/esp-idf-rust-std`
[2025-07-06T16:56:15Z INFO ] Serial port: '/dev/ttyUSB0'
[2025-07-06T16:56:15Z INFO ] Connecting...
[2025-07-06T16:56:15Z INFO ] Using flash stub
Chip type:         esp32 (revision v3.1)
Crystal frequency: 40 MHz
Flash size:        4MB
Features:          WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC address:       3c:8a:1f:0c:53:60
App/part. size:    561,504/4,128,768 bytes, 13.60%
...
```

Then open the serial monitor for your device. If there is only one detected this will use it automatically, otherwise you will receive a prompt to select the device. Below my device was automatically selected, so what you see is the connection being established.

```bash
espflash monitor
[2025-07-06T16:24:40Z INFO ] Serial port: '/dev/ttyUSB0'
[2025-07-06T16:24:40Z INFO ] Connecting...
[2025-07-06T16:24:40Z INFO ] Using flash stub
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit
```

Press `CTRL+R` to reset the chip and you'll see the `Hello, world!` output in the [template project we used for generation](https://github.com/esp-rs/esp-idf-template/blob/master/cargo/src/main.rs#L9)

```bash
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:6276
load:0x40078000,len:15748
load:0x40080400,len:4
ho 8 tail 4 room 4
load:0x40080404,len:3860
entry 0x4008063c
I (31) boot: ESP-IDF v5.4.1-426-g3ad36321ea 2nd stage bootloader
I (31) boot: compile time Apr 24 2025 15:52:44
I (31) boot: Multicore bootloader
I (34) boot: chip revision: v3.1
I (37) boot.esp32: SPI Speed      : 40MHz
I (40) boot.esp32: SPI Mode       : DIO
I (44) boot.esp32: SPI Flash Size : 4MB
I (48) boot: Enabling RNG early entropy source...
I (52) boot: Partition Table:
I (55) boot: ## Label            Usage          Type ST Offset   Length
I (61) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (67) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (74) boot:  2 factory          factory app      00 00 00010000 003f0000
I (81) boot: End of partition table
I (84) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=27dcch (163276) map
I (147) esp_image: segment 1: paddr=00037df4 vaddr=3ffb0000 size=026a8h (  9896) load
I (151) esp_image: segment 2: paddr=0003a4a4 vaddr=40080000 size=05b74h ( 23412) load
I (160) esp_image: segment 3: paddr=00040020 vaddr=400d0020 size=4e97ch (321916) map
I (271) esp_image: segment 4: paddr=0008e9a4 vaddr=40085b74 size=05a5ch ( 23132) load
I (285) boot: Loaded app from partition at offset 0x10000
I (285) boot: Disabling RNG early entropy source...
I (295) cpu_start: Multicore app
I (304) cpu_start: Pro cpu start user code
I (304) cpu_start: cpu freq: 160000000 Hz
I (304) cpu_start: Application information:
I (307) cpu_start: Project name:     libespidf
I (312) cpu_start: App version:      1
I (317) cpu_start: Compile time:     Jul  6 2025 11:41:07
I (323) cpu_start: ELF file SHA256:  000000000...
I (328) cpu_start: ESP-IDF:          v5.2.3
I (333) cpu_start: Min chip rev:     v0.0
I (338) cpu_start: Max chip rev:     v3.99
I (342) cpu_start: Chip rev:         v3.1
I (347) heap_init: Initializing. RAM available for dynamic allocation:
I (355) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (360) heap_init: At 3FFB3018 len 0002CFE8 (179 KiB): DRAM
I (367) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (373) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (380) heap_init: At 4008B5D0 len 00014A30 (82 KiB): IRAM
I (387) spi_flash: detected chip: generic
I (390) spi_flash: flash io: dio
W (395) pcnt(legacy): legacy driver is deprecated, please migrate to `driver/pulse_cnt.h`
W (403) i2c: This driver is an old driver, please migrate your application code to adapt `driver/i2c_master.h`
W (414) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (423) main_task: Started on CPU0
I (433) main_task: Calling app_main()
I (433) esp_idf_rust_std: Hello, world!
```