Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 1 | #include <stdio.h> |
| 2 | |
| 3 | #include <algorithm> |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 4 | #include <cmath> |
Ravago Jones | 02773c9 | 2023-02-26 13:19:54 -0800 | [diff] [blame^] | 5 | #include <limits> |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 6 | |
Ravago Jones | 02773c9 | 2023-02-26 13:19:54 -0800 | [diff] [blame^] | 7 | #include "core/VL53L1X_api.h" |
| 8 | #include "core/VL53L1X_calibration.h" |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 9 | #include "hardware/clocks.h" |
| 10 | #include "hardware/i2c.h" |
| 11 | #include "hardware/pwm.h" |
| 12 | #include "hardware/watchdog.h" |
| 13 | #include "pico/bootrom.h" |
| 14 | #include "pico/stdlib.h" |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 15 | |
| 16 | namespace y2023 { |
| 17 | namespace tof_controller { |
| 18 | |
| 19 | static constexpr uint kI2CBaudrate = 100000; |
| 20 | static constexpr uint16_t kExpectedSensorId = 0xEBAA; |
| 21 | |
| 22 | static constexpr uint16_t kTimingBudgetMs = 33; // 15 ms is fastest |
| 23 | static constexpr double kWatchdogTimeoutMs = 2000; |
| 24 | static constexpr int64_t kSensorBootTimeoutMs = 3; |
| 25 | |
| 26 | static constexpr double kSensorSeparation = 0.45; |
| 27 | static constexpr int16_t kSensorOffsetMM = -22; |
| 28 | |
| 29 | /* |
| 30 | // Cone base |
| 31 | static constexpr double kMaxObstructionWidth = 0.22 + 0.07; |
| 32 | // cone tip |
| 33 | static constexpr double kMinObstructionWidth = 0.044 + 0.07; |
| 34 | */ |
| 35 | |
| 36 | namespace sensor1 { |
| 37 | static constexpr uint kPinSCL = 1; |
| 38 | static constexpr uint kPinSDA = 0; |
| 39 | |
| 40 | static constexpr uint kPinStatusGreen = 3; |
| 41 | static constexpr uint kPinStatusRed = 5; |
| 42 | static constexpr uint kPinStatusPWM = 16; |
| 43 | |
| 44 | static constexpr uint kPinInterrupt = 8; |
| 45 | static constexpr uint kPinShutdown = 9; |
| 46 | |
| 47 | static constexpr uint kPinOutput = 2; |
| 48 | } // namespace sensor1 |
| 49 | |
| 50 | namespace sensor2 { |
| 51 | static constexpr uint kPinSCL = 26; |
| 52 | static constexpr uint kPinSDA = 27; |
| 53 | |
| 54 | static constexpr uint kPinStatusGreen = 14; |
| 55 | static constexpr uint kPinStatusRed = 13; |
| 56 | static constexpr uint kPinStatusPWM = 17; |
| 57 | |
| 58 | static constexpr uint kPinInterrupt = 22; |
| 59 | static constexpr uint kPinShutdown = 28; |
| 60 | |
| 61 | static constexpr uint kPinOutput = 4; |
| 62 | } // namespace sensor2 |
| 63 | |
| 64 | class SignalWriter { |
| 65 | public: |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 66 | // PWM counts to this before wrapping |
| 67 | static constexpr uint16_t kPWMTop = 62499; |
| 68 | static constexpr int kPWMFreqHz = 200; |
| 69 | |
| 70 | SignalWriter(uint pin) : pin_(pin) { |
| 71 | gpio_init(pin); |
| 72 | gpio_set_function(pin, GPIO_FUNC_PWM); |
| 73 | |
| 74 | uint slice_num = pwm_gpio_to_slice_num(pin); |
| 75 | pwm_set_enabled(slice_num, true); |
| 76 | |
| 77 | // TODO(Ravago): Verify that this still works the same as the imu board |
| 78 | |
| 79 | /* frequency_pwm = f_sys / ((TOP + 1) * (CSR_PHASE_CORRECT + 1) * (DIV_INT + |
| 80 | * DIV_FRAC / 16)) |
| 81 | * |
| 82 | * f_sys = 125 mhz |
| 83 | * CSR_PHASE_CORRECT = 0; no phase correct |
| 84 | * TARGET_FREQ = 200 hz |
| 85 | * |
| 86 | * 125 mhz / x = 200 hz * 62500 |
| 87 | * |
| 88 | * TOP = 62499 |
| 89 | * DIV_INT = 10 |
| 90 | */ |
| 91 | |
| 92 | float divisor = clock_get_hz(clk_sys) / (kPWMTop + 1) / kPWMFreqHz; |
| 93 | |
| 94 | pwm_config cfg = pwm_get_default_config(); |
| 95 | pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_FREE_RUNNING); |
| 96 | pwm_config_set_clkdiv(&cfg, divisor); |
| 97 | pwm_config_set_wrap(&cfg, kPWMTop); |
| 98 | |
| 99 | pwm_init(slice_num, &cfg, true); |
| 100 | |
| 101 | Write(); |
| 102 | } |
| 103 | |
| 104 | void SetValue(double value) { |
| 105 | value_ = std::clamp(value, 0.0, 1.0); |
| 106 | Write(); |
| 107 | } |
| 108 | |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 109 | private: |
| 110 | void Write() { |
Ravago Jones | 02773c9 | 2023-02-26 13:19:54 -0800 | [diff] [blame^] | 111 | uint16_t level = value_ * kPWMTop; |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 112 | |
| 113 | pwm_set_gpio_level(pin_, level); |
| 114 | } |
| 115 | |
| 116 | double value_ = 0.0; |
| 117 | uint pin_; |
| 118 | bool enabled_ = true; |
| 119 | }; |
| 120 | |
| 121 | class BrightnessLED { |
| 122 | public: |
| 123 | BrightnessLED(uint pin) : pin_(pin) { |
| 124 | gpio_init(pin); |
| 125 | gpio_set_function(pin, GPIO_FUNC_PWM); |
| 126 | |
| 127 | uint slice_num = pwm_gpio_to_slice_num(pin); |
| 128 | pwm_set_enabled(slice_num, true); |
| 129 | |
| 130 | Write(); |
| 131 | } |
| 132 | |
| 133 | void SetValue(double value) { |
| 134 | value_ = std::clamp(value, 0.0, 1.0); |
| 135 | Write(); |
| 136 | } |
| 137 | |
| 138 | private: |
| 139 | void Write() { |
| 140 | uint16_t level = value_ * std::numeric_limits<uint16_t>::max(); |
| 141 | pwm_set_gpio_level(pin_, level); |
| 142 | } |
| 143 | |
| 144 | double value_ = 0; |
| 145 | uint pin_; |
| 146 | }; |
| 147 | |
| 148 | class StatusLED { |
| 149 | public: |
| 150 | StatusLED(uint pin_green, uint pin_red) |
| 151 | : pin_green_(pin_green), pin_red_(pin_red) { |
| 152 | gpio_init(pin_green); |
| 153 | gpio_init(pin_red); |
| 154 | gpio_set_dir(pin_green, GPIO_OUT); |
| 155 | gpio_set_dir(pin_red, GPIO_OUT); |
| 156 | |
| 157 | SetError(); |
| 158 | } |
| 159 | |
| 160 | void SetOk() { Set(true); } |
| 161 | |
| 162 | void SetError() { Set(false); } |
| 163 | |
| 164 | void Set(bool is_good) { |
| 165 | status_ok_ = is_good; |
| 166 | Write(); |
| 167 | } |
| 168 | |
| 169 | void Toggle() { |
| 170 | status_ok_ = !status_ok_; |
| 171 | Write(); |
| 172 | } |
| 173 | |
| 174 | bool get_status() { return status_ok_; } |
| 175 | |
| 176 | private: |
| 177 | void Write() { |
| 178 | if (status_ok_) { |
| 179 | gpio_put(pin_green_, true); |
| 180 | gpio_put(pin_red_, false); |
| 181 | } else { |
| 182 | gpio_put(pin_green_, false); |
| 183 | gpio_put(pin_red_, true); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | uint pin_green_, pin_red_; |
| 188 | bool status_ok_ = false; |
| 189 | }; |
| 190 | |
| 191 | class VL53L1X { |
| 192 | public: |
| 193 | VL53L1X(int id, StatusLED status_light, BrightnessLED brightness, |
| 194 | uint interrupt_pin, uint shutdown_pin) |
| 195 | : id_(id), |
| 196 | status_light_(status_light), |
| 197 | brightness_(brightness), |
| 198 | interrupt_pin_(interrupt_pin), |
| 199 | shutdown_pin_(shutdown_pin) { |
| 200 | gpio_init(interrupt_pin); |
| 201 | gpio_set_dir(interrupt_pin, GPIO_IN); |
| 202 | |
| 203 | gpio_init(shutdown_pin); |
| 204 | gpio_set_dir(shutdown_pin, GPIO_OUT); |
| 205 | gpio_put(shutdown_pin, true); |
| 206 | |
| 207 | InitSensor(); |
| 208 | } |
| 209 | |
| 210 | void WaitForData() { |
| 211 | uint8_t data_ready = false; |
| 212 | while (!data_ready) { |
| 213 | CheckOk(VL53L1X_CheckForDataReady(id_, &data_ready)); |
| 214 | |
| 215 | if (errors > 0) { |
| 216 | // don't block the main loop if we're in an errored state |
| 217 | break; |
| 218 | } |
| 219 | |
| 220 | sleep_us(1); |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | VL53L1X_Result_t Read() { |
| 225 | VL53L1X_Result_t result; |
| 226 | |
| 227 | CheckOk(VL53L1X_GetResult(id_, &result)); |
| 228 | |
| 229 | double meters = static_cast<double>(result.Distance) * 0.001; |
| 230 | brightness_.SetValue(meters / kSensorSeparation); |
| 231 | |
| 232 | return result; |
| 233 | } |
| 234 | |
| 235 | void Stop() { |
| 236 | CheckOk(VL53L1X_StopRanging(id_)); |
| 237 | |
| 238 | // StopRanging command is checked when the interrupt is cleared |
| 239 | CheckOk(VL53L1X_ClearInterrupt(id_)); |
| 240 | |
| 241 | // wait for it to actually turn off the light so it doesn't interfere with |
| 242 | // the other sensor |
| 243 | sleep_ms(3); |
| 244 | } |
| 245 | |
| 246 | void Start() { CheckOk(VL53L1X_StartRanging(id_)); } |
| 247 | |
| 248 | // Try to reinitialize the sensor if it is in a bad state (eg. lost power |
| 249 | // but is now reconnected) |
| 250 | void MaybeAttemptRecovery() { |
| 251 | if (errors > 0 && consecutive_errors == 0) { |
| 252 | // it's probably back |
| 253 | printf("Attempting to recover from an errored state on sensor %d.\n", |
| 254 | id_); |
| 255 | printf("Previously had %d total errors\n", errors); |
| 256 | |
| 257 | // Reboot to put it in a known state. |
| 258 | gpio_put(shutdown_pin_, false); |
| 259 | sleep_ms(500); |
| 260 | gpio_put(shutdown_pin_, true); |
| 261 | |
| 262 | InitSensor(); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | int errors = 0; |
| 267 | int consecutive_errors = 0; |
| 268 | |
| 269 | private: |
| 270 | void InitSensor() { |
| 271 | printf("Waiting for sensor %d to boot.\n", id_); |
| 272 | |
| 273 | absolute_time_t start_time = get_absolute_time(); |
| 274 | uint8_t boot_state = 0; |
| 275 | while (boot_state == 0) { |
| 276 | CheckOk(VL53L1X_BootState(id_, &boot_state)); |
| 277 | |
| 278 | int64_t diff = absolute_time_diff_us(start_time, get_absolute_time()); |
| 279 | |
| 280 | if (diff > (kSensorBootTimeoutMs * 1000)) { |
| 281 | printf("Timed out after %lld us\n", diff); |
| 282 | return; |
| 283 | } |
| 284 | |
| 285 | sleep_us(1); |
| 286 | } |
| 287 | printf("Bootstate: %d\n", boot_state); |
| 288 | |
| 289 | int status = 0; |
| 290 | |
| 291 | printf("Getting sensor id\n"); |
| 292 | // Validate sensor model ID and Type |
| 293 | uint16_t sensor_id; |
| 294 | status += VL53L1X_GetSensorId(id_, &sensor_id); |
| 295 | if (sensor_id != kExpectedSensorId) { // Bad connection, wrong chip, etc |
| 296 | printf("Bad sensor id %x, continuing anyways\n", sensor_id); |
| 297 | status_light_.SetError(); |
| 298 | } |
| 299 | printf("Got sensor id: %x\n", sensor_id); |
| 300 | |
| 301 | // SensorInit will cause the processor to hang and hit the watchdog if the |
| 302 | // sensor is in a bad state |
| 303 | status += VL53L1X_SensorInit(id_); |
| 304 | |
| 305 | status += VL53L1X_SetDistanceMode(id_, 1); // Set distance mode to short |
| 306 | status += VL53L1X_SetTimingBudgetInMs(id_, kTimingBudgetMs); |
| 307 | status += VL53L1X_SetInterMeasurementInMs(id_, kTimingBudgetMs); |
| 308 | status += VL53L1X_SetOffset(id_, kSensorOffsetMM); |
| 309 | status += VL53L1X_StartRanging(id_); |
| 310 | |
| 311 | if (status == 0) { |
| 312 | printf("Successfully configured sensor %d\n", id_); |
| 313 | |
| 314 | status_light_.SetOk(); |
| 315 | errors = 0; |
| 316 | consecutive_errors = 0; |
| 317 | } else { |
| 318 | printf("Failed to configure sensor %d\n", id_); |
| 319 | } |
| 320 | } |
| 321 | |
| 322 | void CheckOk(VL53L1X_ERROR error) { |
| 323 | if (error == 0) { |
| 324 | consecutive_errors = 0; |
| 325 | return; |
| 326 | } |
| 327 | |
| 328 | consecutive_errors++; |
| 329 | errors++; |
| 330 | |
| 331 | status_light_.SetError(); |
| 332 | } |
| 333 | |
| 334 | // id of the sensor ie. sensor 1 or sensor 2 |
| 335 | int id_; |
| 336 | StatusLED status_light_; |
| 337 | BrightnessLED brightness_; |
| 338 | uint interrupt_pin_; |
| 339 | uint shutdown_pin_; |
| 340 | }; |
| 341 | |
| 342 | int main() { |
| 343 | stdio_init_all(); |
| 344 | |
| 345 | if (watchdog_caused_reboot()) { |
| 346 | printf("Rebooted by Watchdog!\n"); |
| 347 | } else { |
| 348 | printf("Clean boot\n"); |
| 349 | } |
| 350 | |
| 351 | // Enable the watchdog, requiring the watchdog to be updated every 100ms or |
| 352 | // the chip will reboot second arg is pause on debug which means the watchdog |
| 353 | // will pause when stepping through code |
| 354 | watchdog_enable(kWatchdogTimeoutMs, 1); |
| 355 | |
| 356 | i2c_init(i2c0, kI2CBaudrate); |
| 357 | gpio_set_function(sensor1::kPinSCL, GPIO_FUNC_I2C); |
| 358 | gpio_set_function(sensor1::kPinSDA, GPIO_FUNC_I2C); |
| 359 | |
| 360 | i2c_init(i2c1, kI2CBaudrate); |
| 361 | gpio_set_function(sensor2::kPinSCL, GPIO_FUNC_I2C); |
| 362 | gpio_set_function(sensor2::kPinSDA, GPIO_FUNC_I2C); |
| 363 | |
| 364 | StatusLED status_light1(sensor1::kPinStatusGreen, sensor1::kPinStatusRed); |
| 365 | StatusLED status_light2(sensor2::kPinStatusGreen, sensor2::kPinStatusRed); |
| 366 | BrightnessLED dist_light1(sensor1::kPinStatusPWM); |
| 367 | BrightnessLED dist_light2(sensor2::kPinStatusPWM); |
| 368 | |
| 369 | BrightnessLED output_indicator(PICO_DEFAULT_LED_PIN); |
| 370 | SignalWriter output_writer(sensor1::kPinOutput); |
| 371 | |
| 372 | printf("Initializing\n"); |
| 373 | |
| 374 | VL53L1X sensor1(1, status_light1, dist_light1, sensor1::kPinInterrupt, |
| 375 | sensor1::kPinShutdown); |
| 376 | VL53L1X sensor2(2, status_light2, dist_light2, sensor2::kPinInterrupt, |
| 377 | sensor2::kPinShutdown); |
| 378 | |
| 379 | /* temporary*/ int n = 0; |
| 380 | double mean = 0; |
| 381 | double M2 = 0; |
| 382 | |
| 383 | absolute_time_t last_reading = get_absolute_time(); |
| 384 | |
| 385 | // Measure and print continuously |
| 386 | while (1) { |
| 387 | sensor1.Start(); |
| 388 | sensor1.WaitForData(); |
| 389 | VL53L1X_Result_t result1 = sensor1.Read(); |
| 390 | sensor1.Stop(); |
| 391 | |
| 392 | sensor2.Start(); |
| 393 | sensor2.WaitForData(); |
| 394 | VL53L1X_Result_t result2 = sensor2.Read(); |
| 395 | sensor2.Stop(); |
| 396 | |
| 397 | double dist1 = static_cast<double>(result1.Distance) * 0.001; |
| 398 | double dist2 = static_cast<double>(result2.Distance) * 0.001; |
| 399 | |
| 400 | // Estimates the center of the obstruction |
| 401 | // where 0 is at sensor 1 (left) |
| 402 | // and kSensorSeparation is at sensor 2 (right) |
| 403 | double width_of_obstruction = kSensorSeparation - (dist1 + dist2); |
| 404 | |
| 405 | double left_estimate = (dist1 + (width_of_obstruction / 2)); |
| 406 | double right_estimate = |
| 407 | (kSensorSeparation - dist2) - (width_of_obstruction / 2); |
| 408 | |
| 409 | double averaged_estimate = (left_estimate + right_estimate) / 2; |
| 410 | |
Ravago Jones | 02773c9 | 2023-02-26 13:19:54 -0800 | [diff] [blame^] | 411 | const bool sensor1_good = sensor1.errors == 0 && result1.Status == 0; |
| 412 | const bool sensor2_good = sensor2.errors == 0 && result2.Status == 0; |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 413 | |
Ravago Jones | 02773c9 | 2023-02-26 13:19:54 -0800 | [diff] [blame^] | 414 | const bool data_good = |
| 415 | sensor1_good && sensor2_good && width_of_obstruction > 0; |
milind-u | 3a7f921 | 2023-02-24 20:46:59 -0800 | [diff] [blame] | 416 | |
Ravago Jones | 02773c9 | 2023-02-26 13:19:54 -0800 | [diff] [blame^] | 417 | double output = std::max(0.05, std::min(averaged_estimate * 2.0, 0.90)); |
| 418 | |
| 419 | if (!data_good) { |
| 420 | if (!sensor1_good && !sensor2_good) { |
| 421 | output = 0.98; |
| 422 | } else if (!sensor2_good) { |
| 423 | output = 0.97; |
| 424 | } else if (!sensor1_good) { |
| 425 | output = 0.96; |
| 426 | } else { |
| 427 | output = 0.95; |
| 428 | } |
| 429 | } |
| 430 | |
milind-u | 3a7f921 | 2023-02-24 20:46:59 -0800 | [diff] [blame] | 431 | output_writer.SetValue(output); |
| 432 | |
| 433 | output_indicator.SetValue(data_good ? averaged_estimate : 0); |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 434 | |
| 435 | /*Temporary */ if (data_good) { |
| 436 | n += 1; |
| 437 | double x = dist1 * 1000; |
| 438 | double delta = x - mean; |
| 439 | mean += delta / n; |
| 440 | M2 += delta * (x - mean); |
| 441 | printf("Std dev: %f mm\n", sqrt(M2 / (n - 1))); |
| 442 | } else { |
| 443 | n = 0; |
| 444 | mean = 0; |
| 445 | M2 = 0; |
| 446 | } |
| 447 | |
| 448 | watchdog_update(); |
| 449 | absolute_time_t now = get_absolute_time(); |
| 450 | int64_t diff = absolute_time_diff_us(last_reading, now); |
| 451 | last_reading = now; |
| 452 | double period_ms = static_cast<double>(diff) * 0.001; |
| 453 | |
| 454 | printf( |
| 455 | "Status = %2d, dist = %5d mm, Ambient = %3d, Signal = %5d, #ofSpads " |
| 456 | "= %3d\nStatus = %2d, " |
| 457 | "dist = %5d mm, " |
| 458 | "Ambient = %3d, Signal = %5d, " |
| 459 | "#ofSpads = %3d\nPeriod: %f Errors: %d %d %d %d\nx: %f, width: %f " |
milind-u | 3a7f921 | 2023-02-24 20:46:59 -0800 | [diff] [blame] | 460 | "data: %s, sending: %f\n", |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 461 | result1.Status, result1.Distance, result1.Ambient, result1.SigPerSPAD, |
| 462 | result1.NumSPADs, result2.Status, result2.Distance, result2.Ambient, |
| 463 | result2.SigPerSPAD, result2.NumSPADs, period_ms, sensor1.errors, |
| 464 | sensor2.errors, sensor1.consecutive_errors, sensor2.consecutive_errors, |
milind-u | 3a7f921 | 2023-02-24 20:46:59 -0800 | [diff] [blame] | 465 | averaged_estimate, width_of_obstruction, data_good ? "good" : "bad", |
| 466 | output); |
Ravago Jones | 9c10b2a | 2023-02-06 12:41:59 -0800 | [diff] [blame] | 467 | |
| 468 | // Try to reinitialize the sensor if it is in a bad state (eg. lost power |
| 469 | // but is now reconnected) |
| 470 | sensor1.MaybeAttemptRecovery(); |
| 471 | sensor2.MaybeAttemptRecovery(); |
| 472 | |
| 473 | // allow the user to enter the bootloader without removing power or having |
| 474 | // to install a reset button |
| 475 | char user_input = getchar_timeout_us(0); |
| 476 | if (user_input == 'q') { |
| 477 | printf("Going down! entering bootloader\n"); |
| 478 | reset_usb_boot(0, 0); |
| 479 | } |
| 480 | |
| 481 | // Calibration mode |
| 482 | if (user_input == 'c') { |
| 483 | printf( |
| 484 | "Entering calibration mode\n" |
| 485 | "Please place 17%% gray target 100 mm from sensor 1 and press c " |
| 486 | "again to continue.\n"); |
| 487 | while (true) { |
| 488 | user_input = getchar_timeout_us(0); |
| 489 | watchdog_update(); |
| 490 | if (user_input == 'c') break; |
| 491 | } |
| 492 | |
| 493 | printf("Taking 50 measurements\n"); |
| 494 | int16_t offset = 0; |
| 495 | VL53L1X_CalibrateOffset(1, 100, &offset); |
| 496 | printf("Got an offset of %d\n", offset); |
| 497 | |
| 498 | sleep_ms(1000); |
| 499 | } |
| 500 | } |
| 501 | } |
| 502 | |
| 503 | } // namespace tof_controller |
| 504 | } // namespace y2023 |
| 505 | |
| 506 | int main() { y2023::tof_controller::main(); } |