blob: f8e8fb93b2f87ebf03e1259b8de3b1f2bc18d71f [file] [log] [blame]
Ravago Jones9c10b2a2023-02-06 12:41:59 -08001#include <stdio.h>
2
3#include <algorithm>
Ravago Jones9c10b2a2023-02-06 12:41:59 -08004#include <cmath>
Ravago Jones02773c92023-02-26 13:19:54 -08005#include <limits>
Ravago Jones9c10b2a2023-02-06 12:41:59 -08006
Ravago Jones02773c92023-02-26 13:19:54 -08007#include "core/VL53L1X_api.h"
8#include "core/VL53L1X_calibration.h"
Ravago Jones9c10b2a2023-02-06 12:41:59 -08009#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 Jones9c10b2a2023-02-06 12:41:59 -080015
16namespace y2023 {
17namespace tof_controller {
18
19static constexpr uint kI2CBaudrate = 100000;
20static constexpr uint16_t kExpectedSensorId = 0xEBAA;
21
22static constexpr uint16_t kTimingBudgetMs = 33; // 15 ms is fastest
23static constexpr double kWatchdogTimeoutMs = 2000;
24static constexpr int64_t kSensorBootTimeoutMs = 3;
25
26static constexpr double kSensorSeparation = 0.45;
27static constexpr int16_t kSensorOffsetMM = -22;
28
29/*
30// Cone base
31static constexpr double kMaxObstructionWidth = 0.22 + 0.07;
32// cone tip
33static constexpr double kMinObstructionWidth = 0.044 + 0.07;
34*/
35
36namespace sensor1 {
37static constexpr uint kPinSCL = 1;
38static constexpr uint kPinSDA = 0;
39
40static constexpr uint kPinStatusGreen = 3;
41static constexpr uint kPinStatusRed = 5;
42static constexpr uint kPinStatusPWM = 16;
43
44static constexpr uint kPinInterrupt = 8;
45static constexpr uint kPinShutdown = 9;
46
47static constexpr uint kPinOutput = 2;
48} // namespace sensor1
49
50namespace sensor2 {
51static constexpr uint kPinSCL = 26;
52static constexpr uint kPinSDA = 27;
53
54static constexpr uint kPinStatusGreen = 14;
55static constexpr uint kPinStatusRed = 13;
56static constexpr uint kPinStatusPWM = 17;
57
58static constexpr uint kPinInterrupt = 22;
59static constexpr uint kPinShutdown = 28;
60
61static constexpr uint kPinOutput = 4;
62} // namespace sensor2
63
64class SignalWriter {
65 public:
Ravago Jones9c10b2a2023-02-06 12:41:59 -080066 // 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 Jones9c10b2a2023-02-06 12:41:59 -0800109 private:
110 void Write() {
Ravago Jones02773c92023-02-26 13:19:54 -0800111 uint16_t level = value_ * kPWMTop;
Ravago Jones9c10b2a2023-02-06 12:41:59 -0800112
113 pwm_set_gpio_level(pin_, level);
114 }
115
116 double value_ = 0.0;
117 uint pin_;
118 bool enabled_ = true;
119};
120
121class 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
148class 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
191class 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
342int 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 Jones02773c92023-02-26 13:19:54 -0800411 const bool sensor1_good = sensor1.errors == 0 && result1.Status == 0;
412 const bool sensor2_good = sensor2.errors == 0 && result2.Status == 0;
Ravago Jones9c10b2a2023-02-06 12:41:59 -0800413
Ravago Jones02773c92023-02-26 13:19:54 -0800414 const bool data_good =
415 sensor1_good && sensor2_good && width_of_obstruction > 0;
milind-u3a7f9212023-02-24 20:46:59 -0800416
Ravago Jones02773c92023-02-26 13:19:54 -0800417 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-u3a7f9212023-02-24 20:46:59 -0800431 output_writer.SetValue(output);
432
433 output_indicator.SetValue(data_good ? averaged_estimate : 0);
Ravago Jones9c10b2a2023-02-06 12:41:59 -0800434
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-u3a7f9212023-02-24 20:46:59 -0800460 "data: %s, sending: %f\n",
Ravago Jones9c10b2a2023-02-06 12:41:59 -0800461 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-u3a7f9212023-02-24 20:46:59 -0800465 averaged_estimate, width_of_obstruction, data_good ? "good" : "bad",
466 output);
Ravago Jones9c10b2a2023-02-06 12:41:59 -0800467
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
506int main() { y2023::tof_controller::main(); }