Calibration
Tuning values live in the runtime config on the robot (SPIFFS
/config.json, edited from the Diag page — no reflash). Only values describing physical hardware, likeTURBIDITY_DIVIDER_RATIO, stay compile-time inhardware_pins.h. Shipped values are estimates — measure and update each one before Testing Day. Category 1 (30 pts) requires readings within ±10% of the judges' reference values; calibration is the highest-value engineering time in this project.
Run the steps in order — later steps depend on earlier ones. (Canonical checklist: arduino/CALIBRATION.md in the repo.)
| # | What | Tool | Config keys | When |
|---|---|---|---|---|
| 1 | Soil moisture endpoints | Diag → Soil probe wizard (or tests/test_soil) |
soilDryVal, soilWetVal |
Bench, any time |
| 2 | Turbidity zero + divider check | Diag → Turbidity wizard (or tests/test_turbidity) |
turbidityZeroRaw (+ verify TURBIDITY_DIVIDER_RATIO) |
Bench, before Testing Day |
| 3 | Drive speed | Diag → Motion wizard | mmPerSecAtDrive |
Arena-like surface, charged battery |
| 4 | Wheel base (turn rate) | Same wizard | wheelBaseMm |
After step 3 |
| 5 | Collision stop distance | Diag → Collision distance wizard (or tests/test_ultrasonic) |
collisionGuardOn, obstacleStopMm |
On the real arena, after step 3 |
1. Soil Moisture Endpoints
- Flash
tests/test_soil. Serial monitor @115200. - Probe in dry air (or bone-dry soil), wait for the raw value to flatten →
SOIL_DRY_VAL. - Probe in a cup of water up to the marked line (never the electronics) →
SOIL_WET_VAL. - Sanity: dry raw must be higher than wet raw (capacitive probes fall when wet). Verify air ≈ 0%, water ≈ 100% after reflashing.
2. SEN0189 Turbidity (highest priority)
Two things must be right: the divider ratio and the curve.
- Flash
tests/test_turbidity. Dunk the probe in clear water:Vsensorshould read ~4.1–4.3V and NTU near 0.Vsensorway off (e.g. half of expected) → the hardware divider isn't the configured ratio. Recompute(R_top + R_bottom) / R_bottomfrom the actual resistors and fixTURBIDITY_DIVIDER_RATIO.
- Test a murky sample (stir soil in): NTU should rise clearly.
- For absolute accuracy: prepare reference samples (clear water + 2–3 diluted-milk or standard steps of known NTU), record firmware NTU vs reference across the range.
- Within ±10% → done. Outside → refit the quadratic in
readTurbidityNTU()from your measured (voltage, NTU) points — the DFRobot curve is only trusted ~2.5–4.2V and assumes direct 5V output. - On Testing Day, note readings against the judges' reference sample and offset in analysis — don't bend the curve blind.
3. Drive Speed → mmPerSecAtDrive
Robot on battery, untethered, arena-like surface, race-day charge (open-loop speed sags with voltage).
- Phone → Diag page (🔧) → Motion calibration wizard.
- Tape-mark the floor at the robot's front edge.
- The "Run" step drives ~3s at
driveSpeedand auto-stops. - Measure start mark → front edge in mm; enter it in the wizard.
- The wizard computes
mmPerSecAtDriveand, at the end, saves it straight to the runtime config (SPIFFS) — no reflash to apply.
4. Wheel Base → wheelBaseMm
Continues in the same Motion wizard — it uses the drive speed you just measured (read live from the config, no hardcoded mirror).
- Tape an arrow on the robot; note its start direction against a floor mark.
- The "Run" step spins ~2s in place, auto-stops.
- Enter the total rotation = full turns × 360 + final offset (e.g. 2 turns + 90° = 810°). The wizard computes
wheelBaseMm. - This is the effective wheel base (includes spin friction) — expect slightly larger than the ruler measurement. "Save to editor" writes both values to the runtime config; "Spin again to verify" re-runs the check.
Verify the pair: drive a ~1m square via RC watching the x/y/h posbar readout. Back at start: x/y within ~±150mm, heading near 0°. Drift is normal and grows with jerky driving — the path log is decoration, never navigation.
5. Collision Stop Distance → obstacleStopMm
This is the safety number, and it must be larger than the robot's stopping distance — a threshold below it means the robot brakes into what it just detected.
- Measure the stopping distance: drive forward at full
driveSpeedon the arena, hit ■ STOP, measure the overshoot. Repeat 3×, take the worst. - Diag page → Collision distance wizard. Step 1: park the robot facing an arena wall at the gap you want it to hold, capture the ping. A "no echo" result means the wall is not square to the sensor or the ECHO divider is wrong — fix that before continuing.
- Step 2: enter the stopping distance. The wizard sets
obstacleStopMm= standoff + stopping distance. - Verify live: the RC page shows the ping in mm and flips to red ■ BLOCKED when the guard trips. Walk a board toward the robot and watch it cut.
The guard is optional. collisionGuardOn (checkbox in the config editor, default on) switches the intervention off for deliberate close work — creeping up to a wall, or sampling hard against an edge. Distance is still measured and shown (amber ⚠ guard off); re-enabling resets the debounce so it cannot trip on a stale reading.
Angled and soft walls reflect the ping away and read as "nothing in range", and the sensor cannot see into a recessed water slot. The guard is a backstop, not a substitute for watching the robot.
Timing Constants Reference
| Constant | Shipped | Tune when |
|---|---|---|
sampleSettleMs |
2000 | Reading unstable after arm deploy → increase (covers ~600ms arm travel + probe settle) |
sampleWindowMs / SAMPLE_TICK_MS |
3000 / 50 | Median window: ~60 readings per sample. Longer = smoother but slower |
obstacleStopMm |
120 | Collision-guard threshold — see §5 |
BRAKE_MS |
80 | Active short-brake pulse on STOP. Too short = coasting; too long = heats the driver |
OBSTACLE_PING_MS |
100 | Guard ping rate (compile-time) |
OBSTACLE_TRIP_HITS / OBSTACLE_HYST_MM |
2 / 40 | Guard debounce and hysteresis (compile-time) |
Caveats: recalibrate speed after a battery swap or heavy drain (10% voltage drop is a visible speed drop) · calibrate on the surface you'll drive on · the Diag Motion wizard reads driveSpeed/turnSpeed live from the runtime config, so changing them needs no code edit — the old hardcoded mirror in the RC page is gone.
Pre-Run Sequence (Testing Day morning)
- Full battery. Flash latest firmware.
- Power on → join
RobotAP→ RC page loads at192.168.4.1/envirobot.local. live_plot.py --port COMxon the laptop — verify sample lines parse (this is the judged output).- Dip test SEN0189 in known water → NTU plausible.
- Press soil sensor into test medium → % plausible.
- Walk a board at the robot → guard trips → reverse clears it.
- One full rehearsal sample of each type → ⏱ starts on first command → ⬇ Download JSON → drop into the visualiser.