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