diff --git a/backend/app/services/stm32_lib_manager.py b/backend/app/services/stm32_lib_manager.py index 4f43214b..067dfd2a 100644 --- a/backend/app/services/stm32_lib_manager.py +++ b/backend/app/services/stm32_lib_manager.py @@ -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', } diff --git a/frontend/src/components/ComponentPickerModal.tsx b/frontend/src/components/ComponentPickerModal.tsx index 9e0d2a99..3b14b6dd 100644 --- a/frontend/src/components/ComponentPickerModal.tsx +++ b/frontend/src/components/ComponentPickerModal.tsx @@ -57,6 +57,12 @@ const BOARD_DESCRIPTIONS: Record = { '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> = { '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 { diff --git a/frontend/src/components/editor/EditorToolbar.tsx b/frontend/src/components/editor/EditorToolbar.tsx index 610001e5..8218db49 100644 --- a/frontend/src/components/editor/EditorToolbar.tsx +++ b/frontend/src/components/editor/EditorToolbar.tsx @@ -68,6 +68,12 @@ const BOARD_PILL_ICON: Record = { '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 = { @@ -83,6 +89,12 @@ const BOARD_PILL_COLOR: Record = { '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 = ({ diff --git a/frontend/src/components/editor/FileExplorer.tsx b/frontend/src/components/editor/FileExplorer.tsx index 91160a40..3183ce26 100644 --- a/frontend/src/components/editor/FileExplorer.tsx +++ b/frontend/src/components/editor/FileExplorer.tsx @@ -141,6 +141,12 @@ const BOARD_ICON: Record = { '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 = { '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 }) { diff --git a/frontend/src/components/simulator/BoardOnCanvas.tsx b/frontend/src/components/simulator/BoardOnCanvas.tsx index 03ec29f9..b7b74451 100644 --- a/frontend/src/components/simulator/BoardOnCanvas.tsx +++ b/frontend/src/components/simulator/BoardOnCanvas.tsx @@ -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 = { '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 ; case 'stm32-blackpill': return ; + case 'stm32-bluepill-f103cb': + return ; + case 'stm32-blackpill-f401': + return ; + case 'stm32-f4-discovery': + return ; + case 'stm32-olimex-h405': + return ; + case 'stm32-netduino-plus2': + return ; + case 'stm32-netduino2': + return ; case 'attiny85': return ; } diff --git a/frontend/src/components/velxio-components/Stm32BluePill.tsx b/frontend/src/components/velxio-components/Stm32BluePill.tsx index 25e31538..24cfd885 100644 --- a/frontend/src/components/velxio-components/Stm32BluePill.tsx +++ b/frontend/src/components/velxio-components/Stm32BluePill.tsx @@ -1,17 +1,19 @@ import './Stm32BluePillElement'; +type StmIntrinsic = React.DetailedHTMLProps, HTMLElement>; + declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace JSX { interface IntrinsicElements { - 'velxio-stm32-bluepill': React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - >; - 'velxio-stm32-blackpill': React.DetailedHTMLProps< - React.HTMLAttributes, - 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) => ( ); + +export const Stm32BluePillF103CB = ({ id, x, y }: Props) => ( + +); + +export const Stm32BlackPillF401 = ({ id, x, y }: Props) => ( + +); + +export const Stm32F4Discovery = ({ id, x, y }: Props) => ( + +); + +export const Stm32OlimexH405 = ({ id, x, y }: Props) => ( + +); + +export const Stm32NetduinoPlus2 = ({ id, x, y }: Props) => ( + +); + +export const Stm32Netduino2 = ({ id, x, y }: Props) => ( + +); diff --git a/frontend/src/components/velxio-components/Stm32BluePillElement.ts b/frontend/src/components/velxio-components/Stm32BluePillElement.ts index bee064e9..1d4a3af7 100644 --- a/frontend/src/components/velxio-components/Stm32BluePillElement.ts +++ b/frontend/src/components/velxio-components/Stm32BluePillElement.ts @@ -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: and - * , 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 = { + '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 = { '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 = { + '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 = ` +
+
${label ?? 'STM32'}
+
+ ${padHtml} +
+
+ `; + } } 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); + } } diff --git a/frontend/src/data/examples.ts b/frontend/src/data/examples.ts index c45fec92..9ac4014d 100644 --- a/frontend/src/data/examples.ts +++ b/frontend/src/data/examples.ts @@ -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', diff --git a/frontend/src/store/useSimulatorStore.ts b/frontend/src/store/useSimulatorStore.ts index 8fccbfc2..19c65e1d 100644 --- a/frontend/src/store/useSimulatorStore.ts +++ b/frontend/src/store/useSimulatorStore.ts @@ -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((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); diff --git a/frontend/src/types/board.ts b/frontend/src/types/board.ts index 683b812a..7d24db97 100644 --- a/frontend/src/types/board.ts +++ b/frontend/src/types/board.ts @@ -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 = { '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 = { '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', }; diff --git a/frontend/src/utils/boardPinMapping.ts b/frontend/src/utils/boardPinMapping.ts index 8cc35ad8..e89b43d8 100644 --- a/frontend/src/utils/boardPinMapping.ts +++ b/frontend/src/utils/boardPinMapping.ts @@ -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', ];