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