Sensors

SEN0189 — Water Turbidity

Interface: ADC on GPIO 36, via voltage divider. The sensor runs from 5V and outputs up to ~4.5V; the ESP32 ADC maximum is 3.3V. Without the divider, clean-water readings clip flat and the pin can be damaged.

// sensors.cpp
float readTurbidityNTU() {
  long raw = 0;
  for (int i = 0; i < SENSOR_SAMPLES; i++) raw += analogRead(PIN_TURBIDITY);
  float vAdc = (raw / (float)SENSOR_SAMPLES) * (3.3f / 4095.0f);
  float v = vAdc * TURBIDITY_DIVIDER_RATIO;   // undo divider → true sensor volts
  float ntu = -1120.4f * v * v + 5742.3f * v - 4353.8f;  // DFRobot curve
  return constrain(ntu, 0.0f, 3000.0f);
}

The DFRobot quadratic is a starting point only, valid roughly 2.5–4.2V. It must be verified against reference samples before Testing Day — Category 1 requires ±10% accuracy. See Calibration.

NTU Classification (visualiser)

Range Classification Colour
< 40 NTU Clean Blue (#0277BD)
40–100 NTU Moderate Orange (#F57C00)
> 100 NTU Turbid Red (#C62828)

Capacitive Soil Moisture v2.0

Interface: ADC on GPIO 39, direct (output is 3.3V-safe). Lower raw reading = wetter soil.

// config.h — measure these at build time, they vary per unit
#define SOIL_DRY_VAL  2850   // ADC in dry air
#define SOIL_WET_VAL  1200   // ADC fully wet

float readSoilMoisturePct() {
  long raw = 0;
  for (int i = 0; i < SENSOR_SAMPLES; i++) raw += analogRead(PIN_SOIL_SENSE);
  raw /= SENSOR_SAMPLES;
  float pct = (float)(SOIL_DRY_VAL - raw) / (float)(SOIL_DRY_VAL - SOIL_WET_VAL) * 100.0f;
  return constrain(pct, 0.0f, 100.0f);
}

TCS34725 — Colour Sensor (1×)

Single sensor mounted ahead of the front wheels, facing down. On I2C bus 0 alongside the MPU6050.

Roles: detect water slots (blue, bright) and soil discs (reddish-brown) during autonomous mode.

// Thresholds live in config.h — calibrate on the REAL arena.
bool isWaterZone() {
  tcs.getRawData(&r, &g, &b, &c);
  if (c < 100) return false;
  return c > WATER_CLEAR_MIN && ((float)r / c) < WATER_RED_RATIO_MAX;
}

bool isSoilPatch() {
  tcs.getRawData(&r, &g, &b, &c);
  if (c < 50) return false;
  return ((float)r / c) > SOIL_RED_RATIO_MIN && ((float)b / c) < SOIL_BLUE_RATIO_MAX;
}

Sand and wet soil can false-positive under venue lighting — the thresholds (WATER_CLEAR_MIN, WATER_RED_RATIO_MAX, SOIL_RED_RATIO_MIN, SOIL_BLUE_RATIO_MAX) are estimates until measured on the actual arena.

Why the mounting position matters: water slots are recessed. The sensor must see the slot before a wheel reaches it, so the firmware can stop with the wheels clear. Detection at the sensor = stop command; the lookahead distance is the safety margin.


VL53L0X — ToF Distance Sensor

Interface: I2C bus 1 (Wire1, GPIO 16/17). It shares address 0x29 with the TCS34725, so it gets its own bus — no multiplexer needed.

Role: forward-facing wall detection in autonomous mode. The robot stops and picks a new heading when the reading drops below WALL_STOP_MM (120mm).

// initSensors()
Wire1.begin(PIN_I2C2_SDA, PIN_I2C2_SCL);
tof.setBus(&Wire1);
tof.init();
tof.startContinuous();

Reliable range 30–2000mm — ample reaction distance at competition drive speeds.