← SHEET 02 · ASSEMBLIES HW-001
Arduino PID Motor Rig: Sim-First Design
| PART NO | HW-001 |
|---|---|
| MATL / SYSTEM | DC MOTOR + ENCODER |
| TOOLS | Python · Arduino · L298N |
A simulation-first closed-loop motor rig. A brushed DC-motor model and a discrete PID with anti-windup and a filtered derivative were tuned in Python (Kp=12, Ki=22, Kd=1) to a 90° step response of 13% overshoot, 1.45 s settling, and zero steady-state error, then mirrored 1:1 in Arduino firmware (encoder on D2/D3, L298N on D7–D9) so the control logic is validated before any purchase. The bill of materials is sourced locally for Uzbekistan, following a four-layer prototype plan from sim to hardware. Honest status: simulation, firmware, and BOM are complete; physical assembly is pending parts.
OVERVIEW & MOTIVATION
Most hobby PID builds start at the breadboard and discover the control problem by trial and
error. This one inverts that order: design and tune the whole closed loop in software first, so
that when parts are bought the gains and the wiring logic are already known to work. The plan
(PROTOTYPE_PLAN.md) sets out four layers — control design in Python, firmware written in the
same discrete-PID shape as the sim, a virtual-circuit check (Wokwi/Proteus/SimulIDE), then real
hardware — and each layer is meant to de-risk the next before money is spent. The target plant is
a closed-loop DC-gearmotor position control: drive a motor to a commanded angle, hold it there
under load, using quadrature-encoder feedback.
HARDWARE & BILL OF MATERIALS
The BOM (PID_parts_uzbekistan.md, build_pid_bom.py) is priced against the Uzbek market
(Tashkent OLX listings, Uzum/Glotr marketplaces, and the roboshop Telegram channel), compiled
June 2026 at ~1 USD ≈ 12,800 so’m. The two parts that actually define the project — the encoder
motor and the H-bridge driver — are flagged as not reliably stocked on OLX and sourced from
marketplaces or Telegram sellers instead. Estimated total for individual parts: ~400,000–570,000
so’m (≈ $31–45); a bundled Arduino starter kit (192,216 so’m, Glotr) can replace the Uno +
breadboard + jumpers + USB line items.
| ITEM | SPEC | ROLE |
|---|---|---|
| Arduino Uno R3 | ATmega328-based, ~90,800 so’m confirmed (Glotr) | controller, runs the discrete PID loop |
| DC gearmotor w/ encoder | JGB37-520, 12 V, ~200,000–350,000 so’m (est) | plant + feedback sensor — the essential, hardest-to-source part |
| L298N motor driver | dual H-bridge, ~30,000–50,000 so’m (est) | drives motor from PWM + direction pins |
| 12 V 2 A power supply | ~32,130 so’m confirmed (sts-hik) | motor power rail |
| Breadboard | 830-point, ~20,000–40,000 so’m (est) | solderless wiring |
| Dupont jumper wires | 40+ M-M/M-F, ~15,000–30,000 so’m (est) | interconnects |
| USB cable (A–B) | ~15,000–30,000 so’m (est), often bundled | programming the Uno |
| Potentiometer (optional) | ~5,000–10,000 so’m (est) | live setpoint knob |
CONTROL DESIGN
The controller is a discrete PID with derivative-on-measurement and a low-pass filter on the D
term, plus conditional integral anti-windup — the same structure in both sim/motor_pid_sim.py
and firmware/arduino_pid.ino, running at a fixed 200 Hz loop (DT = 0.005). The sim models a
brushed DC motor’s electrical (R, L, back-EMF) and mechanical (inertia, viscous friction, constant
load torque) dynamics and steps P, PD, and full PID gain sets through a 90° position command under
load. Anti-windup only integrates error while the output isn’t saturating in the same direction as
the error; the derivative is filtered with time constant TAU_D = 0.02 s to avoid amplifying
encoder noise. The firmware mirrors this line-for-line: encoder A/B on D2 (interrupt) and D3
(quadrature x2 decode), L298N direction pins on D7/D8 and PWM on D9, COUNTS_PER_REV left as a
placeholder to be set from the actual encoder/gear ratio, output clamped to the 0–255 PWM range.
The same repo also carries two adjacent control-system sims built with the same rigor: an LQR-
balanced single-wheel inverted pendulum (balancer/balancer_pid.py, gains from a linearized
continuous-time Riccati solve, not PID despite the filename) and a 2-link IK arm
(arm/ik_arm.py) whose joint-angle profile was designed to drive a Fusion 360 animation.
STATUS & RESULTS
Simulated (verified): the step response comparison below, from sim/results/pid_step_response.png
and the sim’s printed metrics, for a 90° step with a constant load torque:
| CONTROLLER | OVERSHOOT | SETTLING (2%) | STEADY-STATE ERROR |
|---|---|---|---|
| P (Kp=8) | 5.8% | offset, no settle | ~2.9° |
| PD (Kp=12, Kd=0.8) | 0% | offset, no settle | ~1.9° |
| PID (Kp=12, Ki=22, Kd=1.0) | 13% | 1.45 s | ~0° |
Written, not yet bench-tested: firmware/arduino_pid.ino compiles against the same control
logic and starts from the sim’s gains, but has not been run on real hardware — no encoder counts-
per-rev has been measured, no real-motor step response has been logged. Planned: Layer 3
(virtual-circuit check in Wokwi/Proteus/SimulIDE) and Layer 4 (bench build, re-tuning, and a
predicted-vs-measured step-response comparison) are both still ahead. Parts are priced and
sourced but not confirmed purchased. This is honestly a software-verified control design with a
firmware port awaiting hardware.
USE CASES & APPLICATIONS
PID position/speed loops are the default controller in industrial motion: servo axes on CNC machines and 3D printers, motor-speed regulation in conveyors and fans, robot-joint position control, and — cascaded into inner/outer loops — drone attitude and altitude hold. Temperature- control loops (ovens, 3D-printer hotends, HVAC) use the same three-term structure on a slower plant. The anti-windup and derivative-filtering details built into this sim and firmware are the same fixes production controllers need once output saturates or feedback is noisy.
FILES & REPRODUCTION
sim/motor_pid_sim.py— runpython motor_pid_sim.pyfromsim/; edit gains/params at the top, re-run, inspectresults/pid_step_response.pngand the printed metrics table.firmware/arduino_pid.ino— flash to an Uno/Nano once wired per the header comment (encoder A/B → D2/D3, L298N ENA/IN1/IN2 → D9/D7/D8); setCOUNTS_PER_REVfor the actual encoder/gearbox.PROTOTYPE_PLAN.md— the four-layer plan and the sim-to-hardware parameter mapping.PID_parts_uzbekistan.md,build_pid_bom.py— sourced BOM and the script that generates the priced, color-coded parts spreadsheet.balancer/balancer_pid.py,arm/ik_arm.py— adjacent control-sim explorations (LQR balance, IK-driven arm animation) built with the same Python/matplotlib animation pipeline.