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.