feat: add ESP32 emulator with QEMU WebAssembly integration and basic GPIO functionality tests

pull/12/head
David Montero Crespo 2026-03-10 18:01:05 -03:00
parent 3fe71b57af
commit 02c69c23f6
8 changed files with 338 additions and 0 deletions

View File

@ -18,6 +18,19 @@
<!-- Google Search Console verification — replace token after verifying in Search Console -->
<!-- <meta name="google-site-verification" content="REPLACE_WITH_YOUR_VERIFICATION_TOKEN" /> -->
<!-- ═══════════════════════════════════════════════
GOOGLE ANALYTICS 4
Replace G-XXXXXXXXXX with your real Measurement ID
(analytics.google.com → Admin → Data Streams → Measurement ID)
═══════════════════════════════════════════════ -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-HPD8HTV3TG"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-HPD8HTV3TG');
</script>
<!-- ═══════════════════════════════════════════════
OPEN GRAPH (Facebook, LinkedIn, WhatsApp, Telegram, Discord, Slack…)
═══════════════════════════════════════════════ -->

View File

@ -0,0 +1,29 @@
# ESP32 Emulation Test Suite
This directory contains tests for ESP32 emulation using QEMU compiled to WebAssembly.
## Structure
- `sketches/` - Arduino sketches for ESP32 (copied from test/esp32/sketches)
- `binaries/` - Compiled firmware binaries (`.bin`, `.elf`)
- `qemu-config/` - QEMU machine configurations
- `scripts/` - Build and test scripts
- `web/` - HTML/JS wrapper for WebAssembly QEMU
## Prerequisites
1. QEMU fork built as WebAssembly (see `wokwi-libs/qemu-lcgamboa/`)
2. ESP32 toolchain (arduino-cli with esp32 platform)
3. Emscripten SDK (for building QEMU to WASM)
## Initial Test
The first test is to compile the blink sketch and run it in QEMU (native) to verify basic GPIO emulation.
## Goals
- Emulate GPIO pins (digitalWrite, digitalRead)
- Emulate Serial output (UART)
- Emulate WiFi (station and AP modes)
- Emulate other peripherals (SPI, I2C, ADC, etc.)
- Run entirely in browser via WebAssembly

View File

View File

@ -0,0 +1,40 @@
# Dockerfile to build QEMU for ESP32 with Emscripten
FROM emscripten/emsdk:latest AS builder
WORKDIR /src
# Clone QEMU fork (lcgamboa)
RUN git clone --depth 1 https://github.com/lcgamboa/qemu.git -b picsimlab-esp32 qemu
WORKDIR /src/qemu
# Apply patches if needed (none for now)
# Configure for Emscripten
# Use emconfigure wrapper to set up environment
RUN emconfigure ./configure \
--target-list=xtensa-softmmu \
--disable-system \
--disable-user \
--disable-tools \
--disable-docs \
--disable-guest-agent \
--disable-slirp \
--disable-werror \
--enable-emscripten \
--extra-cflags="-O2 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1" \
--cross-prefix=em \
--cpu=wasm32
# Build with emmake
RUN emmake make -j$(nproc)
# The resulting binary will be qemu-system-xtensa.wasm? Actually QEMU builds to .js and .wasm
# We need to copy the generated files to output directory
RUN mkdir -p /output
RUN cp -r build/* /output/
# Also build the shared library version for easier integration
# (optional)
FROM scratch AS export
COPY --from=builder /output/ /

View File

@ -0,0 +1,20 @@
// Simple ESP32 Blink Test
// Tests basic GPIO functionality
#define LED_PIN 2 // Built-in LED on most ESP32 dev boards
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
Serial.println("ESP32 Blink Test Started");
}
void loop() {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED ON");
delay(1000);
digitalWrite(LED_PIN, LOW);
Serial.println("LED OFF");
delay(1000);
}

View File

@ -0,0 +1,124 @@
// Mock ESP32 Emulator using QEMU WebAssembly (placeholder)
// This is a temporary simulation until real QEMU WASM is integrated.
class ESP32Emulator {
constructor() {
this.running = false;
this.interval = null;
this.gpioState = {};
this.serialBuffer = '';
this.canvas = document.getElementById('simulationCanvas');
this.ctx = this.canvas.getContext('2d');
this.ledX = 400;
this.ledY = 200;
this.ledRadius = 30;
this.ledOn = false;
this.initGPIO();
this.renderCanvas();
}
initGPIO() {
// Initialize GPIO pins 0-39
for (let i = 0; i < 40; i++) {
this.gpioState[i] = { mode: 'input', value: 0 };
}
// Set pin 2 as output (built-in LED)
this.gpioState[2].mode = 'output';
this.updateGPIOView();
}
updateGPIOView() {
const table = document.getElementById('gpioTable');
table.innerHTML = '';
for (let i = 0; i < 40; i++) {
const row = document.createElement('tr');
row.innerHTML = `<td>${i}</td><td>${this.gpioState[i].mode}</td><td>${this.gpioState[i].value}</td>`;
table.appendChild(row);
}
}
logSerial(message) {
const logDiv = document.getElementById('serialLog');
logDiv.textContent += message + '\n';
logDiv.scrollTop = logDiv.scrollHeight;
}
renderCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Draw LED
this.ctx.beginPath();
this.ctx.arc(this.ledX, this.ledY, this.ledRadius, 0, Math.PI * 2);
this.ctx.fillStyle = this.ledOn ? '#ff4444' : '#444444';
this.ctx.fill();
this.ctx.strokeStyle = '#888';
this.ctx.stroke();
// Draw label
this.ctx.fillStyle = 'white';
this.ctx.font = '16px Arial';
this.ctx.textAlign = 'center';
this.ctx.fillText('GPIO 2 (LED)', this.ledX, this.ledY + this.ledRadius + 20);
}
start() {
if (this.running) return;
this.running = true;
this.logSerial('ESP32 Emulator started');
this.interval = setInterval(() => {
// Simulate blink sketch: toggle GPIO2 every second
this.gpioState[2].value = this.gpioState[2].value ? 0 : 1;
this.ledOn = this.gpioState[2].value === 1;
this.updateGPIOView();
this.renderCanvas();
this.logSerial(this.ledOn ? 'LED ON' : 'LED OFF');
}, 1000);
this.logSerial('Blink sketch running...');
}
stop() {
if (!this.running) return;
clearInterval(this.interval);
this.running = false;
this.logSerial('Emulator stopped');
}
reset() {
this.stop();
this.initGPIO();
this.ledOn = false;
this.renderCanvas();
document.getElementById('serialLog').textContent = '';
this.logSerial('ESP32 reset');
}
}
const emulator = new ESP32Emulator();
// UI functions
function loadEmulator() {
const status = document.getElementById('status');
status.className = 'status running';
status.textContent = 'Emulator loaded (mock)';
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = false;
document.getElementById('resetBtn').disabled = false;
emulator.logSerial('QEMU WASM module loaded (simulated)');
}
function startEmulation() {
emulator.start();
}
function stopEmulation() {
emulator.stop();
}
function resetEmulation() {
emulator.reset();
}
// Initialize buttons state
window.onload = function() {
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = true;
document.getElementById('resetBtn').disabled = true;
};

View File

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 Emulator Test</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background: #f0f0f0;
}
.container {
max-width: 1000px;
margin: auto;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
}
.status {
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.status.running {
background-color: #d4edda;
color: #155724;
}
.status.stopped {
background-color: #f8d7da;
color: #721c24;
}
.canvas-container {
margin: 20px 0;
text-align: center;
}
canvas {
border: 1px solid #ccc;
background: #222;
}
.controls button {
padding: 10px 20px;
margin-right: 10px;
font-size: 16px;
cursor: pointer;
}
.log {
background: #1e1e1e;
color: #d4d4d4;
font-family: monospace;
padding: 10px;
border-radius: 5px;
height: 300px;
overflow-y: auto;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="container">
<h1>ESP32 Emulator Test (QEMU + WebAssembly)</h1>
<p>This test loads QEMU compiled to WebAssembly and runs a compiled ESP32 blink sketch.</p>
<div class="status stopped" id="status">Emulator not loaded</div>
<div class="controls">
<button onclick="loadEmulator()">Load Emulator</button>
<button onclick="startEmulation()" disabled id="startBtn">Start</button>
<button onclick="stopEmulation()" disabled id="stopBtn">Stop</button>
<button onclick="resetEmulation()" disabled id="resetBtn">Reset</button>
</div>
<div class="canvas-container">
<canvas id="simulationCanvas" width="800" height="400"></canvas>
</div>
<h3>Serial Output</h3>
<div class="log" id="serialLog"></div>
<h3>GPIO State</h3>
<div id="gpioState">
<table border="1" cellpadding="5">
<thead>
<tr><th>Pin</th><th>Mode</th><th>State</th></tr>
</thead>
<tbody id="gpioTable">
</tbody>
</table>
</div>
</div>
<script src="emulator.js"></script>
</body>
</html>

View File

@ -0,0 +1,20 @@
// Simple ESP32 Blink Test
// Tests basic GPIO functionality
#define LED_PIN 2 // Built-in LED on most ESP32 dev boards
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
Serial.println("ESP32 Blink Test Started");
}
void loop() {
digitalWrite(LED_PIN, HIGH);
Serial.println("LED ON");
delay(1000);
digitalWrite(LED_PIN, LOW);
Serial.println("LED OFF");
delay(1000);
}