# ESP32 Test
```cpp=
#include <Arduino.h>
#include <Wire.h>
#include <vl53l4cx_class.h>
#include <Adafruit_LSM6DSOX.h>
#include <Adafruit_LIS3MDL.h>
#include <Adafruit_AHRS.h>
//#define TOF_DEBUG 1
#define USE_XSHUT_PIN 1
// ==== Pin Assignments ====
#if USE_XSHUT_PIN
constexpr uint8_t kXshutPin = 4; // ToF XSHUT pin
#endif
constexpr uint8_t kSdaPin = 19; // Sensor I2C SDA
constexpr uint8_t kSclPin = 18; // Sensor I2C SCL
constexpr uint8_t kPiSdaPin = 21; // RPi-ESP32 I2C SDA
constexpr uint8_t kPiSclPin = 22; // RPi-ESP32 I2C SCL
constexpr uint8_t kSlaveAddr = 0x54;// ESP32 I2C slave address for RPi
constexpr uint8_t kMotorPwmPin = 25;
constexpr uint8_t kMotorDirPin = 26;
constexpr uint8_t kMotorBrakePin = 27;
constexpr uint8_t kEncoderA_Pin = 35;
constexpr uint8_t kEncoderB_Pin = 34;
// ==== Constants ====
constexpr uint32_t kI2cFreqHz = 400000;
constexpr uint32_t kSerialBaud = 115200;
constexpr uint32_t kMotorPwmFreq = 5000;
constexpr uint8_t kMotorPwmRes = 8;
constexpr float kTicksPerRev = 100.0;
// ==== ToF-LIDAR Globals ====
TwoWire *activeBus = &Wire;
VL53L4CX tof;
enum RangeMode : uint8_t {
MODE_SHORT = VL53L4CX_DISTANCEMODE_SHORT,
MODE_MEDIUM = VL53L4CX_DISTANCEMODE_MEDIUM,
MODE_LONG = VL53L4CX_DISTANCEMODE_LONG
};
static RangeMode gCurrentMode = MODE_SHORT;
static uint8_t gNoTargetFrames = 0;
static uint8_t gStableTargetFrames = 0;
constexpr uint8_t kNoTargetThreshold = 15;
constexpr uint8_t kStableTargetThreshold = 8;
constexpr int16_t kShortMaxMm = 1200;
constexpr int16_t kMediumMaxMm = 3500;
static int16_t gPrevDistanceMm = -1;
static uint8_t gConsecutiveReadErrors = 0;
constexpr uint8_t kReadErrorThreshold = 4;
// ==== I2C Slave Globals ====
volatile uint16_t gLatestDistanceMm = 0;
volatile bool gHasValidDistance = false;
volatile unsigned long gLastPiRequestMs = 0;
constexpr uint16_t kNoTargetValue = 0xFFFF;
// ==== Encoder Globals ====
volatile long gEncoderTicks = 0;
unsigned long gLastRpsTime = 0;
long gLastRpsTicks = 0;
// ==== IMU + Sensor Fusion Globals ====
Adafruit_LSM6DSOX sox;
Adafruit_LIS3MDL lis3mdl;
Adafruit_NXPSensorFusion filter;
float gYaw = 0, gPitch = 0, gRoll = 0;
// ==== Function Prototypes ====
void onPiRequest();
void onPiReceive(int);
void maybeReportMissingPi();
void IRAM_ATTR updateEncoder();
float getRPS();
void brakeAndWait(float stopThresholdRPS = 0.2);
static void applyMode(RangeMode mode) {
tof.VL53L4CX_Off();
uint32_t budget_us = 8000;
switch (mode) {
case MODE_SHORT: budget_us = 8000; break;
case MODE_MEDIUM: budget_us = 16000; break;
case MODE_LONG: budget_us = 33000; break;
}
tof.VL53L4CX_SetDistanceMode(static_cast<uint16_t>(mode));
tof.VL53L4CX_SetMeasurementTimingBudgetMicroSeconds(budget_us);
tof.VL53L4CX_StartMeasurement();
}
#if USE_XSHUT_PIN
static bool sensorHardReset() {
digitalWrite(kXshutPin, LOW);
delay(10);
digitalWrite(kXshutPin, HIGH);
delay(50);
if (tof.InitSensor(VL53L4CX_DEFAULT_DEVICE_ADDRESS) != VL53L4CX_ERROR_NONE) {
Serial.println(F("[ToF] Re-init after reset failed"));
return false;
}
applyMode(gCurrentMode);
Serial.println(F("[ToF] Sensor recovered via XSHUT reset"));
return true;
}
#endif
static const __FlashStringHelper *statusText(uint8_t s) {
switch (s) {
case VL53L4CX_RANGESTATUS_RANGE_VALID: return F("VALID");
case VL53L4CX_RANGESTATUS_SIGMA_FAIL: return F("SIGMA");
case VL53L4CX_RANGESTATUS_OUTOFBOUNDS_FAIL: return F("OOB");
case VL53L4CX_RANGESTATUS_HARDWARE_FAIL: return F("HW_FAIL");
case VL53L4CX_RANGESTATUS_RANGE_VALID_MIN_RANGE_CLIPPED: return F("CLIPPED");
default: return F("UNKNOWN");
}
}
static bool probeDevice(uint8_t address, TwoWire &bus) {
bus.beginTransmission(address);
return (bus.endTransmission() == 0);
}
void onPiRequest() {
uint16_t d = gHasValidDistance ? gLatestDistanceMm : kNoTargetValue;
Wire1.write(static_cast<uint8_t>(d >> 8));
Wire1.write(static_cast<uint8_t>(d & 0xFF));
gLastPiRequestMs = millis();
}
void onPiReceive(int) {
while (Wire1.available()) Wire1.read();
}
void maybeReportMissingPi() {
static unsigned long lastWarn = 0;
const unsigned long now = millis();
const unsigned long kTimeoutMs = 5000;
if (now - gLastPiRequestMs > kTimeoutMs && now - lastWarn > kTimeoutMs) {
Serial.println(F("[I2C] No Pi requests detected – continuing measurements"));
lastWarn = now;
}
}
// ====== Motor & Encoder Functions =======
void IRAM_ATTR updateEncoder() {
if (digitalRead(kEncoderB_Pin) == LOW) {
gEncoderTicks++;
} else {
gEncoderTicks--;
}
}
float getRPS() {
unsigned long currentTime = millis();
long currentTicks = gEncoderTicks;
unsigned long deltaTime = currentTime - gLastRpsTime;
if (deltaTime == 0) return 0.0;
long deltaTicks = currentTicks - gLastRpsTicks;
gLastRpsTime = currentTime;
gLastRpsTicks = currentTicks;
return (float)abs(deltaTicks) / kTicksPerRev / ((float)deltaTime / 1000.0);
}
void brakeAndWait(float stopThresholdRPS) {
digitalWrite(kMotorDirPin, LOW);
digitalWrite(kMotorBrakePin, LOW);
ledcWrite(kMotorPwmPin, 0);
delay(100);
while (true) {
if (getRPS() <= stopThresholdRPS) break;
delay(50);
}
delay(100);
}
void setup() {
Serial.begin(kSerialBaud);
while (!Serial) { ; }
Serial.println(F("Booting Combined ToF-LIDAR + IMU Controller..."));
// --- Motor & Encoder Setup ---
pinMode(kMotorDirPin, OUTPUT);
pinMode(kMotorBrakePin, OUTPUT);
pinMode(kEncoderA_Pin, INPUT_PULLUP);
pinMode(kEncoderB_Pin, INPUT_PULLUP);
digitalWrite(kMotorDirPin, HIGH);
digitalWrite(kMotorBrakePin, LOW);
attachInterrupt(digitalPinToInterrupt(kEncoderA_Pin), updateEncoder, RISING);
ledcAttach(kMotorPwmPin, kMotorPwmFreq, kMotorPwmRes);
ledcWrite(kMotorPwmPin, 0);
brakeAndWait();
// --- ToF Sensor Setup ---
#if USE_XSHUT_PIN
pinMode(kXshutPin, OUTPUT);
digitalWrite(kXshutPin, LOW);
delay(10);
digitalWrite(kXshutPin, HIGH);
delay(50);
#endif
Wire.begin(kSdaPin, kSclPin);
Wire.setClock(kI2cFreqHz);
// --- ToF init ---
if (!probeDevice(VL53L4CX_DEFAULT_DEVICE_ADDRESS >> 1, Wire)) {
Serial.println(F("[ToF] Sensor not detected on primary I2C bus. Halting."));
while (true) delay(1000);
}
tof.setI2cDevice(activeBus);
if (tof.InitSensor(VL53L4CX_DEFAULT_DEVICE_ADDRESS) != VL53L4CX_ERROR_NONE) {
Serial.println(F("[ToF] InitSensor failed. Halting."));
while (true) delay(1000);
}
applyMode(gCurrentMode);
// --- IMU Init (LSM6DSOX + LIS3MDL) ---
if (!sox.begin_I2C(0x6A, &Wire)) {
Serial.println("Failed to find LSM6DSOX chip");
while (1) delay(10);
}
if (!lis3mdl.begin_I2C(0x1C, &Wire)) {
Serial.println("Failed to find LIS3MDL chip");
while (1) delay(10);
}
sox.setAccelRange(LSM6DS_ACCEL_RANGE_4_G);
sox.setGyroRange(LSM6DS_GYRO_RANGE_500_DPS);
lis3mdl.setRange(LIS3MDL_RANGE_4_GAUSS);
filter.begin(200); // Sensor fusion update rate (Hz)
// --- RPi I2C Slave Setup ---
Wire1.begin(kSlaveAddr, kPiSdaPin, kPiSclPin, kI2cFreqHz);
Wire1.onRequest(onPiRequest);
Wire1.onReceive(onPiReceive);
gLastRpsTime = millis();
Serial.println(F("──────────────────────────────"));
Serial.println(F(" System Ready + IMU "));
Serial.println(F("──────────────────────────────"));
}
void loop() {
// --- ToF-LIDAR (distance & motor PWM) ---
uint8_t newData = 0;
if (tof.VL53L4CX_GetMeasurementDataReady(&newData) != 0 || !newData) {
// No new ToF data yet; IMU/fusion can still run!
goto IMU_FUSION;
}
VL53L4CX_MultiRangingData_t data;
if (tof.VL53L4CX_GetMultiRangingData(&data) != 0) {
gConsecutiveReadErrors++;
#if TOF_DEBUG
Serial.println(F("[ToF] Read error"));
#endif
#if USE_XSHUT_PIN
if (gConsecutiveReadErrors >= kReadErrorThreshold) {
sensorHardReset();
gConsecutiveReadErrors = 0;
}
#endif
tof.VL53L4CX_ClearInterruptAndStartMeasurement();
goto IMU_FUSION;
}
gConsecutiveReadErrors = 0;
if (data.NumberOfObjectsFound == 0) {
gNoTargetFrames++;
gHasValidDistance = false;
ledcWrite(kMotorPwmPin, 0);
if (gNoTargetFrames > kNoTargetThreshold && gCurrentMode != MODE_LONG) {
gCurrentMode = (gCurrentMode == MODE_SHORT) ? MODE_MEDIUM : MODE_LONG;
applyMode(gCurrentMode);
gNoTargetFrames = 0;
tof.VL53L4CX_ClearInterruptAndStartMeasurement();
goto IMU_FUSION;
}
}
int bestIdx = -1;
int16_t bestDelta = INT16_MAX;
for (int i = 0; i < data.NumberOfObjectsFound; ++i) {
const auto &r = data.RangeData[i];
int16_t mm = r.RangeMilliMeter;
if (gPrevDistanceMm < 0) {
if (bestIdx < 0 || mm < data.RangeData[bestIdx].RangeMilliMeter) {
bestIdx = i;
}
} else {
int16_t delta = abs(mm - gPrevDistanceMm);
if (delta < bestDelta) {
bestDelta = delta;
bestIdx = i;
}
}
}
if (bestIdx >= 0) {
const auto &best = data.RangeData[bestIdx];
bool modeChanged = false;
int16_t distMm = best.RangeMilliMeter;
if (gCurrentMode == MODE_LONG && distMm < kMediumMaxMm) {
bool wantShort = (distMm < kShortMaxMm);
gStableTargetFrames++;
if (gStableTargetFrames > kStableTargetThreshold) {
gCurrentMode = wantShort ? MODE_SHORT : MODE_MEDIUM;
applyMode(gCurrentMode);
modeChanged = true;
gStableTargetFrames = 0;
}
} else if (gCurrentMode == MODE_MEDIUM && distMm < kShortMaxMm) {
gStableTargetFrames++;
if (gStableTargetFrames > kStableTargetThreshold) {
gCurrentMode = MODE_SHORT;
applyMode(gCurrentMode);
modeChanged = true;
gStableTargetFrames = 0;
}
} else {
gStableTargetFrames = 0;
}
gNoTargetFrames = 0;
if (modeChanged) {
tof.VL53L4CX_ClearInterruptAndStartMeasurement();
goto IMU_FUSION;
}
if (best.RangeStatus == VL53L4CX_RANGESTATUS_RANGE_VALID ||
best.RangeStatus == VL53L4CX_RANGESTATUS_RANGE_VALID_MIN_RANGE_CLIPPED ||
best.RangeStatus == VL53L4CX_RANGESTATUS_RANGE_VALID_MERGED_PULSE)
{
Serial.print(static_cast<uint16_t>(best.RangeMilliMeter));
Serial.print(F(" mm"));
const int16_t min_dist_mm = 50;
const int16_t max_dist_mm = 400;
long pwm_value = map(distMm, min_dist_mm, max_dist_mm, 255, 0);
pwm_value = constrain(pwm_value, 0, 255);
ledcWrite(kMotorPwmPin, pwm_value);
Serial.print(F(" -> PWM: "));
Serial.print(pwm_value);
float currentRPS = getRPS();
Serial.print(F(" | RPS: "));
Serial.print(currentRPS, 2);
gPrevDistanceMm = best.RangeMilliMeter;
gLatestDistanceMm = best.RangeMilliMeter;
gHasValidDistance = true;
Serial.print(F(" | "));
} else {
ledcWrite(kMotorPwmPin, 0);
gHasValidDistance = false;
Serial.print(F("-- mm -> PWM: 0 | RPS: -- | "));
}
}
tof.VL53L4CX_ClearInterruptAndStartMeasurement();
maybeReportMissingPi();
IMU_FUSION:
// --- IMU Read + Fusion ---
sensors_event_t accel, gyro, temp, mag;
sox.getEvent(&accel, &gyro, &temp);
lis3mdl.getEvent(&mag);
// Convert gyro from rad/s to deg/s for fusion
float gx = gyro.gyro.x * SENSORS_RADS_TO_DPS;
float gy = gyro.gyro.y * SENSORS_RADS_TO_DPS;
float gz = gyro.gyro.z * SENSORS_RADS_TO_DPS;
filter.update(
gx, gy, gz,
accel.acceleration.x, accel.acceleration.y, accel.acceleration.z,
mag.magnetic.x, mag.magnetic.y, mag.magnetic.z
);
gYaw = filter.getYaw(); if (gYaw < 0) gYaw += 360.0;
gPitch = filter.getPitch(); if (gPitch < 0) gPitch += 360.0;
gRoll = filter.getRoll(); if (gRoll < 0) gRoll += 360.0;
Serial.print("Yaw: "); Serial.print(gYaw, 2); Serial.print(" deg, ");
Serial.print("Pitch: "); Serial.print(gPitch, 2); Serial.print(" deg, ");
Serial.print("Roll: "); Serial.print(gRoll, 2); Serial.println(" deg");
delay(10); // ~100Hz update
}
```