feat(boards): add 6 STM32 boards (F4 Discovery, Olimex H405, Netduino 2/+2, Pill variants)
Adds stm32-f4-discovery, stm32-olimex-h405, stm32-netduino-plus2, stm32-netduino2, stm32-blackpill-f401 and stm32-bluepill-f103cb, mapped to existing qemu-lcgamboa machines (netduinoplus2, olimex-stm32-h405, netduino2, stm32vldiscovery). A generic inline board renderer (no SVG) draws the Discovery/Olimex/Netduino boards from a header pin layout; the Pill variants reuse the Blue/Black Pill SVGs. Per-board onboard-LED pin and polarity via STM32_LED. One blink+serial example per board. tsc --noEmit clean; all new FQBN pnum variants present in STM32 core 2.12.0; worker smoke tests pass for the new machines. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d9f98709d2
commit
6813891f91
|
|
@ -54,13 +54,21 @@ EventCallback = Callable[[str, dict], Awaitable[None]]
|
|||
# the bridge today (F100 / F405) get full GPIO; F2 (netduino2) is serial-only
|
||||
# until its GPIO is wired.
|
||||
_MACHINE: dict[str, str] = {
|
||||
'stm32-bluepill': 'stm32vldiscovery', # F103 approximated by F100 SoC
|
||||
'stm32-blackpill': 'netduinoplus2', # F411 approximated by F405 SoC (Cortex-M4)
|
||||
'stm32-vldiscovery': 'stm32vldiscovery',
|
||||
'stm32f4-discovery': 'netduinoplus2', # F407 approximated by F405 SoC
|
||||
'netduinoplus2': 'netduinoplus2',
|
||||
'olimex-stm32-h405': 'olimex-stm32-h405',
|
||||
'netduino2': 'netduino2',
|
||||
# Canonical BoardKind strings (sent verbatim by the frontend Stm32Bridge).
|
||||
'stm32-bluepill': 'stm32vldiscovery', # F103 approximated by F100 SoC
|
||||
'stm32-blackpill': 'netduinoplus2', # F411 approximated by F405 SoC (Cortex-M4)
|
||||
'stm32-bluepill-f103cb': 'stm32vldiscovery', # F103CB approximated by F100 SoC
|
||||
'stm32-blackpill-f401': 'netduinoplus2', # F401 approximated by F405 SoC
|
||||
'stm32-f4-discovery': 'netduinoplus2', # F407 approximated by F405 SoC
|
||||
'stm32-olimex-h405': 'olimex-stm32-h405', # F405 (native machine)
|
||||
'stm32-netduino-plus2': 'netduinoplus2', # F405 (native machine)
|
||||
'stm32-netduino2': 'netduino2', # F205 (serial-only until GPIO wired)
|
||||
# Legacy / alias keys (kept so older saved projects still resolve).
|
||||
'stm32-vldiscovery': 'stm32vldiscovery',
|
||||
'stm32f4-discovery': 'netduinoplus2',
|
||||
'netduinoplus2': 'netduinoplus2',
|
||||
'olimex-stm32-h405': 'olimex-stm32-h405',
|
||||
'netduino2': 'netduino2',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,12 @@ const BOARD_DESCRIPTIONS: Record<BoardKind, string> = {
|
|||
'aitewinrobot-esp32c3-supermini': 'ESP32-C3 SuperMini (QEMU)',
|
||||
'stm32-bluepill': 'STM32F103C8 Cortex-M3, 64KB flash, 37 GPIO (QEMU)',
|
||||
'stm32-blackpill': 'STM32F411CE Cortex-M4, 512KB flash, 50 GPIO (QEMU)',
|
||||
'stm32-bluepill-f103cb': 'STM32F103CB Cortex-M3, 128KB flash, 37 GPIO (QEMU)',
|
||||
'stm32-blackpill-f401': 'STM32F401CE Cortex-M4, 512KB flash, 50 GPIO (QEMU)',
|
||||
'stm32-f4-discovery': 'STM32F407VG Cortex-M4, 1MB flash, 4 onboard LEDs (QEMU)',
|
||||
'stm32-olimex-h405': 'Olimex STM32-H405, F405RG Cortex-M4, 1MB flash (QEMU)',
|
||||
'stm32-netduino-plus2': 'Netduino Plus 2, STM32F405 Cortex-M4 (QEMU)',
|
||||
'stm32-netduino2': 'Netduino 2, STM32F205 Cortex-M3 (QEMU, serial)',
|
||||
attiny85: '8-bit AVR, 8KB flash, 6 GPIO (browser)',
|
||||
};
|
||||
|
||||
|
|
@ -81,6 +87,12 @@ const ALL_BOARDS: BoardKind[] = [
|
|||
'aitewinrobot-esp32c3-supermini',
|
||||
'stm32-bluepill',
|
||||
'stm32-blackpill',
|
||||
'stm32-bluepill-f103cb',
|
||||
'stm32-blackpill-f401',
|
||||
'stm32-f4-discovery',
|
||||
'stm32-olimex-h405',
|
||||
'stm32-netduino-plus2',
|
||||
'stm32-netduino2',
|
||||
'attiny85',
|
||||
];
|
||||
|
||||
|
|
@ -424,6 +436,12 @@ const BOARD_TAG: Partial<Record<BoardKind, string>> = {
|
|||
'aitewinrobot-esp32c3-supermini': 'velxio-esp32',
|
||||
'stm32-bluepill': 'velxio-stm32-bluepill',
|
||||
'stm32-blackpill': 'velxio-stm32-blackpill',
|
||||
'stm32-bluepill-f103cb': 'velxio-stm32-bluepill-f103cb',
|
||||
'stm32-blackpill-f401': 'velxio-stm32-blackpill-f401',
|
||||
'stm32-f4-discovery': 'velxio-stm32-f4-discovery',
|
||||
'stm32-olimex-h405': 'velxio-stm32-olimex-h405',
|
||||
'stm32-netduino-plus2': 'velxio-stm32-netduino-plus2',
|
||||
'stm32-netduino2': 'velxio-stm32-netduino2',
|
||||
};
|
||||
|
||||
interface BoardCardProps {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,12 @@ const BOARD_PILL_ICON: Record<BoardKind, string> = {
|
|||
'esp32-c3': '⬡',
|
||||
'stm32-bluepill': '◈',
|
||||
'stm32-blackpill': '◈',
|
||||
'stm32-bluepill-f103cb': '◈',
|
||||
'stm32-blackpill-f401': '◈',
|
||||
'stm32-f4-discovery': '◈',
|
||||
'stm32-olimex-h405': '◈',
|
||||
'stm32-netduino-plus2': '◈',
|
||||
'stm32-netduino2': '◈',
|
||||
};
|
||||
|
||||
const BOARD_PILL_COLOR: Record<BoardKind, string> = {
|
||||
|
|
@ -83,6 +89,12 @@ const BOARD_PILL_COLOR: Record<BoardKind, string> = {
|
|||
'esp32-c3': '#a5d6a7',
|
||||
'stm32-bluepill': '#80cbc4',
|
||||
'stm32-blackpill': '#b0bec5',
|
||||
'stm32-bluepill-f103cb': '#80cbc4',
|
||||
'stm32-blackpill-f401': '#b0bec5',
|
||||
'stm32-f4-discovery': '#90caf9',
|
||||
'stm32-olimex-h405': '#a5d6a7',
|
||||
'stm32-netduino-plus2': '#ce93d8',
|
||||
'stm32-netduino2': '#ce93d8',
|
||||
};
|
||||
|
||||
export const EditorToolbar = ({
|
||||
|
|
|
|||
|
|
@ -141,6 +141,12 @@ const BOARD_ICON: Record<BoardKind, string> = {
|
|||
'esp32-c3': '⬡',
|
||||
'stm32-bluepill': '◈',
|
||||
'stm32-blackpill': '◈',
|
||||
'stm32-bluepill-f103cb': '◈',
|
||||
'stm32-blackpill-f401': '◈',
|
||||
'stm32-f4-discovery': '◈',
|
||||
'stm32-olimex-h405': '◈',
|
||||
'stm32-netduino-plus2': '◈',
|
||||
'stm32-netduino2': '◈',
|
||||
};
|
||||
|
||||
// Color accent per board family
|
||||
|
|
@ -155,6 +161,12 @@ const BOARD_COLOR: Record<BoardKind, string> = {
|
|||
'esp32-c3': '#a5d6a7',
|
||||
'stm32-bluepill': '#80cbc4',
|
||||
'stm32-blackpill': '#b0bec5',
|
||||
'stm32-bluepill-f103cb': '#80cbc4',
|
||||
'stm32-blackpill-f401': '#b0bec5',
|
||||
'stm32-f4-discovery': '#90caf9',
|
||||
'stm32-olimex-h405': '#a5d6a7',
|
||||
'stm32-netduino-plus2': '#ce93d8',
|
||||
'stm32-netduino2': '#ce93d8',
|
||||
};
|
||||
|
||||
function FileIcon({ name }: { name: string }) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,16 @@ import { RaspberryPi5 } from '../velxio-components/RaspberryPi5';
|
|||
import { Esp32 } from '../velxio-components/Esp32';
|
||||
import { Attiny85 } from '../velxio-components/Attiny85';
|
||||
import { PiPicoW } from '../velxio-components/PiPicoW';
|
||||
import { Stm32BluePill, Stm32BlackPill } from '../velxio-components/Stm32BluePill';
|
||||
import {
|
||||
Stm32BluePill,
|
||||
Stm32BlackPill,
|
||||
Stm32BluePillF103CB,
|
||||
Stm32BlackPillF401,
|
||||
Stm32F4Discovery,
|
||||
Stm32OlimexH405,
|
||||
Stm32NetduinoPlus2,
|
||||
Stm32Netduino2,
|
||||
} from '../velxio-components/Stm32BluePill';
|
||||
import { PinOverlay } from './PinOverlay';
|
||||
|
||||
// Board visual dimensions (width × height) for the drag-overlay sizing.
|
||||
|
|
@ -47,6 +56,14 @@ const BOARD_SIZE: Record<string, { w: number; h: number }> = {
|
|||
'aitewinrobot-esp32c3-supermini': { w: 90, h: 123 },
|
||||
'stm32-bluepill': { w: 114, h: 271 }, // 22.855 × 54.193 mm (wokwi-boards SVG)
|
||||
'stm32-blackpill': { w: 103, h: 266 }, // 20.695 × 53.125 mm (wokwi-boards SVG)
|
||||
'stm32-bluepill-f103cb': { w: 114, h: 271 }, // reuses Blue Pill SVG
|
||||
'stm32-blackpill-f401': { w: 103, h: 266 }, // reuses Black Pill SVG
|
||||
// Inline-rendered boards — sizes match Stm32BoardElement.inlineConfig()
|
||||
// (INLINE_W=158; h = 24 + rows*13 + 16).
|
||||
'stm32-f4-discovery': { w: 158, h: 235 }, // 15 rows per side
|
||||
'stm32-olimex-h405': { w: 158, h: 196 }, // 12 rows per side
|
||||
'stm32-netduino-plus2': { w: 158, h: 196 }, // 12 rows per side
|
||||
'stm32-netduino2': { w: 158, h: 196 }, // 12 rows per side
|
||||
attiny85: { w: 160, h: 132 },
|
||||
};
|
||||
|
||||
|
|
@ -123,6 +140,18 @@ export const BoardOnCanvas = ({
|
|||
return <Stm32BluePill id={id} x={x} y={y} />;
|
||||
case 'stm32-blackpill':
|
||||
return <Stm32BlackPill id={id} x={x} y={y} />;
|
||||
case 'stm32-bluepill-f103cb':
|
||||
return <Stm32BluePillF103CB id={id} x={x} y={y} />;
|
||||
case 'stm32-blackpill-f401':
|
||||
return <Stm32BlackPillF401 id={id} x={x} y={y} />;
|
||||
case 'stm32-f4-discovery':
|
||||
return <Stm32F4Discovery id={id} x={x} y={y} />;
|
||||
case 'stm32-olimex-h405':
|
||||
return <Stm32OlimexH405 id={id} x={x} y={y} />;
|
||||
case 'stm32-netduino-plus2':
|
||||
return <Stm32NetduinoPlus2 id={id} x={x} y={y} />;
|
||||
case 'stm32-netduino2':
|
||||
return <Stm32Netduino2 id={id} x={x} y={y} />;
|
||||
case 'attiny85':
|
||||
return <Attiny85 id={id} x={x} y={y} led1={led13} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import './Stm32BluePillElement';
|
||||
|
||||
type StmIntrinsic = React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'velxio-stm32-bluepill': React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLElement>,
|
||||
HTMLElement
|
||||
>;
|
||||
'velxio-stm32-blackpill': React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLElement>,
|
||||
HTMLElement
|
||||
>;
|
||||
'velxio-stm32-bluepill': StmIntrinsic;
|
||||
'velxio-stm32-blackpill': StmIntrinsic;
|
||||
'velxio-stm32-bluepill-f103cb': StmIntrinsic;
|
||||
'velxio-stm32-blackpill-f401': StmIntrinsic;
|
||||
'velxio-stm32-f4-discovery': StmIntrinsic;
|
||||
'velxio-stm32-olimex-h405': StmIntrinsic;
|
||||
'velxio-stm32-netduino-plus2': StmIntrinsic;
|
||||
'velxio-stm32-netduino2': StmIntrinsic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,3 +32,27 @@ export const Stm32BluePill = ({ id, x, y }: Props) => (
|
|||
export const Stm32BlackPill = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-blackpill id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
||||
export const Stm32BluePillF103CB = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-bluepill-f103cb id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
||||
export const Stm32BlackPillF401 = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-blackpill-f401 id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
||||
export const Stm32F4Discovery = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-f4-discovery id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
||||
export const Stm32OlimexH405 = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-olimex-h405 id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
||||
export const Stm32NetduinoPlus2 = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-netduino-plus2 id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
||||
export const Stm32Netduino2 = ({ id, x, y }: Props) => (
|
||||
<velxio-stm32-netduino2 id={id} style={{ position: 'absolute', left: x, top: y }} />
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
/**
|
||||
* STM32 board Web Components (Blue Pill, Black Pill).
|
||||
* STM32 board Web Components.
|
||||
*
|
||||
* Per CLAUDE.md rule 6a, boards that take wire connections MUST be real Web
|
||||
* Components exposing a `pinInfo` getter. Renders the official wokwi-boards
|
||||
* SVG with the pin coordinates from that board.json (mm x 5 px/mm), plus an
|
||||
* overlay for the onboard PC13 LED (active-LOW: lit when PC13 is LOW).
|
||||
* Components exposing a `pinInfo` getter. Two render modes:
|
||||
*
|
||||
* Pin `name`s are the real port labels ('PA9', 'PC13', ...) so examples wire
|
||||
* to them; Stm32Bridge.stm32PinNameToLinear() maps them to the backend's
|
||||
* linear pin index (port*16 + pin).
|
||||
* - 'svg': renders the official wokwi-boards SVG with hand-placed pin
|
||||
* coordinates (Blue Pill / Black Pill, and the pin-compatible
|
||||
* F103CB / F401 variants reuse those exact SVGs).
|
||||
* - 'inline': draws the board in-component (no SVG file) — a green PCB with an
|
||||
* MCU square, labeled header pads computed from `headers`, and an
|
||||
* onboard LED dot. Used by the Discovery / Olimex / Netduino boards
|
||||
* for which wokwi-boards ships no SVG.
|
||||
*
|
||||
* Registers two custom elements: <velxio-stm32-bluepill> and
|
||||
* <velxio-stm32-blackpill>, selected by config.
|
||||
* Pin `name`s are the real port labels ('PA9', 'PC13', ...) so examples wire to
|
||||
* them; Stm32Bridge.stm32PinNameToLinear() maps them to the backend's linear
|
||||
* pin index (port*16 + pin).
|
||||
*
|
||||
* Registers one custom element per board (one tag, one subclass), all driven by
|
||||
* the shared Stm32BoardElement class and the CONFIGS table.
|
||||
*/
|
||||
|
||||
interface PinDef {
|
||||
|
|
@ -21,14 +27,33 @@ interface PinDef {
|
|||
}
|
||||
|
||||
interface BoardConfig {
|
||||
svgUrl: string;
|
||||
render: 'svg' | 'inline';
|
||||
w: number;
|
||||
h: number;
|
||||
pins: PinDef[];
|
||||
/** Onboard PC13 LED center (px). */
|
||||
/** Onboard LED center (px). */
|
||||
led: { x: number; y: number };
|
||||
/** Active-LOW boards (Blue/Black Pill PC13) are lit when the pin is LOW. */
|
||||
ledActiveLow: boolean;
|
||||
// 'svg' mode only:
|
||||
svgUrl?: string;
|
||||
// 'inline' mode only:
|
||||
label?: string;
|
||||
}
|
||||
|
||||
/** Which port pin carries the onboard LED, and its polarity, per board kind.
|
||||
* Consumed by useSimulatorStore to light the correct LED dot. */
|
||||
export const STM32_LED: Record<string, { pin: string; activeLow: boolean }> = {
|
||||
'stm32-bluepill': { pin: 'PC13', activeLow: true },
|
||||
'stm32-blackpill': { pin: 'PC13', activeLow: true },
|
||||
'stm32-bluepill-f103cb': { pin: 'PC13', activeLow: true },
|
||||
'stm32-blackpill-f401': { pin: 'PC13', activeLow: true },
|
||||
'stm32-f4-discovery': { pin: 'PD12', activeLow: false },
|
||||
'stm32-olimex-h405': { pin: 'PC12', activeLow: false },
|
||||
'stm32-netduino-plus2': { pin: 'PA10', activeLow: false },
|
||||
'stm32-netduino2': { pin: 'PA10', activeLow: false },
|
||||
};
|
||||
|
||||
// ── STM32 Blue Pill (F103C8) — 22.855 x 54.193 mm -> 114 x 271 px ───────────
|
||||
const BLUEPILL_PINS: PinDef[] = [
|
||||
{ name: 'PB12', x: 18, y: 14 }, { name: 'PB13', x: 18, y: 27 },
|
||||
|
|
@ -77,15 +102,109 @@ const BLACKPILL_PINS: PinDef[] = [
|
|||
{ name: 'PB13', x: 89, y: 243 }, { name: 'PB12', x: 89, y: 256 },
|
||||
];
|
||||
|
||||
// ── Inline-rendered boards (no SVG) — generic PCB drawn from a header layout ──
|
||||
// Geometry constants shared by every inline board.
|
||||
const PAD_PITCH = 13;
|
||||
const MARGIN_TOP = 24;
|
||||
const MARGIN_BOTTOM = 16;
|
||||
const INLINE_W = 158;
|
||||
const TIP_L = 9; // left-column pin tip x (wire connection point)
|
||||
const TIP_R = INLINE_W - 9; // right-column pin tip x
|
||||
|
||||
/** Build a flat BoardConfig for an inline board from its header pin lists.
|
||||
* Computes pad/tip coordinates so `pinInfo` returns correct wire endpoints
|
||||
* without per-pin hand placement. */
|
||||
function inlineConfig(opts: {
|
||||
label: string;
|
||||
left: string[];
|
||||
right: string[];
|
||||
ledPin: string;
|
||||
ledActiveLow: boolean;
|
||||
}): BoardConfig {
|
||||
const rows = Math.max(opts.left.length, opts.right.length);
|
||||
const h = MARGIN_TOP + rows * PAD_PITCH + MARGIN_BOTTOM;
|
||||
const pins: PinDef[] = [];
|
||||
opts.left.forEach((name, i) =>
|
||||
pins.push({ name, x: TIP_L, y: MARGIN_TOP + i * PAD_PITCH + PAD_PITCH / 2 }),
|
||||
);
|
||||
opts.right.forEach((name, i) =>
|
||||
pins.push({ name, x: TIP_R, y: MARGIN_TOP + i * PAD_PITCH + PAD_PITCH / 2 }),
|
||||
);
|
||||
// LED dot sits in the free center column, just under the board title.
|
||||
return {
|
||||
render: 'inline',
|
||||
label: opts.label,
|
||||
w: INLINE_W,
|
||||
h,
|
||||
pins,
|
||||
led: { x: INLINE_W / 2, y: MARGIN_TOP + 2 },
|
||||
ledActiveLow: opts.ledActiveLow,
|
||||
};
|
||||
}
|
||||
|
||||
const CONFIGS: Record<string, BoardConfig> = {
|
||||
'stm32-bluepill': {
|
||||
svgUrl: '/boards/stm32-bluepill.svg', w: 114, h: 271,
|
||||
pins: BLUEPILL_PINS, led: { x: 37, y: 228 },
|
||||
render: 'svg', svgUrl: '/boards/stm32-bluepill.svg', w: 114, h: 271,
|
||||
pins: BLUEPILL_PINS, led: { x: 37, y: 228 }, ledActiveLow: true,
|
||||
},
|
||||
'stm32-blackpill': {
|
||||
svgUrl: '/boards/stm32-blackpill.svg', w: 103, h: 266,
|
||||
pins: BLACKPILL_PINS, led: { x: 27, y: 55 },
|
||||
render: 'svg', svgUrl: '/boards/stm32-blackpill.svg', w: 103, h: 266,
|
||||
pins: BLACKPILL_PINS, led: { x: 27, y: 55 }, ledActiveLow: true,
|
||||
},
|
||||
// Pin-compatible variants — reuse the Pill SVGs and pin coordinates.
|
||||
'stm32-bluepill-f103cb': {
|
||||
render: 'svg', svgUrl: '/boards/stm32-bluepill.svg', w: 114, h: 271,
|
||||
pins: BLUEPILL_PINS, led: { x: 37, y: 228 }, ledActiveLow: true,
|
||||
},
|
||||
'stm32-blackpill-f401': {
|
||||
render: 'svg', svgUrl: '/boards/stm32-blackpill.svg', w: 103, h: 266,
|
||||
pins: BLACKPILL_PINS, led: { x: 27, y: 55 }, ledActiveLow: true,
|
||||
},
|
||||
// Inline boards (F405 / F205 SoCs) — drawn generically.
|
||||
'stm32-f4-discovery': inlineConfig({
|
||||
label: 'STM32F4 Discovery',
|
||||
left: ['3V3', 'GND', 'PA0', 'PA1', 'PA2', 'PA3', 'PA4', 'PA5', 'PA6', 'PA7',
|
||||
'PB0', 'PB1', 'PB2', 'PE0', 'PE1'],
|
||||
right: ['5V', 'GND', 'PD12', 'PD13', 'PD14', 'PD15', 'PC0', 'PC1', 'PC2',
|
||||
'PC3', 'PB6', 'PB7', 'PB8', 'PB9', 'PE2'],
|
||||
ledPin: 'PD12', ledActiveLow: false,
|
||||
}),
|
||||
'stm32-olimex-h405': inlineConfig({
|
||||
label: 'Olimex STM32-H405',
|
||||
left: ['3V3', 'GND', 'PA0', 'PA1', 'PA2', 'PA3', 'PB6', 'PB7', 'PB8', 'PB9',
|
||||
'PC0', 'PC1'],
|
||||
right: ['5V', 'GND', 'PC12', 'PC10', 'PC11', 'PA4', 'PA5', 'PA6', 'PA7',
|
||||
'PB0', 'PB1', 'PB10'],
|
||||
ledPin: 'PC12', ledActiveLow: false,
|
||||
}),
|
||||
'stm32-netduino-plus2': inlineConfig({
|
||||
label: 'Netduino Plus 2',
|
||||
left: ['3V3', 'GND', 'PA0', 'PA1', 'PA2', 'PA3', 'PA4', 'PA5', 'PB6', 'PB7',
|
||||
'PB8', 'PB9'],
|
||||
right: ['5V', 'GND', 'PA10', 'PB11', 'PC0', 'PC1', 'PC2', 'PC3', 'PA6',
|
||||
'PA7', 'PB0', 'PB1'],
|
||||
ledPin: 'PA10', ledActiveLow: false,
|
||||
}),
|
||||
'stm32-netduino2': inlineConfig({
|
||||
label: 'Netduino 2',
|
||||
left: ['3V3', 'GND', 'PA0', 'PA1', 'PA2', 'PA3', 'PA4', 'PA5', 'PB6', 'PB7',
|
||||
'PB8', 'PB9'],
|
||||
right: ['5V', 'GND', 'PA10', 'PB11', 'PC0', 'PC1', 'PC2', 'PC3', 'PA6',
|
||||
'PA7', 'PB0', 'PB1'],
|
||||
ledPin: 'PA10', ledActiveLow: false,
|
||||
}),
|
||||
};
|
||||
|
||||
/** Tag name -> CONFIGS key. One custom element per board kind. */
|
||||
const TAG_TO_KIND: Record<string, string> = {
|
||||
'velxio-stm32-bluepill': 'stm32-bluepill',
|
||||
'velxio-stm32-blackpill': 'stm32-blackpill',
|
||||
'velxio-stm32-bluepill-f103cb': 'stm32-bluepill-f103cb',
|
||||
'velxio-stm32-blackpill-f401': 'stm32-blackpill-f401',
|
||||
'velxio-stm32-f4-discovery': 'stm32-f4-discovery',
|
||||
'velxio-stm32-olimex-h405': 'stm32-olimex-h405',
|
||||
'velxio-stm32-netduino-plus2': 'stm32-netduino-plus2',
|
||||
'velxio-stm32-netduino2': 'stm32-netduino2',
|
||||
};
|
||||
|
||||
class Stm32BoardElement extends HTMLElement {
|
||||
|
|
@ -112,9 +231,7 @@ class Stm32BoardElement extends HTMLElement {
|
|||
}
|
||||
|
||||
private _defaultKind(): string {
|
||||
return this.tagName.toLowerCase() === 'velxio-stm32-blackpill'
|
||||
? 'stm32-blackpill'
|
||||
: 'stm32-bluepill';
|
||||
return TAG_TO_KIND[this.tagName.toLowerCase()] ?? 'stm32-bluepill';
|
||||
}
|
||||
|
||||
/** Pin tip coordinates in CSS px relative to element top-left (rule 6a). */
|
||||
|
|
@ -122,7 +239,7 @@ class Stm32BoardElement extends HTMLElement {
|
|||
return this.config.pins;
|
||||
}
|
||||
|
||||
/** Onboard PC13 LED (visual lit state; store passes polarity-corrected value). */
|
||||
/** Onboard LED (visual lit state; store passes polarity-corrected value). */
|
||||
set led(on: boolean) {
|
||||
if (this._ledOn === on) return;
|
||||
this._ledOn = on;
|
||||
|
|
@ -135,7 +252,16 @@ class Stm32BoardElement extends HTMLElement {
|
|||
|
||||
private render() {
|
||||
if (!this.shadowRoot) return;
|
||||
const { svgUrl, w, h, led } = this.config;
|
||||
if (this.config.render === 'inline') {
|
||||
this.renderInline(this.config);
|
||||
} else {
|
||||
this.renderSvg(this.config);
|
||||
}
|
||||
}
|
||||
|
||||
private renderSvg(cfg: BoardConfig) {
|
||||
if (!this.shadowRoot) return;
|
||||
const { svgUrl, w, h, led } = cfg;
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host { display: inline-block; line-height: 0; position: relative; }
|
||||
|
|
@ -151,16 +277,72 @@ class Stm32BoardElement extends HTMLElement {
|
|||
<div id="led"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderInline(cfg: BoardConfig) {
|
||||
if (!this.shadowRoot) return;
|
||||
const { w, h, pins, led, label } = cfg;
|
||||
const mcu = 50;
|
||||
const padHtml = pins
|
||||
.map((p) => {
|
||||
const onLeft = p.x <= w / 2;
|
||||
const padX = onLeft ? 4 : w - 14; // 10px-wide pad, center aligns to tip
|
||||
const labelStyle = onLeft
|
||||
? `left:${padX + 13}px; text-align:left;`
|
||||
: `right:${w - padX + 3}px; text-align:right;`;
|
||||
return (
|
||||
`<div class="pad" style="left:${padX}px; top:${p.y - 4}px;"></div>` +
|
||||
`<div class="plabel" style="top:${p.y - 5}px; ${labelStyle}">${p.name}</div>`
|
||||
);
|
||||
})
|
||||
.join('');
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host { display: inline-block; line-height: 0; position: relative; }
|
||||
.pcb {
|
||||
position: relative; box-sizing: border-box;
|
||||
background: #0b6b3a; border-radius: 8px;
|
||||
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10);
|
||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.title {
|
||||
position: absolute; top: 4px; left: 0; right: 0; text-align: center;
|
||||
font-size: 8px; color: #d6efe0; letter-spacing: 0.3px; line-height: 1;
|
||||
}
|
||||
.mcu {
|
||||
position: absolute; background: #161616; border-radius: 3px;
|
||||
box-shadow: inset 0 0 0 1px #333, 0 1px 2px rgba(0,0,0,0.4);
|
||||
}
|
||||
.pad {
|
||||
position: absolute; width: 10px; height: 8px;
|
||||
background: #d4af37; border-radius: 2px;
|
||||
}
|
||||
.plabel {
|
||||
position: absolute; font-size: 7px; color: #eaf5ee;
|
||||
line-height: 1; white-space: nowrap;
|
||||
}
|
||||
#led {
|
||||
position: absolute; left: ${led.x - 5}px; top: ${led.y - 5}px;
|
||||
width: 10px; height: 10px; border-radius: 50%;
|
||||
background: #00e676; box-shadow: 0 0 6px 2px #00e676;
|
||||
opacity: 0; transition: opacity 40ms linear; pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<div class="pcb" style="width:${w}px; height:${h}px;">
|
||||
<div class="title">${label ?? 'STM32'}</div>
|
||||
<div class="mcu" style="left:${w / 2 - mcu / 2}px; top:${h * 0.42}px; width:${mcu}px; height:${mcu}px;"></div>
|
||||
${padHtml}
|
||||
<div id="led"></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export { Stm32BoardElement as Stm32BluePillElement };
|
||||
|
||||
if (!customElements.get('velxio-stm32-bluepill')) {
|
||||
customElements.define('velxio-stm32-bluepill', Stm32BoardElement);
|
||||
}
|
||||
if (!customElements.get('velxio-stm32-blackpill')) {
|
||||
// Separate class instance per tag name (customElements requires a unique
|
||||
// constructor per name); subclass so both register cleanly.
|
||||
class Stm32BlackPillElement extends Stm32BoardElement {}
|
||||
customElements.define('velxio-stm32-blackpill', Stm32BlackPillElement);
|
||||
// One unique constructor per tag — customElements.define() requires it.
|
||||
for (const tag of Object.keys(TAG_TO_KIND)) {
|
||||
if (!customElements.get(tag)) {
|
||||
const cls = class extends Stm32BoardElement {};
|
||||
customElements.define(tag, cls);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,216 @@ void loop() {
|
|||
wires: [],
|
||||
tags: ['stm32', 'blue pill', 'serial', 'usart', 'qemu'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-f4-discovery-blink',
|
||||
title: 'STM32F4 Discovery LED Blink',
|
||||
description:
|
||||
'Blink the onboard PD12 green LED and print to Serial on an STM32F4 Discovery (STM32F407VG, Cortex-M4) under QEMU.',
|
||||
category: 'basics',
|
||||
difficulty: 'beginner',
|
||||
boardFilter: 'stm32-f4-discovery',
|
||||
boards: [
|
||||
{
|
||||
boardKind: 'stm32-f4-discovery',
|
||||
x: 100,
|
||||
y: 100,
|
||||
code: `// STM32F4 Discovery (STM32F407VG) — onboard LEDs PD12..PD15 (active HIGH).
|
||||
// PD12 green, PD13 orange, PD14 red, PD15 blue. Runs under QEMU (libqemu-arm).
|
||||
|
||||
void setup() {
|
||||
pinMode(PD12, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("velxio stm32f4 discovery");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(PD12, HIGH);
|
||||
delay(200);
|
||||
digitalWrite(PD12, LOW);
|
||||
delay(200);
|
||||
}`,
|
||||
},
|
||||
],
|
||||
code: '',
|
||||
components: [],
|
||||
wires: [],
|
||||
tags: ['stm32', 'discovery', 'f407', 'cortex-m4', 'qemu', 'blink'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-olimex-h405-blink',
|
||||
title: 'Olimex STM32-H405 Blink',
|
||||
description:
|
||||
'Blink the onboard PC12 status LED and print to Serial on an Olimex STM32-H405 (STM32F405RG, Cortex-M4) under QEMU.',
|
||||
category: 'basics',
|
||||
difficulty: 'beginner',
|
||||
boardFilter: 'stm32-olimex-h405',
|
||||
boards: [
|
||||
{
|
||||
boardKind: 'stm32-olimex-h405',
|
||||
x: 100,
|
||||
y: 100,
|
||||
code: `// Olimex STM32-H405 (STM32F405RG) — onboard status LED on PC12 (active HIGH).
|
||||
|
||||
void setup() {
|
||||
pinMode(PC12, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("velxio olimex h405");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(PC12, HIGH);
|
||||
delay(250);
|
||||
digitalWrite(PC12, LOW);
|
||||
delay(250);
|
||||
}`,
|
||||
},
|
||||
],
|
||||
code: '',
|
||||
components: [],
|
||||
wires: [],
|
||||
tags: ['stm32', 'olimex', 'h405', 'f405', 'cortex-m4', 'qemu'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-netduino-plus2-blink',
|
||||
title: 'Netduino Plus 2 Blink',
|
||||
description:
|
||||
'Blink the onboard PA10 LED and print to Serial on a Netduino Plus 2 (STM32F405, Cortex-M4) under QEMU.',
|
||||
category: 'basics',
|
||||
difficulty: 'beginner',
|
||||
boardFilter: 'stm32-netduino-plus2',
|
||||
boards: [
|
||||
{
|
||||
boardKind: 'stm32-netduino-plus2',
|
||||
x: 100,
|
||||
y: 100,
|
||||
code: `// Netduino Plus 2 (STM32F405) — onboard LED on PA10 (active HIGH).
|
||||
|
||||
void setup() {
|
||||
pinMode(PA10, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("velxio netduino plus 2");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(PA10, HIGH);
|
||||
delay(300);
|
||||
digitalWrite(PA10, LOW);
|
||||
delay(300);
|
||||
}`,
|
||||
},
|
||||
],
|
||||
code: '',
|
||||
components: [],
|
||||
wires: [],
|
||||
tags: ['stm32', 'netduino', 'f405', 'cortex-m4', 'qemu', 'blink'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-netduino2-serial',
|
||||
title: 'Netduino 2 Serial Counter',
|
||||
description:
|
||||
'Netduino 2 (STM32F205, Cortex-M3) prints an incrementing counter over Serial. GPIO/LED support arrives once the F205 SoC is wired; serial works today under QEMU.',
|
||||
category: 'communication',
|
||||
difficulty: 'beginner',
|
||||
boardFilter: 'stm32-netduino2',
|
||||
boards: [
|
||||
{
|
||||
boardKind: 'stm32-netduino2',
|
||||
x: 100,
|
||||
y: 100,
|
||||
code: `// Netduino 2 (STM32F205) — serial counter at 115200 baud.
|
||||
// Onboard LED is on PA10; GPIO becomes visual once the F205 SoC is wired.
|
||||
unsigned long n = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
pinMode(PA10, OUTPUT);
|
||||
Serial.println("Netduino 2 ready");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
n++;
|
||||
Serial.print("count=");
|
||||
Serial.print(n);
|
||||
Serial.print(" uptime_ms=");
|
||||
Serial.println(millis());
|
||||
digitalWrite(PA10, n % 2 ? HIGH : LOW);
|
||||
delay(1000);
|
||||
}`,
|
||||
},
|
||||
],
|
||||
code: '',
|
||||
components: [],
|
||||
wires: [],
|
||||
tags: ['stm32', 'netduino', 'f205', 'serial', 'qemu'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-blackpill-f401-blink',
|
||||
title: 'STM32 Black Pill (F401) Blink',
|
||||
description:
|
||||
'Blink the onboard PC13 LED and print to Serial on an STM32F401CE Black Pill (Cortex-M4) under QEMU.',
|
||||
category: 'basics',
|
||||
difficulty: 'beginner',
|
||||
boardFilter: 'stm32-blackpill-f401',
|
||||
boards: [
|
||||
{
|
||||
boardKind: 'stm32-blackpill-f401',
|
||||
x: 100,
|
||||
y: 100,
|
||||
code: `// STM32 Black Pill (STM32F401CE) — onboard LED on PC13 (active LOW).
|
||||
|
||||
void setup() {
|
||||
pinMode(PC13, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("velxio stm32 f401 blackpill");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(PC13, LOW); // LED on
|
||||
delay(200);
|
||||
digitalWrite(PC13, HIGH); // LED off
|
||||
delay(200);
|
||||
}`,
|
||||
},
|
||||
],
|
||||
code: '',
|
||||
components: [],
|
||||
wires: [],
|
||||
tags: ['stm32', 'black pill', 'f401', 'cortex-m4', 'qemu', 'blink'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-bluepill-f103cb-blink',
|
||||
title: 'STM32 Blue Pill (F103CB) Blink',
|
||||
description:
|
||||
'Blink the onboard PC13 LED and print to Serial on an STM32F103CB Blue Pill (Cortex-M3, 128KB flash) under QEMU.',
|
||||
category: 'basics',
|
||||
difficulty: 'beginner',
|
||||
boardFilter: 'stm32-bluepill-f103cb',
|
||||
boards: [
|
||||
{
|
||||
boardKind: 'stm32-bluepill-f103cb',
|
||||
x: 100,
|
||||
y: 100,
|
||||
code: `// STM32 Blue Pill (STM32F103CB, 128KB flash) — onboard LED on PC13 (active LOW).
|
||||
|
||||
void setup() {
|
||||
pinMode(PC13, OUTPUT);
|
||||
Serial.begin(115200);
|
||||
Serial.println("velxio stm32 f103cb blue pill");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
digitalWrite(PC13, LOW); // LED on
|
||||
delay(200);
|
||||
digitalWrite(PC13, HIGH); // LED off
|
||||
delay(200);
|
||||
}`,
|
||||
},
|
||||
],
|
||||
code: '',
|
||||
components: [],
|
||||
wires: [],
|
||||
tags: ['stm32', 'blue pill', 'f103cb', 'cortex-m3', 'qemu', 'blink'],
|
||||
},
|
||||
{
|
||||
id: 'stm32-uno-gpio-mirror',
|
||||
title: '[STM32 + Arduino] GPIO Mirror',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { useOscilloscopeStore } from './useOscilloscopeStore';
|
|||
import { RaspberryPi3Bridge } from '../simulation/RaspberryPi3Bridge';
|
||||
import { Esp32Bridge } from '../simulation/Esp32Bridge';
|
||||
import { Stm32Bridge, stm32PinNameToLinear } from '../simulation/Stm32Bridge';
|
||||
import { STM32_LED } from '../components/velxio-components/Stm32BluePillElement';
|
||||
import { useEditorStore } from './useEditorStore';
|
||||
import { useVfsStore } from './useVfsStore';
|
||||
import { boardPinToNumber, isBoardComponent } from '../utils/boardPinMapping';
|
||||
|
|
@ -1097,14 +1098,18 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
simulatorMap.set(id, shim);
|
||||
} else if (isStm32BoardKind(boardKind)) {
|
||||
const bridge = new Stm32Bridge(id, boardKind);
|
||||
// Onboard-LED pin + polarity per board kind. Blue/Black Pill drive PC13
|
||||
// active-LOW; the F4 Discovery / Olimex / Netduino boards drive their LED
|
||||
// active-HIGH on a different port pin (see STM32_LED).
|
||||
const ledCfg = STM32_LED[boardKind] ?? { pin: 'PC13', activeLow: true };
|
||||
const ledLinear = stm32PinNameToLinear(ledCfg.pin);
|
||||
bridge.onSerialData = serialCallback;
|
||||
bridge.onPinChange = (gpioPin, state) => {
|
||||
const boardPm = pinManagerMap.get(id);
|
||||
if (boardPm) boardPm.triggerPinChange(gpioPin, state, 'mcu');
|
||||
// Onboard LED on PC13 (linear pin 45) is active-LOW: lit when LOW.
|
||||
if (gpioPin === stm32PinNameToLinear('PC13')) {
|
||||
if (gpioPin === ledLinear) {
|
||||
const dom = document.getElementById(id) as (HTMLElement & { led?: boolean }) | null;
|
||||
if (dom && 'led' in dom) dom.led = !state;
|
||||
if (dom && 'led' in dom) dom.led = ledCfg.activeLow ? !state : !!state;
|
||||
}
|
||||
};
|
||||
bridge.onPinChangeWithTime = getOscilloscopeCallback(id);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ export type BoardKind =
|
|||
| 'aitewinrobot-esp32c3-supermini' // ESP32-C3 SuperMini, QEMU backend
|
||||
| 'stm32-bluepill' // STM32F103C8 (Cortex-M3), QEMU backend (libqemu-arm)
|
||||
| 'stm32-blackpill' // STM32F411CE (Cortex-M4), QEMU backend (libqemu-arm)
|
||||
| 'stm32-bluepill-f103cb' // STM32F103CB (Cortex-M3, 128KB), QEMU (F100 SoC)
|
||||
| 'stm32-blackpill-f401' // STM32F401CE (Cortex-M4), QEMU (F405 SoC)
|
||||
| 'stm32-f4-discovery' // STM32F407VG Discovery (Cortex-M4), QEMU (F405 SoC)
|
||||
| 'stm32-olimex-h405' // Olimex STM32-H405 (F405RG, Cortex-M4), QEMU
|
||||
| 'stm32-netduino-plus2' // Netduino Plus 2 (F405, Cortex-M4), QEMU
|
||||
| 'stm32-netduino2' // Netduino 2 (F205, Cortex-M3), QEMU (serial until F205 GPIO wired)
|
||||
| 'attiny85'; // AVR ATtiny85, browser emulation (avr8js)
|
||||
|
||||
export type LanguageMode = 'arduino' | 'micropython';
|
||||
|
|
@ -114,6 +120,12 @@ export const BOARD_KIND_LABELS: Record<BoardKind, string> = {
|
|||
'aitewinrobot-esp32c3-supermini': 'ESP32-C3 SuperMini',
|
||||
'stm32-bluepill': 'STM32 Blue Pill',
|
||||
'stm32-blackpill': 'STM32 Black Pill',
|
||||
'stm32-bluepill-f103cb': 'STM32 Blue Pill (F103CB)',
|
||||
'stm32-blackpill-f401': 'STM32 Black Pill (F401)',
|
||||
'stm32-f4-discovery': 'STM32F4 Discovery',
|
||||
'stm32-olimex-h405': 'Olimex STM32-H405',
|
||||
'stm32-netduino-plus2': 'Netduino Plus 2',
|
||||
'stm32-netduino2': 'Netduino 2',
|
||||
attiny85: 'ATtiny85',
|
||||
};
|
||||
|
||||
|
|
@ -141,5 +153,11 @@ export const BOARD_KIND_FQBN: Record<BoardKind, string | null> = {
|
|||
'aitewinrobot-esp32c3-supermini': 'esp32:esp32:esp32c3',
|
||||
'stm32-bluepill': 'STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8',
|
||||
'stm32-blackpill': 'STMicroelectronics:stm32:GenF4:pnum=BLACKPILL_F411CE',
|
||||
'stm32-bluepill-f103cb': 'STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103CB',
|
||||
'stm32-blackpill-f401': 'STMicroelectronics:stm32:GenF4:pnum=BLACKPILL_F401CE',
|
||||
'stm32-f4-discovery': 'STMicroelectronics:stm32:Disco:pnum=DISCO_F407VG',
|
||||
'stm32-olimex-h405': 'STMicroelectronics:stm32:GenF4:pnum=GENERIC_F405RGTX',
|
||||
'stm32-netduino-plus2': 'STMicroelectronics:stm32:GenF4:pnum=GENERIC_F405RGTX',
|
||||
'stm32-netduino2': 'STMicroelectronics:stm32:GenF2:pnum=GENERIC_F205RGTX',
|
||||
attiny85: 'ATTinyCore:avr:attinyx5:chip=85,clock=16pll',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -228,6 +228,13 @@ export const BOARD_COMPONENT_IDS = [
|
|||
'xiao-esp32-c3',
|
||||
'aitewinrobot-esp32c3-supermini',
|
||||
'stm32-bluepill',
|
||||
'stm32-blackpill',
|
||||
'stm32-bluepill-f103cb',
|
||||
'stm32-blackpill-f401',
|
||||
'stm32-f4-discovery',
|
||||
'stm32-olimex-h405',
|
||||
'stm32-netduino-plus2',
|
||||
'stm32-netduino2',
|
||||
'attiny85',
|
||||
];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue