QEMU (emulator)#

QEMU#

The espp::Qemu component is a board-support package for Espressif’s QEMU: not a board, but the few virtual peripherals the emulator offers, wrapped with the same shape as the real-board BSPs so an espp application runs with no hardware at all. It builds for the three targets the emulator supports (esp32, esp32c3, esp32s3).

  • Display: the virtual RGB panel as an LVGL display (initialize_display()). What LVGL draws appears in the QEMU window, or off screen in a headless run (SDL_VIDEODRIVER=dummy), and can be captured with the QEMU monitor’s screendump. The display needs a display backend: with -display none the panel driver’s first flush waits forever.

  • Ethernet: the OpenCores Ethernet MAC (initialize_ethernet()), a real lwIP interface on QEMU’s user-mode NAT or on a tap device. Needs CONFIG_ETH_USE_OPENETH.

  • SD card (ESP32 machine only): the SD/MMC host (initialize_sdcard()), a FAT volume on the image file passed with -drive file=...,if=sd.

Everything else (touch, I2C, I2S, Wi-Fi, BLE, USB) is not emulated. See Running espp in QEMU for installing the emulator, headless runs, screenshots, networking and debugging.

API Reference#

Header File#

Classes#

class Qemu : public espp::BaseComponent#

Board-support package for Espressif’s QEMU emulator.

QEMU is not a board, but it has a few virtual peripherals that no real board has, and an application that targets them runs with no hardware at all: on a developer’s machine, in CI, or in a container. This BSP wraps the ones the emulator offers, with the same shape as the real-board BSPs:

  • the virtual RGB panel, as an LVGL display (`initialize_display()`): what LVGL draws appears in the QEMU window (`-display sdl` / `idf.py qemu —graphics`) and can be captured with the QEMU monitor’s `screendump`;

  • the OpenCores Ethernet MAC (`initialize_ethernet()`): a real lwIP interface on QEMU’s user-mode NAT (`-nic user,model=open_eth`, the default of `idf.py qemu`) or on a tap device;

  • the SD/MMC host (`initialize_sdcard()`, ESP32 only): a FAT volume on an image file passed with `-drive file=sd.img,if=sd,format=raw`.

Everything else (touch, I2C, I2S, Wi-Fi, BLE, USB) is not emulated. The component builds for the three targets the emulator supports (esp32, esp32c3, esp32s3); see the component README for the QEMU command lines.

QEMU Example#

  auto &qemu = espp::Qemu::get();

  // The virtual RGB panel: appears as the QEMU window with --graphics /
  // -display sdl, and renders headless otherwise (screendump works either
  // way). Nothing to do with real hardware: initialize_display() fails there.
  logger.info("Initializing display...");
  if (!qemu.initialize_display({.width = 320, .height = 240})) {
    logger.error("Failed to initialize the display (not running in QEMU?)");
    return;
  }

  // the UI: status lines + a sweeping arc, updated from here and from the
  // Ethernet callbacks
  Gui gui({.log_level = espp::Logger::Verbosity::WARN});

  // The OpenCores Ethernet MAC on QEMU's user-mode network: a DHCP client
  // gets 10.0.2.15 from QEMU, with 10.0.2.2 as the gateway (the host) and
  // 10.0.2.3 as the DNS server.
  logger.info("Initializing Ethernet...");
  bool eth_ok = qemu.initialize_ethernet({
      .mode = espp::Qemu::DhcpMode::CLIENT,
      .on_link_up = [&]() { gui.set_ethernet_text("Ethernet: link up, waiting for DHCP"); },
      .on_link_down = [&]() { gui.set_ethernet_text("Ethernet: link down"); },
      .on_got_ip =
          [&](esp_ip4_addr_t ip) {
            std::string text = "Ethernet: " + std::to_string(esp_ip4_addr1(&ip)) + "." +
                               std::to_string(esp_ip4_addr2(&ip)) + "." +
                               std::to_string(esp_ip4_addr3(&ip)) + "." +
                               std::to_string(esp_ip4_addr4(&ip));
            logger.info("{}", text);
            gui.set_ethernet_text(text);
          },
  });
  if (!eth_ok) {
    logger.error("Failed to initialize Ethernet");
    gui.set_ethernet_text("Ethernet: failed (CONFIG_ETH_USE_OPENETH?)");
  }

#if CONFIG_IDF_TARGET_ESP32
  // The SD card image attached with -drive file=sd.img,if=sd,format=raw.
  // QEMU emulates the SD/MMC host on the ESP32 machine only (the ESP32-S3 has
  // the peripheral in silicon, but not in the emulator), so the other targets
  // skip this.
  logger.info("Initializing SD card...");
  if (qemu.initialize_sdcard({.mount_point = "/sdcard"})) {
    const char *path = "/sdcard/hello.txt";
    std::string status = "SD card: mounted";
    if (FILE *f = fopen(path, "w"); f != nullptr) {
      fprintf(f, "hello from espp in QEMU, boot %lld us\n", esp_timer_get_time());
      fclose(f);
      char line[80] = {};
      if (FILE *r = fopen(path, "r"); r != nullptr) {
        if (fgets(line, sizeof(line), r) != nullptr) {
          logger.info("Read back from {}: {}", path, line);
          status = "SD card: hello.txt written and read back";
        }
        fclose(r);
      }
    } else {
      status = "SD card: mounted, but could not write hello.txt";
    }
    gui.set_sdcard_text(status);
  } else {
    logger.warn("No SD card (attach an image with -drive file=sd.img,if=sd,format=raw)");
    gui.set_sdcard_text("SD card: none (see README)");
  }
#else
  gui.set_sdcard_text("SD card: not emulated on this target");
#endif // CONFIG_IDF_TARGET_ESP32

  // Keep the uptime line and a heap figure moving, so a log capture or a
  // screenshot taken at any time shows a live system.
  int seconds = 0;
  while (true) {
    std::this_thread::sleep_for(1s);
    seconds++;
    gui.set_uptime_text("Uptime: " + std::to_string(seconds) + " s");
    if (seconds % 10 == 0) {
      logger.info("Uptime {} s, free heap {} bytes, Ethernet {}", seconds,
                  heap_caps_get_free_size(MALLOC_CAP_DEFAULT),
                  qemu.is_ethernet_connected() ? "up" : "down");
    }
  }

Public Types

enum class DhcpMode#

Whether the Ethernet interface runs a DHCP client or a DHCP server.

Values:

enumerator CLIENT#

Ask QEMU’s user-mode network (or the tap’s network) for an address.

enumerator SERVER#

Serve addresses (tap networking with a host interface bridged in)

using Pixel = lv_color16_t#

The pixel type the virtual panel is driven with (RGB565)

Public Functions

bool initialize_display(const DisplayConfig &config)#

Create the virtual RGB panel and the LVGL display on it

Parameters:

config – Panel size and draw-buffer configuration

Returns:

true on success (false outside QEMU: the panel’s registers do not exist on real hardware, and the driver refuses them)

bool initialize_display()#

Create the virtual RGB panel and the LVGL display with the default configuration (320 x 240)

Returns:

true on success

inline std::shared_ptr<Display<Pixel>> display() const#
Returns:

The LVGL display, or null before initialize_display()

inline esp_lcd_panel_handle_t lcd_panel() const#
Returns:

The esp_lcd panel handle, or null before initialize_display()

inline size_t display_width() const#
Returns:

Panel width in pixels (0 before initialize_display())

inline size_t display_height() const#
Returns:

Panel height in pixels (0 before initialize_display())

void write_lcd_lines(int xs, int ys, int xe, int ye, const Pixel *data)#

Write pixels straight to the panel, bypassing LVGL

Parameters:
  • xs, ys – Top-left corner (inclusive)

  • xe, ye – Bottom-right corner (inclusive)

  • data – (xe - xs + 1) * (ye - ys + 1) pixels, row-major

bool initialize_ethernet(const EthernetConfig &config)#

Bring up the OpenCores Ethernet interface

Note

Needs CONFIG_ETH_USE_OPENETH (ESP-IDF: Component config -> Ethernet -> Support OpenCores Ethernet MAC); fails otherwise.

Parameters:

config – DHCP mode, addresses and callbacks

Returns:

true once the interface is started

bool initialize_ethernet()#

Bring up the OpenCores Ethernet interface as a DHCP client

Returns:

true once the interface is started

inline bool is_ethernet_connected() const#
Returns:

true while the link is up

inline esp_ip4_addr_t ethernet_ip() const#
Returns:

The interface’s IPv4 address (zero until obtained)

inline std::shared_ptr<Ethernet> ethernet() const#
Returns:

The Ethernet component, or null before initialize_ethernet()

inline const std::string &get_name() const#

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

template<typename T = void>
inline void set_log_tag(const std::string_view&)#

Not available: the tag is fixed at construction (pass it to the constructor). Enable CONFIG_ESPP_LOGGER_MUTABLE_TAG (menuconfig: ESPP Logger -> Mutable tag) to change it afterwards.

inline espp::Logger::Verbosity get_log_level() const#

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)#

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)#

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const#

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)#

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Functions

static inline Qemu &get()#

Access the singleton

Returns:

The one Qemu instance

struct DisplayConfig#

Configuration for the virtual RGB panel + LVGL display.

Public Members

size_t width = {320}#

Panel (QEMU window) width in pixels.

size_t height = {240}#

Panel (QEMU window) height in pixels

size_t pixel_buffer_size = {0}#

Size of each LVGL draw buffer, in pixels (two of them with double_buffered, else one); 0 = a tenth of the panel. The panel has its own frame buffer, in QEMU-side memory, that draw_bitmap() copies into; LVGL renders into these internal-RAM draw buffers.

bool double_buffered = {true}#

Two draw buffers (render one while the other is flushed)

struct EthernetConfig#

Configuration for the OpenCores Ethernet interface.

Public Members

DhcpMode mode = {DhcpMode::CLIENT}#

DHCP client (default) or server

esp_netif_ip_info_t ip_info = {}#

CLIENT: if `ip.addr` is non-zero, a static address instead of DHCP. SERVER: the interface / gateway address (zero = 192.168.4.1/24).

std::string hostname = {}#

netif hostname (empty = ESP-IDF default)

Link came up (immediate in QEMU)

Link went down.

Ethernet::IpCallback on_got_ip = {}#

An IPv4 address was obtained.

std::function<void()> on_lost_ip = {}#

The IPv4 address was lost.

Ethernet::ClientIpCallback on_client_assigned = {}#

SERVER: a lease was handed out.