PhotoCar — Complete Build & Wiring Guide
GPIO12 is dead/shorted-to-ground on this board, so PWM stays on GPIO18. The 433 MHz keyfob input is on GPIO17 (pin 11). Source: MotorController.ts defaults used by the running photocar-ble.service.1. Bill of Materials & Specs
| Part | Qty | Specification | Terminals / role |
|---|---|---|---|
| Raspberry Pi Zero 2 W (or 2 WH) | 1 | 65 × 30 mm; 4× M2.5 on 58 × 23 mm; BCM43430 BLE 4.2 + WiFi; 40-pin J8 header | Runs the photocar-ble service; PWM/DIR out on GPIO18/23 |
| Cytron MD25HV motor driver | 1 | Single channel, 7–58 V, 25 A, PWM/DIR mode (not I²C); PCB 111.76 × 60.96 × 1.6 mm; 4× Ø3 on 106.68 × 55.88 mm | Power VB+/VB−, motor MA/MB; control GND/5VO/PWM/DIR |
| 12 V wiper-style gear motor | 1 | ~100 RPM; ~2.4 Ω → ~5.3 A / ~68 W running at 12.8 V; ~179 mm overall, Ø9.5 D-shaft | Two armature leads → MA/MB; park-switch taps unused |
| 12 V 10 Ah LiFePO4 battery (A & B) | 2 | 12.8 V nominal, 20 A limit; 151 × 65 × 94 mm; F2 spade terminals | POS/NEG → A/B/OFF selector |
| A/B/OFF battery selector | 1 | Rotary selector, red ring | Chooses pack A, pack B, or OFF |
| Emergency stop + master On/Off (DPST) | 1 ea | E-stop cutoff; DPST master switch | In series between Main(+) bus and Switched(+) bus |
| Bus bars | 4 | Main(+), Main(−), Switched(+), Switched(−) | Distribution to MD25HV + buck |
| 5 V DC-DC buck converter | 1 | 67 × 38 × 14 mm; holes 61.05 × 31.45 mm; set output 5.1 V, ≥ 3 A | INP/INN in, OUTP/OUTN out → Pi 5 V |
| Onboard LiFePO4 charger + inlet | 1 | 14.6 V 10 A LiFePO4 charger; 120 V AC charge inlet | Charges the pack(s) in place |
| Electronics mounting plate | 1 | 180 × 180 × 2 mm PLA, 22 holes (Pi M2.5 Ø3.4, MD25/buck M3 Ø3.8, grid/corner M4 Ø4.7) | Bolts the board stack to the deck |
| 3D-printed board enclosure | 1 | Bolt-down case, side port cutout (board_case_*) | Houses MD25HV / Pi / buck |
| 2.54 mm connector housings + signal wire | 1 | 22–26 AWG; one 1×3 for PWM+GND+DIR | Pi control: PWM pin 12, GND pin 14, DIR pin 16 (consecutive) |
| Single center knobby wheel, chain, sprockets | set | One-stage chain reduction | Motor → chain → center drive wheel |
| RGB LED module | 1 | Onboard resistors (no external resistors); common-cathode; only R, G, and - wired (B leg unconnected); 1×3 housing | Boot status indicator on pins 35 / 37 / 39 |
2. Parts — Reuse, Buy & Budget
Conservative list including the small wiring and mounting parts that usually get missed. Buy the genuine Raspberry Pi from a trustworthy listing; save money on generic wiring, housings, heat-shrink, standoffs, fuse holders, and buck converters — never by removing the Pi fuse, undersizing the 5 V regulator, or powering the Pi from the MD25HV 5VO.
Reuse from the existing car
| Part | Use | Notes |
|---|---|---|
| Cytron MD25HV motor driver | Motor power stage | Keep existing motor + battery screw-terminal wiring if it already runs the motor. |
| Charger, XT60 leads, power distribution | Charging / wiring | A LiFe-capable charger handles the 4S LiFePO4 pack; reuse XT60s and distribution. |
| 12 VDC gear motor + connector | Drive motor | Leave motor wiring as heavy-current wiring — never route motor current into GPIO harnesses. |
| Enclosure, glands, lever connectors, loom | Mounting / service | Reuse where practical; add strain relief where wires leave the box. |
Buy / add
| Part | Qty | Minimum spec | Why |
|---|---|---|---|
| 4S LiFePO4 battery pack | 1–2 | 12.8 V nominal (14.6 V full), 10 Ah, balance lead; BMS ≥ 20 A continuous | Main power. 12.8 V matches the 12 V motor. Two packs + A/B/OFF selector for runtime. |
| Raspberry Pi Zero 2 WH (or 2 W + soldered header) | 1 | Onboard BLE, 40-pin header | WH saves soldering. Runs the photocar-ble service. |
| microSD card | 1 | 16–32 GB, reputable brand | Raspberry Pi OS + control program. |
| 5 V DC-DC buck converter | 1 | Input 8–30 V; output 5.1 V; ≥ 3 A (LM2596/MP1584) | Powers the Pi from the 12.8 V bus. Set to 5.1 V before connecting the Pi. |
| Inline fuse holder + fuses | 1 | Mini blade, 2 A to start | Protects the buck/Pi branch. |
| Pre-crimped 1-pin Dupont leads | 20–40 | 22–26 AWG, 2.54 mm | Each conductor starts as a 1-pin lead, then loads into housings. |
| 2.54 mm connector housings | assort. | 1×3, 1×4, plus 1×2 / 2×n | Build keyed groups: the Pi control plug and power plugs. |
| 18 AWG red/black wire | 6–10 ft | Stranded copper | Battery branch to fuse, switch, buck input. |
| 22–26 AWG signal wire | 3–6 ft | Blue / black / purple | PWM, GND, DIR control leads. |
| Ferrules / heat-shrink / zip ties / M2.5 nylon standoffs | sets | Match gauges | Clean terminations, strain relief, and a non-conductive Pi mount. |
| Multimeter | 1 | DC volts + continuity | Required to verify buck output before plugging the Pi. |
Optional add-ons
| Part | Include when |
|---|---|
| Pi Camera Module + Zero CSI ribbon | The car should capture photos/video (narrow Pi-Zero ribbon). |
| Micro-USB OTG adapter | Setup / rescue access before BLE is stable. |
| Low-current status LED/buzzer (via resistor/driver) | Boot/control status feedback — never drive high current from GPIO. |
3. System Architecture
High-current drive power and low-current control signals stay on separate paths. The selected battery feeds a main bus, through the E-stop and master switch to a switched bus, which feeds both the MD25HV power input and the buck converter. The buck powers the Pi at 5 V. The Pi commands the MD25HV with three signal wires: PWM, DIR, and a shared GND.
4. Control Wiring — Pi to MD25HV
The MD25HV runs in PWM/DIR mode. Three wires connect it to the Pi: PWM = GPIO18 (physical pin 12), DIR = GPIO23 (physical pin 16), and a shared GND (pin 14). GPIO18 is used because GPIO12 is dead/shorted-to-ground on this board. Pins 12, 14, and 16 are three consecutive outer-row pins, so the whole Pi side is a single 1×3 housing.
Pi Zero header → MD25HV
| Pi physical pin | Pi signal | MD25HV terminal | Wire |
|---|---|---|---|
| Pin 12 | GPIO18 (PWM0) | PWM | Blue — motor speed |
| Pin 14 | GND | GND | Black — common reference (next to PWM) |
| Pin 16 | GPIO23 | DIR | Purple — direction |
| — | no connection | 5VO | Leave empty / unwired |
Connector plan
- Pi side: one 1×3 housing on pins
12 / 14 / 16(PWM = GPIO18, GND, DIR = GPIO23) — three consecutive outer-row pins. - MD25HV side: land on the green control screw terminal at the
PWM,GND, andDIRpositions; use ferrules. - The
5VOterminal stays empty. Do not use it to power the Pi. - Do not use loose single-pin jumpers — they shake loose in a moving car. Use the 1×3 housing.
- Pi GPIO is 3.3 V and the MD25HV accepts it directly. No level shifter needed.
MD25HV board — connect here, not there
The MD25HV has more than one control area. Ignore the small manual potentiometer/rocker headers (those were for standalone manual control). For Pi/software control, land on the green control screw terminal silk-screened GND / 5VO / PWM / DIR. Battery power goes to the high-current terminals on the opposite edge: VB+, VB−, and the motor on MA / MB. Match the printed labels on your physical board before powering anything.
MA / MB. The 5VO terminal stays empty — never use it to power the Pi.DIR_FORWARD_LEVEL=1 (forward = DIR high). PWM is hardware-timed at 1 kHz on PWM channel 0 via dtoverlay=pwm,pin=18,func=2; on boot the firmware sets GPIO18 to its ALT5 PWM function with pinctrl. If the motor spins the wrong way, swap the two motor leads or flip DIR_FORWARD_LEVEL.
5. Power Wiring
The car's power path is: selected battery → main bus → E-stop → master On/Off (DPST) → switched bus → MD25HV power input and buck converter. Negatives return through the Main(−) and Switched(−) bus bars. The buck output powers the Pi at 5 V, which also establishes the Pi–MD25HV common ground through the control cable.
| From | To | Method | Notes |
|---|---|---|---|
Battery A/B POS / NEG | A/B/OFF selector | F2 spade | Selector picks pack A, B, or OFF. |
| Selector out (+) | Main(+) bus | Bus bar | Single main positive distribution. |
| Main(+) bus | E-stop → On/Off (DPST) | Inline | E-stop then master switch in series. |
| On/Off out | Switched(+) bus | Bus bar | Everything downstream is switch-controlled. |
| Switched(+) bus | MD25HV VB+ | Screw / ferrule | Heavy wire (motor current). |
| Switched(+) bus | Buck INP | Screw / solder | Battery voltage in — not from MD25HV 5VO. |
Battery NEG | Main(−) / Switched(−) bus | Bus bar | Common ground. |
| Switched(−) bus | MD25HV VB− + Buck INN | Screw / ferrule | Motor + buck return. |
Buck OUTP/OUTN | Pi 5 V input | Existing Pi power method | Verify 5.1 V with a meter before plugging the Pi. |
MD25HV MA/MB | Wiper motor armature leads | Screw terminal | Swap leads to invert direction. |
| Charge inlet (120 V AC) | Onboard 14.6 V LiFePO4 charger → pack | Inlet + leads | Charges in place; do not drive while charging. |
Soldered Y-split harness (positive & negative distribution)
Where the switched bus feeds both the MD25HV and the buck, a clean removable harness is a soldered Y-split rather than soldering directly to fixed components:
- Cut one trunk wire (from the switched bus / switch output) and two branch wires (MD25HV + buck).
- Slide adhesive-lined heat-shrink onto the trunk before soldering.
- Strip enough insulation that the three copper ends overlap mechanically; twist the two branches together, then wrap with the trunk.
- Solder so it flows through the joint, not just on the outside; let it cool without movement.
- Cover with the adhesive heat-shrink; add an outer sleeve or cable wrap if the harness will vibrate.
- Repeat for the negative side. Use heavier wire on the MD25HV branch (motor current) than on the buck branch.
5VO is not wired to the Pi. Lift the wheel off the ground for the first motor test.
6. Software & Control
The Pi runs motor-control (TypeScript/Node) as the systemd service photocar-ble.service, installed by scripts/provision-photocar.sh. It accepts drive commands over Bluetooth LE (from the PhotoCar tablet/phone app) and a WiFi TCP control port.
Provisioning a fresh Pi — two ways, same result
Both paths end at the same place: the Pi-side provisioner scripts/provision-photocar.sh runs as root, installs packages, writes the PWM overlay, builds the app, installs photocar-ble.service, and reboots once if the boot config changed. It is idempotent — safe to re-run for updates too.
Step 0 — flash the right OS onto the microSD
- Install Raspberry Pi Imager on any computer with an SD card reader.
- Choose Device: Raspberry Pi Zero 2 W.
- Choose OS: Raspberry Pi OS (Bookworm, Debian 12). This matters: the provisioner and its PWM/boot-config handling target Bookworm — do not pick a Trixie (Debian 13) image. If the Imager's default entry says Trixie, go to Raspberry Pi OS (other) and pick a release explicitly labelled Bookworm (the “Legacy” entries once Trixie becomes the default). 32-bit or 64-bit both work on the Zero 2 W.
- Click the gear / “Edit Settings” before writing and set:
- Hostname:
photorcar(this is whatdeploy.ps1and its-Scanmode look for). - Username / password:
dealermade/ the usual password (the provisioner assumes userdealermadeexists). - WiFi: your SSID + password, and set the wireless LAN country (e.g.
US) — without a country the radio can stay rfkill-blocked. - Services tab: enable SSH with password authentication.
- Hostname:
- Write the card, put it in the Pi, and power up. First boot takes a couple of minutes (it resizes the filesystem and joins WiFi).
Step 0.5 — SSH into the Pi
# from any machine on the same WiFi network
ssh dealermade@photorcar.local
- Accept the host-key prompt on first connect, then enter the password you set in the Imager.
- If
photorcar.localdoes not resolve (mDNS can be flaky on Windows), find the Pi's IP in your router's client list — or from the PC just run.\deploy.ps1 -Scan, which sweeps the subnet and identifies the Pi by hostname — thenssh dealermade@<ip>. - Sanity checks once you're in:
hostnameshould sayphotorcar, andcat /etc/os-releaseshould showVERSION_CODENAME=bookworm. If it saystrixie, reflash with the Bookworm image before provisioning.
Way 1 — from the PC: deploy.ps1 (the single deploy script)
deploy.ps1is the only deploy script (it replaced the old separateProvision-PhotoCar.ps1). Full end-to-end: discovers the Pi, auto-pins its SSH host key, packagesmotor-control, uploads, provisions as root, rides out the reboot, then verifies the service and runs the PWM self-test.- Run from the repo root on Windows:
.\deploy.ps1— or.\deploy.ps1 -Scanto find the Pi on the local subnet,-HostName 192.168.1.66to target an IP,-DryRunMotorto provision without driving the motor. - It also refreshes
PhotoCarTablet/pages-guide/photocar-motor.tar.gz, so pushing the tablet repo republishes the same payload to the guide site for Way 2.
Way 2 — on the car itself, over WiFi with curl (no PC needed)
The guide site hosts a bootstrap script and the full motor-control payload, so the Pi can fetch and install everything itself. In the SSH session from Step 0.5:
# 1) download the setup script from the guide site
curl -fsSL https://photocar.userinterfaces.shop/setup.sh -o /tmp/photocar-setup.sh
# 2) execute it (root required — it installs apt packages and a systemd service)
sudo bash /tmp/photocar-setup.sh
Or as a one-liner:
curl -fsSL https://photocar.userinterfaces.shop/setup.sh | sudo bash
What the install does, in order:
setup.shdownloadsphotocar-motor.tar.gzfrom the same site and extracts it to/home/dealermade/photocar-motor.- It hands off to
scripts/provision-photocar.sh provision— the exact same provisioner Way 1 runs: apt packages (bluetooth bluez nodejs npm build-essential…— the apt step takes several minutes on a Zero 2 W), PWM overlay inconfig.txt,npm install && npm run build, then installs and enablesphotocar-ble.serviceand the boot LED service. - On a fresh image the boot config changed, so the Pi reboots itself once (your SSH session drops — that's expected). After the reboot the service starts on its own; when the status LED goes solid green the car is ready to drive.
- Re-running the same two commands later is safe — it re-downloads the latest payload and updates in place.
Verifying either way
- PWM hardware self-test on the Pi:
sudo bash /home/dealermade/photocar-motor/scripts/provision-photocar.sh selftest(looks for the channel reportingenabled).deploy.ps1runs this automatically at the end. - Service up:
systemctl is-active photocar-ble.service; WiFi control port:ss -ltn | grep 7777.
Service environment (the source of truth for pins)
| Variable | Value | Meaning |
|---|---|---|
PWM_GPIO | 18 | Throttle PWM on GPIO18 / PWM0 (physical pin 12). GPIO12 is dead on this board. |
DIR_GPIO | 23 | Direction on GPIO23 (physical pin 16). |
PWM_FREQUENCY_HZ | 1000 | 1 kHz hardware PWM (period 1 ms). Keep ≤~1 kHz — the MD25HV only responds at 100% duty near 20 kHz. |
MAX_THROTTLE | 100 | Throttle is clamped 0–100%. |
WATCHDOG_TIMEOUT_MS | 1200 | Motor stops if no command arrives within 1.2 s. |
DIRECTION_CHANGE_PAUSE_MS | 20 | Ramps to 0 then pauses before reversing. |
WIFI_CONTROL_PORT / HOST | 7777 / 0.0.0.0 | WiFi TCP control listener. |
BLUETOOTH_ENABLED | 1 | BLE GATT control enabled (BCM43430, HCI 4.2). |
PHOTOCAR_REAL_MOTOR | 1 | 1 = drive real GPIO/PWM; 0 = dry run. |
{"cmd":"drive","dir":"forward","throttle":50,"seq":1}, plus stop, ping, and (when ALLOW_SHUTDOWN=1) power-off. The 1.2 s watchdog stops the motor if commands stop arriving, so a dropped link fails safe. Pack-voltage telemetry on the UI requires an ADC/voltage divider wired to the Pi (BATTERY_VOLTAGE_FILE); until then the UI marks battery as not configured.
Boot status LEDs
A single RGB LED module shows boot state: red blinks from the first seconds of kernel boot, then goes out and green comes on solid once the receiver (photocar-ble.service) is up and the car is ready to drive. If the service later stops, it falls back to blinking red. The early blink is the kernel itself — the installer adds dtoverlay=gpio-led,…,trigger=timer lines to config.txt (one reboot required after install), so red blinks long before the OS is up; photocar-bootled.service then takes over and swaps red for green when ready.
| LED | Red (R) | Green (G) | Common (-) |
|---|---|---|---|
| Status LED | pin 35 (GPIO19) | pin 37 (GPIO26) | pin 39 (GND) |
7. Assembly Steps
1. Bench-test the Pi before installing it
Image Raspberry Pi OS Bookworm with WiFi + SSH configured, then provision: either run .\deploy.ps1 from the PC, or SSH in and run curl -fsSL https://photocar.userinterfaces.shop/setup.sh | sudo bash (see §6).
Pair the tablet/controller while on USB bench power.
Confirm PWM hardware drives: bash scripts/provision-photocar.sh selftest before touching car wiring.
2. Mount the Pi and buck converter
Vacuum debris from the enclosure; mount the Pi on non-conductive standoffs on the electronics plate.
Place the buck close enough to keep the 5 V leads short; keep the Pi antenna area clear of metal and large bundles.
Keep low-current electronics away from the battery terminals and motor leads.
3. Add the fused, switched buck branch
Install the fuse close to the bus tap, not next to the Pi; verify buck input polarity before connecting power.
Set buck output to 5.1 V with a meter before plugging into the Pi.
4. Build the control harness from 1-pin pre-crimp leads
Build a single 1×3 housing for PWM + GND + DIR (GPIO18 pin 12 / GND pin 14 / GPIO23 pin 16), in the same order as the wiring table.
Tug-test each lead after insertion; if a crimp backs out, replace that lead. Label the harness before plugging it in.
Do not solder loose wires directly to the Pi header — serviceable connectors survive a moving car.
5. Connect to the MD25HV control terminal only
Land on the MD25HV green control screw terminal (GND / 5VO / PWM / DIR), not the heavy motor terminals; leave 5VO empty.
Keep existing motor screw-terminal wiring intact. After plugging in, check continuity from Pi GND to MD25HV GND.
First motor test at low throttle with the wheel lifted off the ground.
8. Bring-Up Checklist
- Selector set, E-stop released, master switch off until checks pass.
- MD25HV power polarity and buck input polarity verified with a meter.
- Buck output set and verified at 5.1 V before plugging the Pi.
- MD25HV
5VOis not connected to the Pi. - Control wiring: PWM on pin 12 (GPIO18), GND on pin 14, DIR on pin 16 (GPIO23) — one 1×3 — landing on MD25HV
PWM / GND / DIRby label. - Boot config has
dtoverlay=pwm,pin=18,func=2and the Pi has been rebooted since. provision-photocar.sh selftestpasses (PWM channel reports enabled).- Wheel lifted off the ground for the first
forward/backtest; confirm direction, then test STOP and the 1.2 s watchdog. - If direction is reversed: swap the two motor leads or flip
DIR_FORWARD_LEVEL.
Source Notes
Control-side facts (pins, PWM mode, frequency, service env, provisioning) are read from the live firmware in motor-control/ — primarily scripts/provision-photocar.sh and src/MotorController.ts. Part specifications (MD25HV, Pi Zero 2 W, buck, battery, motor, mounting plate, bus/selector/E-stop power chain) are the modeled PhotoCar parts.