esp-generate

repository·main·Indexed 43 days ago

https://github.com/esp-rs/esp-generate

A template generation tool for creating no_std Rust applications targeting Espressif SoCs, including the ESP32, ESP32-C, ESP32-H2, and ESP32-S series. It features a Terminal User Interface (TUI) and a CLI for configuring project options, managing toolchain versions, and validating hardware-specific metadata for supported chips.

Tokens
9.7K
Snippets
27
Records
44
Agent score
52%

What's inside esp-generate

  1. Understand the constraints of an esp-generate project

    main

    Projects generated by esp-generate are designed for Espressif MCUs and follow specific embedded Rust patterns:

    • Target Chip Compatibility: Ensure all dependencies, HAL features, and examples remain compatible with the selected target chip (e.g., esp32c6).
    • No Standard Library: These are #![no_std] and #![no_main] projects. Do not use std; use core and other embedded-friendly crates.
    • Memory Management:
      • If alloc is enabled, the heap is initialized in src/bin/main.rs using esp_alloc::heap_allocator!. You can adjust the heap size there if allocations fail.
      • If alloc is not enabled, avoid heap-backed types like Vec, String, Box, or boxed futures.
    • Configuration: Project defaults, target settings, and runner flags are located in .cargo/config.toml. Use the [env] section for project-specific environment variables rather than hard-coding them in the source code.
  2. Configure logging for esp-generate projects

    main

    Logging behavior depends on which feature was selected during generation:

    • defmt: If defmt is enabled, logging uses the defmt framework. The default filter is set via the DEFMT_LOG=info environment variable in .cargo/config.toml.
    • log/esp-println: If defmt is not used but log is enabled, logging uses log/esp-println. The default filter is set via the ESP_LOG=info environment variable in .cargo/config.toml.

    You can override these filters from your environment.

  3. Generate a new `no_std` project

    main

    There are two ways to generate a project for Espressif SoCs (ESP32, ESP32-C series, ESP32-H2, ESP32-S series):

    1. Using the Terminal User Interface (TUI)

    Run the command without arguments to launch an interactive prompt. You will be asked for a project name and can select the target chip and other configuration options via the TUI.

    2. Using the Command Line Interface (CLI)

    Use the -o or --option flags to specify project features and the target chip. Use the --headless flag to bypass the TUI.

    Example CLI usage:

    esp-generate -o esp32 -o embassy -o unstable-hal -o alloc -o wifi your-project

    Note: Replace esp32 with your target chip (e.g., esp32c6) and your-project with your desired project name.

  4. Troubleshoot flashing and debugging with probe-rs or espflash

    main

    The method used by cargo run depends on your configuration in .cargo/config.toml:

    Using probe-rs

    If probe-rs is configured, it is intended for RTT/defmt and debugging. This requires a supported debug probe/JTAG connection (not just a USB-UART adapter). Troubleshooting steps:

    • Run probe-rs list to verify connections.
    • Check probe permissions and udev rules.
    • Verify board wiring and JTAG support.
    • Ensure the --chip value in .cargo/config.toml matches your target chip.

    Using espflash

    If probe-rs is not used, cargo run uses espflash, which flashes via the board's serial/USB bootloader. Troubleshooting steps:

    • Check if the board is in bootloader mode.
    • Verify serial-port permissions.
    • Ensure the correct port is selected if multiple boards are connected.
    • Ensure the --chip value in .cargo/config.toml matches your target chip.
  5. Understand GeneratorOption constraints

    main

    When defining or interacting with GeneratorOption items, the following fields govern how they behave within the configuration lifecycle:

    • requires: A list of requirements. A requirement can be a positive requirement (e.g., "log") or a negative requirement (e.g., "!probe-rs").
    • compatible: An IndexMap defining constraints based on other selection groups. For example, an option might only be compatible if the log-frontend group has defmt selected.
    • selection_group: The name of the group this option belongs to (e.g., "chip").
    • sets: (Implicitly used) defines options that are automatically toggled when this option is selected.
  6. How the TUI handles visibility and compatibility

    main

    The TUI does not show all items in the GeneratorOptionItem tree. Instead, it renders a filtered view based on compatibility constraints:

    1. Option Visibility: An Option is only visible if its compatible constraints are met (e.g., a specific driver is only visible if the correct chip is selected).
    2. Category Visibility: A Category is only visible if at least one of its children is visible.
    3. Row Mapping: Because the TUI renders a filtered list, the row index provided by the UI (e.g., from ListState) does not match the index in the underlying tree. You must use repository.visible_item(row) to translate a UI row index into the actual GeneratorOptionItem.
  7. Understand the 'will deselect' warning mechanism

    main

    To prevent accidental configuration changes, the TUI provides visual feedback when an action would cause a cascade of deselections:

    • Selection Side: If selecting an option would force other currently active options to be deselected (due to conflicting requirements), a yellow warning - will deselect: [item1, item2] appears on the hovered row.
    • Deselection Side: If hovering over an already selected option that has dependents, the TUI warns that toggling it off will deselect those dependents.
    • Optimization: To keep the UI clean, this warning only appears on the hovered row. If the warning text is too long for the terminal width, it collapses to a count: - will deselect N.
  8. How template options and `sets` work

    main

    Template options can carry arbitrary data used during code generation via the sets field. This data is merged into the generation context using the option's name as the key.

    sets supports two shapes via the SetValue enum:

    • Scalars: Used for simple text substitutions (e.g., #REPLACE directives). Example: wokwi-board: board-esp32-c6-devkitc-1.
    • Lists: Used for complex code-generation paths like pin reservations. Example: remove_pins: [spi_flash, spi_psram].

    Important: Keys in sets must not collide with fixed generator variables like project-name, mcu, or rust_target. If a collision occurs, the generator-provided value takes precedence.

    sets:
      wokwi-board: board-esp32-c6-devkitc-1       # Scalar
      remove_pins: [spi_flash, spi_psram]         # List
  9. Structure of a generated async Embassy entrypoint

    main

    When using esp-generate to create an async project with the embassy option, the generated src/bin/main.rs follows a specific initialization pattern. The entrypoint uses the #[esp_rtos::main] attribute to define an async fn main(spawner: Spawner) -> !.

    Key initialization steps include:

    1. Logging/RTT Setup: Depending on selected options (defmt, log, or probe-rs), the application initializes RTT or a logger.
    2. Hardware Initialization: Uses esp_hal::init(config) to acquire peripherals.
    3. RTOS Startup: Uses esp_rtos::start with a timer and software interrupt to drive the async executor.
    4. Memory Allocation: If the alloc option is enabled, a heap allocator is initialized via esp_alloc::heap_allocator!.
    5. Peripheral Setup: Conditional initialization for Wi-Fi (esp_radio::wifi) or Bluetooth (trouble_host via BleConnector) based on project configuration.
    #[esp_rtos::main]
    async fn main(spawner: Spawner) -> ! {
        // ... initialization ...
        let peripherals = esp_hal::init(config);
        
        // Start the RTOS executor
        esp_rtos::start(timg0.timer0, sw_interrupt.software_interrupt0);
    
        loop {
            info!("Hello world!");
            Timer::after(Duration::from_secs(1)).await;
        }
    }
  10. Understand how option dependencies and chip switches work in the TUI

    main

    The TUI manages complex dependencies between configuration options, especially when switching between different ESP chips:

    • Required Groups: Some configuration categories (like chip selection) are required. You cannot save the project until you have made a selection in every required group. The footer will display a message like Cannot save yet — pick one option from: [group_name] if requirements are unmet.
    • Chip Switching & Eviction: When you switch to a different chip, the TUI automatically handles the 'blast radius' of that change:
      • Remap-by-name: Options that no longer exist in the new chip's configuration tree are dropped.
      • Cascade Eviction: If an option's requirements are no longer met (e.g., a dependency on a feature only available on the previous chip), it is automatically deselected.
      • Preview: When hovering over a chip option, the TUI provides a preview in the list trailer showing which currently selected options will deselect due to the impending switch.
    • Radio Button Behavior: Chip selection groups act as radio buttons. Selecting a different chip automatically deselects the previous one.
  11. How option selection and cascading de-selection works

    main

    The esp-generate configuration system uses a dependency-based model to manage project options. When an option is selected or deselected, the system automatically manages related options through two primary mechanisms:

    1. Requirement Cascades: If you select an option that requires another (e.g., panic-rtt-target requires probe-rs), the system ensures requirements are met. Conversely, if you deselect an option, the system will automatically deselect any currently active options that depend on it (e.g., deselecting probe-rs will automatically deselect panic-rtt-target).
    2. Compatibility Cascades: Options can define compatible constraints against specific selection groups. If a selection in a group (like chip or log-frontend) makes an option incompatible, that option is automatically cleared from the active configuration.

    Key Behaviors:

    • No Resurrection: If an option is cleared because its requirements were no longer met, it is not automatically re-selected if the requirements become valid again later. You must manually toggle it back on.
    • Non-Actionable Conflicts: If an option is currently unavailable due to a compatibility mismatch or an unmet requirement, attempting to toggle it will result in no change, and the system will not report a cascade of de-selections for it.