feat: add ESP32 emulator with QEMU WebAssembly integration and basic GPIO functionality tests
parent
3fe71b57af
commit
02c69c23f6
|
|
@ -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…)
|
||||
═══════════════════════════════════════════════ -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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/ /
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue