feat(boards): expose Raspberry Pi 4 and Pi 5 in the picker (UI + pin wiring)
The BoardKind type and the QEMU backend already supported
raspberry-pi-4 (Cortex-A72) and raspberry-pi-5 (Cortex-A76) by reusing
the Pi 3 arm64 image set, but the frontend had no way to actually
select either: the board picker, the canvas renderer, the serial
monitor, the oscilloscope channel list, and the editor toolbar all
hard-coded "raspberry-pi-3" as the only Pi entry. ComponentRegistry
even registered Pi 4 / Pi 5 metadata pointing at the velxio-raspberry-pi-3
custom-element tag — a placeholder that meant both boards rendered as
a Pi 3 in the picker thumbnail and on the canvas.
Add dedicated boards top-to-bottom:
* `RaspberryPi4Element.ts` / `RaspberryPi5Element.ts` — Velxio-style
schematic SVG (authored from scratch, not traced). Pi 4 is the
green PCB with BCM2711 SoC, 4× USB-A, USB-C power, dual µHDMI;
Pi 5 is the darker green PCB with BCM2712 + RP1 southbridge,
2.5 GbE, USB-C 5V/5A, PCIe FFC connector, dedicated power
button. Both carry a small "velxio" mark in the corner.
* `pi40PinHeader.ts` — shared `buildPi40PinHeader()` helper that
returns the 40-pin BCM layout. Every Pi from the 1B+ onwards
uses the same physical pin positions and same BCM GPIO
assignment, so Pi 3 / Pi 4 / Pi 5 elements all consume this
helper and example wires drawn against one model transfer to
the others without re-routing.
* React wrappers `RaspberryPi4.tsx` / `RaspberryPi5.tsx` render the
custom elements at absolute positions (mirrors how
RaspberryPi3.tsx handles the Pi 3 illustration).
* Wire-up across the editor surface:
- BoardOnCanvas: BOARD_SIZE entry + switch case.
- BoardPickerModal: description, icon, kinds list.
- ComponentPickerModal: thumbnails now instantiate the dedicated
custom element (was velxio-raspberry-pi-3 fallback).
- SerialMonitor / EditorToolbar: pill labels, icons, colours.
- Oscilloscope: GPIO channel list (28 BCM pins).
- SimulatorCanvas: remote-boards filter for run/stop sync.
- SPICE boardPinGroups: same 5V / 3V3 / GND as Pi 3.
- boardPinToNumber: accepts physical pin numbers ("1"-"40"),
BCM names ("GPIO14") and power labels for any Pi 3/4/5 id.
- ComponentRegistry: dedicated tagNames + per-board thumbnails
(green for Pi 4, darker green for Pi 5).
* EditorToolbar's Pi 3 special cases (Linux/Python compile path,
Run/Stop routing) now use `isPiBoardKind()` so Pi 4 and Pi 5
inherit the same behaviour automatically, and any future Pi
family member (Zero / 1 / 2) lands in the right code paths the
moment its backend boots.
QEMU backend was already wired (qemu_manager.py:71/82 + manifest entry
'raspberry-pi-3-virt' shared across arm64 Pis), so this commit makes
both boards selectable end-to-end without any backend follow-up.
This commit is contained in:
parent
2146b09c29
commit
f619a2cc7e
|
|
@ -41,6 +41,8 @@ const BOARD_DESCRIPTIONS: Record<BoardKind, string> = {
|
|||
'raspberry-pi-pico': 'RP2040 dual-core Cortex-M0+',
|
||||
'pi-pico-w': 'RP2040 + WiFi/BT, same emulator as Pico',
|
||||
'raspberry-pi-3': 'ARM64 Cortex-A53 quad-core, Linux/Python (QEMU)',
|
||||
'raspberry-pi-4': 'ARM64 Cortex-A72 quad-core, Linux/Python (QEMU)',
|
||||
'raspberry-pi-5': 'ARM64 Cortex-A76 quad-core + RP1 I/O, Linux/Python (QEMU)',
|
||||
esp32: 'Xtensa LX6 dual-core, WiFi+BT, 38 GPIO (QEMU)',
|
||||
'esp32-devkit-c-v4': 'ESP32 DevKit C V4, official Espressif (QEMU)',
|
||||
'esp32-cam': 'ESP32 + 2MP camera, microSD (QEMU)',
|
||||
|
|
@ -61,6 +63,8 @@ const ALL_BOARDS: BoardKind[] = [
|
|||
'raspberry-pi-pico',
|
||||
'pi-pico-w',
|
||||
'raspberry-pi-3',
|
||||
'raspberry-pi-4',
|
||||
'raspberry-pi-5',
|
||||
'esp32',
|
||||
'esp32-devkit-c-v4',
|
||||
'esp32-cam',
|
||||
|
|
@ -424,8 +428,21 @@ const BoardCard: React.FC<BoardCardProps> = ({ kind, onSelect }) => {
|
|||
|
||||
React.useEffect(() => {
|
||||
if (!thumbnailRef.current) return;
|
||||
// React-rendered boards and Pi3 handled in JSX below
|
||||
// React-rendered boards and Pi family handled below: Pi 3 has a custom
|
||||
// illustration SVG; Pi 4 / Pi 5 instantiate their own velxio-* custom
|
||||
// element directly because they don't go through BOARD_TAG.
|
||||
if (kind === 'raspberry-pi-3' || kind === 'attiny85') return;
|
||||
if (kind === 'raspberry-pi-4' || kind === 'raspberry-pi-5') {
|
||||
const tagName = kind === 'raspberry-pi-4' ? 'velxio-raspberry-pi-4' : 'velxio-raspberry-pi-5';
|
||||
const el = document.createElement(tagName) as HTMLElement;
|
||||
el.style.transform = 'scale(0.35)';
|
||||
el.style.transformOrigin = 'center center';
|
||||
thumbnailRef.current.innerHTML = '';
|
||||
thumbnailRef.current.appendChild(el);
|
||||
return () => {
|
||||
if (thumbnailRef.current) thumbnailRef.current.innerHTML = '';
|
||||
};
|
||||
}
|
||||
|
||||
const tag = BOARD_TAG[kind];
|
||||
if (!tag) return;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { BOARD_PIN_GROUPS } from '../../simulation/spice/boardPinGroups';
|
|||
import { CircuitVerificationModal } from '../simulator/CircuitVerificationModal';
|
||||
import type { PinSourceState } from '../../simulation/spice/types';
|
||||
import type { BoardKind, LanguageMode } from '../../types/board';
|
||||
import { BOARD_KIND_FQBN, BOARD_KIND_LABELS, BOARD_SUPPORTS_MICROPYTHON } from '../../types/board';
|
||||
import { BOARD_KIND_FQBN, BOARD_KIND_LABELS, BOARD_SUPPORTS_MICROPYTHON, isPiBoardKind } from '../../types/board';
|
||||
import { compileCode } from '../../services/compilation';
|
||||
import {
|
||||
compileRom,
|
||||
|
|
@ -61,6 +61,8 @@ const BOARD_PILL_ICON: Record<BoardKind, string> = {
|
|||
'arduino-mega': '▬',
|
||||
'raspberry-pi-pico': '◆',
|
||||
'raspberry-pi-3': '⬛',
|
||||
'raspberry-pi-4': '⬛',
|
||||
'raspberry-pi-5': '⬛',
|
||||
esp32: '⬡',
|
||||
'esp32-s3': '⬡',
|
||||
'esp32-c3': '⬡',
|
||||
|
|
@ -72,6 +74,8 @@ const BOARD_PILL_COLOR: Record<BoardKind, string> = {
|
|||
'arduino-mega': '#4fc3f7',
|
||||
'raspberry-pi-pico': '#ce93d8',
|
||||
'raspberry-pi-3': '#ef9a9a',
|
||||
'raspberry-pi-4': '#ef9a9a',
|
||||
'raspberry-pi-5': '#ef9a9a',
|
||||
esp32: '#a5d6a7',
|
||||
'esp32-s3': '#a5d6a7',
|
||||
'esp32-c3': '#a5d6a7',
|
||||
|
|
@ -259,7 +263,7 @@ export const EditorToolbar = ({
|
|||
const kind = activeBoard?.boardKind;
|
||||
|
||||
// Raspberry Pi 3B doesn't need arduino-cli compilation
|
||||
if (kind === 'raspberry-pi-3') {
|
||||
if (isPiBoardKind(kind)) {
|
||||
addLog({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
|
|
@ -566,7 +570,7 @@ export const EditorToolbar = ({
|
|||
}
|
||||
|
||||
const isQemuBoard =
|
||||
board?.boardKind === 'raspberry-pi-3' ||
|
||||
board?.boardKind && isPiBoardKind(board.boardKind) ||
|
||||
board?.boardKind === 'esp32' ||
|
||||
board?.boardKind === 'esp32-s3' ||
|
||||
board?.boardKind === 'esp32-cam' ||
|
||||
|
|
@ -713,7 +717,7 @@ export const EditorToolbar = ({
|
|||
for (const board of boardsList) {
|
||||
const label = BOARD_KIND_LABELS[board.boardKind] ?? board.boardKind;
|
||||
|
||||
if (board.boardKind === 'raspberry-pi-3') {
|
||||
if (isPiBoardKind(board.boardKind)) {
|
||||
addLog({
|
||||
timestamp: new Date(),
|
||||
type: 'info',
|
||||
|
|
@ -815,7 +819,7 @@ export const EditorToolbar = ({
|
|||
codeChangedSinceLastCompile ||
|
||||
boardsList.some(
|
||||
(b) =>
|
||||
b.boardKind !== 'raspberry-pi-3' &&
|
||||
!isPiBoardKind(b.boardKind) &&
|
||||
b.languageMode !== 'micropython' &&
|
||||
!b.compiledProgram,
|
||||
);
|
||||
|
|
@ -830,7 +834,7 @@ export const EditorToolbar = ({
|
|||
for (const board of refreshed) {
|
||||
if (board.running) continue;
|
||||
const isQemu =
|
||||
board.boardKind === 'raspberry-pi-3' ||
|
||||
isPiBoardKind(board.boardKind) ||
|
||||
board.boardKind === 'esp32' ||
|
||||
board.boardKind === 'esp32-s3';
|
||||
if (isQemu || board.compiledProgram || board.languageMode === 'micropython') {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { ArduinoMega } from '../velxio-components/ArduinoMega';
|
|||
// re-wire it back in. If someone genuinely needs a Nano RP2040 Connect
|
||||
// board (D2..D13 labels), add a new boardKind 'arduino-nano-rp2040'.
|
||||
import { RaspberryPi3 } from '../velxio-components/RaspberryPi3';
|
||||
import { RaspberryPi4 } from '../velxio-components/RaspberryPi4';
|
||||
import { RaspberryPi5 } from '../velxio-components/RaspberryPi5';
|
||||
import { Esp32 } from '../velxio-components/Esp32';
|
||||
import { Attiny85 } from '../velxio-components/Attiny85';
|
||||
import { PiPicoW } from '../velxio-components/PiPicoW';
|
||||
|
|
@ -29,6 +31,8 @@ const BOARD_SIZE: Record<string, { w: number; h: number }> = {
|
|||
// the boardKind name.
|
||||
'raspberry-pi-pico': { w: 105, h: 264 },
|
||||
'raspberry-pi-3': { w: 250, h: 160 }, // RaspberryPi3Element: PI_WIDTH=250 PI_HEIGHT=160
|
||||
'raspberry-pi-4': { w: 250, h: 160 }, // RaspberryPi4Element — same 40-pin footprint
|
||||
'raspberry-pi-5': { w: 250, h: 160 }, // RaspberryPi5Element — same 40-pin footprint
|
||||
esp32: { w: 141, h: 265 }, // esp32-devkit-v1: 28.2 × 53 mm
|
||||
'esp32-s3': { w: 128, h: 350 }, // esp32-s3-devkitc-1: 25.5 × 70 mm
|
||||
'esp32-c3': { w: 127, h: 215 }, // esp32-c3-devkitm-1: 25.4 × 42.9 mm
|
||||
|
|
@ -97,6 +101,10 @@ export const BoardOnCanvas = ({
|
|||
return <PiPicoW id={id} x={x} y={y} />;
|
||||
case 'raspberry-pi-3':
|
||||
return <RaspberryPi3 id={id} x={x} y={y} />;
|
||||
case 'raspberry-pi-4':
|
||||
return <RaspberryPi4 id={id} x={x} y={y} />;
|
||||
case 'raspberry-pi-5':
|
||||
return <RaspberryPi5 id={id} x={x} y={y} />;
|
||||
case 'esp32':
|
||||
case 'esp32-devkit-c-v4':
|
||||
case 'esp32-cam':
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ const BOARD_DESCRIPTIONS: Record<BoardKind, string> = {
|
|||
'arduino-mega': '8-bit AVR, 256KB flash, 54 digital I/O',
|
||||
'raspberry-pi-pico': 'RP2040 dual-core Cortex-M0+',
|
||||
'raspberry-pi-3': 'ARM64 Cortex-A53 quad-core, Linux/Python (QEMU)',
|
||||
'raspberry-pi-4': 'ARM64 Cortex-A72 quad-core, Linux/Python (QEMU)',
|
||||
'raspberry-pi-5': 'ARM64 Cortex-A76 quad-core + RP1 I/O, Linux/Python (QEMU)',
|
||||
esp32: 'Xtensa LX6 dual-core, WiFi+BT, 38 GPIO (QEMU)',
|
||||
'esp32-s3': 'Xtensa LX7 dual-core, WiFi+BT, AI accel (QEMU)',
|
||||
'esp32-c3': 'RISC-V single-core, WiFi+BLE, 22 GPIO (QEMU)',
|
||||
|
|
@ -20,6 +22,8 @@ const BOARD_ICON: Record<BoardKind, string> = {
|
|||
'arduino-mega': '▬',
|
||||
'raspberry-pi-pico': '◆',
|
||||
'raspberry-pi-3': '⬛',
|
||||
'raspberry-pi-4': '⬛',
|
||||
'raspberry-pi-5': '⬛',
|
||||
esp32: '⬡',
|
||||
'esp32-s3': '⬡',
|
||||
'esp32-c3': '⬡',
|
||||
|
|
@ -37,6 +41,8 @@ const BOARDS: BoardKind[] = [
|
|||
'arduino-mega',
|
||||
'raspberry-pi-pico',
|
||||
'raspberry-pi-3',
|
||||
'raspberry-pi-4',
|
||||
'raspberry-pi-5',
|
||||
'esp32',
|
||||
'esp32-s3',
|
||||
'esp32-c3',
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ function getPinsForBoardKind(boardKind: BoardKind): { pin: number; label: string
|
|||
case 'aitewinrobot-esp32c3-supermini':
|
||||
return Array.from({ length: 22 }, (_, i) => ({ pin: i, label: `GPIO${i}` }));
|
||||
case 'raspberry-pi-3':
|
||||
case 'raspberry-pi-4':
|
||||
case 'raspberry-pi-5':
|
||||
return Array.from({ length: 28 }, (_, i) => ({ pin: i, label: `GPIO${i}` }));
|
||||
default:
|
||||
// arduino-uno, arduino-nano
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ const BOARD_SHORT_LABEL: Partial<Record<string, string>> = {
|
|||
'raspberry-pi-pico': 'Pico',
|
||||
'pi-pico-w': 'Pico W',
|
||||
'raspberry-pi-3': 'Pi 3B',
|
||||
'raspberry-pi-4': 'Pi 4B',
|
||||
'raspberry-pi-5': 'Pi 5',
|
||||
esp32: 'ESP32',
|
||||
'esp32-devkit-c-v4': 'ESP32',
|
||||
'esp32-cam': 'ESP32-CAM',
|
||||
|
|
@ -38,6 +40,8 @@ const BOARD_ICON: Partial<Record<string, string>> = {
|
|||
'raspberry-pi-pico': '◆',
|
||||
'pi-pico-w': '◆',
|
||||
'raspberry-pi-3': '⬛',
|
||||
'raspberry-pi-4': '⬛',
|
||||
'raspberry-pi-5': '⬛',
|
||||
esp32: '⬡',
|
||||
'esp32-devkit-c-v4': '⬡',
|
||||
'esp32-cam': '⬡',
|
||||
|
|
@ -58,6 +62,8 @@ const BOARD_COLOR: Partial<Record<string, string>> = {
|
|||
'raspberry-pi-pico': '#ce93d8',
|
||||
'pi-pico-w': '#ce93d8',
|
||||
'raspberry-pi-3': '#ef9a9a',
|
||||
'raspberry-pi-4': '#ef9a9a',
|
||||
'raspberry-pi-5': '#ef9a9a',
|
||||
esp32: '#a5d6a7',
|
||||
'esp32-devkit-c-v4': '#a5d6a7',
|
||||
'esp32-cam': '#a5d6a7',
|
||||
|
|
|
|||
|
|
@ -413,6 +413,8 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
|
|||
const remoteBoards = boards.filter(
|
||||
(b) =>
|
||||
b.boardKind === 'raspberry-pi-3' ||
|
||||
b.boardKind === 'raspberry-pi-4' ||
|
||||
b.boardKind === 'raspberry-pi-5' ||
|
||||
b.boardKind === 'esp32' ||
|
||||
b.boardKind === 'esp32-s3' ||
|
||||
b.boardKind === 'esp32-c3',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Raspberry Pi 4 React wrapper — renders the velxio-raspberry-pi-4 custom
|
||||
* element (defined in RaspberryPi4Element.ts) at an absolute position so
|
||||
* BoardOnCanvas can drop it on the simulator canvas. The wire system
|
||||
* reads `pinInfo` directly from the custom element via DOM, so no
|
||||
* additional pinInfo wiring is needed here.
|
||||
*/
|
||||
import './RaspberryPi4Element';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'velxio-raspberry-pi-4': any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RaspberryPi4Props {
|
||||
id?: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
}
|
||||
|
||||
export const RaspberryPi4 = ({ id = 'raspberry-pi-4', x = 0, y = 0 }: RaspberryPi4Props) => (
|
||||
<velxio-raspberry-pi-4
|
||||
id={id}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
display: 'block',
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Raspberry Pi 4 Model B — Velxio schematic board.
|
||||
*
|
||||
* Authored from scratch (not traced from RPi Foundation art); the layout
|
||||
* is the publicly-known PCB topology (BCM2711 SoC centre-right, USB-C
|
||||
* power on the south edge, dual micro-HDMI mid-south, 4× USB-A and
|
||||
* gigabit Ethernet stacked on the east edge, 40-pin GPIO header on the
|
||||
* north edge, microSD on the underside). Rendered at the same
|
||||
* 250×160 footprint and pin pitch as RaspberryPi3Element so wires from
|
||||
* existing canvases line up byte-for-byte.
|
||||
*
|
||||
* The 40-pin GPIO header is electrically identical to every Pi from
|
||||
* the 1B+ onwards — pin numbers 1-40 map to the same BCM GPIO lines
|
||||
* the firmware sees, so reusing the same pin-name scheme means Velxio
|
||||
* routes and example projects transfer between Pi models without
|
||||
* touching wires.
|
||||
*/
|
||||
|
||||
import { buildPi40PinHeader } from './pi40PinHeader';
|
||||
|
||||
const PI_WIDTH = 250;
|
||||
const PI_HEIGHT = 160;
|
||||
|
||||
class RaspberryPi4Element extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
get pinInfo() {
|
||||
return buildPi40PinHeader();
|
||||
}
|
||||
|
||||
render() {
|
||||
let pinsSvg = '';
|
||||
const pins = this.pinInfo;
|
||||
pins.forEach((pin) => {
|
||||
pinsSvg += `<rect x="${pin.x - 3}" y="${pin.y - 3}" width="6" height="6" fill="#D4AF37" />`;
|
||||
pinsSvg += `<circle cx="${pin.x}" cy="${pin.y}" r="2" fill="#000" />`;
|
||||
});
|
||||
|
||||
this.shadowRoot!.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
width: ${PI_WIDTH}px;
|
||||
height: ${PI_HEIGHT}px;
|
||||
position: relative;
|
||||
}
|
||||
svg { width: 100%; height: 100%; }
|
||||
.board {
|
||||
fill: #006633; /* Pi 4 green PCB */
|
||||
stroke: #003d1f;
|
||||
stroke-width: 2;
|
||||
rx: 8;
|
||||
}
|
||||
.cpu { fill: #1a1a1a; stroke: #000; rx: 2; }
|
||||
.usb { fill: #ccc; stroke: #999; rx: 2; }
|
||||
.usbc { fill: #888; stroke: #555; rx: 1; }
|
||||
.eth { fill: #bbb; stroke: #888; rx: 2; }
|
||||
.hdmi { fill: #555; stroke: #333; rx: 1; }
|
||||
.gpio-header { fill: #222; }
|
||||
.rp1 { fill: #2a2a2a; stroke: #000; rx: 2; }
|
||||
.label { fill: #FFF; font-family: sans-serif; }
|
||||
</style>
|
||||
<svg viewBox="0 0 ${PI_WIDTH} ${PI_HEIGHT}">
|
||||
<!-- PCB -->
|
||||
<rect class="board" x="2" y="2" width="${PI_WIDTH - 4}" height="${PI_HEIGHT - 4}" />
|
||||
|
||||
<!-- BCM2711 SoC (centre-right, larger than Pi 3) -->
|
||||
<rect class="cpu" x="100" y="55" width="48" height="48" />
|
||||
<text x="124" y="76" class="label" font-size="7" text-anchor="middle">BCM2711</text>
|
||||
<text x="124" y="86" class="label" font-size="6" text-anchor="middle" opacity="0.7">A72 ×4</text>
|
||||
|
||||
<!-- 4× USB-A (east edge, two stacked banks) -->
|
||||
<rect class="usb" x="${PI_WIDTH - 38}" y="18" width="36" height="28" />
|
||||
<text x="${PI_WIDTH - 20}" y="34" class="label" font-size="5" text-anchor="middle" opacity="0.7">USB3</text>
|
||||
<rect class="usb" x="${PI_WIDTH - 38}" y="50" width="36" height="28" />
|
||||
<text x="${PI_WIDTH - 20}" y="66" class="label" font-size="5" text-anchor="middle" opacity="0.7">USB2</text>
|
||||
|
||||
<!-- Gigabit Ethernet -->
|
||||
<rect class="eth" x="${PI_WIDTH - 38}" y="82" width="36" height="36" />
|
||||
<text x="${PI_WIDTH - 20}" y="102" class="label" font-size="5" text-anchor="middle" opacity="0.7">GbE</text>
|
||||
|
||||
<!-- USB-C Power (south edge, west side) -->
|
||||
<rect class="usbc" x="6" y="${PI_HEIGHT - 18}" width="18" height="10" />
|
||||
<text x="15" y="${PI_HEIGHT - 22}" class="label" font-size="5" text-anchor="middle" opacity="0.7">USB-C 5V</text>
|
||||
|
||||
<!-- 2× micro-HDMI (south edge, centre) -->
|
||||
<rect class="hdmi" x="40" y="${PI_HEIGHT - 16}" width="20" height="10" />
|
||||
<rect class="hdmi" x="66" y="${PI_HEIGHT - 16}" width="20" height="10" />
|
||||
<text x="63" y="${PI_HEIGHT - 20}" class="label" font-size="5" text-anchor="middle" opacity="0.7">µHDMI 0 / 1</text>
|
||||
|
||||
<!-- Audio jack (south edge, east of HDMI) -->
|
||||
<circle cx="100" cy="${PI_HEIGHT - 11}" r="5" fill="#222" stroke="#000" />
|
||||
|
||||
<!-- GPIO Header base -->
|
||||
<rect class="gpio-header" x="15" y="5" width="200" height="20" rx="1" />
|
||||
|
||||
<!-- Pins -->
|
||||
${pinsSvg}
|
||||
|
||||
<!-- Board name + Velxio mark -->
|
||||
<text x="60" y="125" class="label" font-size="14" font-weight="bold">Raspberry Pi 4</text>
|
||||
<text x="60" y="140" class="label" font-size="9" opacity="0.85">Model B · Cortex-A72</text>
|
||||
<text x="${PI_WIDTH - 8}" y="${PI_HEIGHT - 6}" class="label" font-size="6" text-anchor="end" opacity="0.6">velxio</text>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get('velxio-raspberry-pi-4')) {
|
||||
customElements.define('velxio-raspberry-pi-4', RaspberryPi4Element);
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Raspberry Pi 5 React wrapper — renders the velxio-raspberry-pi-5 custom
|
||||
* element at an absolute position. Pin info lives on the custom element
|
||||
* via its `pinInfo` getter; the wire system reads it from the DOM.
|
||||
*/
|
||||
import './RaspberryPi5Element';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'velxio-raspberry-pi-5': any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RaspberryPi5Props {
|
||||
id?: string;
|
||||
x?: number;
|
||||
y?: number;
|
||||
}
|
||||
|
||||
export const RaspberryPi5 = ({ id = 'raspberry-pi-5', x = 0, y = 0 }: RaspberryPi5Props) => (
|
||||
<velxio-raspberry-pi-5
|
||||
id={id}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: `${x}px`,
|
||||
top: `${y}px`,
|
||||
display: 'block',
|
||||
userSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* Raspberry Pi 5 — Velxio schematic board.
|
||||
*
|
||||
* Authored from scratch (not traced from RPi Foundation art); the layout
|
||||
* is the publicly-known PCB topology: BCM2712 SoC centre, RP1 southbridge
|
||||
* to its right driving the I/O, 4× USB-A + 2.5 GbE on the east edge,
|
||||
* USB-C power + dual micro-HDMI on the south, 40-pin GPIO header on the
|
||||
* north, dedicated power button on the west, PCIe FFC connector on the
|
||||
* underside, fan header to the north-east of the SoC.
|
||||
*
|
||||
* The 40-pin GPIO header is electrically identical to every Pi from
|
||||
* the 1B+ onwards, so the pin coords mirror RaspberryPi3Element /
|
||||
* RaspberryPi4Element byte-for-byte — wires snap to the same physical
|
||||
* positions when a user swaps boards.
|
||||
*/
|
||||
|
||||
import { buildPi40PinHeader } from './pi40PinHeader';
|
||||
|
||||
const PI_WIDTH = 250;
|
||||
const PI_HEIGHT = 160;
|
||||
|
||||
class RaspberryPi5Element extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
get pinInfo() {
|
||||
return buildPi40PinHeader();
|
||||
}
|
||||
|
||||
render() {
|
||||
let pinsSvg = '';
|
||||
const pins = this.pinInfo;
|
||||
pins.forEach((pin) => {
|
||||
pinsSvg += `<rect x="${pin.x - 3}" y="${pin.y - 3}" width="6" height="6" fill="#D4AF37" />`;
|
||||
pinsSvg += `<circle cx="${pin.x}" cy="${pin.y}" r="2" fill="#000" />`;
|
||||
});
|
||||
|
||||
this.shadowRoot!.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
width: ${PI_WIDTH}px;
|
||||
height: ${PI_HEIGHT}px;
|
||||
position: relative;
|
||||
}
|
||||
svg { width: 100%; height: 100%; }
|
||||
.board {
|
||||
fill: #004d27; /* Pi 5 darker green PCB */
|
||||
stroke: #002b15;
|
||||
stroke-width: 2;
|
||||
rx: 8;
|
||||
}
|
||||
.cpu { fill: #1a1a1a; stroke: #000; rx: 2; }
|
||||
.rp1 { fill: #2a2a2a; stroke: #000; rx: 2; }
|
||||
.usb { fill: #ccc; stroke: #999; rx: 2; }
|
||||
.usbc { fill: #888; stroke: #555; rx: 1; }
|
||||
.eth { fill: #bbb; stroke: #888; rx: 2; }
|
||||
.hdmi { fill: #555; stroke: #333; rx: 1; }
|
||||
.gpio-header { fill: #222; }
|
||||
.pcie { fill: #4a4a4a; stroke: #222; rx: 1; }
|
||||
.pwrbtn { fill: #c00; stroke: #800; rx: 1.5; }
|
||||
.label { fill: #FFF; font-family: sans-serif; }
|
||||
</style>
|
||||
<svg viewBox="0 0 ${PI_WIDTH} ${PI_HEIGHT}">
|
||||
<!-- PCB -->
|
||||
<rect class="board" x="2" y="2" width="${PI_WIDTH - 4}" height="${PI_HEIGHT - 4}" />
|
||||
|
||||
<!-- BCM2712 SoC (centre) -->
|
||||
<rect class="cpu" x="90" y="55" width="44" height="44" />
|
||||
<text x="112" y="76" class="label" font-size="7" text-anchor="middle">BCM2712</text>
|
||||
<text x="112" y="86" class="label" font-size="6" text-anchor="middle" opacity="0.7">A76 ×4</text>
|
||||
|
||||
<!-- RP1 southbridge I/O chip (Raspberry Pi's own silicon, east of SoC) -->
|
||||
<rect class="rp1" x="146" y="65" width="26" height="26" />
|
||||
<text x="159" y="80" class="label" font-size="6" text-anchor="middle">RP1</text>
|
||||
|
||||
<!-- 4× USB-A (east edge, two stacked banks of 2) -->
|
||||
<rect class="usb" x="${PI_WIDTH - 38}" y="18" width="36" height="28" />
|
||||
<text x="${PI_WIDTH - 20}" y="34" class="label" font-size="5" text-anchor="middle" opacity="0.7">USB3</text>
|
||||
<rect class="usb" x="${PI_WIDTH - 38}" y="50" width="36" height="28" />
|
||||
<text x="${PI_WIDTH - 20}" y="66" class="label" font-size="5" text-anchor="middle" opacity="0.7">USB3</text>
|
||||
|
||||
<!-- 2.5 Gigabit Ethernet -->
|
||||
<rect class="eth" x="${PI_WIDTH - 38}" y="82" width="36" height="36" />
|
||||
<text x="${PI_WIDTH - 20}" y="102" class="label" font-size="5" text-anchor="middle" opacity="0.7">2.5GbE</text>
|
||||
|
||||
<!-- USB-C power (south edge) -->
|
||||
<rect class="usbc" x="6" y="${PI_HEIGHT - 18}" width="18" height="10" />
|
||||
<text x="15" y="${PI_HEIGHT - 22}" class="label" font-size="5" text-anchor="middle" opacity="0.7">USB-C 5V/5A</text>
|
||||
|
||||
<!-- Power button (west edge) — Pi 5 has a real on/off button -->
|
||||
<rect class="pwrbtn" x="3" y="80" width="6" height="6" />
|
||||
<text x="14" y="86" class="label" font-size="5" opacity="0.7">PWR</text>
|
||||
|
||||
<!-- 2× micro-HDMI (south, centre) -->
|
||||
<rect class="hdmi" x="40" y="${PI_HEIGHT - 16}" width="20" height="10" />
|
||||
<rect class="hdmi" x="66" y="${PI_HEIGHT - 16}" width="20" height="10" />
|
||||
<text x="63" y="${PI_HEIGHT - 20}" class="label" font-size="5" text-anchor="middle" opacity="0.7">µHDMI 0 / 1</text>
|
||||
|
||||
<!-- PCIe FFC connector (south, east of HDMI) -->
|
||||
<rect class="pcie" x="100" y="${PI_HEIGHT - 14}" width="36" height="6" />
|
||||
<text x="118" y="${PI_HEIGHT - 18}" class="label" font-size="5" text-anchor="middle" opacity="0.7">PCIe FFC</text>
|
||||
|
||||
<!-- GPIO Header base -->
|
||||
<rect class="gpio-header" x="15" y="5" width="200" height="20" rx="1" />
|
||||
|
||||
<!-- Pins -->
|
||||
${pinsSvg}
|
||||
|
||||
<!-- Board name + Velxio mark -->
|
||||
<text x="60" y="125" class="label" font-size="14" font-weight="bold">Raspberry Pi 5</text>
|
||||
<text x="60" y="140" class="label" font-size="9" opacity="0.85">Cortex-A76 · BCM2712 · RP1</text>
|
||||
<text x="${PI_WIDTH - 8}" y="${PI_HEIGHT - 6}" class="label" font-size="6" text-anchor="end" opacity="0.6">velxio</text>
|
||||
</svg>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get('velxio-raspberry-pi-5')) {
|
||||
customElements.define('velxio-raspberry-pi-5', RaspberryPi5Element);
|
||||
}
|
||||
|
||||
export {};
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Shared 40-pin GPIO header layout used by every Raspberry Pi from the
|
||||
* 1B+ onwards (Zero W, 1B+, 2, 3, 4, 5). Same physical positions, same
|
||||
* BCM GPIO assignment, same naming convention — so wires drawn against
|
||||
* a Pi 3 example transfer to a Pi 4 or Pi 5 board without re-routing.
|
||||
*
|
||||
* Coordinates are in CSS pixels relative to the board element's top-left,
|
||||
* sized to fit the 250×160 footprint shared by every Velxio Pi element.
|
||||
* Pin "1" sits top-left; odd pins on the top row, even on the bottom.
|
||||
*
|
||||
* Names follow the Pi 3 wrapper convention (BCM GPIO numbers like
|
||||
* "GPIO14" plus power/ground labels) so example wires using those names
|
||||
* resolve to the same physical position on every Pi model.
|
||||
*/
|
||||
|
||||
export interface PinInfo {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
signals: string[];
|
||||
}
|
||||
|
||||
const PIN_X_START = 20;
|
||||
const PIN_X_STEP = 10;
|
||||
const PIN_Y_TOP = 10;
|
||||
const PIN_Y_BOT = 20;
|
||||
|
||||
const PI_PIN_NAMES: Record<number, string> = {
|
||||
1: '3V3', 2: '5V',
|
||||
3: 'GPIO2', 4: '5V',
|
||||
5: 'GPIO3', 6: 'GND',
|
||||
7: 'GPIO4', 8: 'GPIO14',
|
||||
9: 'GND', 10: 'GPIO15',
|
||||
11: 'GPIO17', 12: 'GPIO18',
|
||||
13: 'GPIO27', 14: 'GND',
|
||||
15: 'GPIO22', 16: 'GPIO23',
|
||||
17: '3V3', 18: 'GPIO24',
|
||||
19: 'GPIO10', 20: 'GND',
|
||||
21: 'GPIO9', 22: 'GPIO25',
|
||||
23: 'GPIO11', 24: 'GPIO8',
|
||||
25: 'GND', 26: 'GPIO7',
|
||||
27: 'ID_SD', 28: 'ID_SC',
|
||||
29: 'GPIO5', 30: 'GND',
|
||||
31: 'GPIO6', 32: 'GPIO12',
|
||||
33: 'GPIO13', 34: 'GND',
|
||||
35: 'GPIO19', 36: 'GPIO16',
|
||||
37: 'GPIO26', 38: 'GPIO20',
|
||||
39: 'GND', 40: 'GPIO21',
|
||||
};
|
||||
|
||||
export function buildPi40PinHeader(): PinInfo[] {
|
||||
const pins: PinInfo[] = [];
|
||||
for (let col = 0; col < 20; col++) {
|
||||
const oddPin = col * 2 + 1; // top row
|
||||
const evenPin = col * 2 + 2; // bottom row
|
||||
const px = PIN_X_START + col * PIN_X_STEP;
|
||||
pins.push({
|
||||
name: PI_PIN_NAMES[oddPin] ?? `P${oddPin}`,
|
||||
x: px,
|
||||
y: PIN_Y_TOP,
|
||||
signals: [],
|
||||
});
|
||||
pins.push({
|
||||
name: PI_PIN_NAMES[evenPin] ?? `P${evenPin}`,
|
||||
x: px,
|
||||
y: PIN_Y_BOT,
|
||||
signals: [],
|
||||
});
|
||||
}
|
||||
return pins;
|
||||
}
|
||||
|
|
@ -121,12 +121,12 @@ export class ComponentRegistry {
|
|||
});
|
||||
data.components.push({
|
||||
id: 'raspberry-pi-4',
|
||||
tagName: 'velxio-raspberry-pi-3', // reuse Pi 3 board art (40-pin layout identical)
|
||||
tagName: 'velxio-raspberry-pi-4',
|
||||
name: 'Raspberry Pi 4',
|
||||
category: 'boards',
|
||||
description: 'Raspberry Pi 4 Model B with 40-pin GPIO. QEMU virt + Cortex-A72 backend.',
|
||||
thumbnail:
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#83B81A" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi4</text></svg>',
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#006633" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi4</text></svg>',
|
||||
properties: [],
|
||||
defaultValues: {},
|
||||
pinCount: 40,
|
||||
|
|
@ -134,12 +134,12 @@ export class ComponentRegistry {
|
|||
});
|
||||
data.components.push({
|
||||
id: 'raspberry-pi-5',
|
||||
tagName: 'velxio-raspberry-pi-3', // reuse art for now (Phase 3 polish: Pi 5 PCB SVG)
|
||||
tagName: 'velxio-raspberry-pi-5',
|
||||
name: 'Raspberry Pi 5',
|
||||
category: 'boards',
|
||||
description: 'Raspberry Pi 5 with 40-pin GPIO. QEMU virt + Cortex-A76 backend (no raspi5 machine in QEMU yet).',
|
||||
description: 'Raspberry Pi 5 with 40-pin GPIO + RP1 southbridge. QEMU virt + Cortex-A76 backend.',
|
||||
thumbnail:
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#76323F" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi5</text></svg>',
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#004d27" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi5</text></svg>',
|
||||
properties: [],
|
||||
defaultValues: {},
|
||||
pinCount: 40,
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ export const BOARD_PIN_GROUPS: Record<AllBoardKinds, BoardPinGroup> = {
|
|||
vcc_pins: ['3V3', 'VBUS', 'VSYS'],
|
||||
},
|
||||
'raspberry-pi-3': { vcc: 5, gnd: ['GND'], vcc_pins: ['5V', '3V3'] },
|
||||
'raspberry-pi-4': { vcc: 5, gnd: ['GND'], vcc_pins: ['5V', '3V3'] },
|
||||
'raspberry-pi-5': { vcc: 5, gnd: ['GND'], vcc_pins: ['5V', '3V3'] },
|
||||
|
||||
esp32: { vcc: 3.3, gnd: ['GND', 'GND.1', 'GND.2'], vcc_pins: ['3V3', 'VIN', '5V'] },
|
||||
'esp32-devkit-c-v4': { vcc: 3.3, gnd: ['GND', 'GND.1', 'GND.2'], vcc_pins: ['3V3', 'VIN', '5V'] },
|
||||
|
|
|
|||
|
|
@ -213,6 +213,8 @@ export const BOARD_COMPONENT_IDS = [
|
|||
'arduino-mega',
|
||||
'nano-rp2040',
|
||||
'raspberry-pi-3',
|
||||
'raspberry-pi-4',
|
||||
'raspberry-pi-5',
|
||||
'raspberry-pi-pico',
|
||||
'pi-pico-w',
|
||||
'esp32',
|
||||
|
|
@ -295,9 +297,21 @@ export function boardPinToNumber(boardId: string, pinName: string): number | nul
|
|||
return null;
|
||||
}
|
||||
|
||||
// Raspberry Pi 3B — pinName is the physical pin number ("1" … "40")
|
||||
// We return the BCM GPIO number, or -1 for power/GND pins.
|
||||
if (boardId === 'raspberry-pi-3' || boardId.startsWith('raspberry-pi-3')) {
|
||||
// Raspberry Pi 3 / 4 / 5 (and any future 40-pin Pi) all share the same
|
||||
// physical pin layout and BCM GPIO assignment, so the same lookup
|
||||
// table works. `pinName` may be either the physical pin number
|
||||
// ("1" … "40") OR a BCM-style name ("GPIO14") emitted by the Pi
|
||||
// element's pinInfo — power / GND pins return -1.
|
||||
if (
|
||||
boardId === 'raspberry-pi-3' || boardId.startsWith('raspberry-pi-3') ||
|
||||
boardId === 'raspberry-pi-4' || boardId.startsWith('raspberry-pi-4') ||
|
||||
boardId === 'raspberry-pi-5' || boardId.startsWith('raspberry-pi-5')
|
||||
) {
|
||||
if (/^(GND|VCC|3V3|5V|ID_S[DC])/.test(pinName)) return -1;
|
||||
if (pinName.startsWith('GPIO')) {
|
||||
const n = parseInt(pinName.substring(4), 10);
|
||||
if (!isNaN(n)) return n;
|
||||
}
|
||||
const physical = parseInt(pinName, 10);
|
||||
if (!isNaN(physical)) return PI3_PHYSICAL_TO_BCM[physical] ?? null;
|
||||
return null;
|
||||
|
|
|
|||
Loading…
Reference in New Issue