# 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 } ```