feat(motors): fix stepper rotation + add A4988 driver (real Fritzing SVG)

The stepper-motor and biaxial-stepper parts only decoded a one-hot wave-drive coil sequence, so they never rotated under the common two-phase full-step / Stepper.h / AccelStepper drive that Wokwi's own examples use -- only the servo moved. Rewrote both decoders to track the net magnetic-field vector of the coils (atan2 of the H-bridge currents), so the rotor follows wave, two-phase full-step and half-step drive alike, whether driven directly from GPIO or through a driver's outputs.

Also adds an A4988 STEP/DIR stepper driver (parity with Wokwi's wokwi-a4988): velxio-a4988 element renders the real Pololu A4988 Fritzing breadboard SVG (public/components/a4988.svg); MotorDriverParts.ts finds the wired stepper via the netlist and advances it one (micro)step per STEP rising edge in the DIR direction (MS1-3 microstep + active-low ENABLE). Metadata in component-overrides.json. Three examples (Uno/ESP32/Pico) wire MCU STEP/DIR -> A4988 -> stepper, coil map aligned to Wokwi (1A->B+,1B->B-,2A->A+,2B->A-).

Verified in-browser: motor rotates on Arduino Uno (avr8js) and Raspberry Pi Pico (rp2040js). tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Montero Crespo 2026-05-31 16:28:29 -03:00
parent c66a5b0514
commit 0d5a1d5838
10 changed files with 1849 additions and 60 deletions

View File

@ -2333,6 +2333,27 @@
"category": "logic",
"description": "2-input XOR logic gate. Output Y = A ⊕ B."
},
{
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"16\" y=\"7\" width=\"32\" height=\"50\" rx=\"3\" fill=\"#0b3d2e\" stroke=\"#2d8f5a\" stroke-width=\"0.8\"/>\n <rect x=\"25\" y=\"25\" width=\"14\" height=\"14\" rx=\"1\" fill=\"#15151a\" stroke=\"#333\"/>\n <text x=\"32\" y=\"19\" text-anchor=\"middle\" font-family=\"sans-serif\" font-size=\"6\" fill=\"#cfe8d8\" font-weight=\"bold\">A4988</text>\n <g fill=\"#d4af37\"><rect x=\"11\" y=\"14\" width=\"5\" height=\"2\"/><rect x=\"11\" y=\"22\" width=\"5\" height=\"2\"/><rect x=\"11\" y=\"30\" width=\"5\" height=\"2\"/><rect x=\"11\" y=\"38\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"14\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"22\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"30\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"38\" width=\"5\" height=\"2\"/></g>\n <text x=\"32\" y=\"51\" text-anchor=\"middle\" font-family=\"sans-serif\" font-size=\"5\" fill=\"#8fbfa6\">STEP/DIR</text></svg>",
"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",

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -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 =
`<style>${STYLE}</style>` +
`<img src="${SVG_URL}" width="${W}" height="${H}" draggable="false" alt="A4988 stepper driver" />`;
}
}
if (!customElements.get('velxio-a4988')) {
customElements.define('velxio-a4988', A4988Element);
}
export {};

View File

@ -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',

View File

@ -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';

View File

@ -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);
}

View File

@ -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());
},
});

View File

@ -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;
}

View File

@ -8,6 +8,7 @@ import './BasicParts';
import './ComplexParts';
import './ChipParts';
import './SensorParts';
import './MotorDriverParts';
import './LogicGateParts';
import './ProtocolParts';
import './CustomChipPart';

View File

@ -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": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"64\" height=\"64\" fill=\"#1a2332\" rx=\"4\"/>\n <rect x=\"16\" y=\"7\" width=\"32\" height=\"50\" rx=\"3\" fill=\"#0b3d2e\" stroke=\"#2d8f5a\" stroke-width=\"0.8\"/>\n <rect x=\"25\" y=\"25\" width=\"14\" height=\"14\" rx=\"1\" fill=\"#15151a\" stroke=\"#333\"/>\n <text x=\"32\" y=\"19\" text-anchor=\"middle\" font-family=\"sans-serif\" font-size=\"6\" fill=\"#cfe8d8\" font-weight=\"bold\">A4988</text>\n <g fill=\"#d4af37\"><rect x=\"11\" y=\"14\" width=\"5\" height=\"2\"/><rect x=\"11\" y=\"22\" width=\"5\" height=\"2\"/><rect x=\"11\" y=\"30\" width=\"5\" height=\"2\"/><rect x=\"11\" y=\"38\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"14\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"22\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"30\" width=\"5\" height=\"2\"/><rect x=\"48\" y=\"38\" width=\"5\" height=\"2\"/></g>\n <text x=\"32\" y=\"51\" text-anchor=\"middle\" font-family=\"sans-serif\" font-size=\"5\" fill=\"#8fbfa6\">STEP/DIR</text></svg>"
},
{
"id": "logic-gate-and",
"tagName": "velxio-logic-and",