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’sscreendump. The display needs a display backend: with-display nonethe 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. NeedsCONFIG_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)
-
enumerator CLIENT#
-
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
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)#
Set the log level for the logger
See also
See also
- 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
See also
See also
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
See also
See also
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
See also
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
-
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)
-
size_t width = {320}#
-
struct EthernetConfig#
Configuration for the OpenCores Ethernet interface.
Public Members
-
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)
-
std::function<void()> on_link_up = {}#
Link came up (immediate in QEMU)
-
std::function<void()> on_link_down = {}#
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.
-
esp_netif_ip_info_t ip_info = {}#