Chapter 01
What to build, in what order
One motor on the desk today with the supply you own; three motors only after the 24 V rail can hold 60 A.
Four decisions carry the whole set, and the bench run settled the fourth one.
- Two pins per axis, not one. A BTS7960 module is two independent half-bridges. Direction is chosen by which input carries the duty, so an axis needs two PWM lines. Six PWM pins on the board cover three axes.
- No inverter, no PWM expander. An inverter would let one pin do the job through locked antiphase, but it also keeps both bridges switching at standstill and heats the motor for nothing. Six PWM pins exist on the board, so the simple path wins.
- The enable line is the safety, and it needs one resistor after all. Both inputs idle low once the sketch owns them, which on this bridge means brake - not coast, and not a runaway.
inhibitAllOutputs()is the first statement insetup(), before the serial port and before any delay, so the eight candidate GPIOs are driven low as early as software can reach them, andwriteMotors()raises the enables only after the first duty is on the pins. What software cannot cover is the boot ROM window beforesetup()runs: the ESP8266 holdsGPIO2high through its own boot strap, and a floatingR_ENreads as enabled, which drove one axis at full duty for the first fraction of a second and made the head jump at power-up. Fit a 10 kOhm pull-down from each module's jumperedR_EN/L_ENpair toGND; the bridge is then inhibited from the instant the rail comes up, whatever the board's pins are doing.R_ENandL_ENstay jumpered together, so one board hole still enables both halves. - Duty is average motor voltage, so the floor is a machine setting. The first build scaled duty from a 15 percent floor to full at 180 deg/s. A 60 deg/s pan slew therefore asked for 24 percent duty, which is about 5.8 V on a 24 V rail: not enough to break a geared motor away from standstill. The shipped floor is now 40 percent at 90 deg/s full scale, and both numbers change live through
/tune.
What the bench actually proved
D9 + D5 on the pan axis: 122 deg of travel, duty peaking at 100 percent, GPIO16
high, and the motor turning. The three faults that stood between wiring and rotation were
all on the power side, not in the firmware: module VCC on 3V3 instead of 5 V, no common
point between the 24 V return and the board ground, and the motor across the supply
instead of M+ / M-.
Check the supply label before wiring anything
An S-120-12 label reads 12 V / 10 A / 120 W. The 24 V member of that family is the
S-240-24 at 24 V / 10 A / 240 W. On a 12 V rail a 24 V motor turns at roughly half
speed and half torque, which reads exactly like a "weak driver" fault while the wiring is
perfectly fine.
Chapter 02
The four parts, as they actually terminate
Terminal order, printed ratings and the numbers that size fuse, wire and heatsink.
Component plate with terminal layouts and ratings
| Part | Figure that matters | Where it comes from |
|---|---|---|
| Bridge supply window | 5.5 - 27.5 V, lock-out above 27.6 V | data sheet 4.3.4 / 4.1.1 |
| Current limit | 43 A typ low side at 25 C (34 A min, 61 A max) | data sheet 4.3.7 |
| Path resistance | 16 mOhm typ at 25 C, so 6.4 W at 20 A | data sheet basic features |
| PWM ceiling | 25 kHz with active freewheeling | data sheet 4.2 |
| Logic input | high from 2.0 V, absolute limit 5.3 V | data sheet 4.4.6 |
| Current sense | 8500:1, so 1 kOhm gives 1 V per 8.5 A | data sheet 4.4.4 |
| Thermal path | 1.0 K/W junction to case, 35 K/W to air on 6 cm2 | data sheet 5 |
| Motor | 350 W, 24 V, 20 A rated | nameplate, not yet measured |
3.3 V drives the inputs, but `VCC` still wants 5 V
The input threshold is high from 2.0 V worst case with a 5.3 V ceiling, so an ESP8266 pin
is inside the window with margin. No level shifter, no 5 V buffer. The module's own VCC
logic supply is a different pin and is not optional: on 3V3 the gate drive never opens
fully, which reads as a dead motor while every signal measures correct. It draws about
10 mA, so the board's 5V pin carries it.
Chapter 03
One axis, two inputs, one enable
The pin map split by header strip, the bridge truth table, and what a hold-and-reverse cycle looks like on the wires.
Pin map, bridge truth table and one hold-and-reverse cycle
The two header strips are split by function, so one ribbon leaves each side: PWM pairs on the top strip, enable lines on the bottom strip. Every ESP8266 GPIO appears twice on the Uno footprint, which is what makes the split free - the labels below name the hole this layout uses, not a different signal.
| Axis | RPWM, top strip | LPWM, top strip | Enable, bottom strip |
|---|---|---|---|
| Pan | D9 / GPIO2 | D13 / GPIO14 | D2 / GPIO16 |
| Tilt | D10 / GPIO15 | D12 / GPIO12 | D4 / GPIO4 |
| Jaw | D11 / GPIO13 | D15 (SCL) / GPIO5 | D14 (SDA) / GPIO4 |
D4 and D14 are the same GPIO4, so modules 2 and 3 get their own hole from one line.
Three modules, three holes, no splitter - and R_EN plus L_EN stay jumpered at each
module, which is what turns one hole into two enabled half-bridges.
| Axis | Bench proof |
|---|---|
| Pan | 122 deg of travel, duty to 100 percent, EN 1, motor turns |
| Tilt | pins verified from the core variant file, module not yet fitted |
| Jaw | same; GPIO5 is D15/SCL on the top strip and D3 on the bottom |
| Enable | EN 0 at boot, EN 1 after the first duty write |
Chapter 04
Schema A - bench, one motor
Turns a motorOne module, one motor, the 10 A supply already on the desk, the bring-up order that works, and the four faults that stand between a correct schematic and a turning shaft.
Schema A - single axis bench wiring
The supply question answers itself at this size: one motor spinning free draws its no-load current, which is a small fraction of the 20 A nameplate. That is what makes the existing 10 A unit adequate for this test. Loading the shaft or stalling it is out of scope here, because the draw then climbs past what the supply can hold.
| Run | Gauge | Carries | Note |
|---|---|---|---|
| Mains to supply | 1.5 mm2 | 220 V, ~1 A | 2 A fuse and a switch in the live wire, PE to the case |
+V to B+ | 2.5 mm2 (AWG 13) | 24 V, bench load | 30 A blade fuse in this run, keep under 1 m |
-V to B- | 2.5 mm2 (AWG 13) | 24 V return | same length as the positive run |
M+ / M- to motor | 2.5 mm2 (AWG 13) | chopped 24 V | swap the pair to invert the turning direction |
Wemos 5V to VCC | 0.35 mm2 | 5 V, ~10 mA | logic supply only, never the motor rail |
Wemos GND to module GND | 0.5 mm2 | logic return | module ground then goes to the star point |
| Signal lines | 0.35 mm2 | 3.3 V, 2 kHz | direct wire from board pin to module pin, no series resistors |
Bring-up order
The Output Pin Test card in the simulator is the instrument: it commands the axis, then reports the duty the board is really writing, the enable state and the travel it measured. No multimeter is needed to prove the firmware side.
- Supply alone: 24.0 V +/- 0.5 V at the module's
B+/B-screw terminals, not at the supply terminals. - Board alone, over USB: the page auto-connects, telemetry arrives,
EN 0, all six duties 0, and the shaft turns freely by hand. - Pin test on one axis:
RPWM 100% 3sholds one input at full duty for three seconds.M+toM-must read close to the rail while the hold is live. - Full pin test: travel above 30 deg with duty peaking near 100 percent and
EN 1is a pass. The card prints the verdict. - Duty floor: if the shaft hesitates from standstill, raise the floor slider until it breaks away cleanly. That is a live change, not a reflash.
- Failsafe: pull the client. The axis stops inside 350 ms and holds, because both inputs low with enable high is a brake.
- Thermal: one minute of motion must leave the module plate under 60 C.
Four faults, in the order they actually happened
Every one of these reads like a dead driver while the signal side measures perfect.
Module VCC on 3V3 instead of 5 V, so the gate drive never opens fully. No common point
between the 24 V return and the board ground, so a 100 percent duty has no reference.
The motor wired across the supply instead of M+ / M-, so the bridge drives nothing.
And a duty floor too low for the gearbox: 24 percent of 24 V is 5.8 V, which a geared
motor answers by sitting still.
Chapter 05
Schema B - operational, three motors
Needs a 24 V rail that holds 60 AThe same logic wiring three times, plus bus fusing, an emergency stop and a supply sized for the sum.
Schema B - three axes and the roar on one fused 24 V bus
Where the 24 V current goes when all three axes pull rated load
| Item | Value | Why that number |
|---|---|---|
| Source | 24 V at 62.5 A, or a 24 V battery plus charger | 3 x 20 A = 60 A, 1440 W |
| Main fuse | 80 A ANL | above the 60 A sum, protects the bus bar |
| Emergency stop | DC-rated, 60 A, in the +24 V rail | also cuts the enable line, so axes go limp |
| Bus bars | 13 mm2 (AWG 6) | 60 A continuous with margin |
| Branch runs | 4 mm2 (AWG 11), under 1.5 m | 20 A rated draw per axis |
| Power wiring | 4 mm2 (AWG 11) | direct B+ and B- to bus / supply, no extra capacitors needed |
| Cooling | heatsink plus airflow per module | 6.4 W at 20 A through 16 mOhm |
A 24 V rail leaves 3.5 V of headroom before the bridge locks out
The BTS7960 module includes its own onboard decoupling and buffer. Direct wiring from the 24 V rail to B+ / B- and from the board pins to the logic header has been bench-verified with the motor under full load.
id: blueprints
title: Print sheets - the two wiring blueprints
nav: Blueprints
eyebrow: Chapter 06
tag: target Print these
lead: The same two schemas as connection blueprints: pictorial, monochrome, with a title block, a bill of materials and the notes a builder needs at the bench.
The block schematics above answer "what connects to what". These two sheets answer "what do I hold in my hand, and which hole does this wire go into". They are drawn in black and white on purpose: line weight and dash pattern carry the net, so a laser print and a screen read the same, and a photocopy loses nothing.
Sheet 1 of 2 - bench test wiring, one axis
Sheet 2 of 2 - operational wiring, three axes and the audio chain
| Sheet | Covers | Status on the title block |
|---|---|---|
| ADR-W-001 | one axis, one module, one motor, the supply on the desk | verified on hardware |
| ADR-W-002 | three axes, bus bars, branch fusing, emergency stop, audio chain | not built, needs a 60 A rail |
Both sheets carry the same conventions: a reference designator on every part (PS1, U1,
M1, F2, C1, R4), a filled dot for a joint, a crossing without a dot for two wires
that only pass, and a hollow circle for an optional joint such as the battery source.
Neither sheet bundles or hides a conductor. Where three wires do the same job on three
axes, the sheet draws three wires, because a bundle on a drawing is a wire nobody strips.
Chapter 07
What changes in the firmware
One build, no flagOne header for the mapping, one output stage, two live-tunable numbers. The kinematics, the HTTP API and the chain playback are untouched.
The brushed motor cannot consume an angle, and the smoother already tracks angular
velocity, so the conversion is one mapping instead of a new control loop. Holding a
direction ramps the commanded angle, the ramp is a rate, the rate becomes duty - which
means the smoother's ramp is the motor's soft start. That ramp is jerk-limited: the
commanded acceleration slews toward its demand over jerkRampSec, so the duty a bridge
sees rises on a curve rather than a step.
| Seam | File | Classification |
|---|---|---|
| Rate to duty mapping, dead rate, break-away floor, reversal dwell | firmware/libraries/DinoMotion/src/dino_drive.h | add |
Output stage, writeMotors() | firmware/d1_wifi_receiver/d1_wifi_receiver.ino | replaced writeServos() |
| Second input per axis and the shared enable pin | same sketch, ESP8266 pin block | add |
| PWM range and frequency, enable held low at boot | same sketch, setup() | add |
Live tuning, /tune and $tune | same sketch, HTTP and serial handlers | add |
Bench duty hold, /drive and $drive | same sketch | add |
| Kinematics, HTTP API, chain playback | unchanged | reuse |
| Jerk limit and braking-distance speed cap | firmware/libraries/DinoMotion/src/fourier_smoother.h | add |
| Mapping assertions | sim/dino_sim.cpp, driveTest() | add |
Servo output stage, Servo.h, the ESP32 branch, DINO_DRIVE_BTS7960 | deleted | remove |
One sketch, one command:
arduino-cli compile --fqbn esp8266:esp8266:d1 \
--libraries firmware/libraries firmware/d1_wifi_receiver
It prints [Drive] 3x BTS7960, PWM 2000 Hz, min duty 40%, bridges inhibited until first write at boot, and lands at 30 percent flash and 45 percent RAM.
Two numbers per axis are machine settings, not constants, so they move without a reflash:
| Route | Form | Effect |
|---|---|---|
GET /tune?axis=0&floor=650&full=90 | HTTP, key-gated | pan floor to 65 percent, full scale to 90 deg/s |
$tune 0 650 90 | USB serial | same, over the cable |
GET /status | HTTP | reports tune[3] so the page can show what the board holds |
GET /drive?axis=0&duty=1000&ms=3000 | HTTP, key-gated | holds one bridge input at a fixed duty, then expires |
GET /smooth?w=12&=2.4&hz=0.25&jerk=0.12 | HTTP, key-gated | filter cutoff, breathing, and the jerk ramp that shapes every duty climb |
$smooth 12 2.4 0.25 0.12 | USB serial | same, over the cable |
The floor is the value that decides whether a geared motor starts at all. Raise it until the shaft breaks away cleanly from standstill; lower it if the axis lurches.
rate below the dead rate
Holding - both inputs low, enable high
The axis brakes rather than coasts, because both bridge halves pull the motor terminals to ground. A head on a worm gear stays where it was left.
Permitted
new direction command, chain playback
Always available
current sense readable
Reason code
rate < 3 deg/s
Open loop, and the doc says so
Nothing on this drive measures where the joint actually is. The firmware knows the angle it commanded; friction, load and a missed dwell all leak real position away from that model. The software clamp on each axis is the only limit there is. The upgrade is one 10 kOhm potentiometer per joint into an ADS1115, which needs the I2C pair - and this layout spends GPIO5 on the jaw and GPIO4 on the enables, so joint feedback and a third axis compete for the same two pins. That is the same wall as the axis count: it moves when the controller moves.
Chapter 08
Sound matching: DFPlayer Mini, TDA7297 amp and TOA speaker
Sound matchingOne Wemos D1 owns the jaw motor and DFPlayer. Closing the jaw stops the recording; reopening starts it again. Physical jaw and speaker timing still require a bench test.
Playback behavior
The receiver uses its smoothed jaw angle for both motor output and audio control. It does not need a browser to run an armed chain. The angle is a software estimate, not a measured linkage position.
| State | Same Wemos D1 and DFPlayer | Browser preview without hardware |
|---|---|---|
| Jaw opens to at least 38 degrees | Set volume, then start the selected SD track | Start the recorded roar once |
| Jaw moves between 25 and 65 degrees | Scale volume to the configured ceiling, rounded to DFPlayer steps | Smoothly adjust gain to the jaw opening |
| Jaw reaches 25 degrees or less | Send STOP, bypassing normal command spacing | Fade to zero over 20 ms and stop the source |
| Jaw reopens after closing | Restart the selected track from its beginning | Restart the recording from its beginning |
| Recording ends while the jaw stays open | No automatic restart; close and reopen for another roar | No loop and no stacked sources |
| Browser disconnects during an armed chain | Board continues the chain and jaw-driven audio | Browser is not required for physical playback |
| USB or Wi-Fi controls a board | Board owns physical sound | Browser audio stays silent to avoid duplicate sound |
Firmware volume and PLAY commands are spaced by at least 100 ms. Initial volume is sent before PLAY, so crossing the opening threshold is not a promise of immediate speaker output. STOP bypasses this spacing. A real DFPlayer can add its own playback latency.
Load the roar and control playback
- Format the micro-SD card as FAT32. Copy the recorded roar as the first file onto an empty card. Command
0x03selects storage index 1, not a filename such as/mp3/0001.mp3. - Connect the DFPlayer to the same Wemos D1 using the wiring below. Use its DAC output for the amplifier input.
- Open and close the jaw with the existing controls or an armed chain. No second Arduino or browser sound command is needed.
| Command | Effect |
|---|---|
/sound?track=N or /sound?play=1 | Select the track for the next jaw-gated start; does not force sound with a closed jaw |
/sound?vol=25 | Set the jaw-volume ceiling within DFPlayer's 0..30 range |
/sound?stop=1 | Stop and inhibit playback until the jaw closes; a later opening can start again |
$sound 1 25 / $sound stop | Serial equivalents for track/ceiling selection and stop |
HTTP sound commands require k=<device key> when a key is configured. The TDA7297 amplifier needs a separate 12 V supply or buck converter from the motor rail; its chip supply range is 6.5..18 V. Never feed it directly from 24 V.
The sound chain is not a separate drawing. It sits on both sheets above, because it shares the board, the ground and the 24 V rail with the motors: Schema B carries it as a fourth row, and sheet ADR-W-002 draws its eleven runs conductor by conductor with the buck converter, the amplifier, the horn and the DFPlayer in the same title block as the drive stage.
| Component | Rating / Model | Wiring | Purpose |
|---|---|---|---|
| Audio Player | DFPlayer Mini | 5 V from WeMos, RX to D8 | MP3/WAV playback from micro-SD |
| Micro-SD Card | FAT32, up to 32 GB | in DFPlayer slot | Roar copied first for index 1 |
| Amplifier | TDA7297 dual-bridge | 12 V DC (buck from 24 V) | 15 W + 15 W power stage |
| Horn Speaker | TOA 8 Ohm / 30 W | SPK_OUT+ and SPK_OUT- | Directional, high-decibel roar |
| Control Line | 9600 baud 8N1 serial | WeMos D8 -> DFPlayer RX | Packet: 7E FF 06 03 00 00 01 FE F7 EF |
Use the DFPlayer DAC output for an external amplifier, never its bridged speaker output. Do not connect either TDA7297 speaker terminal to ground. See the DFRobot connection guidance and command implementation.
Run py -E tools/test_jaw_audio.py to check the receiver scheduler's emitted commands for opening, partial closure, immediate STOP, reopening and operation without client input. This checks software packets, not electrical UART delivery or speaker output.
Chapter 09
Three axes is the ceiling, and a head wants more
A convincing neck needs four or five channels, this board holds three, and the gap has exactly one cheap answer.
Nine GPIOs on a Wemos D1 can drive an output: 0, 2, 4, 5, 12, 13, 14, 15, 16. Three are spoken for before any motor: GPIO1 and GPIO3 carry the serial telemetry, and GPIO6 to GPIO11 belong to the flash chip. GPIO0 looks free and is not: a BTS7960 input holds its line low, and GPIO0 low at reset selects UART flash mode, so the board would never boot.
That leaves eight. Six become the three PWM pairs and two become the enable lines, which is exactly zero spare. A fourth axis is not a wiring problem, it is a controller problem.
| Controller | PWM available | Axes with 2 PWM + enable | What it costs |
|---|---|---|---|
| Wemos D1, ESP8266 | 8 usable outputs | 3 | shipped today, zero spare pins, IRAM at 94 percent |
| ESP32 dev board | 16 LEDC channels, ~25 usable GPIO | 8 | one bounded port, per-axis enable becomes real |
| ESP8266 + PCA9685 | 16 channels over I2C, 2 board pins | 5 with enables on the same chip | 1526 Hz ceiling, I2C no longer free for joint feedback, and the ESP8266 is already nearly full |
| One board per group | 3 axes each | unbounded | two boards in one head means two playheads, so neck and jaw drift apart |
What a head actually needs
Smoothness and axis count are different problems. A fourth motor buys a new degree of freedom, not smoother motion: jerk comes from gearbox backlash, a duty floor set too low, a reversal dwell too short for the mass, and a jerk ramp set faster than the linkage can absorb. Tune those four before buying a motor. What more axes buy is the look of a living neck - a base joint that leads and a head joint that follows.
| Channel | Job | Drive |
|---|---|---|
| Neck yaw | look left and right | brushed, BTS7960 |
| Neck pitch, upper | raise and drop the skull | brushed, BTS7960 |
| Neck pitch or roll, base | the lead-and-follow that reads as alive | brushed, BTS7960 |
| Jaw | mandible | brushed, but a 350 W motor is oversized for it |
| Eyes or blink | small, fast, light | a 9 g hobby servo on one pin, no bridge |
That is five channels, four of them bridges. The plan is therefore an ESP32 with four BTS7960 modules plus one servo pin, and the ESP8266 rig stays exactly as it is until the mechanics for the fourth joint exist.
The port is bounded, because the logic is already Arduino-free
dino_motion.h, fourier_smoother.h, dino_autonomy.h and dino_drive.h carry the
kinematics, the smoother, the chain player and the rate-to-duty mapping with no
Arduino include, and they compile on the host today. An ESP32 port therefore touches the
sketch only: the pin map, analogWriteRange and analogWriteFreq become
analogWriteResolution and analogWriteFrequency, and ESP8266WebServer becomes
WebServer. Nothing in the motion math moves.
Do not buy the fourth axis by deleting the enable line
Four axes fit the ESP8266's eight outputs exactly - and only by leaving zero pins for
enable. Tying R_EN and L_EN to 5 V instead would drop the one invariant that keeps a
reset from twitching a motor, and it would do so on the build with the least flash
headroom. The pin arithmetic says three; respect it and change the controller instead.
Chapter 10
Proof already on record
Host checks verify motion and emitted audio commands. Browser measurements verify closed-jaw silence. Physical DFPlayer output, actual jaw timing and the remaining motor measurements are separate bench checks.
| Check | Command | Result | State |
|---|---|---|---|
| Rate-to-duty mapping, dwell, rollover, single-input invariant | sim/dino_sim.exe --test | dino_drive self-check: all assertions passed | verified |
| Kinematics and failsafe | sim/dino_sim.exe --test | dino_motion self-check: all assertions passed | verified |
| Chain playback | sim/dino_sim.exe --test | dino_autonomy self-check: all assertions passed | verified |
| Pin map validation, both sides | sim/dino_sim.exe --test, bun tools/parity.mjs | dino_pins self-check: all assertions passed; 156 pin maps judged identically (dino_pins.h vs dino_pins.js) | verified |
| Pin map over the wire | headless page with a faked board | duplicate GPIO refused locally with 0 requests sent; Apply sends all nine roles on /pins; a board-reported remap overrides the selectors; /pins?panr=0 answers 400 not_output on panr; Peta default sends /pins?reset=1 | verified |
| Simulator parity, kinematics | bun tools/parity.mjs | parity: 71 frames identical | verified |
| Simulator parity, smoother | bun tools/parity.mjs | parity: 180 smoother frames identical (fourier_smoother.h vs fourier_smoother.js) | verified |
| Jerk bound and no overshoot | sim/dino_sim.exe --test | fourier_smoother self-check: all assertions passed; peak jerk equals the configured ceiling, first tick after a step only ramps, overshoot below 0.05 deg | verified |
| Jerk before and after the limiter | 30 deg step at 150 deg/s, 60 fps | 71 986 -> 10 000 deg/s3, the configured ceiling; acceleration ramps 167, 333, 500, 667 instead of jumping to 1200 | verified |
| Browser jerk readout and live trace | headless page, mode 3, 60 deg step | jerk chip peaks at 4533 deg/s3 with a 0.30 s ramp and never above; angle stops at 119.47 of 120 | verified |
| Three speed sliders and the wire | headless page with a faked board | /speed carries p=60&t=60&j=96 at follow 60 x convert 1.00, p=150&t=150&j=240 at convert 2.50, p=100&t=100&j=160 at follow 40 | verified |
| Raw target rate independent of frame rate | headless page, CPU throttled 1x, 6x and 20x | 25.6, 23.9 and 25.0 deg/s measured against a 25 deg/s slider, no backward step | verified |
| Setting slots and reset | headless page | save, load, reload and reset restore every knob; both saved slots survive the reset | verified |
| Board reaches the smoothing sliders | headless page with a transport | four /smooth?w=..&=..&hz=..&jerk=.. requests, one per slider | verified |
| Board actions refuse to lie when offline | headless page, no transport | disarm and tune print Belum ada koneksi board with zero requests sent; board-only controls disabled | verified |
| Audio packets, jaw trigger and gain mapping | sim/dino_sim.exe --test | dino_sound self-check: all assertions passed | verified |
| Receiver audio lifecycle | py -E tools/test_jaw_audio.py | Start during changing volume, partial fade, immediate STOP, reopen, offline operation and stop inhibition passed | verified |
| Browser open-close-reopen output | Deployed build 23437e2a, real jaw slider and Web Audio analyser | Peak amplitude 0.331 -> 0 -> 0.331 | verified |
| Browser suppression when hardware connects | Local browser analyser, connection-state simulation | Preview RMS 0.1236, connected RMS 0 | verified |
| Firmware build | py -E tools/flash.py build | 326 532 B flash (31 percent), 45 percent RAM; runtime pin map plus boot inhibit | verified |
| Physical jaw and DFPlayer synchronization | Flash receiver, load SD recording, exercise jaw and standalone chain | No board detected during the audio verification run; UART delivery and speaker timing not measured | open |
| Enable line raised only after the first duty | telemetry tail on USB | EN 0 at boot, EN 1 after the first command | verified |
| Power-up jump ("kaget") | Reported on the bench: the head jerked at every power-up | Cause: pins unclaimed until setup() reached its pin block, roughly 300 ms in, while GPIO2 sits high through the ESP8266 boot strap and a floating R_EN reads as enabled. inhibitAllOutputs() is now the first statement in setup(), ahead of Serial.begin and the 200 ms delay. Measured on the board after flashing: [Boot] every output pulled low at 79 ms | verified |
Boot ROM window before setup() | Same report; cannot be reached from software | Requires a 10 kOhm pull-down from each jumpered R_EN/L_EN pair to GND, so the bridge is inhibited from the instant the rail comes up | open |
| Runtime pin map on the board | uv run tmp/pins_serial.py COM3, COM3 attached | $pins 0 ... rejected not_output on panR, a duplicate rejected duplicate on en2, the remap 2,14,15,12,13,4,16,16,0 applied, survived a reset from EEPROM, and $pins reset restored 2,14,15,12,13,5,16,4,0 across another reset | verified |
| Pin map in the telemetry tail | same run | Pins:2,14,15,12,13,5,16,4,0 present on every line; the removed sweep leaves no Cal: or Lim: segment | verified |
| Desktop app flashes the board | built exe on its headless seam, uv run tmp/watch_console_windows.py --deploy COM3 | Compiled and uploaded to COM3, esptool wrote 365 744 B; 0 console windows, where the same run before the fix showed one arduino-cli.exe window per call | verified |
| Upload sync flake | three deploys through the app | one Timed out waiting for packet header in three attempts on a healthy board; a single retry of that one message is now built in, go test ./... pins it to the upload stage | verified |
| Axis card names no fixed pin | headless page | chip reads GPIO2 / 15 / 13 and follows a remap to GPIO2 / 15 / 4; sliders carry the model limits (30-150, 60-120, 20-70) and the presets read 30/60/90/120/150, 60/75/90/105/120, 20/37/54/70, so no control offers an angle the board would clamp | verified |
| Single-input invariant on the wires | telemetry Drive:motor,... during a pan sweep | RPWM ramps while LPWM stays 0, and the reverse | verified |
| Duty ramp after the tuning change | pan to 150 deg over USB | 1000 -> 752 -> 472 -> 0, was 1000 -> 442 -> 209 | verified |
| Pan axis under power | Output Pin Test card, D9 + D5 | 122 deg of travel, duty peak 100 percent, EN 1, motor turns | verified |
| Tilt and jaw axes | same card, D10 + D6 and D11 | modules not yet fitted | open |
| Motor no-load current | clamp meter on B+ | to be measured | open |
| Module temperature at 20 A | IR thermometer on the plate after one minute | to be measured | open |
- S1
Infineon BTS 7960 High Current PN Half Bridge NovalithIC data sheet, Rev. 1.1, 2004-12-07 - supply window, current limit, path resistance, logic thresholds, current sense ratio, truth table, thermal resistance and layout notes.
https://www.infineon.com/assets/row/public/documents/10/57/infineon-bts7960-ds-en.pdf - S2
NXP PCA9685 16-channel 12-bit PWM controller data sheet, Rev. 4, 2015-04-16 - checked and set aside: 1526 Hz maximum output frequency, and the board already has six usable PWM pins.
https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf - S3
DFRobot DFPlayer Mini connection guidance - DAC outputs feed an external amplifier; SPK outputs drive a small speaker directly.
https://wiki.dfrobot.com/dfr0299 - S4
DFRobotDFPlayerMini command implementation - storage-index play 0x03, volume 0x06 and stop 0x16.
https://github.com/DFRobot/DFRobotDFPlayerMini/blob/master/DFRobotDFPlayerMini.cpp - S5
Repository firmware and host assertions - dino_drive.h, d1_wifi_receiver.ino output stage, driveTest() in sim/dino_sim.cpp.
- S6
Part labels supplied by the owner - 350 W 24 V 20 A brushed gear motor, BTS7960 43 A module, S-120-12 supply, 5 V USB charger.