diff --git a/frontend/public/components-metadata.json b/frontend/public/components-metadata.json
index 56fae136..3a295ed5 100644
--- a/frontend/public/components-metadata.json
+++ b/frontend/public/components-metadata.json
@@ -2333,6 +2333,27 @@
"category": "logic",
"description": "2-input XOR logic gate. Output Y = A ⊕ B."
},
+ {
+ "thumbnail": "",
+ "tags": [
+ "a4988",
+ "stepper",
+ "driver",
+ "motor",
+ "step",
+ "dir",
+ "pololu",
+ "motors"
+ ],
+ "properties": [],
+ "defaultValues": {},
+ "pinCount": 16,
+ "id": "a4988",
+ "tagName": "velxio-a4988",
+ "name": "A4988 Stepper Driver",
+ "category": "motors",
+ "description": "A4988 bipolar stepper motor driver (STEP/DIR). Pulse STEP to advance one step; DIR sets the direction; MS1-MS3 select microstepping. Outputs 1A/1B (coil A) and 2A/2B (coil B) to a stepper's A+/A-/B+/B- terminals."
+ },
{
"id": "biaxial-stepper",
"tagName": "wokwi-biaxial-stepper",
diff --git a/frontend/public/components/a4988.svg b/frontend/public/components/a4988.svg
new file mode 100644
index 00000000..794d7502
--- /dev/null
+++ b/frontend/public/components/a4988.svg
@@ -0,0 +1,1461 @@
+
+
+
diff --git a/frontend/src/components/velxio-components/MotorDriverElements.ts b/frontend/src/components/velxio-components/MotorDriverElements.ts
new file mode 100644
index 00000000..17e88692
--- /dev/null
+++ b/frontend/src/components/velxio-components/MotorDriverElements.ts
@@ -0,0 +1,63 @@
+/**
+ * MotorDriverElements.ts — velxio-a4988 bipolar stepper driver (Pololu-style).
+ *
+ * Logic side (left column) takes STEP / DIR (+ EN, MS1-3, RST, SLP) from the
+ * MCU; the motor side (right column) outputs the two coil pairs 1A/1B (coil A)
+ * and 2A/2B (coil B) to a bipolar stepper's A+/A-/B+/B- terminals.
+ *
+ * The element is purely visual (a DIP-style module). The rotation logic lives
+ * in simulation/parts/MotorDriverParts.ts: on each STEP rising edge it advances
+ * the connected wokwi-stepper-motor by one (micro)step in the DIR direction.
+ *
+ * Tag: velxio-a4988 (metadataId 'a4988' via stripBrandPrefix).
+ */
+
+const STYLE = ':host{display:inline-block;line-height:0;position:relative}';
+
+type Pin = { name: string; x: number; y: number; number: number; signals: string[] };
+
+// Pin order matches the Fritzing Pololu A4988 part (pololu0002_a988):
+// left column, top -> bottom: ENABLE MS1 MS2 MS3 RESET SLEEP STEP DIR
+// right column, top -> bottom: VMOT GND 2B 2A 1A 1B VDD GND
+const LEFT = ['ENABLE', 'MS1', 'MS2', 'MS3', 'RESET', 'SLEEP', 'STEP', 'DIR'];
+const RIGHT = ['VMOT', 'GND', '2B', '2A', '1A', '1B', 'VDD', 'GND.2'];
+
+// Render the real Fritzing A4988 carrier SVG (public/components/a4988.svg).
+// The board is a uniform 2x8 0.1" header, so the connection points sit on an
+// even grid aligned to the two pad columns (margins tuned to the artwork).
+const SVG_URL = '/components/a4988.svg';
+const W = 72;
+const H = 96;
+const TOP_FRAC = 0.115;
+const BOT_FRAC = 0.885;
+const LEFT_FRAC = 0.085;
+const RIGHT_FRAC = 0.915;
+
+function a4988Pins(): Pin[] {
+ const pins: Pin[] = [];
+ const topY = TOP_FRAC * H;
+ const stepY = ((BOT_FRAC - TOP_FRAC) * H) / (LEFT.length - 1);
+ LEFT.forEach((name, i) =>
+ pins.push({ name, x: LEFT_FRAC * W, y: topY + i * stepY, number: i + 1, signals: [] }),
+ );
+ RIGHT.forEach((name, i) =>
+ pins.push({ name, x: RIGHT_FRAC * W, y: topY + i * stepY, number: LEFT.length + i + 1, signals: [] }),
+ );
+ return pins;
+}
+
+class A4988Element extends HTMLElement {
+ readonly pinInfo = a4988Pins();
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' }).innerHTML =
+ `` +
+ `
`;
+ }
+}
+
+if (!customElements.get('velxio-a4988')) {
+ customElements.define('velxio-a4988', A4988Element);
+}
+
+export {};
diff --git a/frontend/src/data/examples.ts b/frontend/src/data/examples.ts
index 6742ff1e..2a9a7c73 100644
--- a/frontend/src/data/examples.ts
+++ b/frontend/src/data/examples.ts
@@ -1410,6 +1410,150 @@ void loop() {
],
tags: ['stm32', 'blue pill', 'gpio', 'stepper', 'motor'],
},
+ {
+ id: 'uno-stepper-a4988',
+ title: 'Arduino Uno: Stepper + A4988',
+ description:
+ 'Spin a bipolar stepper motor from an Arduino Uno through an A4988 driver. The MCU only pulses STEP and sets DIR; the A4988 drives the coils. The rotor turns continuously.',
+ category: 'motors',
+ difficulty: 'intermediate',
+ boardFilter: 'arduino-uno',
+ boards: [
+ {
+ boardKind: 'arduino-uno',
+ x: 40,
+ y: 80,
+ code: `// Arduino Uno + A4988 stepper driver (STEP/DIR)
+// D3 -> STEP, D4 -> DIR. One STEP pulse = one step; DIR sets direction.
+const int STEP_PIN = 3, DIR_PIN = 4;
+
+void setup() {
+ pinMode(STEP_PIN, OUTPUT);
+ pinMode(DIR_PIN, OUTPUT);
+ digitalWrite(DIR_PIN, HIGH); // HIGH = clockwise
+ Serial.begin(115200);
+ Serial.println("A4988 stepper running");
+}
+
+void loop() {
+ digitalWrite(STEP_PIN, HIGH);
+ delayMicroseconds(800);
+ digitalWrite(STEP_PIN, LOW);
+ delayMicroseconds(800);
+}`,
+ },
+ ],
+ code: '',
+ components: [
+ { type: 'velxio-a4988', id: 'drv1', x: 330, y: 70, properties: {} },
+ { type: 'wokwi-stepper-motor', id: 'stp1', x: 560, y: 120, properties: {} },
+ ],
+ wires: [
+ { id: 'u-step', start: { componentId: 'arduino-uno', pinName: '3' }, end: { componentId: 'drv1', pinName: 'STEP' }, color: '#f59e0b' },
+ { id: 'u-dir', start: { componentId: 'arduino-uno', pinName: '4' }, end: { componentId: 'drv1', pinName: 'DIR' }, color: '#10b981' },
+ { id: 'u-1a', start: { componentId: 'drv1', pinName: '1A' }, end: { componentId: 'stp1', pinName: 'B+' }, color: '#ff5555' },
+ { id: 'u-1b', start: { componentId: 'drv1', pinName: '1B' }, end: { componentId: 'stp1', pinName: 'B-' }, color: '#ff9955' },
+ { id: 'u-2a', start: { componentId: 'drv1', pinName: '2A' }, end: { componentId: 'stp1', pinName: 'A+' }, color: '#55aaff' },
+ { id: 'u-2b', start: { componentId: 'drv1', pinName: '2B' }, end: { componentId: 'stp1', pinName: 'A-' }, color: '#aa77ff' },
+ ],
+ tags: ['arduino', 'uno', 'stepper', 'motor', 'a4988', 'driver'],
+ },
+ {
+ id: 'esp32-stepper-a4988',
+ title: 'ESP32: Stepper + A4988',
+ description:
+ 'Spin a bipolar stepper motor from an ESP32 through an A4988 driver (STEP = GPIO26, DIR = GPIO27).',
+ category: 'motors',
+ difficulty: 'intermediate',
+ boardFilter: 'esp32',
+ boards: [
+ {
+ boardKind: 'esp32',
+ x: 40,
+ y: 80,
+ code: `// ESP32 + A4988 stepper driver (STEP/DIR)
+// GPIO26 -> STEP, GPIO27 -> DIR.
+const int STEP_PIN = 26, DIR_PIN = 27;
+
+void setup() {
+ pinMode(STEP_PIN, OUTPUT);
+ pinMode(DIR_PIN, OUTPUT);
+ digitalWrite(DIR_PIN, HIGH);
+ Serial.begin(115200);
+ Serial.println("A4988 stepper running");
+}
+
+void loop() {
+ digitalWrite(STEP_PIN, HIGH);
+ delayMicroseconds(800);
+ digitalWrite(STEP_PIN, LOW);
+ delayMicroseconds(800);
+}`,
+ },
+ ],
+ code: '',
+ components: [
+ { type: 'velxio-a4988', id: 'drv1', x: 330, y: 70, properties: {} },
+ { type: 'wokwi-stepper-motor', id: 'stp1', x: 560, y: 120, properties: {} },
+ ],
+ wires: [
+ { id: 'e-step', start: { componentId: 'esp32', pinName: '26' }, end: { componentId: 'drv1', pinName: 'STEP' }, color: '#f59e0b' },
+ { id: 'e-dir', start: { componentId: 'esp32', pinName: '27' }, end: { componentId: 'drv1', pinName: 'DIR' }, color: '#10b981' },
+ { id: 'e-1a', start: { componentId: 'drv1', pinName: '1A' }, end: { componentId: 'stp1', pinName: 'B+' }, color: '#ff5555' },
+ { id: 'e-1b', start: { componentId: 'drv1', pinName: '1B' }, end: { componentId: 'stp1', pinName: 'B-' }, color: '#ff9955' },
+ { id: 'e-2a', start: { componentId: 'drv1', pinName: '2A' }, end: { componentId: 'stp1', pinName: 'A+' }, color: '#55aaff' },
+ { id: 'e-2b', start: { componentId: 'drv1', pinName: '2B' }, end: { componentId: 'stp1', pinName: 'A-' }, color: '#aa77ff' },
+ ],
+ tags: ['esp32', 'stepper', 'motor', 'a4988', 'driver'],
+ },
+ {
+ id: 'pico-stepper-a4988',
+ title: 'Raspberry Pi Pico: Stepper + A4988',
+ description:
+ 'Spin a bipolar stepper motor from a Raspberry Pi Pico through an A4988 driver (STEP = GP3, DIR = GP4).',
+ category: 'motors',
+ difficulty: 'intermediate',
+ boardFilter: 'raspberry-pi-pico',
+ boards: [
+ {
+ boardKind: 'raspberry-pi-pico',
+ x: 40,
+ y: 80,
+ code: `// Raspberry Pi Pico + A4988 stepper driver (STEP/DIR)
+// GP3 -> STEP, GP4 -> DIR.
+const int STEP_PIN = 3, DIR_PIN = 4;
+
+void setup() {
+ pinMode(STEP_PIN, OUTPUT);
+ pinMode(DIR_PIN, OUTPUT);
+ digitalWrite(DIR_PIN, HIGH);
+ Serial.begin(115200);
+ Serial.println("A4988 stepper running");
+}
+
+void loop() {
+ digitalWrite(STEP_PIN, HIGH);
+ delayMicroseconds(800);
+ digitalWrite(STEP_PIN, LOW);
+ delayMicroseconds(800);
+}`,
+ },
+ ],
+ code: '',
+ components: [
+ { type: 'velxio-a4988', id: 'drv1', x: 330, y: 70, properties: {} },
+ { type: 'wokwi-stepper-motor', id: 'stp1', x: 560, y: 120, properties: {} },
+ ],
+ wires: [
+ { id: 'p-step', start: { componentId: 'raspberry-pi-pico', pinName: 'GP3' }, end: { componentId: 'drv1', pinName: 'STEP' }, color: '#f59e0b' },
+ { id: 'p-dir', start: { componentId: 'raspberry-pi-pico', pinName: 'GP4' }, end: { componentId: 'drv1', pinName: 'DIR' }, color: '#10b981' },
+ { id: 'p-1a', start: { componentId: 'drv1', pinName: '1A' }, end: { componentId: 'stp1', pinName: 'B+' }, color: '#ff5555' },
+ { id: 'p-1b', start: { componentId: 'drv1', pinName: '1B' }, end: { componentId: 'stp1', pinName: 'B-' }, color: '#ff9955' },
+ { id: 'p-2a', start: { componentId: 'drv1', pinName: '2A' }, end: { componentId: 'stp1', pinName: 'A+' }, color: '#55aaff' },
+ { id: 'p-2b', start: { componentId: 'drv1', pinName: '2B' }, end: { componentId: 'stp1', pinName: 'A-' }, color: '#aa77ff' },
+ ],
+ tags: ['raspberry pi pico', 'rp2040', 'stepper', 'motor', 'a4988', 'driver'],
+ },
{
id: 'blink-led',
title: 'Blink LED',
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index cd0ee633..890d42cf 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -13,6 +13,7 @@ import './components/velxio-components/PowerElements';
import './components/velxio-components/DiodeElements';
import './components/velxio-components/RelayElements';
import './components/velxio-components/LogicICElements';
+import './components/velxio-components/MotorDriverElements';
import './components/velxio-components/FlipFlopElements';
import './components/velxio-components/RaspberryPi3Element';
import './components/velxio-components/Bmp280Element';
diff --git a/frontend/src/simulation/parts/BasicParts.ts b/frontend/src/simulation/parts/BasicParts.ts
index 570603c5..c0e342ab 100644
--- a/frontend/src/simulation/parts/BasicParts.ts
+++ b/frontend/src/simulation/parts/BasicParts.ts
@@ -444,22 +444,6 @@ PartSimulationRegistry.register('biaxial-stepper', {
const el = element as any;
const STEP_ANGLE = 1.8;
- // Full-step table: [A+, B+, A-, B-]
- const stepTable: [boolean, boolean, boolean, boolean][] = [
- [true, false, false, false],
- [false, true, false, false],
- [false, false, true, false],
- [false, false, false, true],
- ];
-
- function stepIndexFromCoils(ap: boolean, bp: boolean, am: boolean, bm: boolean): number {
- for (let i = 0; i < stepTable.length; i++) {
- const [tap, tbp, tam, tbm] = stepTable[i];
- if (ap === tap && bp === tbp && am === tam && bm === tbm) return i;
- }
- return -1;
- }
-
function makeMotorTracker(
pinAminus: number | null,
pinAplus: number | null,
@@ -472,20 +456,25 @@ PartSimulationRegistry.register('biaxial-stepper', {
bPlus = false,
bMinus = false;
let cumAngle = 0;
- let prevIdx = -1;
+ let prevField = Number.NaN;
const unsubs: (() => void)[] = [];
+ // Rotor follows the net magnetic-field vector of the two coils — works
+ // for wave, two-phase full-step and half-step drive alike.
function onCoilChange() {
- const idx = stepIndexFromCoils(aPlus, bPlus, aMinus, bMinus);
- if (idx < 0) return;
- if (prevIdx < 0) {
- prevIdx = idx;
+ const a = (aPlus ? 1 : 0) - (aMinus ? 1 : 0);
+ const b = (bPlus ? 1 : 0) - (bMinus ? 1 : 0);
+ if (a === 0 && b === 0) return;
+ const field = Math.atan2(b, a);
+ if (Number.isNaN(prevField)) {
+ prevField = field;
return;
}
- const diff = (idx - prevIdx + 4) % 4;
- if (diff === 1) cumAngle += STEP_ANGLE;
- else if (diff === 3) cumAngle -= STEP_ANGLE;
- prevIdx = idx;
+ let delta = field - prevField;
+ while (delta > Math.PI) delta -= 2 * Math.PI;
+ while (delta < -Math.PI) delta += 2 * Math.PI;
+ prevField = field;
+ cumAngle += (delta / (Math.PI / 2)) * STEP_ANGLE;
setAngle(((cumAngle % 360) + 360) % 360);
}
diff --git a/frontend/src/simulation/parts/MotorDriverParts.ts b/frontend/src/simulation/parts/MotorDriverParts.ts
new file mode 100644
index 00000000..c947715f
--- /dev/null
+++ b/frontend/src/simulation/parts/MotorDriverParts.ts
@@ -0,0 +1,112 @@
+/**
+ * MotorDriverParts.ts — simulation logic for stepper motor drivers.
+ *
+ * a4988 — Pololu-style bipolar stepper driver with a STEP/DIR interface.
+ * The MCU pulses STEP (one rising edge = one step) and sets DIR for the
+ * direction; MS1/MS2/MS3 select microstepping. The driver's coil outputs
+ * (1A/1B = coil A, 2A/2B = coil B) wire to a bipolar stepper's A+/A-/B+/B-.
+ *
+ * We model the driver, not the coil waveform: on each STEP rising edge we
+ * rotate the connected wokwi-stepper-motor (or wokwi-biaxial-stepper) by one
+ * (micro)step in the DIR direction. This matches how real STEP/DIR drivers
+ * behave and how AccelStepper / the Stepper library drive them.
+ */
+
+import { PartSimulationRegistry } from './PartSimulationRegistry';
+import { useSimulatorStore } from '../../store/useSimulatorStore';
+
+/** Find the DOM element wired to `componentId`'s `pinName`, or null. */
+function getConnectedElement(componentId: string, pinName: string): HTMLElement | null {
+ const { wires } = useSimulatorStore.getState();
+ for (const wire of wires) {
+ let otherId: string | null = null;
+ if (wire.start.componentId === componentId && wire.start.pinName === pinName) {
+ otherId = wire.end.componentId;
+ } else if (wire.end.componentId === componentId && wire.end.pinName === pinName) {
+ otherId = wire.start.componentId;
+ }
+ if (otherId) {
+ const el = document.getElementById(otherId);
+ if (el) return el;
+ }
+ }
+ return null;
+}
+
+const STEPPER_TAGS = ['wokwi-stepper-motor', 'wokwi-biaxial-stepper'];
+const FULL_STEP_DEG = 1.8;
+
+PartSimulationRegistry.register('a4988', {
+ attachEvents: (element, simulator, getArduinoPinHelper, componentId) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const pinManager = (simulator as any).pinManager;
+ if (!pinManager) return () => {};
+
+ const pinSTEP = getArduinoPinHelper('STEP');
+ const pinDIR = getArduinoPinHelper('DIR');
+ const pinEN = getArduinoPinHelper('ENABLE');
+ const pinMS1 = getArduinoPinHelper('MS1');
+ const pinMS2 = getArduinoPinHelper('MS2');
+ const pinMS3 = getArduinoPinHelper('MS3');
+
+ // Locate the stepper wired to any coil output.
+ function findMotor(): (HTMLElement & { angle?: number }) | null {
+ for (const out of ['1A', '1B', '2A', '2B']) {
+ const el = getConnectedElement(componentId, out);
+ if (el && STEPPER_TAGS.includes(el.tagName.toLowerCase())) {
+ return el as HTMLElement & { angle?: number };
+ }
+ }
+ return null;
+ }
+ const motor = findMotor();
+
+ let dirHigh = false;
+ let enabled = true; // EN is active-LOW; unconnected = enabled (tied to GND)
+ let ms1 = false;
+ let ms2 = false;
+ let ms3 = false;
+ let prevStep = false;
+ let cumAngle = motor ? Number(motor.angle) || 0 : 0;
+
+ function microFactor(): number {
+ // MS3 MS2 MS1 -> full/half/quarter/eighth/sixteenth (A4988 truth table).
+ const code = (ms3 ? 4 : 0) | (ms2 ? 2 : 0) | (ms1 ? 1 : 0);
+ switch (code) {
+ case 1: return 2; // half
+ case 2: return 4; // quarter
+ case 3: return 8; // eighth
+ case 7: return 16; // sixteenth
+ default: return 1; // full
+ }
+ }
+
+ const unsubs: (() => void)[] = [];
+ const sub = (pin: number | null, cb: (s: boolean) => void) => {
+ if (pin !== null) {
+ unsubs.push(pinManager.onPinChange(pin, (_: number, s: boolean) => cb(s)));
+ }
+ };
+
+ sub(pinDIR, (s) => (dirHigh = s));
+ sub(pinEN, (s) => (enabled = !s)); // active-low
+ sub(pinMS1, (s) => (ms1 = s));
+ sub(pinMS2, (s) => (ms2 = s));
+ sub(pinMS3, (s) => (ms3 = s));
+
+ if (pinSTEP !== null) {
+ unsubs.push(
+ pinManager.onPinChange(pinSTEP, (_: number, s: boolean) => {
+ if (s && !prevStep && enabled && motor) {
+ const stepAngle = FULL_STEP_DEG / microFactor();
+ cumAngle += dirHigh ? stepAngle : -stepAngle;
+ motor.angle = ((cumAngle % 360) + 360) % 360;
+ }
+ prevStep = s;
+ }),
+ );
+ }
+
+ return () => unsubs.forEach((u) => u());
+ },
+});
diff --git a/frontend/src/simulation/parts/SensorParts.ts b/frontend/src/simulation/parts/SensorParts.ts
index dc1dc392..09d70ccf 100644
--- a/frontend/src/simulation/parts/SensorParts.ts
+++ b/frontend/src/simulation/parts/SensorParts.ts
@@ -377,46 +377,31 @@ PartSimulationRegistry.register('stepper-motor', {
const coils = { aMinus: false, aPlus: false, bPlus: false, bMinus: false };
let cumAngle = el.angle ?? 0;
- let prevStepIndex = -1;
-
- // Full-step table: index → [A+, B+, A-, B-]
- const stepTable: [boolean, boolean, boolean, boolean][] = [
- [true, false, false, false], // step 0
- [false, true, false, false], // step 1
- [false, false, true, false], // step 2
- [false, false, false, true], // step 3
- ];
-
- function coilToStepIndex(): number {
- for (let i = 0; i < stepTable.length; i++) {
- const [ap, bp, am, bm] = stepTable[i];
- if (
- coils.aPlus === ap &&
- coils.bPlus === bp &&
- coils.aMinus === am &&
- coils.bMinus === bm
- ) {
- return i;
- }
- }
- return -1;
- }
+ // Track the magnetic-field electrical angle instead of matching a fixed
+ // coil table. The rotor follows the net field vector of the two coils, so
+ // this works for ANY drive mode the firmware (or a driver) produces:
+ // wave-drive (one coil), full-step two-phase (two coils), or half-step.
+ let prevField = Number.NaN; // previous field angle in radians, NaN = unset
function onCoilChange() {
- const idx = coilToStepIndex();
- if (idx < 0) return;
- if (prevStepIndex < 0) {
- prevStepIndex = idx;
+ // Coil currents: +1 / 0 / -1 from the H-bridge terminal pair.
+ const a = (coils.aPlus ? 1 : 0) - (coils.aMinus ? 1 : 0);
+ const b = (coils.bPlus ? 1 : 0) - (coils.bMinus ? 1 : 0);
+ if (a === 0 && b === 0) return; // no field → rotor holds position
+
+ const field = Math.atan2(b, a); // electrical angle of the field vector
+ if (Number.isNaN(prevField)) {
+ prevField = field;
return;
}
+ // Shortest signed rotation between the two field angles.
+ let delta = field - prevField;
+ while (delta > Math.PI) delta -= 2 * Math.PI;
+ while (delta < -Math.PI) delta += 2 * Math.PI;
+ prevField = field;
- const diff = (idx - prevStepIndex + 4) % 4;
- if (diff === 1) {
- cumAngle += STEP_ANGLE;
- } else if (diff === 3) {
- cumAngle -= STEP_ANGLE;
- }
- prevStepIndex = idx;
+ // One quarter electrical turn (90°, π/2 rad) = one full mechanical step.
+ cumAngle += (delta / (Math.PI / 2)) * STEP_ANGLE;
el.angle = ((cumAngle % 360) + 360) % 360;
}
diff --git a/frontend/src/simulation/parts/index.ts b/frontend/src/simulation/parts/index.ts
index 6224ecb9..dd81dc1d 100644
--- a/frontend/src/simulation/parts/index.ts
+++ b/frontend/src/simulation/parts/index.ts
@@ -8,6 +8,7 @@ import './BasicParts';
import './ComplexParts';
import './ChipParts';
import './SensorParts';
+import './MotorDriverParts';
import './LogicGateParts';
import './ProtocolParts';
import './CustomChipPart';
diff --git a/scripts/component-overrides.json b/scripts/component-overrides.json
index da7ca3c3..ec0cc4a0 100644
--- a/scripts/component-overrides.json
+++ b/scripts/component-overrides.json
@@ -1,6 +1,18 @@
{
"$comment": "Custom property overrides applied AFTER auto-generation from wokwi-elements. Add entries here to customize controls, add new properties, or change defaults. These survive metadata regeneration. Use `_customComponents` to inject Velxio-only parts not defined in wokwi-elements.",
"_customComponents": [
+ {
+ "id": "a4988",
+ "tagName": "velxio-a4988",
+ "name": "A4988 Stepper Driver",
+ "category": "motors",
+ "description": "A4988 bipolar stepper motor driver (STEP/DIR). Pulse STEP to advance one step; DIR sets the direction; MS1-MS3 select microstepping. Outputs 1A/1B (coil A) and 2A/2B (coil B) to a stepper's A+/A-/B+/B- terminals.",
+ "properties": [],
+ "defaultValues": {},
+ "pinCount": 16,
+ "tags": ["a4988", "stepper", "driver", "motor", "step", "dir", "pololu", "motors"],
+ "thumbnail": ""
+ },
{
"id": "logic-gate-and",
"tagName": "velxio-logic-and",