Refactor code structure for improved readability and maintainability
parent
a40471fa38
commit
2b19181c2b
241
README.md
241
README.md
|
|
@ -1,8 +1,8 @@
|
|||
# Velxio: Arduino Emulator
|
||||
# Velxio: Arduino & Embedded Board Emulator
|
||||
|
||||
**Live at [velxio.dev](https://velxio.dev)**
|
||||
|
||||
A fully local, open-source Arduino emulator. Write Arduino code, compile it, and simulate it with real AVR8 CPU emulation and 48+ interactive electronic components — all running in your browser.
|
||||
A fully local, open-source multi-board emulator. Write Arduino C++ or Python, compile it, and simulate it with real CPU emulation and 48+ interactive electronic components — all running in your browser.
|
||||
|
||||
[](https://velxio.dev)
|
||||
[](https://github.com/davidmonterocrespo24/velxio/pkgs/container/velxio)
|
||||
|
|
@ -12,22 +12,21 @@ A fully local, open-source Arduino emulator. Write Arduino code, compile it, and
|
|||
[](COMMERCIAL_LICENSE.md)
|
||||
|
||||
---
|
||||
<a href="https://www.producthunt.com/products/velxio?embed=true&utm_source=badge-featured&utm_medium=badge&utm_campaign=badge-velxio" target="_blank" rel="noopener noreferrer"><img alt="Velxio - Arduino emulator running entirely in the browser | Product Hunt" width="250" height="54" src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1092514&theme=dark&t=1772998619179"></a>
|
||||
|
||||
|
||||
[](https://www.producthunt.com/products/velxio)
|
||||
|
||||
---
|
||||
|
||||
## Support the Project
|
||||
|
||||
Velxio is free and open-source. Building and maintaining a full Arduino emulator takes a lot of time — if it saves you time or you enjoy the project, sponsoring me directly helps keep development going.
|
||||
Velxio is free and open-source. Building and maintaining a full multi-board emulator takes a lot of time — if it saves you time or you enjoy the project, sponsoring me directly helps keep development going.
|
||||
|
||||
| Platform | Link |
|
||||
|---|---|
|
||||
| --- | --- |
|
||||
| **GitHub Sponsors** (preferred) | [github.com/sponsors/davidmonterocrespo24](https://github.com/sponsors/davidmonterocrespo24) |
|
||||
| **PayPal** | [paypal.me/odoonext](https://paypal.me/odoonext) |
|
||||
|
||||
Your support helps cover server costs, library maintenance, and frees up time to add new boards, components, and features. Thank you!
|
||||
Your support helps cover server costs, library maintenance, and frees up time to add new boards, components, and features. Thank you!
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ To self-host with Docker (single command):
|
|||
docker run -d -p 3080:80 ghcr.io/davidmonterocrespo24/velxio:master
|
||||
```
|
||||
|
||||
Then open **http://localhost:3080**.
|
||||
Then open <http://localhost:3080>.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -65,60 +64,133 @@ Component Picker showing 48 available components with visual previews, search, a
|
|||
|
||||
---
|
||||
|
||||
## Supported Boards
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><img src="frontend/public/boards/pi-pico.svg" width="130" alt="Raspberry Pi Pico"/><br/><b>Raspberry Pi Pico</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/pi-pico-w.svg" width="130" alt="Raspberry Pi Pico W"/><br/><b>Raspberry Pi Pico W</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/esp32-devkit-c-v4.svg" width="130" alt="ESP32 DevKit C"/><br/><b>ESP32 DevKit C</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/esp32-s3.svg" width="130" alt="ESP32-S3"/><br/><b>ESP32-S3</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="frontend/public/boards/esp32-c3.svg" width="130" alt="ESP32-C3"/><br/><b>ESP32-C3</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/xiao-esp32-c3.svg" width="130" alt="Seeed XIAO ESP32-C3"/><br/><b>Seeed XIAO ESP32-C3</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/esp32c3-supermini.svg" width="130" alt="ESP32-C3 SuperMini"/><br/><b>ESP32-C3 SuperMini</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/esp32-cam.svg" width="130" alt="ESP32-CAM"/><br/><b>ESP32-CAM</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><img src="frontend/public/boards/xiao-esp32-s3.svg" width="130" alt="Seeed XIAO ESP32-S3"/><br/><b>Seeed XIAO ESP32-S3</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/arduino-nano-esp32.svg" width="130" alt="Arduino Nano ESP32"/><br/><b>Arduino Nano ESP32</b></td>
|
||||
<td align="center"><img src="frontend/public/boards/Raspberry_Pi_3.svg" width="130" alt="Raspberry Pi 3B"/><br/><b>Raspberry Pi 3B</b></td>
|
||||
<td align="center">Arduino Uno / Nano / Mega<br/>(ATmega328p / 2560)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
| Board | CPU | Engine | Language |
|
||||
| ----- | --- | ------ | -------- |
|
||||
| Arduino Uno | ATmega328p @ 16 MHz | avr8js (browser) | C++ (Arduino) |
|
||||
| Arduino Nano | ATmega328p @ 16 MHz | avr8js (browser) | C++ (Arduino) |
|
||||
| Arduino Mega | ATmega2560 @ 16 MHz | avr8js (browser) | C++ (Arduino) |
|
||||
| Raspberry Pi Pico | RP2040 @ 125 MHz | rp2040js (browser) | C++ (Arduino) |
|
||||
| Raspberry Pi Pico W | RP2040 @ 125 MHz | rp2040js (browser) | C++ (Arduino) |
|
||||
| ESP32 DevKit C | Xtensa LX6 @ 240 MHz | QEMU lcgamboa (backend) | C++ (Arduino) |
|
||||
| ESP32-S3 | Xtensa LX7 @ 240 MHz | QEMU lcgamboa (backend) | C++ (Arduino) |
|
||||
| ESP32-CAM | Xtensa LX6 @ 240 MHz | QEMU lcgamboa (backend) | C++ (Arduino) |
|
||||
| ESP32-C3 | RISC-V RV32IMC @ 160 MHz | RiscVCore.ts (browser) | C++ (Arduino) |
|
||||
| Seeed XIAO ESP32-C3 | RISC-V RV32IMC @ 160 MHz | RiscVCore.ts (browser) | C++ (Arduino) |
|
||||
| ESP32-C3 SuperMini | RISC-V RV32IMC @ 160 MHz | RiscVCore.ts (browser) | C++ (Arduino) |
|
||||
| Seeed XIAO ESP32-S3 | Xtensa LX7 @ 240 MHz | QEMU lcgamboa (backend) | C++ (Arduino) |
|
||||
| Arduino Nano ESP32 | Xtensa LX6 @ 240 MHz | QEMU lcgamboa (backend) | C++ (Arduino) |
|
||||
| Raspberry Pi 3B | ARM Cortex-A53 @ 1.2 GHz | QEMU raspi3b (backend) | Python |
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Code Editing
|
||||
- **Monaco Editor** — Full C++ editor with syntax highlighting, autocomplete, minimap, and dark theme
|
||||
- **Multi-file workspace** — create, rename, delete, and switch between multiple `.ino` / `.h` / `.cpp` files
|
||||
- **Arduino compilation** via `arduino-cli` backend — compile sketches to `.hex` / `.uf2` files
|
||||
|
||||
- **Monaco Editor** — Full C++ / Python editor with syntax highlighting, autocomplete, minimap, and dark theme
|
||||
- **Multi-file workspace** — create, rename, delete, and switch between multiple `.ino` / `.h` / `.cpp` / `.py` files
|
||||
- **Arduino compilation** via `arduino-cli` backend — compile sketches to `.hex` / `.bin` files
|
||||
- **Compile / Run / Stop / Reset** toolbar buttons with status messages
|
||||
- **Compilation console** — resizable output panel showing full compiler output, warnings, and errors
|
||||
|
||||
### Multi-Board Support
|
||||
- **Arduino Uno** (ATmega328p) — full AVR8 emulation via avr8js
|
||||
- **Arduino Nano** (ATmega328p) — full AVR8 emulation
|
||||
- **Arduino Mega** (ATmega2560) — full AVR8 emulation
|
||||
- **Raspberry Pi Pico** (RP2040) — full RP2040 emulation via rp2040js, compiled with arduino-pico core
|
||||
- Board selector in the toolbar — switch boards without restarting
|
||||
### Multi-Board Simulation
|
||||
|
||||
### AVR8 Simulation (Arduino Uno / Nano / Mega)
|
||||
- **Real ATmega328p emulation** at 16 MHz using avr8js
|
||||
- **Full GPIO support** — PORTB (pins 8-13), PORTC (A0-A5), PORTD (pins 0-7)
|
||||
- **Timer0/Timer1/Timer2** peripheral support (`millis()`, `delay()`, PWM via `analogWrite()`)
|
||||
- **USART (Serial)** — full transmit and receive support
|
||||
- **ADC** — `analogRead()` on pins A0-A5, voltage injection from UI components
|
||||
- **SPI** — hardware SPI peripheral (enables ILI9341, SD card, etc.)
|
||||
#### AVR8 (Arduino Uno / Nano / Mega)
|
||||
|
||||
- **Real ATmega328p / ATmega2560 emulation** at 16 MHz via avr8js
|
||||
- **Full GPIO** — PORTB (pins 8–13), PORTC (A0–A5), PORTD (pins 0–7)
|
||||
- **Timer0/Timer1/Timer2** — `millis()`, `delay()`, PWM via `analogWrite()`
|
||||
- **USART** — full transmit and receive, auto baud-rate detection
|
||||
- **ADC** — `analogRead()` on A0–A5, voltage injection from potentiometers on canvas
|
||||
- **SPI** — hardware SPI peripheral (ILI9341, SD card, etc.)
|
||||
- **I2C (TWI)** — hardware I2C with virtual device bus
|
||||
- **~60 FPS simulation loop** with `requestAnimationFrame`
|
||||
- ~60 FPS simulation loop via `requestAnimationFrame`
|
||||
|
||||
### RP2040 Simulation (Raspberry Pi Pico)
|
||||
- **Real RP2040 emulation** via rp2040js at 133 MHz
|
||||
- **UART0** serial output displayed in Serial Monitor
|
||||
- **ADC** — 12-bit, 3.3V reference on GPIO 26-29 (A0-A3)
|
||||
#### RP2040 (Raspberry Pi Pico / Pico W)
|
||||
|
||||
### ESP32 Simulation (via lcgamboa QEMU)
|
||||
- **Real Xtensa LX6 dual-core emulation** via [lcgamboa/qemu](https://github.com/lcgamboa/qemu) fork
|
||||
- **Full GPIO** — all 40 GPIO pins with direction tracking and state callbacks
|
||||
- **UART0/1/2** — multi-UART serial with baud-rate detection
|
||||
- **ADC** — 12-bit, 3.3V reference on all ADC-capable pins (0–3300 mV injection)
|
||||
- **I2C** — synchronous bus with virtual device response support
|
||||
- **Real RP2040 emulation** at 125 MHz via rp2040js — ARM Cortex-M0+
|
||||
- **All 30 GPIO pins** — input/output, event listeners, pin state injection
|
||||
- **UART0 + UART1** — serial output in Serial Monitor; Serial input from UI
|
||||
- **ADC** — 12-bit on GPIO 26–29 (A0–A3) + internal temperature sensor (ch4)
|
||||
- **I2C0 + I2C1** — master mode with virtual device bus (DS1307, TMP102, EEPROM)
|
||||
- **SPI0 + SPI1** — loopback default; custom handler supported
|
||||
- **PWM** — available on any GPIO pin
|
||||
- **WFI optimization** — `delay()` skips ahead in simulation time instead of busy-waiting
|
||||
- **Oscilloscope** — GPIO transition timestamps at ~8 ns resolution
|
||||
- Compiled with the [earlephilhower arduino-pico](https://github.com/earlephilhower/arduino-pico) core
|
||||
|
||||
See [docs/RP2040_EMULATION.md](docs/RP2040_EMULATION.md) for full technical details.
|
||||
|
||||
#### ESP32 / ESP32-S3 (Xtensa QEMU)
|
||||
|
||||
- **Real Xtensa LX6/LX7 dual-core emulation** via [lcgamboa/qemu](https://github.com/lcgamboa/qemu)
|
||||
- **Full GPIO** — all 40 GPIO pins, direction tracking, state callbacks, GPIO32–39 fix
|
||||
- **UART0/1/2** — multi-UART serial, baud-rate detection
|
||||
- **ADC** — 12-bit on all ADC-capable pins (0–3300 mV injection from potentiometers)
|
||||
- **I2C** — synchronous bus with virtual device response
|
||||
- **SPI** — full-duplex with configurable MISO byte injection
|
||||
- **RMT / NeoPixel** — hardware RMT decoder with WS2812 24-bit GRB frame decoding
|
||||
- **LEDC/PWM** — 16-channel duty cycle readout, mapped to LED brightness
|
||||
- **RMT / NeoPixel** — hardware RMT decoder, WS2812 24-bit GRB frame decoding
|
||||
- **LEDC/PWM** — 16-channel duty cycle readout, LEDC→GPIO mapping, LED brightness
|
||||
- **WiFi** — SLIRP NAT emulation (`WiFi.begin("PICSimLabWifi", "")`)
|
||||
- **arduino-esp32 2.0.17 (IDF 4.4.x)** — only compatible version with lcgamboa WiFi emulation
|
||||
- **Crash detection** — banner notification in UI with dismiss button
|
||||
- **Fully included in the Docker image** — zero extra setup required
|
||||
- Requires arduino-esp32 **2.0.17** (IDF 4.4.x) — only version compatible with lcgamboa WiFi
|
||||
|
||||
See [docs/ESP32_EMULATION.md](docs/ESP32_EMULATION.md) for the complete installation guide.
|
||||
See [docs/ESP32_EMULATION.md](docs/ESP32_EMULATION.md) for setup and full technical details.
|
||||
|
||||
#### ESP32-C3 / XIAO-C3 (RISC-V, in-browser)
|
||||
|
||||
- **RV32IMC emulation** in TypeScript — no backend, no QEMU, no WebSocket
|
||||
- **GPIO 0–21** via W1TS/W1TC MMIO registers
|
||||
- **UART0** serial output in Serial Monitor
|
||||
- **Instant startup** — zero latency, works offline
|
||||
- **CI-testable** — same TypeScript runs in Vitest
|
||||
|
||||
See [docs/RISCV_EMULATION.md](docs/RISCV_EMULATION.md) for full technical details.
|
||||
|
||||
#### Raspberry Pi 3B (QEMU raspi3b)
|
||||
|
||||
- **Full BCM2837 emulation** via `qemu-system-aarch64 -M raspi3b`
|
||||
- **Boots real Raspberry Pi OS** (Trixie) — runs Python scripts directly
|
||||
- **RPi.GPIO shim** — drop-in replacement for the GPIO library; sends pin events to the frontend over a text protocol
|
||||
- **GPIO 0–27** — output and input, event detection, PWM (binary state)
|
||||
- **Dual serial** — ttyAMA0 for user Serial Monitor, ttyAMA1 for GPIO protocol
|
||||
- **Virtual File System** — edit Python scripts in the UI, upload to Pi at boot
|
||||
- **Multi-board serial bridge** — Pi ↔ Arduino serial communication on the same canvas
|
||||
- **qcow2 overlay** — base SD image never modified; session changes are isolated
|
||||
|
||||
See [docs/RASPBERRYPI3_EMULATION.md](docs/RASPBERRYPI3_EMULATION.md) for full technical details.
|
||||
|
||||
### Serial Monitor
|
||||
- **Live serial output** — characters as the sketch sends them via `Serial.print()`
|
||||
|
||||
- **Live serial output** — characters as the sketch/script sends them
|
||||
- **Auto baud-rate detection** — reads hardware registers, no manual configuration needed
|
||||
- **Send data** to the Arduino RX pin from the UI
|
||||
- **Send data** to the RX pin from the UI
|
||||
- **Autoscroll** with toggle
|
||||
|
||||
### Component System (48+ Components)
|
||||
|
||||
- **48 electronic components** from wokwi-elements
|
||||
- **Component picker** with search, category filters, and live previews
|
||||
- **Drag-and-drop** repositioning on the simulation canvas
|
||||
|
|
@ -126,16 +198,19 @@ See [docs/ESP32_EMULATION.md](docs/ESP32_EMULATION.md) for the complete installa
|
|||
- **Property dialog** — pin roles, Arduino pin assignment, rotate & delete
|
||||
|
||||
### Wire System
|
||||
|
||||
- **Wire creation** — click a pin to start, click another pin to connect
|
||||
- **Orthogonal routing** — no diagonal paths
|
||||
- **8 signal-type wire colors**: Red (VCC), Black (GND), Blue (Analog), Green (Digital), Purple (PWM), Gold (I2C), Orange (SPI), Cyan (USART)
|
||||
- **Segment-based wire editing** — drag segments perpendicular to their orientation
|
||||
|
||||
### Library Manager
|
||||
|
||||
- Browse and install the full Arduino library index directly from the UI
|
||||
- Live search, installed tab, version display
|
||||
|
||||
### Auth & Project Persistence
|
||||
|
||||
- **Email/password** and **Google OAuth** sign-in
|
||||
- **Project save** with name, description, and public/private visibility
|
||||
- **Project URL** — each project gets a permanent URL at `/project/:id`
|
||||
|
|
@ -143,7 +218,8 @@ See [docs/ESP32_EMULATION.md](docs/ESP32_EMULATION.md) for the complete installa
|
|||
- **User profile** at `/:username` showing public projects
|
||||
|
||||
### Example Projects
|
||||
- 8 built-in examples (Blink, Traffic Light, Button Control, Fade LED, Serial Hello World, RGB LED, Simon Says, LCD 20×4)
|
||||
|
||||
- Built-in examples including Blink, Traffic Light, Button Control, Fade LED, Serial Hello World, RGB LED, Simon Says, LCD 20×4, and Pi + Arduino serial control
|
||||
- One-click loading into the editor
|
||||
|
||||
---
|
||||
|
|
@ -161,9 +237,10 @@ docker run -d \
|
|||
ghcr.io/davidmonterocrespo24/velxio:master
|
||||
```
|
||||
|
||||
Open **http://localhost:3080**.
|
||||
Open <http://localhost:3080>.
|
||||
|
||||
The `/app/data` volume contains:
|
||||
|
||||
- `velxio.db` — SQLite database (users, projects metadata)
|
||||
- `projects/{id}/` — sketch files per project
|
||||
|
||||
|
|
@ -179,7 +256,7 @@ docker compose -f docker-compose.prod.yml up -d
|
|||
#### Environment variables (`backend/.env`)
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| --- | --- | --- |
|
||||
| `SECRET_KEY` | *(required)* | JWT signing secret |
|
||||
| `DATABASE_URL` | `sqlite+aiosqlite:////app/data/velxio.db` | SQLite path |
|
||||
| `DATA_DIR` | `/app/data` | Directory for project files |
|
||||
|
|
@ -209,42 +286,53 @@ npm install
|
|||
npm run dev
|
||||
```
|
||||
|
||||
Open **http://localhost:5173**.
|
||||
Open <http://localhost:5173>.
|
||||
|
||||
**arduino-cli setup (first time):**
|
||||
|
||||
```bash
|
||||
arduino-cli core update-index
|
||||
arduino-cli core install arduino:avr
|
||||
# For Raspberry Pi Pico:
|
||||
|
||||
# For Raspberry Pi Pico / Pico W:
|
||||
arduino-cli config add board_manager.additional_urls \
|
||||
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
|
||||
arduino-cli core install rp2040:rp2040
|
||||
|
||||
# For ESP32 / ESP32-S3 / ESP32-C3:
|
||||
arduino-cli config add board_manager.additional_urls \
|
||||
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
|
||||
arduino-cli core install esp32:esp32@2.0.17
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
```text
|
||||
velxio/
|
||||
├── frontend/ # React + Vite + TypeScript
|
||||
│ └── src/
|
||||
│ ├── pages/ # LandingPage, EditorPage, ProjectByIdPage, ...
|
||||
│ ├── pages/ # LandingPage, EditorPage, UserProfilePage, ...
|
||||
│ ├── components/ # Editor, simulator canvas, modals, layout
|
||||
│ ├── simulation/ # AVRSimulator, RP2040Simulator, PinManager
|
||||
│ ├── store/ # Zustand stores (auth, editor, simulator, project)
|
||||
│ ├── simulation/ # AVRSimulator, RP2040Simulator, RiscVCore,
|
||||
│ │ # RaspberryPi3Bridge, Esp32Bridge, PinManager
|
||||
│ ├── store/ # Zustand stores (auth, editor, simulator, project, vfs)
|
||||
│ └── services/ # API clients
|
||||
├── backend/ # FastAPI + Python
|
||||
│ └── app/
|
||||
│ ├── api/routes/ # compile, auth, projects, libraries
|
||||
│ ├── api/routes/ # compile, auth, projects, libraries, simulation (ws)
|
||||
│ ├── models/ # User, Project (SQLAlchemy)
|
||||
│ ├── services/ # arduino_cli, project_files
|
||||
│ ├── services/ # arduino_cli, esp32_worker, qemu_manager, gpio_shim
|
||||
│ └── core/ # config, security, dependencies
|
||||
├── wokwi-libs/ # Local clones of Wokwi repos
|
||||
│ ├── wokwi-elements/
|
||||
│ ├── avr8js/
|
||||
│ └── rp2040js/
|
||||
│ ├── wokwi-elements/ # Web Components for electronic parts
|
||||
│ ├── avr8js/ # AVR8 CPU emulator
|
||||
│ ├── rp2040js/ # RP2040 emulator
|
||||
│ └── qemu-lcgamboa/ # QEMU fork for ESP32 Xtensa emulation
|
||||
├── img/ # Raspberry Pi 3 boot images (kernel8.img, dtb, OS image)
|
||||
├── deploy/ # nginx.conf, entrypoint.sh
|
||||
├── docs/ # Technical documentation
|
||||
├── Dockerfile.standalone # Single-container production image
|
||||
├── docker-compose.yml # Development compose
|
||||
└── docker-compose.prod.yml # Production compose
|
||||
|
|
@ -255,10 +343,15 @@ velxio/
|
|||
## Technologies
|
||||
|
||||
| Layer | Stack |
|
||||
|---|---|
|
||||
| --- | --- |
|
||||
| Frontend | React 19, Vite 7, TypeScript 5.9, Monaco Editor, Zustand, React Router 7 |
|
||||
| Backend | FastAPI, SQLAlchemy 2.0 async, aiosqlite, uvicorn |
|
||||
| Simulation | avr8js (AVR8), rp2040js (RP2040), wokwi-elements (Web Components) |
|
||||
| AVR Simulation | avr8js (ATmega328p / ATmega2560) |
|
||||
| RP2040 Simulation | rp2040js (ARM Cortex-M0+) |
|
||||
| RISC-V Simulation | RiscVCore.ts (RV32IMC, custom TypeScript) |
|
||||
| ESP32 Simulation | QEMU 8.1.3 lcgamboa fork (Xtensa LX6/LX7) |
|
||||
| Raspberry Pi 3 Simulation | QEMU 8.1.3 (`qemu-system-aarch64 -M raspi3b`) + Raspberry Pi OS Trixie |
|
||||
| UI Components | wokwi-elements (Web Components) |
|
||||
| Compiler | arduino-cli (subprocess) |
|
||||
| Auth | JWT (httpOnly cookie), Google OAuth 2.0 |
|
||||
| Persistence | SQLite + disk volume (`/app/data/projects/{id}/`) |
|
||||
|
|
@ -266,6 +359,24 @@ velxio/
|
|||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
| Topic | Document |
|
||||
| --- | --- |
|
||||
| Getting Started | [docs/getting-started.md](docs/getting-started.md) |
|
||||
| Architecture Overview | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
||||
| Emulator Architecture | [docs/emulator.md](docs/emulator.md) |
|
||||
| Wokwi Libraries Integration | [docs/WOKWI_LIBS.md](docs/WOKWI_LIBS.md) |
|
||||
| RP2040 Emulation (Pico) | [docs/RP2040_EMULATION.md](docs/RP2040_EMULATION.md) |
|
||||
| Raspberry Pi 3 Emulation | [docs/RASPBERRYPI3_EMULATION.md](docs/RASPBERRYPI3_EMULATION.md) |
|
||||
| ESP32 Emulation (Xtensa) | [docs/ESP32_EMULATION.md](docs/ESP32_EMULATION.md) |
|
||||
| RISC-V Emulation (ESP32-C3) | [docs/RISCV_EMULATION.md](docs/RISCV_EMULATION.md) |
|
||||
| Components Reference | [docs/components.md](docs/components.md) |
|
||||
| MCP Server | [docs/MCP.md](docs/MCP.md) |
|
||||
| Roadmap | [docs/roadmap.md](docs/roadmap.md) |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**`arduino-cli: command not found`** — install arduino-cli and add to PATH.
|
||||
|
|
@ -274,7 +385,11 @@ velxio/
|
|||
|
||||
**Serial Monitor shows nothing** — ensure `Serial.begin()` is called before `Serial.print()`.
|
||||
|
||||
**Compilation errors** — check the compilation console; verify the correct core is installed.
|
||||
**ESP32 not starting** — verify `libqemu-xtensa.dll` (Windows) or `libqemu-xtensa.so` (Linux) is present in `backend/app/services/`.
|
||||
|
||||
**Pi 3 takes too long to boot** — QEMU needs 2–5 seconds to initialize; the "booting" status in the UI is expected.
|
||||
|
||||
**Compilation errors** — check the compilation console; verify the correct core is installed for the selected board.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -288,7 +403,7 @@ Join the Discord server to ask questions, share projects, and follow updates:
|
|||
|
||||
Suggestions, bug reports, and pull requests are welcome at [github.com/davidmonterocrespo24/velxio](https://github.com/davidmonterocrespo24/velxio).
|
||||
|
||||
If you'd like to support the project financially, see the [Support the Project](#️-support-the-project) section above or sponsor directly at [github.com/sponsors/davidmonterocrespo24](https://github.com/sponsors/davidmonterocrespo24).
|
||||
If you'd like to support the project financially, see the [Support the Project](#support-the-project) section above or sponsor directly at [github.com/sponsors/davidmonterocrespo24](https://github.com/sponsors/davidmonterocrespo24).
|
||||
|
||||
> **Note:** All contributors must sign a Contributor License Agreement (CLA) so that the dual-licensing model remains valid. A CLA check runs automatically on pull requests.
|
||||
|
||||
|
|
@ -297,7 +412,7 @@ If you'd like to support the project financially, see the [Support the Project](
|
|||
Velxio uses a **dual-licensing** model:
|
||||
|
||||
| Use case | License | Cost |
|
||||
|----------|---------|------|
|
||||
| --- | --- | --- |
|
||||
| Personal, educational, open-source (AGPLv3 compliant) | [AGPLv3](LICENSE) | Free |
|
||||
| Proprietary / closed-source product or SaaS | [Commercial License](COMMERCIAL_LICENSE.md) | Paid |
|
||||
|
||||
|
|
@ -313,5 +428,7 @@ See [LICENSE](LICENSE) and [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md) for fu
|
|||
- [avr8js](https://github.com/wokwi/avr8js) — AVR8 emulator
|
||||
- [wokwi-elements](https://github.com/wokwi/wokwi-elements) — Electronic web components
|
||||
- [rp2040js](https://github.com/wokwi/rp2040js) — RP2040 emulator
|
||||
- [lcgamboa/qemu](https://github.com/lcgamboa/qemu) — QEMU fork for ESP32 Xtensa emulation
|
||||
- [arduino-cli](https://github.com/arduino/arduino-cli) — Arduino compiler
|
||||
- [Monaco Editor](https://microsoft.github.io/monaco-editor/) — Code editor
|
||||
- [QEMU](https://www.qemu.org) — Machine emulator (Raspberry Pi 3)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,593 @@
|
|||
# Raspberry Pi 3 Emulation (BCM2837 / ARM Cortex-A53)
|
||||
|
||||
> Status: **Functional** · Backend QEMU process · WebSocket communication
|
||||
> Engine: **QEMU 8.1.3** (`qemu-system-aarch64 -M raspi3b`)
|
||||
> Platform: **BCM2837 ARM Cortex-A53 @ 1.2 GHz** — 64-bit ARMv8, quad-core
|
||||
> Runs: **Python scripts** (Raspberry Pi OS Trixie) — no Arduino compilation needed
|
||||
> Available on: all platforms (Windows, macOS, Linux, Docker)
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Supported Boards](#2-supported-boards)
|
||||
3. [Emulator Architecture](#3-emulator-architecture)
|
||||
4. [System Components](#4-system-components)
|
||||
5. [Boot Sequence — Step by Step](#5-boot-sequence--step-by-step)
|
||||
6. [GPIO Shim — How Python Controls GPIO](#6-gpio-shim--how-python-controls-gpio)
|
||||
7. [WebSocket Protocol](#7-websocket-protocol)
|
||||
8. [Serial Communication (UART)](#8-serial-communication-uart)
|
||||
9. [Pin Mapping — Physical to BCM GPIO](#9-pin-mapping--physical-to-bcm-gpio)
|
||||
10. [Virtual File System (VFS)](#10-virtual-file-system-vfs)
|
||||
11. [Multi-Board Integration — Pi + Arduino](#11-multi-board-integration--pi--arduino)
|
||||
12. [Boot Images](#12-boot-images)
|
||||
13. [QEMU Launch Command](#13-qemu-launch-command)
|
||||
14. [Known Limitations](#14-known-limitations)
|
||||
15. [Differences vs Other Emulators](#15-differences-vs-other-emulators)
|
||||
16. [Key Files](#16-key-files)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The **Raspberry Pi 3B** is a full Linux single-board computer based on the **Broadcom BCM2837** SoC (4× ARM Cortex-A53, ARMv8 64-bit). Unlike the other boards in Velxio — which compile and run Arduino C++ code — the Raspberry Pi 3 emulation **boots a real Raspberry Pi OS** (Trixie) inside QEMU and lets you run Python scripts that interact with GPIO.
|
||||
|
||||
There is **no compilation step** for the Raspberry Pi: you write a Python script in the editor, the backend uploads it to the emulated filesystem, and the Pi OS executes it directly.
|
||||
|
||||
### Emulation Engine Comparison
|
||||
|
||||
| Board | Engine | Location | Language |
|
||||
| ----- | ------ | -------- | -------- |
|
||||
| Arduino Uno / Nano / Mega | avr8js | Browser | C++ (Arduino) |
|
||||
| Raspberry Pi Pico | rp2040js | Browser | C++ (Arduino) |
|
||||
| ESP32-C3 / XIAO-C3 | RiscVCore.ts | Browser | C++ (Arduino) |
|
||||
| ESP32 / ESP32-S3 | QEMU lcgamboa (Xtensa) | Backend WebSocket | C++ (Arduino) |
|
||||
| **Raspberry Pi 3B** | **QEMU 8.1.3 (raspi3b)** | **Backend WebSocket** | **Python** |
|
||||
|
||||
### Key Differences from Arduino-based Boards
|
||||
|
||||
- **No FQBN** — no arduino-cli compilation; the board kind has `FQBN = null`
|
||||
- **Boots a real OS** — Raspberry Pi OS Trixie runs inside QEMU (~2–5 seconds boot time)
|
||||
- **Python runtime** — scripts use `RPi.GPIO` (or a compatible shim) to interact with GPIO
|
||||
- **Persistent storage** — the OS image is a real disk image; a qcow2 overlay is used per session so the base image is never modified
|
||||
- **Multi-board serial** — the Pi can communicate with co-simulated Arduino boards via virtual serial lines
|
||||
|
||||
---
|
||||
|
||||
## 2. Supported Boards
|
||||
|
||||
| Board | QEMU Machine | CPU | Notes |
|
||||
| ----- | ------------ | --- | ----- |
|
||||
| Raspberry Pi 3B | `raspi3b` | BCM2837, 4× Cortex-A53 | Full Raspberry Pi OS support |
|
||||
|
||||
> **Raspberry Pi 3B+** and **Pi 4** are not currently supported. The `raspi3b` machine type in QEMU closely matches the standard 3B hardware.
|
||||
|
||||
---
|
||||
|
||||
## 3. Emulator Architecture
|
||||
|
||||
```text
|
||||
Python Script (user writes in editor)
|
||||
│
|
||||
▼ (uploaded via WebSocket / VFS)
|
||||
/home/pi/script.py (inside Raspberry Pi OS)
|
||||
│
|
||||
▼ python3 /home/pi/script.py
|
||||
RPi.GPIO (shim) ← intercepted by gpio_shim.py
|
||||
│
|
||||
├── GPIO.output(17, HIGH) → "GPIO 17 1\n" → ttyAMA1 → Backend
|
||||
│ │
|
||||
│ ▼
|
||||
│ gpio_change event
|
||||
│ WebSocket → Frontend
|
||||
│ PinManager → LED visual
|
||||
│
|
||||
└── Serial.print() → ttyAMA0 → Backend → serial_output → Serial Monitor
|
||||
```
|
||||
|
||||
### Communication Channels
|
||||
|
||||
The Raspberry Pi uses **two independent TCP serial ports** exposed through QEMU:
|
||||
|
||||
| Channel | QEMU Serial | TCP Port | Purpose |
|
||||
| ------- | ----------- | -------- | ------- |
|
||||
| User Serial | `-serial tcp:...:N` | dynamic | User `print()` output and `input()` — visible in Serial Monitor |
|
||||
| GPIO Protocol | `-serial tcp:...:M` | dynamic | GPIO shim protocol (`GPIO <pin> <val>\n`) |
|
||||
|
||||
Both ports are allocated dynamically at startup to avoid conflicts on the host machine.
|
||||
|
||||
---
|
||||
|
||||
## 4. System Components
|
||||
|
||||
### Backend
|
||||
|
||||
| Component | File | Responsibility |
|
||||
| --------- | ---- | -------------- |
|
||||
| `QemuManager` | `backend/app/services/qemu_manager.py` | Singleton that manages all Pi instances (one per WebSocket client) |
|
||||
| `PiInstance` | `backend/app/services/qemu_manager.py` | Runtime state for one running Pi: QEMU process, TCP ports, overlay path |
|
||||
| `gpio_shim` | `backend/app/services/gpio_shim.py` | `RPi.GPIO` drop-in replacement; speaks the GPIO text protocol over ttyAMA1 |
|
||||
| WebSocket route | `backend/app/api/routes/simulation.py` | `GET /api/simulation/ws/{client_id}` — bidirectional JSON message bus |
|
||||
|
||||
### Frontend
|
||||
|
||||
| Component | File | Responsibility |
|
||||
| --------- | ---- | -------------- |
|
||||
| `RaspberryPi3Bridge` | `frontend/src/simulation/RaspberryPi3Bridge.ts` | WebSocket connection manager; sends/receives JSON messages |
|
||||
| `useSimulatorStore` | `frontend/src/store/useSimulatorStore.ts` | Zustand store; wires bridge events to board state and pin manager |
|
||||
| `useVfsStore` | `frontend/src/store/useVfsStore.ts` | Virtual filesystem tree per board; Python script editing |
|
||||
| `RaspberryPi3.tsx` | `frontend/src/components/components-wokwi/RaspberryPi3.tsx` | React board component (SVG image, 40-pin header) |
|
||||
| `boardPinMapping.ts` | `frontend/src/utils/boardPinMapping.ts` | Physical pin → BCM GPIO number translation |
|
||||
|
||||
---
|
||||
|
||||
## 5. Boot Sequence — Step by Step
|
||||
|
||||
```text
|
||||
1. User clicks "Start" (or "Run")
|
||||
│
|
||||
▼
|
||||
2. SimulatorCanvas detects board kind 'raspberry-pi-3'
|
||||
→ calls startBoard(boardId)
|
||||
|
||||
3. useSimulatorStore calls RaspberryPi3Bridge.connect()
|
||||
|
||||
4. Bridge opens WebSocket:
|
||||
ws://localhost:8001/api/simulation/ws/<boardId>
|
||||
→ sends { type: 'start_pi', data: { board: 'raspberry-pi-3' } }
|
||||
|
||||
5. Backend (simulation.py) routes to:
|
||||
QemuManager.start_instance(client_id, 'raspberry-pi-3', callback)
|
||||
|
||||
6. QemuManager._boot(inst):
|
||||
a. Allocate two free TCP ports (serial_port, gpio_port)
|
||||
b. Create qcow2 overlay over base SD image:
|
||||
qemu-img create -f qcow2 -b raspios.img overlay_<id>.qcow2
|
||||
c. Launch qemu-system-aarch64 (see Section 13 for full command)
|
||||
d. Emit { type: 'system', event: 'booting' }
|
||||
|
||||
7. Wait ~2 seconds for QEMU to initialize TCP servers
|
||||
|
||||
8. QemuManager._connect_serial(inst):
|
||||
→ Connect to ttyAMA0 TCP socket
|
||||
→ Start async reader loop (forwards bytes as serial_output events)
|
||||
→ Emit { type: 'system', event: 'booted' }
|
||||
|
||||
9. QemuManager._connect_gpio(inst):
|
||||
→ Connect to ttyAMA1 TCP socket
|
||||
→ Start async reader loop (parses "GPIO <pin> <val>\n" lines)
|
||||
|
||||
10. Frontend receives 'booted' event
|
||||
→ Board UI updates to "running" state
|
||||
→ Serial Monitor shows first Linux kernel output
|
||||
|
||||
11. Python script runs:
|
||||
→ python3 /home/pi/script.py (auto-launched via -append init=/bin/sh)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. GPIO Shim — How Python Controls GPIO
|
||||
|
||||
The `gpio_shim.py` module is injected into the Raspberry Pi OS at the standard `RPi.GPIO` installation path:
|
||||
|
||||
```
|
||||
/usr/local/lib/python3.11/dist-packages/RPi/GPIO.py
|
||||
```
|
||||
|
||||
When a Python script does `import RPi.GPIO as GPIO`, it gets this shim instead of the real hardware driver. The shim communicates over `/dev/ttyAMA1` (the second QEMU serial port) using a simple text protocol.
|
||||
|
||||
### GPIO Text Protocol
|
||||
|
||||
```text
|
||||
Pi → Backend (output state change):
|
||||
"GPIO <bcm_pin> <0|1>\n"
|
||||
Example: "GPIO 17 1\n" ← GPIO 17 driven HIGH
|
||||
|
||||
Backend → Pi (external input, e.g. button press from canvas):
|
||||
"SET <bcm_pin> <0|1>\n"
|
||||
Example: "SET 22 1\n" ← button wired to GPIO 22 pressed
|
||||
```
|
||||
|
||||
### Supported RPi.GPIO API
|
||||
|
||||
```python
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
# Numbering mode
|
||||
GPIO.setmode(GPIO.BCM) # use BCM numbers (GPIO17, GPIO22, ...)
|
||||
GPIO.setmode(GPIO.BOARD) # use physical pin numbers (11, 15, ...)
|
||||
|
||||
# Pin direction
|
||||
GPIO.setup(17, GPIO.OUT)
|
||||
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
|
||||
# Digital output
|
||||
GPIO.output(17, GPIO.HIGH) # → sends "GPIO 17 1\n" to backend
|
||||
GPIO.output(17, GPIO.LOW) # → sends "GPIO 17 0\n" to backend
|
||||
GPIO.output(17, True) # equivalent to GPIO.HIGH
|
||||
|
||||
# Digital input
|
||||
state = GPIO.input(22) # reads last known state (updated by "SET" messages)
|
||||
|
||||
# Event detection
|
||||
GPIO.add_event_detect(22, GPIO.RISING, callback=my_callback)
|
||||
GPIO.add_event_detect(22, GPIO.FALLING, callback=my_callback)
|
||||
GPIO.add_event_detect(22, GPIO.BOTH, callback=my_callback)
|
||||
|
||||
# PWM (simplified — simulated as digital output)
|
||||
pwm = GPIO.PWM(18, 1000) # pin 18, 1000 Hz
|
||||
pwm.start(75) # 75% duty cycle → HIGH (duty > 50% → HIGH, else LOW)
|
||||
pwm.ChangeDutyCycle(25) # 25% duty cycle → LOW
|
||||
pwm.stop()
|
||||
|
||||
# Cleanup
|
||||
GPIO.cleanup()
|
||||
GPIO.cleanup(17) # clean specific pin
|
||||
```
|
||||
|
||||
> **PWM limitation:** The shim does not implement real PWM waveforms. It converts duty cycle to a binary state: `duty > 50` → HIGH, `duty ≤ 50` → LOW. Visual LED dimming is not supported for Pi GPIO PWM.
|
||||
|
||||
### Example Python Script (Blink LED)
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(17, GPIO.OUT)
|
||||
|
||||
try:
|
||||
while True:
|
||||
GPIO.output(17, GPIO.HIGH)
|
||||
print("LED ON")
|
||||
time.sleep(1)
|
||||
GPIO.output(17, GPIO.LOW)
|
||||
print("LED OFF")
|
||||
time.sleep(1)
|
||||
finally:
|
||||
GPIO.cleanup()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. WebSocket Protocol
|
||||
|
||||
All communication between the frontend and backend passes through a single WebSocket connection per board instance.
|
||||
|
||||
**Endpoint:** `GET /api/simulation/ws/{client_id}`
|
||||
|
||||
### Frontend → Backend Messages
|
||||
|
||||
| Message Type | Payload | Description |
|
||||
| ------------ | ------- | ----------- |
|
||||
| `start_pi` | `{ board: "raspberry-pi-3" }` | Launch QEMU, start the Pi |
|
||||
| `stop_pi` | _(empty)_ | Stop QEMU, clean up overlay |
|
||||
| `serial_input` | `{ bytes: number[] }` | Send bytes to ttyAMA0 (Serial Monitor → Pi) |
|
||||
| `gpio_in` | `{ pin: number, state: 0\|1 }` | Inject external GPIO state (button press from canvas) |
|
||||
|
||||
### Backend → Frontend Messages
|
||||
|
||||
| Message Type | Payload | Description |
|
||||
| ------------ | ------- | ----------- |
|
||||
| `serial_output` | `{ data: string }` | String data from ttyAMA0 (Pi print output) |
|
||||
| `gpio_change` | `{ pin: number, state: 0\|1 }` | A GPIO pin changed state (driven by Python script) |
|
||||
| `system` | `{ event: "booting"\|"booted"\|"exited" }` | Boot lifecycle events |
|
||||
| `error` | `{ message: string }` | Error from QEMU or backend |
|
||||
|
||||
---
|
||||
|
||||
## 8. Serial Communication (UART)
|
||||
|
||||
The Raspberry Pi 3 exposes two UART ports through QEMU:
|
||||
|
||||
| Port | Device | Physical Pins | Role |
|
||||
| ---- | ------ | ------------- | ---- |
|
||||
| UART0 (ttyAMA0) | `/dev/ttyAMA0` | GPIO14 (TX), GPIO15 (RX) | User serial — `print()` output, `input()`, `serial.Serial()` |
|
||||
| UART1 (ttyAMA1) | `/dev/ttyAMA1` | — (internal) | GPIO shim protocol — reserved, not accessible to user scripts |
|
||||
|
||||
### Serial Monitor Integration
|
||||
|
||||
Anything the Python script writes to stdout or to `/dev/ttyAMA0` appears in the Serial Monitor panel:
|
||||
|
||||
```python
|
||||
# stdout (print) — captured automatically
|
||||
print("Hello from Pi!")
|
||||
|
||||
# Direct ttyAMA0 (explicit serial)
|
||||
import serial
|
||||
port = serial.Serial('/dev/ttyAMA0', baudrate=9600, timeout=1)
|
||||
port.write(b"Hello Arduino!\n")
|
||||
```
|
||||
|
||||
### Sending Text to the Pi
|
||||
|
||||
Text typed in the Serial Monitor input box is sent to ttyAMA0 as a `serial_input` message, which the Pi receives via `input()` or by reading `/dev/ttyAMA0`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Pin Mapping — Physical to BCM GPIO
|
||||
|
||||
The Raspberry Pi 3B has a standard **40-pin GPIO header** (2 rows × 20 columns). The table below shows the mapping from physical pin number to BCM GPIO number:
|
||||
|
||||
| Physical | BCM | Function | Physical | BCM | Function |
|
||||
| -------- | --- | -------- | -------- | --- | -------- |
|
||||
| 1 | — | 3.3 V | 2 | — | 5 V |
|
||||
| 3 | **2** | I2C1 SDA | 4 | — | 5 V |
|
||||
| 5 | **3** | I2C1 SCL | 6 | — | GND |
|
||||
| 7 | **4** | GPIO | 8 | **14** | UART TX |
|
||||
| 9 | — | GND | 10 | **15** | UART RX |
|
||||
| 11 | **17** | GPIO | 12 | **18** | PWM0 |
|
||||
| 13 | **27** | GPIO | 14 | — | GND |
|
||||
| 15 | **22** | GPIO | 16 | **23** | GPIO |
|
||||
| 17 | — | 3.3 V | 18 | **24** | GPIO |
|
||||
| 19 | **10** | SPI MOSI | 20 | — | GND |
|
||||
| 21 | **9** | SPI MISO | 22 | **25** | GPIO |
|
||||
| 23 | **11** | SPI SCLK | 24 | **8** | SPI CE0 |
|
||||
| 25 | — | GND | 26 | **7** | SPI CE1 |
|
||||
| 27 | — | ID_SD | 28 | — | ID_SC |
|
||||
| 29 | **5** | GPIO | 30 | — | GND |
|
||||
| 31 | **6** | GPIO | 32 | **12** | PWM0 |
|
||||
| 33 | **13** | PWM1 | 34 | — | GND |
|
||||
| 35 | **19** | SPI1 MISO | 36 | **16** | SPI1 CE2 |
|
||||
| 37 | **26** | GPIO | 38 | **20** | SPI1 MOSI |
|
||||
| 39 | — | GND | 40 | **21** | SPI1 SCLK |
|
||||
|
||||
> Pins 27 and 28 are reserved for ID EEPROM. Power and GND pins have BCM = —.
|
||||
|
||||
### Pin Resolution in Frontend
|
||||
|
||||
```typescript
|
||||
// Wire connects physical pin "8" on the Pi board
|
||||
boardPinToNumber('raspberry-pi-3', '8') // → 14 (BCM GPIO14, UART TX)
|
||||
boardPinToNumber('raspberry-pi-3', 'GPIO17') // → 17
|
||||
boardPinToNumber('raspberry-pi-3', 'GND') // → null (not a GPIO)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Virtual File System (VFS)
|
||||
|
||||
Each Raspberry Pi 3 board instance has its own **virtual filesystem tree** stored in the `useVfsStore` Zustand store. This lets you create and edit Python scripts directly in the Velxio editor before they are uploaded to the Pi.
|
||||
|
||||
### Default VFS Tree
|
||||
|
||||
```text
|
||||
/
|
||||
└── home/
|
||||
└── pi/
|
||||
├── script.py ← main Python script (editable)
|
||||
└── hello.sh ← example shell script
|
||||
```
|
||||
|
||||
### Default `script.py`
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
import time
|
||||
import RPi.GPIO as GPIO
|
||||
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(17, GPIO.OUT)
|
||||
|
||||
while True:
|
||||
GPIO.output(17, GPIO.HIGH)
|
||||
print("LED on")
|
||||
time.sleep(1)
|
||||
GPIO.output(17, GPIO.LOW)
|
||||
print("LED off")
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
### VFS API
|
||||
|
||||
```typescript
|
||||
const vfs = useVfsStore.getState();
|
||||
|
||||
vfs.initBoardVfs(boardId) // create default tree
|
||||
vfs.createNode(boardId, parentId, 'app.py', 'file') // add new file
|
||||
vfs.setContent(boardId, nodeId, pythonCode) // update file content
|
||||
vfs.serializeForUpload(boardId) // returns [{ path, content }, ...]
|
||||
```
|
||||
|
||||
Files in the VFS are uploaded to the Pi OS at boot via the WebSocket connection before the script is executed.
|
||||
|
||||
---
|
||||
|
||||
## 11. Multi-Board Integration — Pi + Arduino
|
||||
|
||||
The Raspberry Pi 3 can be placed on the same canvas as Arduino or other boards. When wires connect a Pi GPIO pin to an Arduino pin, the stores route data between them automatically.
|
||||
|
||||
### Pi → Arduino (Serial TX)
|
||||
|
||||
```text
|
||||
Pi Python script:
|
||||
port.write(b"LED_ON\n")
|
||||
│
|
||||
▼ ttyAMA0 byte output
|
||||
serial_output WebSocket message
|
||||
│
|
||||
▼ useSimulatorStore (serial callback)
|
||||
AVRSimulator.serialWrite("L") ← feeds byte into Arduino RX FIFO
|
||||
│
|
||||
▼ Arduino sketch:
|
||||
String cmd = Serial.readStringUntil('\n');
|
||||
if (cmd == "LED_ON") digitalWrite(8, HIGH);
|
||||
```
|
||||
|
||||
### Arduino → Pi (Serial RX)
|
||||
|
||||
```text
|
||||
Arduino sketch:
|
||||
Serial.println("SENSOR:1023");
|
||||
│
|
||||
▼ USART byte emitted
|
||||
useSimulatorStore serial callback
|
||||
│
|
||||
▼ bridge.sendSerialBytes([charCode, ...])
|
||||
serial_input WebSocket message → Backend
|
||||
│
|
||||
▼ qemu_manager.send_serial_bytes(client_id, bytes)
|
||||
ttyAMA0 receives bytes → Pi reads with:
|
||||
line = port.readline() # "SENSOR:1023\n"
|
||||
```
|
||||
|
||||
### Example Project: Pi + Arduino LED Control
|
||||
|
||||
This example (included in the gallery as `pi-to-arduino-led-control`) demonstrates bidirectional serial communication:
|
||||
|
||||
**Pi Script:**
|
||||
|
||||
```python
|
||||
import serial, time
|
||||
|
||||
port = serial.Serial('/dev/ttyAMA0', baudrate=9600, timeout=1)
|
||||
|
||||
for _ in range(3):
|
||||
port.write(b"LED1_ON\n")
|
||||
time.sleep(0.5)
|
||||
port.write(b"LED1_OFF\n")
|
||||
time.sleep(0.5)
|
||||
|
||||
port.write(b"LED2_ON\n")
|
||||
time.sleep(2)
|
||||
port.write(b"LED2_OFF\n")
|
||||
```
|
||||
|
||||
**Arduino Sketch:**
|
||||
|
||||
```cpp
|
||||
const int LED1 = 8, LED2 = 9;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(9600);
|
||||
pinMode(LED1, OUTPUT);
|
||||
pinMode(LED2, OUTPUT);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (Serial.available()) {
|
||||
String cmd = Serial.readStringUntil('\n');
|
||||
if (cmd == "LED1_ON") digitalWrite(LED1, HIGH);
|
||||
else if (cmd == "LED1_OFF") digitalWrite(LED1, LOW);
|
||||
else if (cmd == "LED2_ON") digitalWrite(LED2, HIGH);
|
||||
else if (cmd == "LED2_OFF") digitalWrite(LED2, LOW);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Boot Images
|
||||
|
||||
QEMU needs three files from the `/img/` directory to boot the Raspberry Pi 3:
|
||||
|
||||
| File | Size | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| `kernel8.img` | ~6 MB | ARM64 Linux kernel extracted from Raspberry Pi OS Trixie |
|
||||
| `bcm271~1.dtb` | ~40 KB | Device tree binary — defines CPU, RAM, peripheral base addresses |
|
||||
| `2025-12-04-raspios-trixie-armhf.img` | ~5.67 GB | Full Raspberry Pi OS SD card image (root filesystem) |
|
||||
|
||||
> The base SD image is **never modified**. Each session creates a **qcow2 copy-on-write overlay** (`overlay_<session_id>.qcow2`) that records only the changes made during that session. The overlay is automatically deleted when the session ends.
|
||||
|
||||
### Creating the Overlay at Runtime
|
||||
|
||||
```bash
|
||||
# Backend does this automatically for each session:
|
||||
qemu-img create -f qcow2 \
|
||||
-b /img/2025-12-04-raspios-trixie-armhf.img \
|
||||
/tmp/overlay_<session_id>.qcow2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. QEMU Launch Command
|
||||
|
||||
```bash
|
||||
qemu-system-aarch64 \
|
||||
-M raspi3b \
|
||||
-kernel /img/kernel8.img \
|
||||
-dtb /img/bcm271~1.dtb \
|
||||
-drive file=/tmp/overlay_<id>.qcow2,if=sd,format=qcow2 \
|
||||
-m 1G \
|
||||
-smp 4 \
|
||||
-nographic \
|
||||
-serial tcp:127.0.0.1:<serial_port>,server,nowait \
|
||||
-serial tcp:127.0.0.1:<gpio_port>,server,nowait \
|
||||
-append 'console=ttyAMA0 root=/dev/mmcblk0p2 rootwait rw \
|
||||
dwc_otg.lpm_enable=0 quiet init=/bin/sh'
|
||||
```
|
||||
|
||||
### Key Flags
|
||||
|
||||
| Flag | Value | Meaning |
|
||||
| ---- | ----- | ------- |
|
||||
| `-M raspi3b` | machine type | Emulate the Raspberry Pi 3B hardware |
|
||||
| `-m 1G` | RAM | 1 GB RAM (matches real Pi 3B) |
|
||||
| `-smp 4` | CPU cores | 4 ARM Cortex-A53 cores |
|
||||
| `-nographic` | no display | No HDMI/video output — serial only |
|
||||
| `-serial tcp:...:N,server,nowait` | first serial | ttyAMA0 (user serial) served on TCP port N |
|
||||
| `-serial tcp:...:M,server,nowait` | second serial | ttyAMA1 (GPIO shim protocol) served on TCP port M |
|
||||
| `-append ... init=/bin/sh` | kernel cmdline | Boot straight to a shell (skips systemd login) |
|
||||
| `-drive ...,format=qcow2` | disk | qcow2 overlay over the base SD image |
|
||||
|
||||
---
|
||||
|
||||
## 14. Known Limitations
|
||||
|
||||
| Limitation | Detail |
|
||||
| ---------- | ------ |
|
||||
| Boot time | QEMU takes 2–5 seconds to start; the frontend shows a "booting" state during this time |
|
||||
| No real PWM | `GPIO.PWM` simulates duty cycle as binary state (>50% = HIGH, ≤50% = LOW); no analog dimming |
|
||||
| No I2C emulation | `smbus`, `smbus2`, `i2c_msg` — I2C bus transactions are not forwarded to virtual devices |
|
||||
| No SPI emulation | Hardware SPI registers not forwarded; `spidev` library will fail |
|
||||
| Single UART for GPIO | ttyAMA1 is reserved for the GPIO shim; scripts cannot use it for other serial devices |
|
||||
| No GUI / display | HDMI output is disabled (`-nographic`); GUI Python libraries (Tkinter, pygame, etc.) will not work |
|
||||
| No networking | QEMU does not expose a network interface; `requests`, `socket`, `urllib` will fail |
|
||||
| No persistent state | The qcow2 overlay is deleted after shutdown; files written to the Pi OS do not survive a restart |
|
||||
| Reset is reconnect | `resetBoard()` is not implemented; to restart the Pi, stop it and start it again |
|
||||
| Session isolation | Each board instance creates an independent QEMU process; two Pi boards do not share any state |
|
||||
| Resource usage | Each Pi instance launches a full QEMU process (~200 MB RAM); hosting many simultaneous sessions is resource-intensive |
|
||||
|
||||
---
|
||||
|
||||
## 15. Differences vs Other Emulators
|
||||
|
||||
| Aspect | Raspberry Pi 3B | Raspberry Pi Pico | ESP32 (Xtensa) | Arduino AVR |
|
||||
| ------ | --------------- | ----------------- | -------------- | ----------- |
|
||||
| Engine | QEMU raspi3b | rp2040js (browser) | QEMU lcgamboa (backend) | avr8js (browser) |
|
||||
| Backend required | **Yes** (QEMU process) | No | Yes (QEMU process) | No |
|
||||
| Language | **Python** | C++ (Arduino) | C++ (Arduino) | C++ (Arduino) |
|
||||
| Compilation step | **No** | Yes (arduino-cli) | Yes (arduino-cli) | Yes (arduino-cli) |
|
||||
| OS | **Raspberry Pi OS (Linux)** | None (bare metal) | None (ESP-IDF) | None (bare metal) |
|
||||
| Boot time | ~2–5 s | Instant | ~1–2 s | Instant |
|
||||
| GPIO protocol | Text over ttyAMA1 | MMIO direct | QEMU callbacks + WebSocket | Port listeners |
|
||||
| Serial | ttyAMA0 (real UART) | UART0/1 (rp2040js) | UART0 (QEMU) | USART0 (avr8js) |
|
||||
| I2C | Not forwarded to frontend | 2 buses + virtual devices | Emulated | Not emulated |
|
||||
| PWM | Binary (no waveform) | Hardware PWM | LEDC (mapped) | Timer-based |
|
||||
| Multi-board comms | Yes (serial bridge) | No | No | No |
|
||||
| Oscilloscope | No | Yes (8 ns resolution) | No | Yes |
|
||||
| CI tests | No | Yes (Vitest) | No | Yes (Vitest) |
|
||||
| Disk image required | **Yes** (~5.67 GB) | No | No | No |
|
||||
|
||||
---
|
||||
|
||||
## 16. Key Files
|
||||
|
||||
| File | Description |
|
||||
| ---- | ----------- |
|
||||
| `backend/app/services/qemu_manager.py` | `QemuManager` — manages QEMU process lifecycle, TCP sockets, qcow2 overlays |
|
||||
| `backend/app/services/gpio_shim.py` | `RPi.GPIO` drop-in replacement; speaks text protocol over ttyAMA1 |
|
||||
| `backend/app/api/routes/simulation.py` | WebSocket endpoint `/api/simulation/ws/{client_id}` |
|
||||
| `frontend/src/simulation/RaspberryPi3Bridge.ts` | WebSocket client; routes `serial_output`, `gpio_change`, `system` events |
|
||||
| `frontend/src/store/useSimulatorStore.ts` | Board lifecycle, serial bridge to co-simulated AVR/Pico boards |
|
||||
| `frontend/src/store/useVfsStore.ts` | Per-board virtual filesystem (Python script editor) |
|
||||
| `frontend/src/utils/boardPinMapping.ts` | Physical pin → BCM GPIO number mapping table |
|
||||
| `frontend/src/components/components-wokwi/RaspberryPi3.tsx` | Board React component (SVG, 40-pin header coordinates) |
|
||||
| `frontend/src/components/components-wokwi/RaspberryPi3Element.ts` | Web Component for canvas rendering and wire endpoints |
|
||||
| `frontend/src/types/board.ts` | `BoardKind` type, `FQBN = null` for Raspberry Pi 3 |
|
||||
| `img/kernel8.img` | ARM64 kernel extracted from Raspberry Pi OS Trixie |
|
||||
| `img/bcm271~1.dtb` | Device tree binary for BCM2837 |
|
||||
| `img/2025-12-04-raspios-trixie-armhf.img` | Raspberry Pi OS SD image (~5.67 GB) — base for qcow2 overlays |
|
||||
|
|
@ -0,0 +1,609 @@
|
|||
# RP2040 Emulation (Raspberry Pi Pico / Pico W)
|
||||
|
||||
> Status: **Functional** · In-browser emulation · No backend dependencies
|
||||
> Engine: **rp2040js** — ARM Cortex-M0+ emulator in TypeScript
|
||||
> Platform: **RP2040 @ 125 MHz** — dual-core ARM Cortex-M0+ (single core emulated)
|
||||
> Available on: all platforms (Windows, macOS, Linux, Docker)
|
||||
> Applies to: **Raspberry Pi Pico**, **Raspberry Pi Pico W**
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Supported Boards](#2-supported-boards)
|
||||
3. [Emulator Architecture](#3-emulator-architecture)
|
||||
4. [Emulated Memory and Peripherals](#4-emulated-memory-and-peripherals)
|
||||
5. [Full Flow: Compile and Run](#5-full-flow-compile-and-run)
|
||||
6. [Binary Format and Loading](#6-binary-format-and-loading)
|
||||
7. [GPIO](#7-gpio)
|
||||
8. [UART — Serial Monitor](#8-uart--serial-monitor)
|
||||
9. [ADC — Analog Inputs](#9-adc--analog-inputs)
|
||||
10. [I2C Virtual Devices](#10-i2c-virtual-devices)
|
||||
11. [SPI](#11-spi)
|
||||
12. [PWM](#12-pwm)
|
||||
13. [Simulation Execution Loop](#13-simulation-execution-loop)
|
||||
14. [Pin Mapping](#14-pin-mapping)
|
||||
15. [Oscilloscope / Logic Analyzer](#15-oscilloscope--logic-analyzer)
|
||||
16. [Known Limitations](#16-known-limitations)
|
||||
17. [Tests](#17-tests)
|
||||
18. [Differences vs Other Emulators](#18-differences-vs-other-emulators)
|
||||
19. [Key Files](#19-key-files)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
The **Raspberry Pi Pico** and **Pico W** boards use the **RP2040** microcontroller — a dual-core **ARM Cortex-M0+** chip designed by Raspberry Pi. Unlike the ESP32 (which requires QEMU running in the backend), RP2040 emulation runs **entirely in the browser** using the [rp2040js](https://github.com/wokwi/rp2040js) library (a local clone in `wokwi-libs/rp2040js/`).
|
||||
|
||||
### Emulation Engine Comparison
|
||||
|
||||
| Board | CPU | Engine |
|
||||
| ----- | --- | ------ |
|
||||
| Arduino Uno / Nano / Mega | AVR ATmega | avr8js (browser) |
|
||||
| Raspberry Pi Pico / Pico W | RP2040 ARM Cortex-M0+ | **rp2040js (browser, no backend)** |
|
||||
| ESP32-C3, XIAO-C3 | RISC-V RV32IMC | RiscVCore.ts (browser) |
|
||||
| ESP32, ESP32-S3 | Xtensa LX6/LX7 | QEMU lcgamboa (backend WebSocket) |
|
||||
|
||||
### Advantages of the In-Browser Emulator
|
||||
|
||||
- **No network dependencies** — works offline, no WebSocket connection to any backend process
|
||||
- **Instant startup** — emulation begins immediately after compilation (no process launch latency)
|
||||
- **Testable with Vitest** — the same TypeScript code runs in both production and CI tests
|
||||
- **Cross-platform** — identical behavior on Windows, macOS, Linux, and Docker
|
||||
|
||||
---
|
||||
|
||||
## 2. Supported Boards
|
||||
|
||||
| Board | arduino-cli FQBN | Built-in LED | Notes |
|
||||
| ----- | ---------------- | ------------ | ----- |
|
||||
| Raspberry Pi Pico | `rp2040:rp2040:rpipico` | GPIO 25 | Standard Pico |
|
||||
| Raspberry Pi Pico W | `rp2040:rp2040:rpipicow` | GPIO 25 (via CYW43) | WiFi chip not emulated |
|
||||
|
||||
> **Pico W note:** The wireless chip (Infineon CYW43439) is not emulated. GPIO 25 drives the on-board LED in the same way as the standard Pico for simulation purposes.
|
||||
|
||||
---
|
||||
|
||||
## 3. Emulator Architecture
|
||||
|
||||
```text
|
||||
Arduino Sketch (.ino)
|
||||
│
|
||||
▼ arduino-cli (backend, FQBN rp2040:rp2040:rpipico)
|
||||
sketch.ino.bin ← raw ARM binary (no HEX format)
|
||||
│
|
||||
▼ base64 → frontend
|
||||
compileBoardProgram(boardId, base64)
|
||||
│
|
||||
▼ RP2040Simulator.loadBinary(base64)
|
||||
Uint8Array → copied into rp2040.flash at offset 0
|
||||
│
|
||||
▼ rp2040.loadBootrom(bootromB1)
|
||||
Bootrom B1 revision loaded
|
||||
│
|
||||
▼ PC reset to 0x10000000 (FLASH_START_ADDRESS)
|
||||
CortexM0Core.executeInstruction() ← requestAnimationFrame @ 60 FPS
|
||||
│ 2,083,333 cycles/frame (125 MHz ÷ 60)
|
||||
├── GPIO listener → PinManager → visual component update
|
||||
├── UART0/UART1 byte → onSerialData → Serial Monitor
|
||||
├── ADC channel read → channelValues[ch] → analogRead()
|
||||
└── I2C bus events → virtual I2C device callbacks
|
||||
```
|
||||
|
||||
### Main Classes
|
||||
|
||||
| Class | File | Responsibility |
|
||||
| ----- | ---- | -------------- |
|
||||
| `RP2040Simulator` | `simulation/RP2040Simulator.ts` | Main wrapper: bootrom, GPIO, UART, ADC, I2C, SPI, RAF loop |
|
||||
| `RP2040` | `rp2040js` (library) | Full hardware model: CPU, peripherals, memory map |
|
||||
| `CortexM0Core` | `rp2040js` (library) | ARM Cortex-M0+ instruction decoder and executor |
|
||||
| `PinManager` | `simulation/PinManager.ts` | Routes GPIO events to visual components |
|
||||
|
||||
---
|
||||
|
||||
## 4. Emulated Memory and Peripherals
|
||||
|
||||
### Memory Map
|
||||
|
||||
| Region | Base Address | Size | Description |
|
||||
| ------ | ------------ | ---- | ----------- |
|
||||
| Flash (XIP) | `0x10000000` | 16 MB | Program storage (firmware binary) |
|
||||
| SRAM | `0x20000000` | 264 KB | Data RAM (stack, variables) |
|
||||
| USB DPRAM | `0x50100000` | 4 KB | USB dual-port RAM |
|
||||
| Bootrom | `0x00000000` | 4 KB | B1 revision bootrom |
|
||||
|
||||
### Peripherals Emulated by rp2040js
|
||||
|
||||
| Peripheral | Details |
|
||||
| ---------- | ------- |
|
||||
| GPIO | All 30 pins (GPIO0–GPIO29), input/output |
|
||||
| UART0 | TX/RX, baud rate auto-detection |
|
||||
| UART1 | TX/RX, forwarded to same `onSerialData` callback |
|
||||
| I2C0 | Master mode, virtual device callbacks |
|
||||
| I2C1 | Master mode, virtual device callbacks |
|
||||
| SPI0 | Default loopback; custom handler supported |
|
||||
| SPI1 | Default loopback; custom handler supported |
|
||||
| ADC | 5 channels (GPIO26–29 + internal temp sensor, ch4) |
|
||||
| PWM | Available on any GPIO |
|
||||
| Timers | Used internally for `delay()`, `millis()` |
|
||||
| Watchdog | Present (reads return 0) |
|
||||
| PLL / Clock | Simulated at fixed 125 MHz |
|
||||
|
||||
### Peripherals NOT Emulated
|
||||
|
||||
- **WiFi/BLE** (Pico W: CYW43439 wireless chip)
|
||||
- **USB device stack** (USB device enumeration)
|
||||
- **DMA transfers** (DMA control registers return 0)
|
||||
- **PIO state machines** (PIO registers return 0)
|
||||
|
||||
> PIO in particular is a significant limitation — `PicoLED`, `WS2812` (NeoPixel), `DHT`, and other timing-critical libraries that rely on PIO will not function.
|
||||
|
||||
---
|
||||
|
||||
## 5. Full Flow: Compile and Run
|
||||
|
||||
### 5.1 Compile the Sketch
|
||||
|
||||
The backend compiles for the Pico using the earlephilhower arduino-pico core:
|
||||
|
||||
```bash
|
||||
# First-time setup — install the RP2040 board manager:
|
||||
arduino-cli config add board_manager.additional_urls \
|
||||
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
|
||||
|
||||
arduino-cli core install rp2040:rp2040
|
||||
|
||||
# Compile for Raspberry Pi Pico:
|
||||
arduino-cli compile \
|
||||
--fqbn rp2040:rp2040:rpipico \
|
||||
--output-dir build/ \
|
||||
my_sketch/
|
||||
|
||||
# Output: build/my_sketch.ino.bin ← raw binary
|
||||
```
|
||||
|
||||
The Velxio backend automatically finds `sketch.ino.bin` (or `sketch.ino.uf2` as fallback), encodes it as base64, and sends it to the frontend in `CompileResponse.binary_content`.
|
||||
|
||||
### 5.2 Serial Redirection (Important)
|
||||
|
||||
The backend **automatically prepends** `#define Serial Serial1` to `sketch.ino` for RP2040 boards:
|
||||
|
||||
```python
|
||||
# backend/app/services/arduino_cli.py
|
||||
if "rp2040" in board_fqbn and write_name == "sketch.ino":
|
||||
content = "#define Serial Serial1\n" + content
|
||||
```
|
||||
|
||||
**Why?** The RP2040 bootrom uses UART0 for its own communication. To avoid conflicts, the Arduino `Serial` object is remapped to UART1. This means `Serial.print()` in your sketch goes through UART1, which the emulator also captures and shows in the Serial Monitor.
|
||||
|
||||
### 5.3 Minimal Sketch for Raspberry Pi Pico
|
||||
|
||||
```cpp
|
||||
// Blink the built-in LED (GPIO 25)
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("Pico started!");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
Serial.println("LED ON");
|
||||
delay(500);
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
Serial.println("LED OFF");
|
||||
delay(500);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 Analog Read Example
|
||||
|
||||
```cpp
|
||||
// Read a potentiometer on GPIO 26 (ADC channel 0)
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int raw = analogRead(A0); // GPIO26 → ADC ch0, returns 0–4095
|
||||
float voltage = raw * 3.3f / 4095.0f;
|
||||
Serial.print("ADC: ");
|
||||
Serial.print(raw);
|
||||
Serial.print(" → ");
|
||||
Serial.print(voltage);
|
||||
Serial.println(" V");
|
||||
delay(200);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Binary Format and Loading
|
||||
|
||||
The RP2040 uses a **raw binary format** (`.bin`), not the Intel HEX format used by AVR:
|
||||
|
||||
```typescript
|
||||
// RP2040Simulator.loadBinary(base64: string)
|
||||
loadBinary(base64: string): void {
|
||||
const binary = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
|
||||
// Copy directly into flash at offset 0
|
||||
this.rp2040.flash.set(binary, 0);
|
||||
// Load bootrom and reset PC to 0x10000000
|
||||
this.rp2040.loadBootrom(bootromB1);
|
||||
}
|
||||
```
|
||||
|
||||
**Flash memory layout** (as seen by the CPU):
|
||||
|
||||
```text
|
||||
0x10000000 ← FLASH_START_ADDRESS — firmware entry point (PC reset here)
|
||||
0x10000100 ... program .text section
|
||||
0x10xxxxxx ... .rodata, .data, constants
|
||||
0x20000000 ← SRAM — stack, heap, .bss
|
||||
```
|
||||
|
||||
The bootrom B1 (`rp2040-bootrom.ts`) contains the RP2040's factory-programmed ROM code, required for correct flash XIP (eXecute In Place) initialization.
|
||||
|
||||
---
|
||||
|
||||
## 7. GPIO
|
||||
|
||||
All 30 GPIO pins (GPIO0–GPIO29) are emulated. Each pin has an event listener attached at startup:
|
||||
|
||||
```typescript
|
||||
// RP2040Simulator.ts — gpio listener setup
|
||||
for (let gpioIdx = 0; gpioIdx < 30; gpioIdx++) {
|
||||
const gpio = this.rp2040.gpio[gpioIdx];
|
||||
gpio.addListener((state: GPIOPinState) => {
|
||||
const isHigh = state === GPIOPinState.High;
|
||||
this.pinManager.triggerPinChange(gpioIdx, isHigh);
|
||||
if (this.onPinChangeWithTime) {
|
||||
const timeMs = this.rp2040.clock.timeUs / 1000;
|
||||
this.onPinChangeWithTime(gpioIdx, isHigh, timeMs);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### GPIO Input (Push Buttons / Switches)
|
||||
|
||||
External components can drive GPIO pins from the UI:
|
||||
|
||||
```typescript
|
||||
// Set a pin HIGH or LOW from the simulator canvas (e.g. button press)
|
||||
simulator.setPinState(gpioPin: number, state: boolean): void
|
||||
// → rp2040.gpio[gpioPin].setInputValue(state)
|
||||
```
|
||||
|
||||
This is equivalent to the signal propagated by a physical wire from a button to the GPIO pin.
|
||||
|
||||
### LED_BUILTIN
|
||||
|
||||
- `LED_BUILTIN` = GPIO 25 on both Pico and Pico W
|
||||
- A pin change listener on GPIO 25 drives the on-board LED component
|
||||
- `digitalWrite(LED_BUILTIN, HIGH)` → GPIO 25 goes high → LED visual component turns on
|
||||
|
||||
---
|
||||
|
||||
## 8. UART — Serial Monitor
|
||||
|
||||
The RP2040 has two UART controllers. Both are emulated and forwarded to the Serial Monitor:
|
||||
|
||||
| Channel | TX Pin | RX Pin | Arduino Object | Notes |
|
||||
| ------- | ------ | ------ | -------------- | ----- |
|
||||
| UART0 | GPIO0 | GPIO1 | `Serial1` | Used by bootrom; Arduino `Serial` redirected away |
|
||||
| UART1 | GPIO4 | GPIO5 | `Serial` (redirected) | Receives Arduino `Serial.print()` |
|
||||
|
||||
### Serial Output (TX → Monitor)
|
||||
|
||||
```typescript
|
||||
// Both UART0 and UART1 forward bytes to the same callback:
|
||||
this.rp2040.uart[0].onByte = (value: number) => {
|
||||
this.onSerialData?.(String.fromCharCode(value));
|
||||
};
|
||||
this.rp2040.uart[1].onByte = (value: number) => {
|
||||
this.onSerialData?.(String.fromCharCode(value));
|
||||
};
|
||||
```
|
||||
|
||||
### Serial Input (Monitor → Sketch)
|
||||
|
||||
```typescript
|
||||
// Feed text from the Serial Monitor input box into the sketch:
|
||||
simulator.serialWrite("COMMAND\n");
|
||||
// → bytes queued in rp2040.uart[0].feedByte(charCode)
|
||||
// → sketch reads them via Serial.read() / Serial.readString()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. ADC — Analog Inputs
|
||||
|
||||
The RP2040 has a 12-bit ADC (0–4095) with 5 channels:
|
||||
|
||||
| ADC Channel | GPIO Pin | Arduino Alias | Default Value | Notes |
|
||||
| ----------- | -------- | ------------- | ------------- | ----- |
|
||||
| 0 | GPIO26 | A0 | 2048 (~1.65 V) | General purpose |
|
||||
| 1 | GPIO27 | A1 | 2048 (~1.65 V) | General purpose |
|
||||
| 2 | GPIO28 | A2 | 2048 (~1.65 V) | General purpose |
|
||||
| 3 | GPIO29 | A3 | 2048 (~1.65 V) | General purpose |
|
||||
| 4 | — (internal) | — | 876 (~27 °C) | Internal temperature sensor |
|
||||
|
||||
### Injecting ADC Values from UI Components
|
||||
|
||||
When a potentiometer is wired to an ADC pin on the canvas, the simulator canvas reads the potentiometer's value and injects it:
|
||||
|
||||
```typescript
|
||||
// SimulatorCanvas — potentiometer input event
|
||||
simulator.setADCValue(channel: number, value: number): void
|
||||
// value range: 0–4095 (clamped automatically)
|
||||
// → rp2040.adc.channelValues[channel] = value
|
||||
```
|
||||
|
||||
`analogRead(A0)` in the sketch reads `adc.channelValues[0]` directly.
|
||||
|
||||
---
|
||||
|
||||
## 10. I2C Virtual Devices
|
||||
|
||||
The RP2040 emulator supports virtual I2C devices — software objects that respond to I2C bus transactions from the firmware:
|
||||
|
||||
```typescript
|
||||
export interface RP2040I2CDevice {
|
||||
address: number; // 7-bit I2C address
|
||||
writeByte(value: number): boolean; // return true = ACK
|
||||
readByte(): number;
|
||||
stop?(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### Default Virtual Devices (Auto-Registered)
|
||||
|
||||
Three virtual I2C devices are registered automatically when a Pico program is loaded:
|
||||
|
||||
| Device | Address | Bus | Description |
|
||||
| ------ | ------- | --- | ----------- |
|
||||
| `VirtualDS1307` | 0x68 | I2C0 | Real-time clock (DS1307 compatible) |
|
||||
| `VirtualTempSensor` | 0x48 | I2C0 | Temperature sensor (LM75 / TMP102 compatible) |
|
||||
| `I2CMemoryDevice` | 0x50 | I2C0 | 24C04 EEPROM (512 bytes) |
|
||||
|
||||
### Default I2C0 Pins
|
||||
|
||||
| Signal | GPIO |
|
||||
| ------ | ---- |
|
||||
| SDA | GPIO4 |
|
||||
| SCL | GPIO5 |
|
||||
|
||||
### I2C Bus Event Flow
|
||||
|
||||
```text
|
||||
firmware: Wire.beginTransmission(0x68)
|
||||
│
|
||||
▼ I2C master asserts START + address
|
||||
rp2040.i2c[0].onConnect(address=0x68)
|
||||
│
|
||||
▼ device found in registry
|
||||
ds1307.writeByte(register_address)
|
||||
│
|
||||
Wire.requestFrom(0x68, 7)
|
||||
│
|
||||
▼ 7 read calls
|
||||
ds1307.readByte() × 7
|
||||
│
|
||||
▼ I2C STOP
|
||||
ds1307.stop()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. SPI
|
||||
|
||||
Both SPI buses implement a **loopback** by default — bytes transmitted by the master are immediately echoed back as received bytes:
|
||||
|
||||
```typescript
|
||||
// Default: loopback
|
||||
this.rp2040.spi[0].onTransmit = (value: number) => {
|
||||
this.rp2040.spi[0].completeTransmit(value);
|
||||
};
|
||||
```
|
||||
|
||||
### Default SPI0 Pins
|
||||
|
||||
| Signal | GPIO |
|
||||
| ------ | ---- |
|
||||
| MISO | GPIO16 |
|
||||
| CS | GPIO17 |
|
||||
| SCK | GPIO18 |
|
||||
| MOSI | GPIO19 |
|
||||
|
||||
### Custom SPI Handler
|
||||
|
||||
A custom handler can replace the loopback for simulating specific SPI peripherals (displays, sensors):
|
||||
|
||||
```typescript
|
||||
simulator.setSPIHandler(bus: 0 | 1, handler: (value: number) => number): void
|
||||
// handler receives TX byte, returns RX byte
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. PWM
|
||||
|
||||
PWM is available on any GPIO pin through the RP2040's hardware PWM slices. The rp2040js library emulates the PWM peripheral registers, so `analogWrite()` and `ledcWrite()` work at the firmware level.
|
||||
|
||||
Visual PWM feedback (LED dimming) in the simulator canvas uses the `onPwmChange` callback from `PinManager`, which receives the duty cycle as a 0.0–1.0 float and sets `el.style.opacity` on the LED element.
|
||||
|
||||
---
|
||||
|
||||
## 13. Simulation Execution Loop
|
||||
|
||||
The simulation runs at **60 FPS** using `requestAnimationFrame`. Each frame executes enough CPU cycles to match a 125 MHz clock:
|
||||
|
||||
```
|
||||
F_CPU = 125,000,000 Hz
|
||||
CYCLE_NANOS = 1e9 / F_CPU = 8 ns/cycle
|
||||
CYCLES_PER_FRAME = F_CPU / 60 = 2,083,333 cycles/frame
|
||||
```
|
||||
|
||||
### WFI Optimization
|
||||
|
||||
When the ARM core executes a **WFI** (Wait For Interrupt) instruction — which Arduino uses during `delay()` and `sleep()` — the loop skips ahead to the next pending timer alarm instead of executing millions of NOP-equivalent cycles:
|
||||
|
||||
```typescript
|
||||
while (cyclesDone < cyclesTarget) {
|
||||
if (core.waiting) {
|
||||
// CPU is sleeping — jump to next alarm
|
||||
const jump = clock.nanosToNextAlarm;
|
||||
if (jump <= 0) break;
|
||||
clock.tick(jump);
|
||||
cyclesDone += Math.ceil(jump / CYCLE_NANOS);
|
||||
} else {
|
||||
// Execute one ARM instruction
|
||||
const cycles = core.executeInstruction();
|
||||
clock.tick(cycles * CYCLE_NANOS);
|
||||
cyclesDone += cycles;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This allows `delay(1000)` to complete in microseconds of real time instead of simulating all 125 million cycles.
|
||||
|
||||
### Variable Speed
|
||||
|
||||
The simulation speed can be adjusted:
|
||||
|
||||
```typescript
|
||||
simulator.setSpeed(speed: number): void
|
||||
// speed: 0.1 (10% = very slow, for debugging) to 10.0 (10× faster)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Pin Mapping
|
||||
|
||||
The Pico has 40 physical pins. Below is the mapping from board pin names to GPIO numbers:
|
||||
|
||||
| Board Pin | GPIO | Function | Board Pin | GPIO | Function |
|
||||
| --------- | ---- | -------- | --------- | ---- | -------- |
|
||||
| GP0 | 0 | UART0 TX | GP15 | 15 | SPI1 TX |
|
||||
| GP1 | 1 | UART0 RX | GP16 | 16 | SPI0 MISO |
|
||||
| GP2 | 2 | I2C1 SDA | GP17 | 17 | SPI0 CS |
|
||||
| GP3 | 3 | I2C1 SCL | GP18 | 18 | SPI0 SCK |
|
||||
| GP4 | 4 | I2C0 SDA | GP19 | 19 | SPI0 MOSI |
|
||||
| GP5 | 5 | I2C0 SCL | GP20 | 20 | — |
|
||||
| GP6 | 6 | SPI0 SCK | GP21 | 21 | — |
|
||||
| GP7 | 7 | SPI0 TX | GP22 | 22 | — |
|
||||
| GP8 | 8 | UART1 TX | GP26 | 26 | ADC ch0 (A0) |
|
||||
| GP9 | 9 | UART1 RX | GP27 | 27 | ADC ch1 (A1) |
|
||||
| GP10 | 10 | SPI1 SCK | GP28 | 28 | ADC ch2 (A2) |
|
||||
| GP11 | 11 | SPI1 TX | GP29 | 29 | ADC ch3 (A3) |
|
||||
| GP12 | 12 | SPI1 RX | **GP25** | **25** | **LED_BUILTIN** |
|
||||
| GP13 | 13 | SPI1 CS | — | — | VBUS, VSYS, 3V3, GND |
|
||||
| GP14 | 14 | SPI1 SCK | — | — | RUN, ADC_VREF |
|
||||
|
||||
> All GPIO pins support PWM. GPIO 0–22 and 26–29 are available for general-purpose digital I/O. GPIO 23–25 are internal (power control, LED, SMPS mode).
|
||||
|
||||
---
|
||||
|
||||
## 15. Oscilloscope / Logic Analyzer
|
||||
|
||||
The RP2040 emulator provides timestamps for every GPIO transition using the internal simulation clock:
|
||||
|
||||
```typescript
|
||||
// Callback fires on every GPIO state change
|
||||
simulator.onPinChangeWithTime = (pin: number, state: boolean, timeMs: number) => {
|
||||
// timeMs = rp2040.clock.timeUs / 1000
|
||||
// Accurate to ~8 ns (one ARM clock cycle)
|
||||
};
|
||||
```
|
||||
|
||||
The oscilloscope store (`useOscilloscopeStore`) subscribes to this callback and records samples:
|
||||
|
||||
```text
|
||||
GPIO event fired (pin=25, state=HIGH, timeMs=123.456)
|
||||
│
|
||||
▼
|
||||
useOscilloscopeStore.pushSample(channelId, 123.456, true)
|
||||
│
|
||||
▼
|
||||
Oscilloscope waveform display updates
|
||||
```
|
||||
|
||||
This enables the built-in logic analyzer to display accurate waveforms for signals like PWM, UART bit patterns, and I2C clock/data.
|
||||
|
||||
---
|
||||
|
||||
## 16. Known Limitations
|
||||
|
||||
| Limitation | Detail |
|
||||
| ---------- | ------ |
|
||||
| Single-core only | RP2040 is dual-core; emulator runs core 0 only. Code that uses `multicore_launch_core1()` will not execute on core 1 |
|
||||
| No PIO | PIO state machines return 0. Libraries that depend on PIO (WS2812 NeoPixels, DHT sensors, I2S audio, quadrature encoders) will not work |
|
||||
| No WiFi (Pico W) | The CYW43439 wireless chip is not emulated; `WiFi.begin()` will not connect |
|
||||
| No USB device | USB HID, CDC, and MIDI device modes are not emulated |
|
||||
| No DMA | DMA transfers return without moving data; memcpy-based alternatives work fine |
|
||||
| No hardware FPU | ARM Cortex-M0+ has no floating-point unit; float operations are emulated in software (slower, but correct) |
|
||||
| Timing accuracy | Emulation runs at variable speed; `micros()` and `millis()` track simulated time, not wall-clock time |
|
||||
| Flash writes | `LittleFS`, `EEPROM.commit()`, and other flash-write operations may not persist across simulated resets |
|
||||
| Serial redirect | `Serial` is mapped to `Serial1` (UART1) at compile time; code that uses `Serial1` directly alongside `Serial` may behave unexpectedly |
|
||||
|
||||
---
|
||||
|
||||
## 17. Tests
|
||||
|
||||
RP2040 emulation tests are in `frontend/src/__tests__/`:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm test -- RP2040
|
||||
```
|
||||
|
||||
### Test Suites
|
||||
|
||||
| Suite | Tests | What It Verifies |
|
||||
| ----- | ----- | ---------------- |
|
||||
| Lifecycle | 4 | `create`, `loadBinary`, `start`, `stop`, `reset` idempotency |
|
||||
| GPIO | 8 | All 30 pins, state injection, listener callbacks, multiple listeners |
|
||||
| ADC | 3 | Channel access (0–4), value clamping to 0–4095, `analogRead` integration |
|
||||
| Serial | 4 | `onSerialData` callback, `serialWrite` input feed, both UART channels |
|
||||
| I2C | 5 | Virtual device registration, address matching, read/write/stop callbacks |
|
||||
| SPI | 2 | Custom handler registration, loopback default |
|
||||
| Bootrom | 1 | Loads without errors, PC resets to `0x10000000` |
|
||||
|
||||
---
|
||||
|
||||
## 18. Differences vs Other Emulators
|
||||
|
||||
| Aspect | Raspberry Pi Pico (RP2040) | Arduino AVR | ESP32-C3 (RISC-V) | ESP32 (Xtensa) |
|
||||
| ------ | -------------------------- | ----------- | ------------------ | -------------- |
|
||||
| Engine | `rp2040js` (browser) | `avr8js` (browser) | `RiscVCore.ts` (browser) | QEMU backend (WebSocket) |
|
||||
| Backend dependency | **No** | **No** | **No** | Yes |
|
||||
| Architecture | ARM Cortex-M0+ | AVR 8-bit | RISC-V RV32IMC | Xtensa LX6 |
|
||||
| Clock speed | 125 MHz | 16 MHz | 160 MHz | 240 MHz |
|
||||
| Binary format | `.bin` (raw) | `.hex` (Intel HEX) | `.bin` (merged flash) | `.bin` (merged flash) |
|
||||
| Arduino framework | Full | Full | Partial (no FreeRTOS) | Full |
|
||||
| Serial | UART0 + UART1 | USART0 | UART0 | UART0 |
|
||||
| ADC | 4 external + 1 temp | 6 channels (10-bit) | Not emulated | Emulated |
|
||||
| I2C | 2 buses + virtual devices | Not emulated | Not emulated | Emulated |
|
||||
| SPI | 2 buses (loopback) | Not emulated | Not emulated | Emulated |
|
||||
| PIO | **Not emulated** | N/A | N/A | N/A |
|
||||
| PWM | All GPIO pins | Timer-based | Not emulated | LEDC (mapped) |
|
||||
| Oscilloscope support | Yes (8 ns resolution) | Yes | Yes | No |
|
||||
| CI tests | Yes (Vitest) | Yes | Yes | No |
|
||||
|
||||
---
|
||||
|
||||
## 19. Key Files
|
||||
|
||||
| File | Description |
|
||||
| ---- | ----------- |
|
||||
| `frontend/src/simulation/RP2040Simulator.ts` | Main emulator wrapper (GPIO, UART, ADC, I2C, SPI, RAF loop) |
|
||||
| `frontend/src/simulation/rp2040-bootrom.ts` | RP2040 B1 bootrom binary (required for flash XIP) |
|
||||
| `frontend/src/simulation/PinManager.ts` | Routes GPIO events to visual components on canvas |
|
||||
| `frontend/src/store/useSimulatorStore.ts` | Zustand store — `compileBoardProgram()`, `startBoard()`, serial wiring |
|
||||
| `frontend/src/components/simulator/SimulatorCanvas.tsx` | Canvas rendering, pin subscriptions, ADC/button event forwarding |
|
||||
| `frontend/src/components/components-wokwi/PiPicoWElement.ts` | Pico W SVG board rendering and pin coordinate map |
|
||||
| `frontend/src/__tests__/RP2040Simulator.test.ts` | Unit and integration tests |
|
||||
| `backend/app/services/arduino_cli.py` | Compilation — detects RP2040 FQBN, encodes `.bin` as base64, prepends `Serial1` redirect |
|
||||
| `wokwi-libs/rp2040js/` | Local clone of the rp2040js library (ARM Cortex-M0+ emulator) |
|
||||
|
|
@ -114,3 +114,47 @@ void loop() {
|
|||
| LED doesn't blink | Check the browser console for port listener errors; verify pin assignment in the component property dialog. |
|
||||
| Serial Monitor is empty | Ensure `Serial.begin()` is called inside `setup()` before any `Serial.print()`. |
|
||||
| Compilation errors | Check the compilation console at the bottom of the editor for full `arduino-cli` output. |
|
||||
|
||||
---
|
||||
|
||||
## Community & Links
|
||||
|
||||
- **GitHub:** [github.com/davidmonterocrespo24/velxio](https://github.com/davidmonterocrespo24/velxio) — source code, issues, pull requests
|
||||
- **Discord:** [YOUR_DISCORD_INVITE_URL] — ask questions, share projects, report issues
|
||||
- **Live Demo:** [velxio.dev](https://velxio.dev)
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
### Orientation
|
||||
|
||||
- [Introduction](./intro.md) — What is Velxio and why use it
|
||||
- [Getting Started](./getting-started.md) — This page
|
||||
|
||||
### Architecture & Internals
|
||||
|
||||
- [Architecture](./ARCHITECTURE.md) — High-level project architecture
|
||||
- [Emulator Architecture](./emulator.md) — How CPU emulation works layer by layer
|
||||
- [Wokwi Libraries Integration](./WOKWI_LIBS.md) — Local wokwi-elements, avr8js, rp2040js
|
||||
|
||||
### Boards & Emulation
|
||||
|
||||
- [RP2040 Emulation](./RP2040_EMULATION.md) — Raspberry Pi Pico / Pico W in-browser emulator (ARM Cortex-M0+)
|
||||
- [Raspberry Pi 3 Emulation](./RASPBERRYPI3_EMULATION.md) — BCM2837 / QEMU raspi3b, Python + GPIO shim
|
||||
- [ESP32 Emulation](./ESP32_EMULATION.md) — Full Xtensa QEMU emulation (GPIO, ADC, PWM, WiFi, I2C, SPI, RMT)
|
||||
- [RISC-V Emulation](./RISCV_EMULATION.md) — ESP32-C3 / XIAO-C3 in-browser emulator
|
||||
|
||||
### Components & Examples
|
||||
|
||||
- [Components Reference](./components.md) — All 48+ supported electronic components
|
||||
- [Example Projects](./examples/README.md) — Built-in example gallery
|
||||
|
||||
### API & Integrations
|
||||
|
||||
- [MCP Server](./MCP.md) — Model Context Protocol server for AI agent integration
|
||||
|
||||
### Project Status
|
||||
|
||||
- [Roadmap](./roadmap.md) — Implemented, in-progress, and planned features
|
||||
- [Setup Complete](./SETUP_COMPLETE.md) — Feature implementation status log
|
||||
|
|
|
|||
|
|
@ -26,11 +26,44 @@ Write Arduino C++ code, compile it with a real `arduino-cli` backend, and simula
|
|||
|
||||
---
|
||||
|
||||
## Quick Links
|
||||
## Documentation
|
||||
|
||||
### Getting Started
|
||||
|
||||
- [Getting Started](./getting-started.md) — Quick setup guide (hosted, Docker, manual)
|
||||
- [Introduction](./intro.md) — Overview, supported boards, quick links
|
||||
|
||||
### Architecture & Internals
|
||||
|
||||
- [Architecture](./ARCHITECTURE.md) — High-level project architecture
|
||||
- [Emulator Architecture](./emulator.md) — How CPU emulation works layer by layer
|
||||
- [Wokwi Libraries Integration](./WOKWI_LIBS.md) — Local wokwi-elements, avr8js, rp2040js setup
|
||||
|
||||
### Boards & Emulation
|
||||
|
||||
- [RP2040 Emulation](./RP2040_EMULATION.md) — Raspberry Pi Pico / Pico W in-browser emulator (ARM Cortex-M0+)
|
||||
- [Raspberry Pi 3 Emulation](./RASPBERRYPI3_EMULATION.md) — BCM2837 / QEMU raspi3b, Python + GPIO shim
|
||||
- [ESP32 Emulation](./ESP32_EMULATION.md) — Full Xtensa QEMU emulation (GPIO, ADC, PWM, WiFi, I2C, SPI, RMT)
|
||||
- [RISC-V Emulation](./RISCV_EMULATION.md) — ESP32-C3 / XIAO-C3 in-browser emulator
|
||||
|
||||
### Components & Examples
|
||||
|
||||
- [Components Reference](./components.md) — All 48+ supported electronic components
|
||||
- [Example Projects](./examples/README.md) — Built-in example gallery
|
||||
|
||||
### API & Integrations
|
||||
|
||||
- [MCP Server](./MCP.md) — Model Context Protocol server for AI agent integration
|
||||
|
||||
### Project Status
|
||||
|
||||
- [Roadmap](./roadmap.md) — Implemented, in-progress, and planned features
|
||||
- [Setup Complete](./SETUP_COMPLETE.md) — Feature implementation status log
|
||||
|
||||
---
|
||||
|
||||
## Community & Links
|
||||
|
||||
- [Getting Started](./getting-started.md)
|
||||
- [Emulator Architecture](./emulator.md)
|
||||
- [Components Reference](./components.md)
|
||||
- [Roadmap](./roadmap.md)
|
||||
- [Live Demo](https://velxio.dev)
|
||||
- [GitHub Repository](https://github.com/davidmonterocrespo24/velxio)
|
||||
- [Discord](YOUR_DISCORD_INVITE_URL) — Ask questions, share projects, report issues
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 440 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 912 KiB After Width: | Height: | Size: 1.3 MiB |
|
|
@ -238,13 +238,13 @@
|
|||
/* ── Hero ─────────────────────────────────────────────── */
|
||||
.landing-hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: 1fr 1.3fr;
|
||||
align-items: center;
|
||||
gap: 64px;
|
||||
padding: 100px 80px 100px 80px;
|
||||
max-width: 1240px;
|
||||
gap: 56px;
|
||||
padding: 80px 80px 80px 80px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
min-height: 85vh;
|
||||
min-height: 90vh;
|
||||
}
|
||||
|
||||
.hero-left {
|
||||
|
|
@ -387,10 +387,10 @@
|
|||
|
||||
.hero-preview-img {
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius);
|
||||
filter: drop-shadow(0 16px 48px rgba(0, 0, 0, 0.7));
|
||||
filter: drop-shadow(0 24px 64px rgba(0, 0, 0, 0.75));
|
||||
}
|
||||
|
||||
/* ── Sections ─────────────────────────────────────────── */
|
||||
|
|
@ -898,8 +898,9 @@
|
|||
/* ── Responsive ───────────────────────────────────────── */
|
||||
@media (max-width: 1024px) {
|
||||
.landing-hero {
|
||||
gap: 48px;
|
||||
padding: 80px 48px;
|
||||
grid-template-columns: 1fr 1.2fr;
|
||||
gap: 40px;
|
||||
padding: 72px 48px;
|
||||
}
|
||||
|
||||
.landing-section {
|
||||
|
|
|
|||
Loading…
Reference in New Issue