@depz/sensor-sdk 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +13 -5
  2. package/dist/dataset.d.ts.map +1 -1
  3. package/dist/dataset.js +66 -2
  4. package/dist/dataset.js.map +1 -1
  5. package/dist/device/device.d.ts +6 -0
  6. package/dist/device/device.d.ts.map +1 -1
  7. package/dist/device/device.js +29 -6
  8. package/dist/device/device.js.map +1 -1
  9. package/dist/discovery.d.ts.map +1 -1
  10. package/dist/discovery.js +25 -2
  11. package/dist/discovery.js.map +1 -1
  12. package/dist/index.d.ts +21 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +16 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/protocol/bno055.d.ts +101 -0
  17. package/dist/protocol/bno055.d.ts.map +1 -0
  18. package/dist/protocol/bno055.js +105 -0
  19. package/dist/protocol/bno055.js.map +1 -0
  20. package/dist/protocol/identity.d.ts +1 -1
  21. package/dist/protocol/identity.d.ts.map +1 -1
  22. package/dist/protocol/identity.js +6 -0
  23. package/dist/protocol/identity.js.map +1 -1
  24. package/dist/protocol/vl53l7.d.ts +77 -0
  25. package/dist/protocol/vl53l7.d.ts.map +1 -0
  26. package/dist/protocol/vl53l7.js +99 -0
  27. package/dist/protocol/vl53l7.js.map +1 -0
  28. package/dist/protocol/vl53lx.d.ts +72 -0
  29. package/dist/protocol/vl53lx.d.ts.map +1 -0
  30. package/dist/protocol/vl53lx.js +87 -0
  31. package/dist/protocol/vl53lx.js.map +1 -0
  32. package/dist/sensors/bno055/bno055.d.ts +207 -0
  33. package/dist/sensors/bno055/bno055.d.ts.map +1 -0
  34. package/dist/sensors/bno055/bno055.js +547 -0
  35. package/dist/sensors/bno055/bno055.js.map +1 -0
  36. package/dist/sensors/bno055/regs.d.ts +271 -0
  37. package/dist/sensors/bno055/regs.d.ts.map +1 -0
  38. package/dist/sensors/bno055/regs.js +366 -0
  39. package/dist/sensors/bno055/regs.js.map +1 -0
  40. package/dist/sensors/bno086/bno086.d.ts +15 -0
  41. package/dist/sensors/bno086/bno086.d.ts.map +1 -1
  42. package/dist/sensors/bno086/bno086.js +43 -1
  43. package/dist/sensors/bno086/bno086.js.map +1 -1
  44. package/dist/sensors/bno086/sh2.d.ts.map +1 -1
  45. package/dist/sensors/bno086/sh2.js +5 -2
  46. package/dist/sensors/bno086/sh2.js.map +1 -1
  47. package/dist/sensors/reg-bridge.d.ts +35 -0
  48. package/dist/sensors/reg-bridge.d.ts.map +1 -0
  49. package/dist/sensors/reg-bridge.js +44 -0
  50. package/dist/sensors/reg-bridge.js.map +1 -0
  51. package/dist/sensors/vl53l4/vl53l4.d.ts.map +1 -1
  52. package/dist/sensors/vl53l4/vl53l4.js +18 -30
  53. package/dist/sensors/vl53l4/vl53l4.js.map +1 -1
  54. package/dist/sensors/vl53l7/vl53l7.d.ts +97 -0
  55. package/dist/sensors/vl53l7/vl53l7.d.ts.map +1 -0
  56. package/dist/sensors/vl53l7/vl53l7.js +138 -0
  57. package/dist/sensors/vl53l7/vl53l7.js.map +1 -0
  58. package/dist/sensors/vl53l8/assets/index.d.ts +5 -1
  59. package/dist/sensors/vl53l8/assets/index.d.ts.map +1 -1
  60. package/dist/sensors/vl53l8/assets/index.js +7 -1
  61. package/dist/sensors/vl53l8/assets/index.js.map +1 -1
  62. package/dist/sensors/vl53l8/assets/l7ch.d.ts +12 -0
  63. package/dist/sensors/vl53l8/assets/l7ch.d.ts.map +1 -0
  64. package/dist/sensors/vl53l8/assets/l7ch.js +18 -0
  65. package/dist/sensors/vl53l8/assets/l7ch.js.map +1 -0
  66. package/dist/sensors/vl53l8/assets/l7cx.d.ts +14 -0
  67. package/dist/sensors/vl53l8/assets/l7cx.d.ts.map +1 -0
  68. package/dist/sensors/vl53l8/assets/l7cx.js +20 -0
  69. package/dist/sensors/vl53l8/assets/l7cx.js.map +1 -0
  70. package/dist/sensors/vl53l8/cnh.d.ts +4 -3
  71. package/dist/sensors/vl53l8/cnh.d.ts.map +1 -1
  72. package/dist/sensors/vl53l8/cnh.js +4 -3
  73. package/dist/sensors/vl53l8/cnh.js.map +1 -1
  74. package/dist/sensors/vl53l8/uld.d.ts +50 -9
  75. package/dist/sensors/vl53l8/uld.d.ts.map +1 -1
  76. package/dist/sensors/vl53l8/uld.js +241 -35
  77. package/dist/sensors/vl53l8/uld.js.map +1 -1
  78. package/dist/sensors/vl53l8/vl53l8.d.ts +8 -3
  79. package/dist/sensors/vl53l8/vl53l8.d.ts.map +1 -1
  80. package/dist/sensors/vl53l8/vl53l8.js +41 -39
  81. package/dist/sensors/vl53l8/vl53l8.js.map +1 -1
  82. package/dist/sensors/vl53lx/decode.d.ts +70 -0
  83. package/dist/sensors/vl53lx/decode.d.ts.map +1 -0
  84. package/dist/sensors/vl53lx/decode.js +108 -0
  85. package/dist/sensors/vl53lx/decode.js.map +1 -0
  86. package/dist/sensors/vl53lx/uld/arith.d.ts +23 -0
  87. package/dist/sensors/vl53lx/uld/arith.d.ts.map +1 -0
  88. package/dist/sensors/vl53lx/uld/arith.js +42 -0
  89. package/dist/sensors/vl53lx/uld/arith.js.map +1 -0
  90. package/dist/sensors/vl53lx/uld/bare/core.d.ts +345 -0
  91. package/dist/sensors/vl53lx/uld/bare/core.d.ts.map +1 -0
  92. package/dist/sensors/vl53lx/uld/bare/core.js +1085 -0
  93. package/dist/sensors/vl53lx/uld/bare/core.js.map +1 -0
  94. package/dist/sensors/vl53lx/uld/bare/driver.d.ts +91 -0
  95. package/dist/sensors/vl53lx/uld/bare/driver.d.ts.map +1 -0
  96. package/dist/sensors/vl53lx/uld/bare/driver.js +206 -0
  97. package/dist/sensors/vl53lx/uld/bare/driver.js.map +1 -0
  98. package/dist/sensors/vl53lx/uld/bare/hist.d.ts +180 -0
  99. package/dist/sensors/vl53lx/uld/bare/hist.d.ts.map +1 -0
  100. package/dist/sensors/vl53lx/uld/bare/hist.js +949 -0
  101. package/dist/sensors/vl53lx/uld/bare/hist.js.map +1 -0
  102. package/dist/sensors/vl53lx/uld/bare/image.d.ts +53 -0
  103. package/dist/sensors/vl53lx/uld/bare/image.d.ts.map +1 -0
  104. package/dist/sensors/vl53lx/uld/bare/image.js +108 -0
  105. package/dist/sensors/vl53lx/uld/bare/image.js.map +1 -0
  106. package/dist/sensors/vl53lx/uld/bare/imath.d.ts +39 -0
  107. package/dist/sensors/vl53lx/uld/bare/imath.d.ts.map +1 -0
  108. package/dist/sensors/vl53lx/uld/bare/imath.js +99 -0
  109. package/dist/sensors/vl53lx/uld/bare/imath.js.map +1 -0
  110. package/dist/sensors/vl53lx/uld/bare/index.d.ts +22 -0
  111. package/dist/sensors/vl53lx/uld/bare/index.d.ts.map +1 -0
  112. package/dist/sensors/vl53lx/uld/bare/index.js +22 -0
  113. package/dist/sensors/vl53lx/uld/bare/index.js.map +1 -0
  114. package/dist/sensors/vl53lx/uld/bare/nvm.d.ts +85 -0
  115. package/dist/sensors/vl53lx/uld/bare/nvm.d.ts.map +1 -0
  116. package/dist/sensors/vl53lx/uld/bare/nvm.js +148 -0
  117. package/dist/sensors/vl53lx/uld/bare/nvm.js.map +1 -0
  118. package/dist/sensors/vl53lx/uld/bare/regs.d.ts +54 -0
  119. package/dist/sensors/vl53lx/uld/bare/regs.d.ts.map +1 -0
  120. package/dist/sensors/vl53lx/uld/bare/regs.js +496 -0
  121. package/dist/sensors/vl53lx/uld/bare/regs.js.map +1 -0
  122. package/dist/sensors/vl53lx/uld/bare/tuning.d.ts +15 -0
  123. package/dist/sensors/vl53lx/uld/bare/tuning.d.ts.map +1 -0
  124. package/dist/sensors/vl53lx/uld/bare/tuning.js +303 -0
  125. package/dist/sensors/vl53lx/uld/bare/tuning.js.map +1 -0
  126. package/dist/sensors/vl53lx/uld/base.d.ts +129 -0
  127. package/dist/sensors/vl53lx/uld/base.d.ts.map +1 -0
  128. package/dist/sensors/vl53lx/uld/base.js +166 -0
  129. package/dist/sensors/vl53lx/uld/base.js.map +1 -0
  130. package/dist/sensors/vl53lx/uld/l0x.d.ts +306 -0
  131. package/dist/sensors/vl53lx/uld/l0x.d.ts.map +1 -0
  132. package/dist/sensors/vl53lx/uld/l0x.js +1492 -0
  133. package/dist/sensors/vl53lx/uld/l0x.js.map +1 -0
  134. package/dist/sensors/vl53lx/uld/l1.d.ts +69 -0
  135. package/dist/sensors/vl53lx/uld/l1.d.ts.map +1 -0
  136. package/dist/sensors/vl53lx/uld/l1.js +262 -0
  137. package/dist/sensors/vl53lx/uld/l1.js.map +1 -0
  138. package/dist/sensors/vl53lx/uld/l3.d.ts +25 -0
  139. package/dist/sensors/vl53lx/uld/l3.d.ts.map +1 -0
  140. package/dist/sensors/vl53lx/uld/l3.js +51 -0
  141. package/dist/sensors/vl53lx/uld/l3.js.map +1 -0
  142. package/dist/sensors/vl53lx/uld/l4.d.ts +22 -0
  143. package/dist/sensors/vl53lx/uld/l4.d.ts.map +1 -0
  144. package/dist/sensors/vl53lx/uld/l4.js +99 -0
  145. package/dist/sensors/vl53lx/uld/l4.js.map +1 -0
  146. package/dist/sensors/vl53lx/uld/link.d.ts +42 -0
  147. package/dist/sensors/vl53lx/uld/link.d.ts.map +1 -0
  148. package/dist/sensors/vl53lx/uld/link.js +28 -0
  149. package/dist/sensors/vl53lx/uld/link.js.map +1 -0
  150. package/dist/sensors/vl53lx/uld/registry.d.ts +68 -0
  151. package/dist/sensors/vl53lx/uld/registry.d.ts.map +1 -0
  152. package/dist/sensors/vl53lx/uld/registry.js +139 -0
  153. package/dist/sensors/vl53lx/uld/registry.js.map +1 -0
  154. package/dist/sensors/vl53lx/uld/vl53l1-die.d.ts +172 -0
  155. package/dist/sensors/vl53lx/uld/vl53l1-die.d.ts.map +1 -0
  156. package/dist/sensors/vl53lx/uld/vl53l1-die.js +431 -0
  157. package/dist/sensors/vl53lx/uld/vl53l1-die.js.map +1 -0
  158. package/dist/sensors/vl53lx/vl53lx.d.ts +323 -0
  159. package/dist/sensors/vl53lx/vl53lx.d.ts.map +1 -0
  160. package/dist/sensors/vl53lx/vl53lx.js +688 -0
  161. package/dist/sensors/vl53lx/vl53lx.js.map +1 -0
  162. package/dist/transport/webserial.d.ts +4 -0
  163. package/dist/transport/webserial.d.ts.map +1 -1
  164. package/dist/transport/webserial.js +58 -12
  165. package/dist/transport/webserial.js.map +1 -1
  166. package/package.json +9 -2
@@ -0,0 +1,1492 @@
1
+ /**
2
+ * uld/l0x — VL53L0X API 1.0.4: the ranging path. Async port of the Python
3
+ * `depz_sensor_sdk.vl53lx.uld.l0x` (vl53l0x_api.c / vl53l0x_api_core.c /
4
+ * vl53l0x_api_calibration.c): DataInit, StaticInit (reference SPADs from NVM
5
+ * + tuning settings), the timing-budget arithmetic, Start/StopMeasurement,
6
+ * GetRangingMeasurementData with its PAL range status (sigma estimate
7
+ * included), PerformRefCalibration, PerformRefSpadManagement, the offset and
8
+ * crosstalk calibrations and the ranging profiles of the four ST examples.
9
+ *
10
+ * Register sequences, integer widths and the deliberate overflows are kept as
11
+ * in C — do not "simplify" them. Arithmetic that can leave int32 goes through
12
+ * `arith.ts`; the sigma estimate (intermediates past 2^53) runs on BigInt.
13
+ *
14
+ * There is no ROI on this die.
15
+ */
16
+ import { I2C_KHZ_BOOT, ProtocolError, Vl53Error } from "./link.js";
17
+ import { measurement, SensorDriver } from "./base.js";
18
+ import { floorDiv, pyRound, shl, shr, u32 } from "./arith.js";
19
+ export const L0X_API_VERSION = [1, 0, 4];
20
+ // ── registers (vl53l0x_device.h) ─────────────────────────────────────────────
21
+ export const SYSRANGE_START = 0x00;
22
+ export const SYSRANGE_MODE_SINGLESHOT = 0x00;
23
+ export const SYSRANGE_MODE_START_STOP = 0x01;
24
+ export const SYSRANGE_MODE_BACKTOBACK = 0x02;
25
+ export const SYSRANGE_MODE_TIMED = 0x04;
26
+ export const SYSTEM_SEQUENCE_CONFIG = 0x01;
27
+ export const SYSTEM_INTERMEASUREMENT_PERIOD = 0x04;
28
+ export const SYSTEM_RANGE_CONFIG = 0x09;
29
+ export const SYSTEM_INTERRUPT_CONFIG_GPIO = 0x0a;
30
+ export const SYSTEM_INTERRUPT_CLEAR = 0x0b;
31
+ export const SYSTEM_THRESH_HIGH = 0x0c;
32
+ export const SYSTEM_THRESH_LOW = 0x0e;
33
+ export const RESULT_INTERRUPT_STATUS = 0x13;
34
+ export const RESULT_RANGE_STATUS = 0x14;
35
+ export const CROSSTALK_COMPENSATION_PEAK_RATE_MCPS = 0x20;
36
+ export const ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x28;
37
+ export const ALGO_PHASECAL_CONFIG_TIMEOUT = 0x30;
38
+ export const GLOBAL_CONFIG_VCSEL_WIDTH = 0x32;
39
+ export const FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44;
40
+ export const MSRC_CONFIG_TIMEOUT_MACROP = 0x46;
41
+ export const FINAL_RANGE_CONFIG_VALID_PHASE_LOW = 0x47;
42
+ export const FINAL_RANGE_CONFIG_VALID_PHASE_HIGH = 0x48;
43
+ export const DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD = 0x4e;
44
+ export const DYNAMIC_SPAD_REF_EN_START_OFFSET = 0x4f;
45
+ export const PRE_RANGE_CONFIG_VCSEL_PERIOD = 0x50;
46
+ export const PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x51;
47
+ export const PRE_RANGE_CONFIG_VALID_PHASE_LOW = 0x56;
48
+ export const PRE_RANGE_CONFIG_VALID_PHASE_HIGH = 0x57;
49
+ export const MSRC_CONFIG_CONTROL = 0x60;
50
+ export const PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT = 0x64;
51
+ export const FINAL_RANGE_CONFIG_VCSEL_PERIOD = 0x70;
52
+ export const FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x71;
53
+ export const POWER_MANAGEMENT_GO1_POWER_FORCE = 0x80;
54
+ export const GPIO_HV_MUX_ACTIVE_HIGH = 0x84;
55
+ export const SOFT_RESET_GO2_SOFT_RESET_N = 0xbf;
56
+ export const GLOBAL_CONFIG_SPAD_ENABLES_REF_0 = 0xb0;
57
+ export const GLOBAL_CONFIG_REF_EN_START_SELECT = 0xb6;
58
+ export const IDENTIFICATION_MODEL_ID = 0xc0;
59
+ export const OSC_CALIBRATE_VAL = 0xf8;
60
+ // Page 1 (0xFF ← 0x01 first).
61
+ export const ALGO_PHASECAL_LIM = 0x30;
62
+ export const RESULT_PEAK_SIGNAL_RATE_REF = 0xb6;
63
+ /** One byte at 0xC0, unlike the 16-bit family. */
64
+ export const MODEL_ID_VL53L0X = 0xee;
65
+ // Device modes (VL53L0X_DeviceModes).
66
+ export const DEVICEMODE_SINGLE_RANGING = 0;
67
+ export const DEVICEMODE_CONTINUOUS_RANGING = 1;
68
+ export const DEVICEMODE_CONTINUOUS_TIMED = 3;
69
+ export const GPIOFUNCTIONALITY_NEW_MEASURE_READY = 4;
70
+ // Sequence steps (VL53L0X_SequenceStepId).
71
+ export const SEQUENCESTEP_TCC = 0;
72
+ export const SEQUENCESTEP_DSS = 1;
73
+ export const SEQUENCESTEP_MSRC = 2;
74
+ export const SEQUENCESTEP_PRE_RANGE = 3;
75
+ export const SEQUENCESTEP_FINAL_RANGE = 4;
76
+ const SEQ_SET = [0x10, 0x28, 0x04, 0x40, 0x80];
77
+ const SEQ_CLEAR = [0xef, 0xd7, 0xfb, 0xbf, 0x7f];
78
+ const SEQ_TEST = [0x10, 0x08, 0x04, 0x40, 0x80];
79
+ export const VCSEL_PERIOD_PRE_RANGE = 0;
80
+ export const VCSEL_PERIOD_FINAL_RANGE = 1;
81
+ // Limit checks (VL53L0X_CHECKENABLE_*).
82
+ export const CHECK_SIGMA_FINAL_RANGE = 0;
83
+ export const CHECK_SIGNAL_RATE_FINAL_RANGE = 1;
84
+ export const CHECK_SIGNAL_REF_CLIP = 2;
85
+ export const CHECK_RANGE_IGNORE_THRESHOLD = 3;
86
+ export const CHECK_SIGNAL_RATE_MSRC = 4;
87
+ export const CHECK_SIGNAL_RATE_PRE_RANGE = 5;
88
+ export const CHECK_NUMBER_OF_CHECKS = 6;
89
+ export const DEFAULT_MAX_LOOP = 2000;
90
+ /** 20 Mcps in 9.7 format, the ref-SPAD target. */
91
+ export const TARGET_REF_RATE = 0x0a00;
92
+ export const SPEED_OF_LIGHT_IN_AIR = 2997;
93
+ export const REF_SPAD_BUFFER_SIZE = 6;
94
+ /**
95
+ * The ranging modes of the four ST examples: (signal Mcps, sigma mm, budget
96
+ * us, pre-range PCLK, final PCLK). 'default' is what StaticInit leaves.
97
+ */
98
+ export const L0X_MODE_DEFAULT = "default";
99
+ export const L0X_MODE_SETTINGS = new Map([
100
+ ["default", [0.25, 18, 33000, 14, 10]],
101
+ ["long-range", [0.1, 60, 33000, 18, 14]],
102
+ ["high-speed", [0.25, 32, 30000, 14, 10]],
103
+ ["high-accuracy", [0.25, 18, 200000, 14, 10]],
104
+ ]);
105
+ /**
106
+ * DefaultTuningSettings[] (vl53l0x_tuning.h, "update 02/11/2015_v36"): a run
107
+ * of {count, address, count bytes...} records ended by a zero count.
108
+ */
109
+ export const DEFAULT_TUNING_SETTINGS = Uint8Array.from([
110
+ 0x01, 0xff, 0x01, 0x01, 0x00, 0x00,
111
+ 0x01, 0xff, 0x00, 0x01, 0x09, 0x00, 0x01, 0x10, 0x00, 0x01, 0x11, 0x00,
112
+ 0x01, 0x24, 0x01, 0x01, 0x25, 0xff, 0x01, 0x75, 0x00,
113
+ 0x01, 0xff, 0x01, 0x01, 0x4e, 0x2c, 0x01, 0x48, 0x00, 0x01, 0x30, 0x20,
114
+ 0x01, 0xff, 0x00, 0x01, 0x30, 0x09, 0x01, 0x54, 0x00, 0x01, 0x31, 0x04, 0x01, 0x32, 0x03,
115
+ 0x01, 0x40, 0x83, 0x01, 0x46, 0x25, 0x01, 0x60, 0x00, 0x01, 0x27, 0x00, 0x01, 0x50, 0x06,
116
+ 0x01, 0x51, 0x00, 0x01, 0x52, 0x96, 0x01, 0x56, 0x08, 0x01, 0x57, 0x30, 0x01, 0x61, 0x00,
117
+ 0x01, 0x62, 0x00, 0x01, 0x64, 0x00, 0x01, 0x65, 0x00, 0x01, 0x66, 0xa0,
118
+ 0x01, 0xff, 0x01, 0x01, 0x22, 0x32, 0x01, 0x47, 0x14, 0x01, 0x49, 0xff, 0x01, 0x4a, 0x00,
119
+ 0x01, 0xff, 0x00, 0x01, 0x7a, 0x0a, 0x01, 0x7b, 0x00, 0x01, 0x78, 0x21,
120
+ 0x01, 0xff, 0x01, 0x01, 0x23, 0x34, 0x01, 0x42, 0x00, 0x01, 0x44, 0xff, 0x01, 0x45, 0x26,
121
+ 0x01, 0x46, 0x05, 0x01, 0x40, 0x40, 0x01, 0x0e, 0x06, 0x01, 0x20, 0x1a, 0x01, 0x43, 0x40,
122
+ 0x01, 0xff, 0x00, 0x01, 0x34, 0x03, 0x01, 0x35, 0x44,
123
+ 0x01, 0xff, 0x01, 0x01, 0x31, 0x04, 0x01, 0x4b, 0x09, 0x01, 0x4c, 0x05, 0x01, 0x4d, 0x04,
124
+ 0x01, 0xff, 0x00, 0x01, 0x44, 0x00, 0x01, 0x45, 0x20, 0x01, 0x47, 0x08, 0x01, 0x48, 0x28,
125
+ 0x01, 0x67, 0x00, 0x01, 0x70, 0x04, 0x01, 0x71, 0x01, 0x01, 0x72, 0xfe, 0x01, 0x76, 0x00,
126
+ 0x01, 0x77, 0x00,
127
+ 0x01, 0xff, 0x01, 0x01, 0x0d, 0x01,
128
+ 0x01, 0xff, 0x00, 0x01, 0x80, 0x01, 0x01, 0x01, 0xf8,
129
+ 0x01, 0xff, 0x01, 0x01, 0x8e, 0x01, 0x01, 0x00, 0x01, 0x01, 0xff, 0x00, 0x01, 0x80, 0x00,
130
+ 0x00, 0x00, 0x00,
131
+ ]);
132
+ /** Dmax lookup table set up by DataInit, FixPoint16.16. */
133
+ export const DMAX_LUT_AMB_RATE_MCPS = [
134
+ 0x00000000, 0x0000b333, 0x00020000, 0x0003cccc, 0x00074ccc, 0x000a0000, 0x000f0000,
135
+ ];
136
+ export const DMAX_LUT_DMAX_MM = [
137
+ 0x04b00000, 0x044c0000, 0x03840000, 0x02ee0000, 0x02260000, 0x01f40000, 0x01900000,
138
+ ];
139
+ /** The block the MCU streams: RESULT_RANGE_STATUS .. 0x1F. */
140
+ export const L0X_RESULT_BLOCK_ADDR = RESULT_RANGE_STATUS;
141
+ export const L0X_RESULT_BLOCK_LEN = 12;
142
+ /** Two writes release this sensor's interrupt. */
143
+ export const L0X_CLEAR_STEPS = [
144
+ [SYSTEM_INTERRUPT_CLEAR, 0x01],
145
+ [SYSTEM_INTERRUPT_CLEAR, 0x00],
146
+ ];
147
+ /** The only part of the family with 8-bit register addresses. */
148
+ export const L0X_ADDR_WIDTH = 1;
149
+ /** VL53L0X_GetRangeStatusString(). */
150
+ export const L0X_RANGE_STATUS_NAMES = {
151
+ 0: "Range Valid",
152
+ 1: "Sigma Fail",
153
+ 2: "Signal Fail",
154
+ 3: "Min Range Fail",
155
+ 4: "Phase Fail",
156
+ 5: "Hardware Fail",
157
+ 255: "No Update",
158
+ };
159
+ // Reference SPAD array geometry.
160
+ const REF_ARRAY_SPAD_0 = 0;
161
+ const REF_ARRAY_SPAD_5 = 5;
162
+ const REF_ARRAY_SPAD_10 = 10;
163
+ const REF_ARRAY_QUADRANTS = [REF_ARRAY_SPAD_10, REF_ARRAY_SPAD_5, REF_ARRAY_SPAD_0, REF_ARRAY_SPAD_5];
164
+ /** VL53L0X_isqrt() on BigInt — the bit-by-bit integer square root. */
165
+ export function isqrt(numIn) {
166
+ let num = numIn;
167
+ let res = 0n;
168
+ let bit = 1n << 30n;
169
+ while (bit > num)
170
+ bit >>= 2n;
171
+ while (bit !== 0n) {
172
+ if (num >= res + bit) {
173
+ num -= res + bit;
174
+ res = (res >> 1n) + bit;
175
+ }
176
+ else {
177
+ res >>= 1n;
178
+ }
179
+ bit >>= 2n;
180
+ }
181
+ return res;
182
+ }
183
+ export function decodeVcselPeriod(reg) {
184
+ return (reg + 1) << 1;
185
+ }
186
+ export function encodeVcselPeriod(pclks) {
187
+ return (pclks >> 1) - 1;
188
+ }
189
+ /** (LSByte * 2^MSByte) + 1 format. */
190
+ export function encodeTimeout(timeoutMacroClks) {
191
+ if (timeoutMacroClks <= 0)
192
+ return 0;
193
+ let lsByte = timeoutMacroClks - 1;
194
+ let msByte = 0;
195
+ while (lsByte >= 0x100) {
196
+ lsByte = floorDiv(lsByte, 2);
197
+ msByte += 1;
198
+ }
199
+ return (msByte * 256 + (lsByte & 0xff)) & 0xffff;
200
+ }
201
+ export function decodeTimeout(encoded) {
202
+ return shl(encoded & 0x00ff, (encoded & 0xff00) >> 8) + 1;
203
+ }
204
+ /** 2304 vclks per macro period, PLL period fixed at 1655 ps. */
205
+ export function calcMacroPeriodPs(vcselPeriodPclks) {
206
+ return 2304 * vcselPeriodPclks * 1655;
207
+ }
208
+ export function calcTimeoutMclks(timeoutPeriodUs, vcselPeriodPclks) {
209
+ const macroPeriodNs = floorDiv(calcMacroPeriodPs(vcselPeriodPclks) + 500, 1000);
210
+ return floorDiv(timeoutPeriodUs * 1000 + floorDiv(macroPeriodNs, 2), macroPeriodNs);
211
+ }
212
+ export function calcTimeoutUs(timeoutPeriodMclks, vcselPeriodPclks) {
213
+ const macroPeriodNs = floorDiv(calcMacroPeriodPs(vcselPeriodPclks) + 500, 1000);
214
+ return floorDiv(timeoutPeriodMclks * macroPeriodNs + 500, 1000);
215
+ }
216
+ /** Which quadrant a SPAD index falls in decides whether it is an aperture SPAD. */
217
+ export function isAperture(spadIndex) {
218
+ return REF_ARRAY_QUADRANTS[spadIndex >> 6] !== REF_ARRAY_SPAD_0;
219
+ }
220
+ /** → index of the next enabled bit at or after `curr`, or -1. */
221
+ export function getNextGoodSpad(goodSpadArray, size, curr) {
222
+ const startIndex = floorDiv(curr, 8);
223
+ const fineOffset = curr % 8;
224
+ for (let coarse = startIndex; coarse < size; coarse++) {
225
+ let dataByte = goodSpadArray[coarse];
226
+ let fine = 0;
227
+ if (coarse === startIndex) {
228
+ dataByte >>= fineOffset;
229
+ fine = fineOffset;
230
+ }
231
+ while (fine < 8) {
232
+ if (dataByte & 1)
233
+ return coarse * 8 + fine;
234
+ dataByte >>= 1;
235
+ fine += 1;
236
+ }
237
+ }
238
+ return -1;
239
+ }
240
+ export function l0xStatusText(status) {
241
+ return L0X_RANGE_STATUS_NAMES[status] ?? `unknown (${status})`;
242
+ }
243
+ // Fixed scheduler overheads, us (set_measurement_timing_budget_micro_seconds()).
244
+ const START_OVERHEAD_US = 1910;
245
+ const END_OVERHEAD_US = 960;
246
+ const MSRC_OVERHEAD_US = 660;
247
+ const TCC_OVERHEAD_US = 590;
248
+ const DSS_OVERHEAD_US = 690;
249
+ const PRE_RANGE_OVERHEAD_US = 660;
250
+ const FINAL_RANGE_OVERHEAD_US = 550;
251
+ function arraysEqual(a, b) {
252
+ if (a.length !== b.length)
253
+ return false;
254
+ for (let i = 0; i < a.length; i++)
255
+ if (a[i] !== b[i])
256
+ return false;
257
+ return true;
258
+ }
259
+ /** Port of the VL53L0X API 1.0.4 ranging path. */
260
+ export class VL53L0X extends SensorDriver {
261
+ static ADDR_WIDTH = L0X_ADDR_WIDTH;
262
+ static CLEAR_STEPS = L0X_CLEAR_STEPS;
263
+ /** No Fast Mode Plus on this die. */
264
+ static MAX_KHZ = 400;
265
+ static SUPPORTS = new Set([
266
+ "mode",
267
+ "timing",
268
+ "offset",
269
+ "xtalk",
270
+ "calib_offset",
271
+ "calib_xtalk",
272
+ "refspad",
273
+ ]);
274
+ static MODES = [...L0X_MODE_SETTINGS.keys()];
275
+ /** Below ~20 ms the enabled sequence steps no longer fit the budget. */
276
+ static BUDGET_MS = [20, 200];
277
+ /** PALDevData — every field is written before it is read. */
278
+ d = {
279
+ ReadDataFromDeviceDone: 0,
280
+ LinearityCorrectiveGain: 1000,
281
+ OscFrequencyMHz: 618660,
282
+ XTalkCompensationRateMegaCps: 0,
283
+ XTalkCompensationEnable: 0,
284
+ StopVariable: 0,
285
+ SequenceConfig: 0,
286
+ RangeFractionalEnable: 0,
287
+ DeviceMode: DEVICEMODE_SINGLE_RANGING,
288
+ MeasurementTimingBudgetMicroSeconds: 0,
289
+ Pin0GpioFunctionality: GPIOFUNCTIONALITY_NEW_MEASURE_READY,
290
+ ReferenceSpadCount: 0,
291
+ ReferenceSpadType: 0,
292
+ RefGoodSpadMap: new Array(REF_SPAD_BUFFER_SIZE).fill(0),
293
+ RefSpadEnables: new Array(REF_SPAD_BUFFER_SIZE).fill(0),
294
+ PreRangeVcselPulsePeriod: 0,
295
+ FinalRangeVcselPulsePeriod: 0,
296
+ PreRangeTimeoutMicroSecs: 0,
297
+ FinalRangeTimeoutMicroSecs: 0,
298
+ TargetRefRate: TARGET_REF_RATE,
299
+ LimitChecksEnable: new Array(CHECK_NUMBER_OF_CHECKS).fill(0),
300
+ LimitChecksValue: new Array(CHECK_NUMBER_OF_CHECKS).fill(0),
301
+ Part2PartOffsetAdjustmentNVMMicroMeter: 0,
302
+ SignalRateMeasFixed400mm: 0,
303
+ ModuleId: 0,
304
+ Revision: 0,
305
+ ProductId: "",
306
+ PartUIDUpper: 0,
307
+ PartUIDLower: 0,
308
+ };
309
+ /** False when the NVM ref-SPAD record is out of range (static_init). */
310
+ refSpadsFromNvm = true;
311
+ /** Which of MODES is applied (the budget reads back rounded). */
312
+ mode = L0X_MODE_DEFAULT;
313
+ constructor(platform, part) {
314
+ super(platform, part);
315
+ }
316
+ // ── SensorDriver contract ──
317
+ streamBlock() {
318
+ return [L0X_RESULT_BLOCK_ADDR, L0X_RESULT_BLOCK_LEN];
319
+ }
320
+ decode(raw) {
321
+ return l0xAsMeasurement(this.parseResultBlock(raw));
322
+ }
323
+ async readMeasurement() {
324
+ return l0xAsMeasurement(await this.getResult());
325
+ }
326
+ // ── identity ──
327
+ modelId() {
328
+ return this.p.rdByte(IDENTIFICATION_MODEL_ID);
329
+ }
330
+ /** No firmware-status register: the model id answering says the die is up. */
331
+ async waitBoot(timeoutS = 1.0) {
332
+ const deadline = Date.now() + timeoutS * 1000;
333
+ for (;;) {
334
+ try {
335
+ if ((await this.p.rdByte(IDENTIFICATION_MODEL_ID)) === MODEL_ID_VL53L0X)
336
+ return;
337
+ }
338
+ catch (err) {
339
+ // NACK while the die is still booting.
340
+ if (!(err instanceof ProtocolError))
341
+ throw err;
342
+ }
343
+ if (Date.now() > deadline)
344
+ throw new Vl53Error("timeout waiting for MODEL_ID 0xEE at 0xC0");
345
+ await this.p.sleepMs(1);
346
+ }
347
+ }
348
+ // ── init ──
349
+ /** DataInit + StaticInit, and leave the sensor in continuous mode. */
350
+ async sensorInit() {
351
+ await this.p.setAddrWidth(L0X_ADDR_WIDTH);
352
+ await this.p.setI2cSpeed(I2C_KHZ_BOOT);
353
+ await this.waitBoot();
354
+ await this.resetDevice();
355
+ await this.dataInit();
356
+ await this.staticInit();
357
+ if (!this.refSpadsFromNvm)
358
+ await this.performRefSpadManagement();
359
+ await this.performRefCalibration();
360
+ this.setDeviceMode(DEVICEMODE_CONTINUOUS_RANGING);
361
+ this.mode = L0X_MODE_DEFAULT;
362
+ if (this.MAX_KHZ !== I2C_KHZ_BOOT)
363
+ await this.p.setI2cSpeed(this.MAX_KHZ);
364
+ }
365
+ /** VL53L0X_ResetDevice(): pull the die through its soft reset. */
366
+ async resetDevice(timeoutS = 1.0) {
367
+ const deadline = Date.now() + timeoutS * 1000;
368
+ const pollModelId = async (wantZero) => {
369
+ // A rebooting die NACKs its own address for a moment (ERR_HARDWARE_FAULT
370
+ // from the bridge) — "not booted yet", not a fault.
371
+ for (;;) {
372
+ let byte;
373
+ try {
374
+ byte = await this.p.rdByte(IDENTIFICATION_MODEL_ID);
375
+ }
376
+ catch (err) {
377
+ if (!(err instanceof ProtocolError))
378
+ throw err;
379
+ byte = null;
380
+ }
381
+ if (byte !== null && (byte === 0x00) === wantZero)
382
+ return;
383
+ if (Date.now() > deadline)
384
+ throw new Vl53Error("timeout in the soft reset of the sensor");
385
+ }
386
+ };
387
+ await this.p.wrByte(SOFT_RESET_GO2_SOFT_RESET_N, 0x00);
388
+ await pollModelId(true);
389
+ await this.p.sleepMs(1);
390
+ await this.p.wrByte(SOFT_RESET_GO2_SOFT_RESET_N, 0x01);
391
+ await pollModelId(false);
392
+ await this.p.sleepMs(1);
393
+ }
394
+ /** VL53L0X_DataInit(). */
395
+ async dataInit() {
396
+ const p = this.p;
397
+ const d = this.d;
398
+ await p.wrByte(0x88, 0x00); // I2C standard mode
399
+ d.ReadDataFromDeviceDone = 0;
400
+ d.LinearityCorrectiveGain = 1000;
401
+ d.OscFrequencyMHz = 618660;
402
+ d.XTalkCompensationRateMegaCps = 0;
403
+ d.XTalkCompensationEnable = 0;
404
+ d.DeviceMode = DEVICEMODE_SINGLE_RANGING;
405
+ await p.wrByte(0x80, 0x01);
406
+ await p.wrByte(0xff, 0x01);
407
+ await p.wrByte(0x00, 0x00);
408
+ d.StopVariable = await p.rdByte(0x91);
409
+ await p.wrByte(0x00, 0x01);
410
+ await p.wrByte(0xff, 0x00);
411
+ await p.wrByte(0x80, 0x00);
412
+ for (let check = 0; check < CHECK_NUMBER_OF_CHECKS; check++) {
413
+ await this.setLimitCheckEnable(check, 1);
414
+ }
415
+ await this.setLimitCheckEnable(CHECK_SIGNAL_REF_CLIP, 0);
416
+ await this.setLimitCheckEnable(CHECK_RANGE_IGNORE_THRESHOLD, 0);
417
+ await this.setLimitCheckEnable(CHECK_SIGNAL_RATE_MSRC, 0);
418
+ await this.setLimitCheckEnable(CHECK_SIGNAL_RATE_PRE_RANGE, 0);
419
+ await this.setLimitCheckValue(CHECK_SIGMA_FINAL_RANGE, 18 * 65536);
420
+ await this.setLimitCheckValue(CHECK_SIGNAL_RATE_FINAL_RANGE, floorDiv(25 * 65536, 100));
421
+ await this.setLimitCheckValue(CHECK_SIGNAL_REF_CLIP, 35 * 65536);
422
+ await this.setLimitCheckValue(CHECK_RANGE_IGNORE_THRESHOLD, 0);
423
+ d.SequenceConfig = 0xff;
424
+ await p.wrByte(SYSTEM_SEQUENCE_CONFIG, 0xff);
425
+ }
426
+ /** VL53L0X_StaticInit(): reference SPADs, tuning settings, GPIO. */
427
+ async staticInit() {
428
+ const p = this.p;
429
+ const d = this.d;
430
+ await this.getInfoFromDevice(1);
431
+ const count = d.ReferenceSpadCount;
432
+ const apertureSpads = d.ReferenceSpadType;
433
+ this.refSpadsFromNvm = !(apertureSpads > 1 ||
434
+ (apertureSpads === 1 && count > 32) ||
435
+ (apertureSpads === 0 && count > 12));
436
+ if (this.refSpadsFromNvm)
437
+ await this.setReferenceSpads(count, apertureSpads);
438
+ await this.loadTuningSettings(DEFAULT_TUNING_SETTINGS);
439
+ await this.setGpioConfig(GPIOFUNCTIONALITY_NEW_MEASURE_READY, true);
440
+ await p.wrByte(0xff, 0x01);
441
+ const tempword = await p.rdWord(0x84);
442
+ await p.wrByte(0xff, 0x00);
443
+ d.OscFrequencyMHz = shl(tempword, 4); // FixPoint4.12 → 16.16
444
+ d.MeasurementTimingBudgetMicroSeconds = await this.getMeasurementTimingBudget();
445
+ d.RangeFractionalEnable = (await p.rdByte(SYSTEM_RANGE_CONFIG)) & 1;
446
+ d.SequenceConfig = await p.rdByte(SYSTEM_SEQUENCE_CONFIG);
447
+ // MSRC and TCC off by default, as in the C driver.
448
+ await this.setSequenceStepEnable(SEQUENCESTEP_TCC, 0);
449
+ await this.setSequenceStepEnable(SEQUENCESTEP_MSRC, 0);
450
+ d.PreRangeVcselPulsePeriod = await this.getVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE);
451
+ d.FinalRangeVcselPulsePeriod = await this.getVcselPulsePeriod(VCSEL_PERIOD_FINAL_RANGE);
452
+ d.PreRangeTimeoutMicroSecs = await this.getSequenceStepTimeout(SEQUENCESTEP_PRE_RANGE);
453
+ d.FinalRangeTimeoutMicroSecs = await this.getSequenceStepTimeout(SEQUENCESTEP_FINAL_RANGE);
454
+ }
455
+ // ── reference calibration (VHV / phase) ──
456
+ async performSingleRefCalibration(vhvInitByte) {
457
+ await this.p.wrByte(SYSRANGE_START, SYSRANGE_MODE_START_STOP | vhvInitByte);
458
+ await this.waitDataReady(); // measurement_poll_for_completion()
459
+ await this.clearInterrupt();
460
+ await this.p.wrByte(SYSRANGE_START, 0x00);
461
+ }
462
+ /** VL53L0X_ref_calibration_io() in its read direction → [vhv, phase]. */
463
+ async refCalibrationIoRead(vhvEnable, phaseEnable) {
464
+ const p = this.p;
465
+ await p.wrByte(0xff, 0x01);
466
+ await p.wrByte(0x00, 0x00);
467
+ await p.wrByte(0xff, 0x00);
468
+ const vhv = vhvEnable ? await p.rdByte(0xcb) : 0;
469
+ const phase = phaseEnable ? await p.rdByte(0xee) : 0;
470
+ await p.wrByte(0xff, 0x01);
471
+ await p.wrByte(0x00, 0x01);
472
+ await p.wrByte(0xff, 0x00);
473
+ return [vhv, phase & 0xef];
474
+ }
475
+ /** VL53L0X_PerformRefCalibration(): VHV, then phase → [vhv, phase]. */
476
+ async performRefCalibration() {
477
+ const sequenceConfig = this.d.SequenceConfig;
478
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, 0x01); // VHV only
479
+ await this.performSingleRefCalibration(0x40);
480
+ const [vhv] = await this.refCalibrationIoRead(true, false);
481
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, 0x02); // phase only
482
+ await this.performSingleRefCalibration(0x00);
483
+ const [, phase] = await this.refCalibrationIoRead(false, true);
484
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, sequenceConfig);
485
+ this.d.SequenceConfig = sequenceConfig;
486
+ return [vhv, phase];
487
+ }
488
+ // ── NVM ──
489
+ async deviceReadStrobe() {
490
+ await this.p.wrByte(0x83, 0x00);
491
+ let ok = false;
492
+ for (let i = 0; i < DEFAULT_MAX_LOOP; i++) {
493
+ if ((await this.p.rdByte(0x83)) !== 0x00) {
494
+ ok = true;
495
+ break;
496
+ }
497
+ }
498
+ if (!ok)
499
+ throw new Vl53Error("timeout waiting for the NVM read strobe");
500
+ await this.p.wrByte(0x83, 0x01);
501
+ }
502
+ /**
503
+ * VL53L0X_get_info_from_device(): the fuse copy of the reference SPAD
504
+ * record (option 1), the product identification (option 2) and the part
505
+ * UID plus the factory offset (option 4).
506
+ */
507
+ async getInfoFromDevice(option) {
508
+ const d = this.d;
509
+ const done = d.ReadDataFromDeviceDone;
510
+ if (done === 7)
511
+ return;
512
+ const p = this.p;
513
+ await p.wrByte(0x80, 0x01);
514
+ await p.wrByte(0xff, 0x01);
515
+ await p.wrByte(0x00, 0x00);
516
+ await p.wrByte(0xff, 0x06);
517
+ await p.wrByte(0x83, (await p.rdByte(0x83)) | 4);
518
+ await p.wrByte(0xff, 0x07);
519
+ await p.wrByte(0x81, 0x01);
520
+ await p.sleepMs(1); // VL53L0X_PollingDelay()
521
+ await p.wrByte(0x80, 0x01);
522
+ const nvmDword = async (index) => {
523
+ await p.wrByte(0x94, index);
524
+ await this.deviceReadStrobe();
525
+ return p.rdDword(0x90);
526
+ };
527
+ const goodSpadMap = [...d.RefGoodSpadMap];
528
+ let refSpadCount = 0;
529
+ let refSpadType = 0;
530
+ let productId = "";
531
+ let moduleId = 0;
532
+ let revision = 0;
533
+ let partUidUpper = 0;
534
+ let partUidLower = 0;
535
+ let signalRateMeas1104400mm = 0;
536
+ let distMeas1104400mm = 0;
537
+ if ((option & 1) === 1 && (done & 1) === 0) {
538
+ let tmp = await nvmDword(0x6b);
539
+ refSpadCount = (tmp >>> 8) & 0x7f;
540
+ refSpadType = (tmp >>> 15) & 0x01;
541
+ tmp = await nvmDword(0x24);
542
+ goodSpadMap[0] = (tmp >>> 24) & 0xff;
543
+ goodSpadMap[1] = (tmp >>> 16) & 0xff;
544
+ goodSpadMap[2] = (tmp >>> 8) & 0xff;
545
+ goodSpadMap[3] = tmp & 0xff;
546
+ tmp = await nvmDword(0x25);
547
+ goodSpadMap[4] = (tmp >>> 24) & 0xff;
548
+ goodSpadMap[5] = (tmp >>> 16) & 0xff;
549
+ }
550
+ if ((option & 2) === 2 && (done & 2) === 0) {
551
+ await p.wrByte(0x94, 0x02);
552
+ await this.deviceReadStrobe();
553
+ moduleId = await p.rdByte(0x90);
554
+ await p.wrByte(0x94, 0x7b);
555
+ await this.deviceReadStrobe();
556
+ revision = await p.rdByte(0x90);
557
+ const chars = [];
558
+ let tmp = await nvmDword(0x77);
559
+ chars.push((tmp >>> 25) & 0x7f, (tmp >>> 18) & 0x7f, (tmp >>> 11) & 0x7f, (tmp >>> 4) & 0x7f);
560
+ let byte = (tmp & 0x00f) << 3;
561
+ tmp = await nvmDword(0x78);
562
+ chars.push(byte + ((tmp >>> 29) & 0x7f), (tmp >>> 22) & 0x7f, (tmp >>> 15) & 0x7f, (tmp >>> 8) & 0x7f, (tmp >>> 1) & 0x7f);
563
+ byte = (tmp & 0x001) << 6;
564
+ tmp = await nvmDword(0x79);
565
+ chars.push(byte + ((tmp >>> 26) & 0x7f), (tmp >>> 19) & 0x7f, (tmp >>> 12) & 0x7f, (tmp >>> 5) & 0x7f);
566
+ byte = (tmp & 0x01f) << 2;
567
+ tmp = await nvmDword(0x7a);
568
+ chars.push(byte + ((tmp >>> 30) & 0x7f), (tmp >>> 23) & 0x7f, (tmp >>> 16) & 0x7f, (tmp >>> 9) & 0x7f, (tmp >>> 2) & 0x7f);
569
+ productId = chars.map((c) => String.fromCharCode(c & 0x7f)).join("");
570
+ }
571
+ if ((option & 4) === 4 && (done & 4) === 0) {
572
+ partUidUpper = await nvmDword(0x7b);
573
+ partUidLower = await nvmDword(0x7c);
574
+ signalRateMeas1104400mm = ((await nvmDword(0x73)) & 0xff) << 8;
575
+ signalRateMeas1104400mm |= ((await nvmDword(0x74)) & 0xff000000) >>> 24;
576
+ distMeas1104400mm = ((await nvmDword(0x75)) & 0xff) << 8;
577
+ distMeas1104400mm |= ((await nvmDword(0x76)) & 0xff000000) >>> 24;
578
+ }
579
+ await p.wrByte(0x81, 0x00);
580
+ await p.wrByte(0xff, 0x06);
581
+ await p.wrByte(0x83, (await p.rdByte(0x83)) & 0xfb);
582
+ await p.wrByte(0xff, 0x01);
583
+ await p.wrByte(0x00, 0x01);
584
+ await p.wrByte(0xff, 0x00);
585
+ await p.wrByte(0x80, 0x00);
586
+ if ((option & 1) === 1 && (done & 1) === 0) {
587
+ d.ReferenceSpadCount = refSpadCount;
588
+ d.ReferenceSpadType = refSpadType;
589
+ d.RefGoodSpadMap = goodSpadMap;
590
+ }
591
+ if ((option & 2) === 2 && (done & 2) === 0) {
592
+ d.ModuleId = moduleId;
593
+ d.Revision = revision;
594
+ d.ProductId = productId;
595
+ }
596
+ if ((option & 4) === 4 && (done & 4) === 0) {
597
+ d.PartUIDUpper = partUidUpper;
598
+ d.PartUIDLower = partUidLower;
599
+ d.SignalRateMeasFixed400mm = shl(signalRateMeas1104400mm, 9);
600
+ let offsetUm = 0;
601
+ if (distMeas1104400mm !== 0) {
602
+ const offset1104Mm = u32(distMeas1104400mm - (400 << 4));
603
+ offsetUm = -shr(offset1104Mm * 1000, 4);
604
+ }
605
+ d.Part2PartOffsetAdjustmentNVMMicroMeter = offsetUm;
606
+ }
607
+ d.ReadDataFromDeviceDone = done | option;
608
+ }
609
+ // ── reference SPADs ──
610
+ /** VL53L0X_set_reference_spads(): apply the NVM record and verify it. */
611
+ async setReferenceSpads(count, isApertureSpads) {
612
+ const startSelect = 0xb4;
613
+ const spadArraySize = REF_SPAD_BUFFER_SIZE;
614
+ const maxSpadCount = 44;
615
+ const p = this.p;
616
+ await p.wrByte(0xff, 0x01);
617
+ await p.wrByte(DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00);
618
+ await p.wrByte(DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2c);
619
+ await p.wrByte(0xff, 0x00);
620
+ await p.wrByte(GLOBAL_CONFIG_REF_EN_START_SELECT, startSelect);
621
+ const spadArray = new Array(spadArraySize).fill(0);
622
+ let currentSpadIndex = 0;
623
+ if (isApertureSpads) {
624
+ while (!isAperture(startSelect + currentSpadIndex) && currentSpadIndex < maxSpadCount) {
625
+ currentSpadIndex += 1;
626
+ }
627
+ }
628
+ const good = this.d.RefGoodSpadMap;
629
+ for (let i = 0; i < count; i++) {
630
+ const nextGood = getNextGoodSpad(good, spadArraySize, currentSpadIndex);
631
+ if (nextGood === -1)
632
+ throw new Vl53Error("ran out of good reference SPADs");
633
+ if (isAperture(startSelect + nextGood) !== Boolean(isApertureSpads)) {
634
+ throw new Vl53Error("the good SPAD map leaves the requested quadrant");
635
+ }
636
+ currentSpadIndex = nextGood;
637
+ spadArray[floorDiv(currentSpadIndex, 8)] |= 1 << currentSpadIndex % 8;
638
+ currentSpadIndex += 1;
639
+ }
640
+ await p.wrMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, Uint8Array.from(spadArray));
641
+ const check = await p.rdMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, spadArraySize);
642
+ if (!arraysEqual(spadArray, check))
643
+ throw new Vl53Error("reference SPAD map did not read back");
644
+ this.d.RefSpadEnables = spadArray;
645
+ this.d.ReferenceSpadCount = count;
646
+ this.d.ReferenceSpadType = isApertureSpads;
647
+ }
648
+ // ── tuning settings ──
649
+ /** VL53L0X_load_tuning_settings(): {count, address, bytes...} records. */
650
+ async loadTuningSettings(buffer) {
651
+ let index = 0;
652
+ while (buffer[index] !== 0) {
653
+ const numberOfWrites = buffer[index];
654
+ index += 1;
655
+ if (numberOfWrites === 0xff) {
656
+ // Host-side sigma parameters this port does not use.
657
+ index += 3;
658
+ }
659
+ else if (numberOfWrites <= 4) {
660
+ const address = buffer[index];
661
+ index += 1;
662
+ await this.p.wrMulti(address, buffer.slice(index, index + numberOfWrites));
663
+ index += numberOfWrites;
664
+ }
665
+ else {
666
+ throw new Vl53Error(`bad tuning record at offset ${index}`);
667
+ }
668
+ }
669
+ }
670
+ // ── GPIO / interrupt ──
671
+ /** VL53L0X_SetGpioConfig() for pin 0 in its ranging-interrupt role. */
672
+ async setGpioConfig(functionality, polarityLow = true) {
673
+ await this.p.wrByte(SYSTEM_INTERRUPT_CONFIG_GPIO, functionality);
674
+ const data = polarityLow ? 0x00 : 0x10;
675
+ const current = await this.p.rdByte(GPIO_HV_MUX_ACTIVE_HIGH);
676
+ await this.p.wrByte(GPIO_HV_MUX_ACTIVE_HIGH, (current & 0xef) | data);
677
+ this.d.Pin0GpioFunctionality = functionality;
678
+ await this.clearInterrupt();
679
+ }
680
+ /** VL53L0X_ClearInterruptMask(): two writes, then confirm — up to three rounds. */
681
+ async clearInterrupt() {
682
+ for (let i = 0; i < 3; i++) {
683
+ await this.p.wrByte(SYSTEM_INTERRUPT_CLEAR, 0x01);
684
+ await this.p.wrByte(SYSTEM_INTERRUPT_CLEAR, 0x00);
685
+ if (((await this.p.rdByte(RESULT_INTERRUPT_STATUS)) & 0x07) === 0x00)
686
+ return;
687
+ }
688
+ throw new Vl53Error("interrupt not cleared after three rounds");
689
+ }
690
+ async getInterruptMaskStatus() {
691
+ const byte = await this.p.rdByte(RESULT_INTERRUPT_STATUS);
692
+ if (byte & 0x18) {
693
+ throw new Vl53Error(`range error reported in RESULT_INTERRUPT_STATUS 0x${byte.toString(16).toUpperCase().padStart(2, "0")}`);
694
+ }
695
+ return byte & 0x07;
696
+ }
697
+ async checkForDataReady() {
698
+ if (this.d.Pin0GpioFunctionality === GPIOFUNCTIONALITY_NEW_MEASURE_READY) {
699
+ return (await this.getInterruptMaskStatus()) === GPIOFUNCTIONALITY_NEW_MEASURE_READY;
700
+ }
701
+ return Boolean((await this.p.rdByte(RESULT_RANGE_STATUS)) & 0x01);
702
+ }
703
+ // ── sequence steps ──
704
+ /** → enabled flag per sequence step (indexed by SEQUENCESTEP_*). */
705
+ async getSequenceStepEnables() {
706
+ const config = await this.p.rdByte(SYSTEM_SEQUENCE_CONFIG);
707
+ return SEQ_TEST.map((mask) => Boolean(config & mask));
708
+ }
709
+ async setSequenceStepEnable(step, enabled) {
710
+ const config = await this.p.rdByte(SYSTEM_SEQUENCE_CONFIG);
711
+ const next = enabled ? config | SEQ_SET[step] : config & SEQ_CLEAR[step];
712
+ if (next === config)
713
+ return;
714
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, next);
715
+ this.d.SequenceConfig = next;
716
+ // The budget is spread over the enabled steps, so it has to be re-applied.
717
+ await this.setMeasurementTimingBudget(this.d.MeasurementTimingBudgetMicroSeconds);
718
+ }
719
+ async getVcselPulsePeriod(periodType) {
720
+ const reg = periodType === VCSEL_PERIOD_PRE_RANGE
721
+ ? PRE_RANGE_CONFIG_VCSEL_PERIOD
722
+ : FINAL_RANGE_CONFIG_VCSEL_PERIOD;
723
+ return decodeVcselPeriod(await this.p.rdByte(reg));
724
+ }
725
+ async getSequenceStepTimeout(step) {
726
+ if (step === SEQUENCESTEP_TCC || step === SEQUENCESTEP_DSS || step === SEQUENCESTEP_MSRC) {
727
+ const vcsel = await this.getVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE);
728
+ const mclks = decodeTimeout(await this.p.rdByte(MSRC_CONFIG_TIMEOUT_MACROP));
729
+ return calcTimeoutUs(mclks, vcsel);
730
+ }
731
+ if (step === SEQUENCESTEP_PRE_RANGE) {
732
+ const vcsel = await this.getVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE);
733
+ const mclks = decodeTimeout(await this.p.rdWord(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI));
734
+ return calcTimeoutUs(mclks, vcsel);
735
+ }
736
+ if (step === SEQUENCESTEP_FINAL_RANGE) {
737
+ const steps = await this.getSequenceStepEnables();
738
+ let preMclks = 0;
739
+ if (steps[SEQUENCESTEP_PRE_RANGE]) {
740
+ preMclks = decodeTimeout(await this.p.rdWord(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI));
741
+ }
742
+ const vcsel = await this.getVcselPulsePeriod(VCSEL_PERIOD_FINAL_RANGE);
743
+ const finalMclks = decodeTimeout(await this.p.rdWord(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI));
744
+ return calcTimeoutUs((finalMclks - preMclks) & 0xffff, vcsel);
745
+ }
746
+ throw new Vl53Error(`no timeout for sequence step ${step}`);
747
+ }
748
+ async setSequenceStepTimeout(step, timeoutUs) {
749
+ if (step === SEQUENCESTEP_TCC || step === SEQUENCESTEP_DSS || step === SEQUENCESTEP_MSRC) {
750
+ const vcsel = await this.getVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE);
751
+ const mclks = calcTimeoutMclks(timeoutUs, vcsel);
752
+ const encoded = mclks > 256 ? 255 : (mclks - 1) & 0xff;
753
+ await this.p.wrByte(MSRC_CONFIG_TIMEOUT_MACROP, encoded);
754
+ }
755
+ else if (step === SEQUENCESTEP_PRE_RANGE) {
756
+ const vcsel = await this.getVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE);
757
+ const mclks = calcTimeoutMclks(timeoutUs, vcsel);
758
+ await this.p.wrWord(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, encodeTimeout(mclks));
759
+ this.d.PreRangeTimeoutMicroSecs = timeoutUs;
760
+ }
761
+ else if (step === SEQUENCESTEP_FINAL_RANGE) {
762
+ // The final-range register carries pre-range + final range, summed in
763
+ // macro periods (the two steps run at different VCSEL periods).
764
+ const steps = await this.getSequenceStepEnables();
765
+ let preMclks = 0;
766
+ if (steps[SEQUENCESTEP_PRE_RANGE]) {
767
+ preMclks = decodeTimeout(await this.p.rdWord(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI));
768
+ }
769
+ const vcsel = await this.getVcselPulsePeriod(VCSEL_PERIOD_FINAL_RANGE);
770
+ const mclks = calcTimeoutMclks(timeoutUs, vcsel) + preMclks;
771
+ await this.p.wrWord(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, encodeTimeout(mclks));
772
+ this.d.FinalRangeTimeoutMicroSecs = timeoutUs;
773
+ }
774
+ else {
775
+ throw new Vl53Error(`no timeout for sequence step ${step}`);
776
+ }
777
+ }
778
+ // ── timing budget ──
779
+ async setMeasurementTimingBudget(budgetUs) {
780
+ const steps = await this.getSequenceStepEnables();
781
+ let finalBudgetUs = budgetUs - (START_OVERHEAD_US + END_OVERHEAD_US);
782
+ const take = (subTimeout) => {
783
+ if (subTimeout >= finalBudgetUs) {
784
+ throw new Vl53Error(`timing budget ${budgetUs} us is too small for the enabled sequence steps`);
785
+ }
786
+ finalBudgetUs -= subTimeout;
787
+ };
788
+ if (steps[SEQUENCESTEP_TCC] || steps[SEQUENCESTEP_MSRC] || steps[SEQUENCESTEP_DSS]) {
789
+ // TCC, MSRC and DSS share one timeout.
790
+ const msrcUs = await this.getSequenceStepTimeout(SEQUENCESTEP_MSRC);
791
+ if (steps[SEQUENCESTEP_TCC])
792
+ take(msrcUs + TCC_OVERHEAD_US);
793
+ if (steps[SEQUENCESTEP_DSS])
794
+ take(2 * (msrcUs + DSS_OVERHEAD_US));
795
+ else if (steps[SEQUENCESTEP_MSRC])
796
+ take(msrcUs + MSRC_OVERHEAD_US);
797
+ }
798
+ if (steps[SEQUENCESTEP_PRE_RANGE]) {
799
+ const preUs = await this.getSequenceStepTimeout(SEQUENCESTEP_PRE_RANGE);
800
+ take(preUs + PRE_RANGE_OVERHEAD_US);
801
+ }
802
+ if (steps[SEQUENCESTEP_FINAL_RANGE]) {
803
+ // Whatever is left goes to the final range.
804
+ finalBudgetUs -= FINAL_RANGE_OVERHEAD_US;
805
+ await this.setSequenceStepTimeout(SEQUENCESTEP_FINAL_RANGE, finalBudgetUs);
806
+ this.d.MeasurementTimingBudgetMicroSeconds = budgetUs;
807
+ }
808
+ }
809
+ async getMeasurementTimingBudget() {
810
+ const steps = await this.getSequenceStepEnables();
811
+ let budgetUs = START_OVERHEAD_US + END_OVERHEAD_US;
812
+ if (steps[SEQUENCESTEP_TCC] || steps[SEQUENCESTEP_MSRC] || steps[SEQUENCESTEP_DSS]) {
813
+ const msrcUs = await this.getSequenceStepTimeout(SEQUENCESTEP_MSRC);
814
+ if (steps[SEQUENCESTEP_TCC])
815
+ budgetUs += msrcUs + TCC_OVERHEAD_US;
816
+ if (steps[SEQUENCESTEP_DSS])
817
+ budgetUs += 2 * (msrcUs + DSS_OVERHEAD_US);
818
+ else if (steps[SEQUENCESTEP_MSRC])
819
+ budgetUs += msrcUs + MSRC_OVERHEAD_US;
820
+ }
821
+ if (steps[SEQUENCESTEP_PRE_RANGE]) {
822
+ budgetUs += (await this.getSequenceStepTimeout(SEQUENCESTEP_PRE_RANGE)) + PRE_RANGE_OVERHEAD_US;
823
+ }
824
+ if (steps[SEQUENCESTEP_FINAL_RANGE]) {
825
+ budgetUs +=
826
+ (await this.getSequenceStepTimeout(SEQUENCESTEP_FINAL_RANGE)) + FINAL_RANGE_OVERHEAD_US;
827
+ }
828
+ this.d.MeasurementTimingBudgetMicroSeconds = budgetUs;
829
+ return budgetUs;
830
+ }
831
+ async setInterMeasurementPeriod(periodMs) {
832
+ const oscCalibrateVal = await this.p.rdWord(OSC_CALIBRATE_VAL);
833
+ const value = oscCalibrateVal ? periodMs * oscCalibrateVal : periodMs;
834
+ await this.p.wrDword(SYSTEM_INTERMEASUREMENT_PERIOD, value);
835
+ }
836
+ async getInterMeasurementPeriod() {
837
+ const oscCalibrateVal = await this.p.rdWord(OSC_CALIBRATE_VAL);
838
+ const value = await this.p.rdDword(SYSTEM_INTERMEASUREMENT_PERIOD);
839
+ return oscCalibrateVal ? floorDiv(value, oscCalibrateVal) : value;
840
+ }
841
+ // ── the family-wide timing entry point ──
842
+ /** Inter-measurement 0 = back-to-back continuous, else timed continuous. */
843
+ async setRangeTiming(timingBudgetMs, interMeasurementMs) {
844
+ await this.setMeasurementTimingBudget(timingBudgetMs * 1000);
845
+ await this.setInterMeasurementPeriod(interMeasurementMs);
846
+ this.setDeviceMode(interMeasurementMs === 0 ? DEVICEMODE_CONTINUOUS_RANGING : DEVICEMODE_CONTINUOUS_TIMED);
847
+ }
848
+ async getRangeTiming() {
849
+ const budget = floorDiv(await this.getMeasurementTimingBudget(), 1000);
850
+ return [budget, await this.getInterMeasurementPeriod()];
851
+ }
852
+ // ── ranging ──
853
+ setDeviceMode(mode) {
854
+ if (mode !== DEVICEMODE_SINGLE_RANGING &&
855
+ mode !== DEVICEMODE_CONTINUOUS_RANGING &&
856
+ mode !== DEVICEMODE_CONTINUOUS_TIMED) {
857
+ throw new Vl53Error(`device mode ${mode} is not supported`);
858
+ }
859
+ this.d.DeviceMode = mode;
860
+ }
861
+ /** The undocumented prologue of every VL53L0X_StartMeasurement(). */
862
+ async armStopVariable() {
863
+ const p = this.p;
864
+ await p.wrByte(0x80, 0x01);
865
+ await p.wrByte(0xff, 0x01);
866
+ await p.wrByte(0x00, 0x00);
867
+ await p.wrByte(0x91, this.d.StopVariable);
868
+ await p.wrByte(0x00, 0x01);
869
+ await p.wrByte(0xff, 0x00);
870
+ await p.wrByte(0x80, 0x00);
871
+ }
872
+ /** VL53L0X_StartMeasurement() for the two continuous modes. */
873
+ async startRanging() {
874
+ await this.armStopVariable();
875
+ const mode = this.d.DeviceMode;
876
+ if (mode === DEVICEMODE_CONTINUOUS_RANGING) {
877
+ await this.p.wrByte(SYSRANGE_START, SYSRANGE_MODE_BACKTOBACK);
878
+ }
879
+ else if (mode === DEVICEMODE_CONTINUOUS_TIMED) {
880
+ await this.p.wrByte(SYSRANGE_START, SYSRANGE_MODE_TIMED);
881
+ }
882
+ else {
883
+ throw new Vl53Error("start_ranging() needs a continuous device mode");
884
+ }
885
+ }
886
+ /** VL53L0X_StopMeasurement(). */
887
+ async stopRanging() {
888
+ const p = this.p;
889
+ await p.wrByte(SYSRANGE_START, SYSRANGE_MODE_SINGLESHOT);
890
+ await p.wrByte(0xff, 0x01);
891
+ await p.wrByte(0x00, 0x00);
892
+ await p.wrByte(0x91, 0x00);
893
+ await p.wrByte(0x00, 0x01);
894
+ await p.wrByte(0xff, 0x00);
895
+ }
896
+ /** VL53L0X_PerformSingleRangingMeasurement(): one shot, start to finish. */
897
+ async performSingleRangingMeasurement() {
898
+ this.setDeviceMode(DEVICEMODE_SINGLE_RANGING);
899
+ await this.armStopVariable();
900
+ await this.p.wrByte(SYSRANGE_START, 0x01);
901
+ let cleared = false;
902
+ for (let i = 0; i < DEFAULT_MAX_LOOP; i++) {
903
+ if (!((await this.p.rdByte(SYSRANGE_START)) & SYSRANGE_MODE_START_STOP)) {
904
+ cleared = true;
905
+ break;
906
+ }
907
+ }
908
+ if (!cleared)
909
+ throw new Vl53Error("the single-shot start bit never cleared");
910
+ await this.waitDataReady();
911
+ const data = await this.getResult();
912
+ await this.clearInterrupt();
913
+ return data;
914
+ }
915
+ async getResult() {
916
+ return this.parseResultBlock(await this.p.rdMulti(L0X_RESULT_BLOCK_ADDR, L0X_RESULT_BLOCK_LEN));
917
+ }
918
+ // ── result decoding ──
919
+ /**
920
+ * VL53L0X_GetRangingMeasurementData() on the 12 bytes at 0x14. Pure
921
+ * arithmetic over the block and the cached device data.
922
+ */
923
+ parseResultBlock(raw) {
924
+ if (raw.length < L0X_RESULT_BLOCK_LEN) {
925
+ throw new Vl53Error(`result block too short: ${raw.length} bytes`);
926
+ }
927
+ const distance = (raw[10] << 8) + raw[11];
928
+ const signalRateMcps = ((raw[6] << 8) + raw[7]) * 512; // 9.7 → 16.16
929
+ const ambientRateMcps = ((raw[8] << 8) + raw[9]) * 512;
930
+ const effectiveSpads = (raw[2] << 8) + raw[3]; // 8.8
931
+ const deviceRangeStatus = raw[0];
932
+ let rangeMm;
933
+ let fractional;
934
+ if (this.d.RangeFractionalEnable) {
935
+ rangeMm = distance >> 2;
936
+ fractional = (distance & 0x03) << 6;
937
+ }
938
+ else {
939
+ rangeMm = distance;
940
+ fractional = 0;
941
+ }
942
+ const data = {
943
+ rangeStatus: 0,
944
+ distanceMm: rangeMm,
945
+ rangeFractionalPart: fractional,
946
+ signalRateMcps,
947
+ ambientRateMcps,
948
+ effectiveSpadRtnCount: effectiveSpads,
949
+ dmaxMm: 0,
950
+ sigmaMm: 0,
951
+ deviceRangeStatus,
952
+ };
953
+ this.getPalRangeStatus(data);
954
+ return data;
955
+ }
956
+ getTotalXtalkRate(data) {
957
+ if (!this.d.XTalkCompensationEnable)
958
+ return 0n;
959
+ const total = BigInt(data.effectiveSpadRtnCount) * BigInt(this.d.XTalkCompensationRateMegaCps);
960
+ return (total + 0x80n) >> 8n;
961
+ }
962
+ /** VL53L0X_calc_sigma_estimate(), FixPoint16.16 in and out (BigInt). */
963
+ calcSigmaEstimate(data) {
964
+ const cPulseEffectiveWidthCentiNs = 800n;
965
+ const cAmbientEffectiveWidthCentiNs = 600n;
966
+ const cDfltFinalRangeIntegrationTimeMs = 0x00190000n; // 25 ms
967
+ const cVcselPulseWidthPs = 4700n;
968
+ const cSigmaEstMax = 0x028f87aen;
969
+ const cSigmaEstRtnMax = 0xf000n;
970
+ const cAmbToSignalRatioMax = 0xf0000000n / cAmbientEffectiveWidthCentiNs;
971
+ const cTofPerMmPs = 0x0006999an;
972
+ const c16bitRoundingParam = 0x00008000n;
973
+ const cMaxXtalkKcps = 0x00320000n;
974
+ const cPllPeriodPs = 1655n;
975
+ const M32 = 0xffffffffn;
976
+ const min = (a, b) => (a < b ? a : b);
977
+ const ambientRateKcps = (BigInt(data.ambientRateMcps) * 1000n) >> 16n;
978
+ const xtalkCompRateMcps = this.getTotalXtalkRate(data);
979
+ const totalSignalRateMcps = BigInt(data.signalRateMcps) + xtalkCompRateMcps;
980
+ const peakSignalRateKcps = (totalSignalRateMcps * 1000n + 0x8000n) >> 16n;
981
+ const xtalkCompRateKcps = min(xtalkCompRateMcps * 1000n, cMaxXtalkKcps);
982
+ const finalRangeTimeoutUs = this.d.FinalRangeTimeoutMicroSecs;
983
+ const finalRangeVcselPclks = this.d.FinalRangeVcselPulsePeriod;
984
+ const finalRangeMacroPclks = BigInt(calcTimeoutMclks(finalRangeTimeoutUs, finalRangeVcselPclks));
985
+ const preRangeTimeoutUs = this.d.PreRangeTimeoutMicroSecs;
986
+ const preRangeVcselPclks = this.d.PreRangeVcselPulsePeriod;
987
+ const preRangeMacroPclks = BigInt(calcTimeoutMclks(preRangeTimeoutUs, preRangeVcselPclks));
988
+ const vcselWidth = finalRangeVcselPclks === 8 ? 2n : 3n;
989
+ let peakVcselDurationUs = vcselWidth * 2048n * (preRangeMacroPclks + finalRangeMacroPclks);
990
+ peakVcselDurationUs = pyFloorDiv(peakVcselDurationUs + 500n, 1000n);
991
+ peakVcselDurationUs *= cPllPeriodPs;
992
+ peakVcselDurationUs = pyFloorDiv(peakVcselDurationUs + 500n, 1000n);
993
+ const totalSignalRate2408 = (totalSignalRateMcps + 0x80n) >> 8n;
994
+ let vcselTotalEventsRtn = (totalSignalRate2408 * peakVcselDurationUs + 0x80n) >> 8n;
995
+ if (peakSignalRateKcps === 0n)
996
+ return Number(cSigmaEstMax);
997
+ if (vcselTotalEventsRtn < 1n)
998
+ vcselTotalEventsRtn = 1n;
999
+ const sigmaEstimateP1 = cPulseEffectiveWidthCentiNs;
1000
+ let sigmaEstimateP2 = pyFloorDiv(ambientRateKcps << 16n, peakSignalRateKcps);
1001
+ sigmaEstimateP2 = min(sigmaEstimateP2, cAmbToSignalRatioMax);
1002
+ sigmaEstimateP2 *= cAmbientEffectiveWidthCentiNs;
1003
+ const sigmaEstimateP3 = 2n * isqrt(vcselTotalEventsRtn * 12n);
1004
+ const deltaTPs = BigInt(data.distanceMm) * cTofPerMmPs;
1005
+ let diff1Mcps = pyFloorDiv(((((peakSignalRateKcps << 16n) - 2n * xtalkCompRateKcps) & M32) + 500n) & M32, 1000n);
1006
+ const diff2Mcps = pyFloorDiv((peakSignalRateKcps << 16n) + 500n, 1000n);
1007
+ diff1Mcps <<= 8n;
1008
+ let q = pyFloorDiv(diff1Mcps, diff2Mcps);
1009
+ if (q < 0n)
1010
+ q = -q;
1011
+ const xtalkCorrection = q << 8n;
1012
+ let pwMult = pyFloorDiv(deltaTPs, cVcselPulseWidthPs);
1013
+ pwMult = (pwMult * (((1n << 16n) - xtalkCorrection) & M32)) & M32;
1014
+ pwMult = (pwMult + c16bitRoundingParam) >> 16n;
1015
+ pwMult += 1n << 16n;
1016
+ // Squaring 1.xx would leave 32 bits, so the C driver halves it first.
1017
+ pwMult >>= 1n;
1018
+ pwMult = (pwMult * pwMult) >> 14n;
1019
+ let sqr1 = (pwMult * sigmaEstimateP1 + 0x8000n) >> 16n;
1020
+ sqr1 *= sqr1;
1021
+ let sqr2 = (sigmaEstimateP2 + 0x8000n) >> 16n;
1022
+ sqr2 *= sqr2;
1023
+ const sqrtResultCentiNs = isqrt(sqr1 + sqr2) << 16n;
1024
+ let sigmaEstRtn = pyFloorDiv(pyFloorDiv(sqrtResultCentiNs + 50n, 100n), sigmaEstimateP3);
1025
+ sigmaEstRtn *= BigInt(SPEED_OF_LIGHT_IN_AIR);
1026
+ sigmaEstRtn = pyFloorDiv(sigmaEstRtn + 5000n, 10000n);
1027
+ sigmaEstRtn = min(sigmaEstRtn, cSigmaEstRtnMax);
1028
+ const finalRangeIntegrationTimeMs = BigInt(floorDiv(finalRangeTimeoutUs + preRangeTimeoutUs + 500, 1000));
1029
+ // 1 mm * 25 ms / the actual integration time.
1030
+ let sigmaEstRef = isqrt(pyFloorDiv(cDfltFinalRangeIntegrationTimeMs + pyFloorDiv(finalRangeIntegrationTimeMs, 2n), finalRangeIntegrationTimeMs)) << 8n;
1031
+ sigmaEstRef = pyFloorDiv(sigmaEstRef + 500n, 1000n);
1032
+ let sigmaEstimate = 1000n * isqrt(sigmaEstRtn * sigmaEstRtn + sigmaEstRef * sigmaEstRef);
1033
+ if (peakSignalRateKcps < 1n || vcselTotalEventsRtn < 1n || sigmaEstimate > cSigmaEstMax) {
1034
+ sigmaEstimate = cSigmaEstMax;
1035
+ }
1036
+ return Number(sigmaEstimate);
1037
+ }
1038
+ /** VL53L0X_calc_dmax(): ambient-rate → max-range lookup, interpolated. */
1039
+ calcDmax(ambRateMeas) {
1040
+ const amb = DMAX_LUT_AMB_RATE_MCPS;
1041
+ const dmax = DMAX_LUT_DMAX_MM;
1042
+ const last = amb.length - 1;
1043
+ if (ambRateMeas <= amb[0])
1044
+ return shr(dmax[0], 16);
1045
+ if (ambRateMeas >= amb[last])
1046
+ return shr(dmax[last], 16);
1047
+ const index1 = amb.findIndex((a) => ambRateMeas <= a);
1048
+ const index0 = index1 ? index1 - 1 : 0;
1049
+ if (index0 === index1)
1050
+ return shr(dmax[index0], 16);
1051
+ const amb0 = amb[index0];
1052
+ const amb1 = amb[index1];
1053
+ const dmax0 = dmax[index0];
1054
+ const dmax1 = dmax[index1];
1055
+ if (amb1 === amb0)
1056
+ return shr(dmax0, 16);
1057
+ const linearSlope = floorDiv(u32(dmax0 - dmax1), shr(amb1 - amb0, 8));
1058
+ return shr(shr(amb1 - ambRateMeas, 8) * linearSlope + dmax1, 16);
1059
+ }
1060
+ /** VL53L0X_get_pal_range_status(): device status plus the enabled limit checks. */
1061
+ getPalRangeStatus(data) {
1062
+ const d = this.d;
1063
+ const internal = (data.deviceRangeStatus & 0x78) >> 3;
1064
+ const noneFlag = [0, 5, 7, 12, 13, 14, 15].includes(internal);
1065
+ let sigmaLimitFlag = false;
1066
+ if (d.LimitChecksEnable[CHECK_SIGMA_FINAL_RANGE]) {
1067
+ const sigmaEstimate = this.calcSigmaEstimate(data);
1068
+ data.sigmaMm = shr(sigmaEstimate, 16);
1069
+ data.dmaxMm = this.calcDmax(data.ambientRateMcps);
1070
+ const sigmaLimitValue = d.LimitChecksValue[CHECK_SIGMA_FINAL_RANGE];
1071
+ sigmaLimitFlag = sigmaLimitValue > 0 && sigmaEstimate > sigmaLimitValue;
1072
+ }
1073
+ const signalRefClipFlag = false; // check disabled by DataInit
1074
+ const rangeIgnoreFlag = false; // check disabled by DataInit
1075
+ let status;
1076
+ if (noneFlag)
1077
+ status = 255;
1078
+ else if (internal === 1 || internal === 2 || internal === 3)
1079
+ status = 5; // hardware fail
1080
+ else if (internal === 6 || internal === 9)
1081
+ status = 4; // phase fail
1082
+ else if (internal === 8 || internal === 10 || signalRefClipFlag)
1083
+ status = 3; // min range
1084
+ else if (internal === 4 || rangeIgnoreFlag)
1085
+ status = 2; // signal fail
1086
+ else if (sigmaLimitFlag)
1087
+ status = 1; // sigma fail
1088
+ else
1089
+ status = 0;
1090
+ data.rangeStatus = status;
1091
+ }
1092
+ // ── VCSEL pulse period and the ranging profiles ──
1093
+ /** VL53L0X_set_vcsel_pulse_period(). */
1094
+ async setVcselPulsePeriod(periodType, pclks) {
1095
+ const limits = {
1096
+ [VCSEL_PERIOD_PRE_RANGE]: { 12: 0x18, 14: 0x30, 16: 0x40, 18: 0x50 },
1097
+ [VCSEL_PERIOD_FINAL_RANGE]: { 8: 0x10, 10: 0x28, 12: 0x38, 14: 0x48 },
1098
+ };
1099
+ const table = limits[periodType] ?? {};
1100
+ if (pclks % 2 || !(pclks in table)) {
1101
+ const kind = periodType === VCSEL_PERIOD_PRE_RANGE ? "pre-range" : "final range";
1102
+ throw new Vl53Error(`VCSEL period ${pclks} PCLK is out of range for the ${kind}`);
1103
+ }
1104
+ const p = this.p;
1105
+ if (periodType === VCSEL_PERIOD_PRE_RANGE) {
1106
+ await p.wrByte(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, table[pclks]);
1107
+ await p.wrByte(PRE_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
1108
+ }
1109
+ else {
1110
+ const finals = {
1111
+ 8: [0x02, 0x0c, 0x30],
1112
+ 10: [0x03, 0x09, 0x20],
1113
+ 12: [0x03, 0x08, 0x20],
1114
+ 14: [0x03, 0x07, 0x20],
1115
+ };
1116
+ const [width, timeout, phasecalLim] = finals[pclks];
1117
+ await p.wrByte(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, table[pclks]);
1118
+ await p.wrByte(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08);
1119
+ await p.wrByte(GLOBAL_CONFIG_VCSEL_WIDTH, width);
1120
+ await p.wrByte(ALGO_PHASECAL_CONFIG_TIMEOUT, timeout);
1121
+ await p.wrByte(0xff, 0x01);
1122
+ await p.wrByte(ALGO_PHASECAL_LIM, phasecalLim);
1123
+ await p.wrByte(0xff, 0x00);
1124
+ }
1125
+ const vcselPeriodReg = encodeVcselPeriod(pclks);
1126
+ if (periodType === VCSEL_PERIOD_PRE_RANGE) {
1127
+ const preTimeout = await this.getSequenceStepTimeout(SEQUENCESTEP_PRE_RANGE);
1128
+ const msrcTimeout = await this.getSequenceStepTimeout(SEQUENCESTEP_MSRC);
1129
+ await p.wrByte(PRE_RANGE_CONFIG_VCSEL_PERIOD, vcselPeriodReg);
1130
+ await this.setSequenceStepTimeout(SEQUENCESTEP_PRE_RANGE, preTimeout);
1131
+ await this.setSequenceStepTimeout(SEQUENCESTEP_MSRC, msrcTimeout);
1132
+ this.d.PreRangeVcselPulsePeriod = pclks;
1133
+ }
1134
+ else {
1135
+ const finalTimeout = await this.getSequenceStepTimeout(SEQUENCESTEP_FINAL_RANGE);
1136
+ await p.wrByte(FINAL_RANGE_CONFIG_VCSEL_PERIOD, vcselPeriodReg);
1137
+ await this.setSequenceStepTimeout(SEQUENCESTEP_FINAL_RANGE, finalTimeout);
1138
+ this.d.FinalRangeVcselPulsePeriod = pclks;
1139
+ }
1140
+ await this.setMeasurementTimingBudget(this.d.MeasurementTimingBudgetMicroSeconds);
1141
+ await this.performPhaseCalibration();
1142
+ }
1143
+ /** VL53L0X_perform_phase_calibration() with restore_config = 1. */
1144
+ async performPhaseCalibration() {
1145
+ const sequenceConfig = this.d.SequenceConfig;
1146
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, 0x02);
1147
+ await this.performSingleRefCalibration(0x00);
1148
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, sequenceConfig);
1149
+ this.d.SequenceConfig = sequenceConfig;
1150
+ }
1151
+ /** Apply one of MODES: limits, budget and both VCSEL periods. */
1152
+ async setMode(name) {
1153
+ const settings = L0X_MODE_SETTINGS.get(name);
1154
+ if (settings === undefined) {
1155
+ throw new Vl53Error(`no such mode: ${name} (have ${this.MODES.join(", ")})`);
1156
+ }
1157
+ const [signalMcps, sigmaMm, budgetUs, prePclks, finalPclks] = settings;
1158
+ await this.setLimitCheckEnable(CHECK_SIGMA_FINAL_RANGE, 1);
1159
+ await this.setLimitCheckEnable(CHECK_SIGNAL_RATE_FINAL_RANGE, 1);
1160
+ await this.setLimitCheckValue(CHECK_SIGNAL_RATE_FINAL_RANGE, Math.trunc(signalMcps * 65536));
1161
+ await this.setLimitCheckValue(CHECK_SIGMA_FINAL_RANGE, sigmaMm * 65536);
1162
+ await this.setMeasurementTimingBudget(budgetUs);
1163
+ await this.setVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE, prePclks);
1164
+ await this.setVcselPulsePeriod(VCSEL_PERIOD_FINAL_RANGE, finalPclks);
1165
+ this.mode = name;
1166
+ }
1167
+ async getMode() {
1168
+ return this.mode;
1169
+ }
1170
+ /** What the five registers a mode writes currently read back as. */
1171
+ async driverInfo() {
1172
+ return {
1173
+ signal_mcps: (await this.getLimitCheckValue(CHECK_SIGNAL_RATE_FINAL_RANGE)) / 65536.0,
1174
+ sigma_mm: (await this.getLimitCheckValue(CHECK_SIGMA_FINAL_RANGE)) / 65536.0,
1175
+ budget_us: await this.getMeasurementTimingBudget(),
1176
+ pre_pclks: await this.getVcselPulsePeriod(VCSEL_PERIOD_PRE_RANGE),
1177
+ final_pclks: await this.getVcselPulsePeriod(VCSEL_PERIOD_FINAL_RANGE),
1178
+ };
1179
+ }
1180
+ // ── offset ──
1181
+ /** 10.2-format register: stored in steps of 250 um. */
1182
+ async setOffsetUm(offsetUm) {
1183
+ offsetUm = Math.max(-512000, Math.min(511000, offsetUm));
1184
+ const steps = floorDiv(Math.abs(offsetUm), 250); // C truncates toward zero
1185
+ const encoded = offsetUm >= 0 ? steps : 4096 - steps;
1186
+ await this.p.wrWord(ALGO_PART_TO_PART_RANGE_OFFSET_MM, encoded & 0xffff);
1187
+ }
1188
+ async getOffsetUm() {
1189
+ const register = (await this.p.rdWord(ALGO_PART_TO_PART_RANGE_OFFSET_MM)) & 0x0fff;
1190
+ return register > 2047 ? (register - 4096) * 250 : register * 250;
1191
+ }
1192
+ setOffset(offsetMm) {
1193
+ return this.setOffsetUm(offsetMm * 1000);
1194
+ }
1195
+ async getOffset() {
1196
+ return pyRound((await this.getOffsetUm()) / 1000.0);
1197
+ }
1198
+ /** VL53L0X_perform_offset_calibration() → the offset in micrometres. */
1199
+ async performOffsetCalibration(calDistanceMm, nbSamples = 50) {
1200
+ if (calDistanceMm <= 0)
1201
+ throw new Vl53Error("the calibration distance must be positive");
1202
+ await this.setOffsetUm(0);
1203
+ const tccWasOn = (await this.getSequenceStepEnables())[SEQUENCESTEP_TCC];
1204
+ await this.setSequenceStepEnable(SEQUENCESTEP_TCC, 0);
1205
+ await this.setLimitCheckEnable(CHECK_RANGE_IGNORE_THRESHOLD, 0);
1206
+ let sumRanging = 0;
1207
+ let count = 0;
1208
+ for (let i = 0; i < nbSamples; i++) {
1209
+ const data = await this.performSingleRangingMeasurement();
1210
+ if (data.rangeStatus === 0) {
1211
+ sumRanging = (sumRanging + data.distanceMm) & 0xffff;
1212
+ count += 1;
1213
+ }
1214
+ }
1215
+ if (count === 0) {
1216
+ throw new Vl53Error("offset calibration got no valid measurement - check that a target is in front of the sensor");
1217
+ }
1218
+ const meanMm = floorDiv(2 * sumRanging + count, 2 * count); // round half up
1219
+ if (meanMm === 0) {
1220
+ throw new Vl53Error("the mean range came out 0 mm - the target is closer than the part-to-part offset; " +
1221
+ "move it out to 100..400 mm and calibrate again");
1222
+ }
1223
+ const offsetUm = (calDistanceMm - meanMm) * 1000;
1224
+ await this.setOffsetUm(offsetUm);
1225
+ if (tccWasOn)
1226
+ await this.setSequenceStepEnable(SEQUENCESTEP_TCC, 1);
1227
+ return offsetUm;
1228
+ }
1229
+ /** The family-wide contract: an offset in whole millimetres. */
1230
+ async calibrateOffset(targetDistMm, nbSamples = 50) {
1231
+ return pyRound((await this.performOffsetCalibration(targetDistMm, nbSamples)) / 1000.0);
1232
+ }
1233
+ // ── crosstalk ──
1234
+ /** VL53L0X_SetXTalkCompensationEnable(). */
1235
+ async setXtalkEnable(enable) {
1236
+ const rate = enable ? this.d.XTalkCompensationRateMegaCps : 0;
1237
+ await this.p.wrWord(CROSSTALK_COMPENSATION_PEAK_RATE_MCPS, shr(rate, 3) & 0xffff); // 16.16 → 3.13
1238
+ this.d.XTalkCompensationEnable = enable ? 1 : 0;
1239
+ }
1240
+ /** VL53L0X_SetXTalkCompensationRateMegaCps(), FixPoint16.16 in. */
1241
+ async setXtalkRateMcps(rateMcps) {
1242
+ if (this.d.XTalkCompensationEnable) {
1243
+ await this.p.wrWord(CROSSTALK_COMPENSATION_PEAK_RATE_MCPS, shr(rateMcps, 3) & 0xffff);
1244
+ }
1245
+ this.d.XTalkCompensationRateMegaCps = rateMcps;
1246
+ }
1247
+ async getXtalkRateMcps() {
1248
+ return shl(await this.p.rdWord(CROSSTALK_COMPENSATION_PEAK_RATE_MCPS), 3);
1249
+ }
1250
+ /** The family-wide contract speaks kcps; the ULD speaks Mcps 16.16. */
1251
+ async setXtalk(xtalkKcps) {
1252
+ await this.setXtalkEnable(1);
1253
+ await this.setXtalkRateMcps(floorDiv(shl(xtalkKcps, 16), 1000));
1254
+ }
1255
+ async getXtalk() {
1256
+ return shr((await this.getXtalkRateMcps()) * 1000, 16);
1257
+ }
1258
+ /** VL53L0X_perform_xtalk_calibration() → the rate in Mcps 16.16. */
1259
+ async performXtalkCalibration(calDistanceMm, nbSamples = 50) {
1260
+ if (calDistanceMm <= 0)
1261
+ throw new Vl53Error("the calibration distance must be positive");
1262
+ await this.setXtalkEnable(0);
1263
+ await this.setLimitCheckEnable(CHECK_RANGE_IGNORE_THRESHOLD, 0);
1264
+ let sumRanging = 0;
1265
+ let sumSignal = 0;
1266
+ let sumSpads = 0;
1267
+ let count = 0;
1268
+ for (let i = 0; i < nbSamples; i++) {
1269
+ const data = await this.performSingleRangingMeasurement();
1270
+ if (data.rangeStatus === 0) {
1271
+ // The C sums are uint16 / uint32; kept that way on purpose.
1272
+ sumRanging = (sumRanging + data.distanceMm) & 0xffff;
1273
+ sumSignal = u32(sumSignal + data.signalRateMcps);
1274
+ sumSpads = (sumSpads + floorDiv(data.effectiveSpadRtnCount, 256)) & 0xffff;
1275
+ count += 1;
1276
+ }
1277
+ }
1278
+ if (count === 0) {
1279
+ throw new Vl53Error("crosstalk calibration got no valid measurement - check that a target is in front of the sensor");
1280
+ }
1281
+ const meanSignal = floorDiv(sumSignal, count);
1282
+ const meanRange = floorDiv(u32(shl(sumRanging, 16)), count);
1283
+ const meanSpads = floorDiv(u32(shl(sumSpads, 16)), count);
1284
+ const meanSpadsInt = shr(meanSpads + 0x8000, 16);
1285
+ const calDistance = shl(calDistanceMm, 16);
1286
+ let rateMcps;
1287
+ if (meanSpadsInt === 0 || meanRange >= calDistance) {
1288
+ rateMcps = 0;
1289
+ }
1290
+ else {
1291
+ let perSpad = floorDiv(meanSignal, meanSpadsInt);
1292
+ perSpad = u32(perSpad * (65536 - floorDiv(meanRange, calDistanceMm)));
1293
+ rateMcps = shr(perSpad + 0x8000, 16);
1294
+ }
1295
+ await this.setXtalkEnable(1);
1296
+ await this.setXtalkRateMcps(rateMcps);
1297
+ return rateMcps;
1298
+ }
1299
+ /** The family-wide contract: a crosstalk rate in kcps. */
1300
+ async calibrateXtalk(targetDistMm, nbSamples = 50) {
1301
+ return shr((await this.performXtalkCalibration(targetDistMm, nbSamples)) * 1000, 16);
1302
+ }
1303
+ // ── reference SPAD management ──
1304
+ getReferenceSpads() {
1305
+ return [this.d.ReferenceSpadCount, this.d.ReferenceSpadType];
1306
+ }
1307
+ setRefSpadMap(spadArray) {
1308
+ return this.p.wrMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, Uint8Array.from(spadArray));
1309
+ }
1310
+ async getRefSpadMap() {
1311
+ return Array.from(await this.p.rdMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, REF_SPAD_BUFFER_SIZE));
1312
+ }
1313
+ /** enable_ref_spads(): append good SPADs, apply and read back → next index. */
1314
+ async enableRefSpads(apertureSpads, spadArray, start, offset, spadCount) {
1315
+ const good = this.d.RefGoodSpadMap;
1316
+ let currentSpad = offset;
1317
+ for (let i = 0; i < spadCount; i++) {
1318
+ const nextGood = getNextGoodSpad(good, REF_SPAD_BUFFER_SIZE, currentSpad);
1319
+ if (nextGood === -1)
1320
+ throw new Vl53Error("ran out of good reference SPADs");
1321
+ if (isAperture(start + nextGood) !== Boolean(apertureSpads)) {
1322
+ throw new Vl53Error("the good SPAD map leaves the requested quadrant");
1323
+ }
1324
+ currentSpad = nextGood;
1325
+ spadArray[floorDiv(currentSpad, 8)] |= 1 << currentSpad % 8;
1326
+ currentSpad += 1;
1327
+ }
1328
+ await this.setRefSpadMap(spadArray);
1329
+ if (!arraysEqual(await this.getRefSpadMap(), spadArray)) {
1330
+ throw new Vl53Error("reference SPAD map did not read back");
1331
+ }
1332
+ return currentSpad;
1333
+ }
1334
+ /** perform_ref_signal_measurement() → peak reference signal rate, 9.7. */
1335
+ async performRefSignalMeasurement() {
1336
+ const sequenceConfig = this.d.SequenceConfig;
1337
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, 0xc0);
1338
+ await this.performSingleRangingMeasurement();
1339
+ await this.p.wrByte(0xff, 0x01);
1340
+ const peak = await this.p.rdWord(RESULT_PEAK_SIGNAL_RATE_REF);
1341
+ await this.p.wrByte(0xff, 0x00);
1342
+ await this.p.wrByte(SYSTEM_SEQUENCE_CONFIG, sequenceConfig);
1343
+ this.d.SequenceConfig = sequenceConfig;
1344
+ return peak;
1345
+ }
1346
+ /** VL53L0X_perform_ref_spad_management() → [count, isAperture]. */
1347
+ async performRefSpadManagement() {
1348
+ const startSelect = 0xb4;
1349
+ const minimumSpadCount = 3;
1350
+ const maxSpadCount = 44;
1351
+ const targetRefRate = this.d.TargetRefRate;
1352
+ const p = this.p;
1353
+ let spadArray = new Array(REF_SPAD_BUFFER_SIZE).fill(0);
1354
+ await p.wrByte(0xff, 0x01);
1355
+ await p.wrByte(DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00);
1356
+ await p.wrByte(DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2c);
1357
+ await p.wrByte(0xff, 0x00);
1358
+ await p.wrByte(GLOBAL_CONFIG_REF_EN_START_SELECT, startSelect);
1359
+ await p.wrByte(POWER_MANAGEMENT_GO1_POWER_FORCE, 0x00);
1360
+ await this.performRefCalibration();
1361
+ let needAptSpads = 0;
1362
+ let currentSpadIndex = await this.enableRefSpads(needAptSpads, spadArray, startSelect, 0, minimumSpadCount);
1363
+ let peak = await this.performRefSignalMeasurement();
1364
+ if (peak > targetRefRate) {
1365
+ // Too bright even at the minimum: start over on aperture SPADs.
1366
+ spadArray = new Array(REF_SPAD_BUFFER_SIZE).fill(0);
1367
+ while (!isAperture(startSelect + currentSpadIndex) && currentSpadIndex < maxSpadCount) {
1368
+ currentSpadIndex += 1;
1369
+ }
1370
+ needAptSpads = 1;
1371
+ currentSpadIndex = await this.enableRefSpads(needAptSpads, spadArray, startSelect, currentSpadIndex, minimumSpadCount);
1372
+ peak = await this.performRefSignalMeasurement();
1373
+ if (peak > targetRefRate) {
1374
+ // Nothing more to give: the minimum aperture set is the answer.
1375
+ this.storeRefSpads(minimumSpadCount, 1, spadArray);
1376
+ return [minimumSpadCount, 1];
1377
+ }
1378
+ }
1379
+ let refSpadCount = minimumSpadCount;
1380
+ let lastSpadArray = [...spadArray];
1381
+ let lastDiff = Math.abs(peak - targetRefRate);
1382
+ while (peak < targetRefRate) {
1383
+ const nextGood = getNextGoodSpad(this.d.RefGoodSpadMap, REF_SPAD_BUFFER_SIZE, currentSpadIndex);
1384
+ if (nextGood === -1)
1385
+ throw new Vl53Error("ran out of good reference SPADs");
1386
+ if (isAperture(startSelect + nextGood) !== Boolean(needAptSpads))
1387
+ break; // quadrant exhausted
1388
+ refSpadCount += 1;
1389
+ currentSpadIndex = nextGood;
1390
+ if (floorDiv(currentSpadIndex, 8) >= REF_SPAD_BUFFER_SIZE) {
1391
+ throw new Vl53Error("reference SPAD index out of range");
1392
+ }
1393
+ spadArray[floorDiv(currentSpadIndex, 8)] |= 1 << currentSpadIndex % 8;
1394
+ currentSpadIndex += 1;
1395
+ await this.setRefSpadMap(spadArray);
1396
+ peak = await this.performRefSignalMeasurement();
1397
+ const diff = Math.abs(peak - targetRefRate);
1398
+ if (peak > targetRefRate) {
1399
+ if (diff > lastDiff) {
1400
+ // The previous map came closer to the target; go back to it.
1401
+ await this.setRefSpadMap(lastSpadArray);
1402
+ spadArray = [...lastSpadArray];
1403
+ refSpadCount -= 1;
1404
+ }
1405
+ break;
1406
+ }
1407
+ lastDiff = diff;
1408
+ lastSpadArray = [...spadArray];
1409
+ }
1410
+ this.storeRefSpads(refSpadCount, needAptSpads, spadArray);
1411
+ return [refSpadCount, needAptSpads];
1412
+ }
1413
+ storeRefSpads(count, isApertureSpads, spadArray) {
1414
+ this.d.RefSpadEnables = [...spadArray];
1415
+ this.d.ReferenceSpadCount = count;
1416
+ this.d.ReferenceSpadType = isApertureSpads;
1417
+ }
1418
+ // ── limit checks ──
1419
+ async setLimitCheckEnable(check, enable) {
1420
+ const d = this.d;
1421
+ if (check >= CHECK_NUMBER_OF_CHECKS)
1422
+ throw new Vl53Error(`no limit check ${check}`);
1423
+ let value;
1424
+ let disable;
1425
+ if (enable === 0) {
1426
+ value = 0;
1427
+ disable = 1;
1428
+ }
1429
+ else {
1430
+ value = d.LimitChecksValue[check];
1431
+ disable = 0;
1432
+ }
1433
+ if (check === CHECK_SIGNAL_RATE_FINAL_RANGE) {
1434
+ await this.p.wrWord(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, shr(value, 9) & 0xffff);
1435
+ }
1436
+ else if (check === CHECK_SIGNAL_RATE_MSRC) {
1437
+ const current = await this.p.rdByte(MSRC_CONFIG_CONTROL);
1438
+ await this.p.wrByte(MSRC_CONFIG_CONTROL, (current & 0xfe) | (disable << 1));
1439
+ }
1440
+ else if (check === CHECK_SIGNAL_RATE_PRE_RANGE) {
1441
+ const current = await this.p.rdByte(MSRC_CONFIG_CONTROL);
1442
+ await this.p.wrByte(MSRC_CONFIG_CONTROL, (current & 0xef) | (disable << 4));
1443
+ }
1444
+ // The other three checks are host-side arithmetic only.
1445
+ d.LimitChecksEnable[check] = enable === 0 ? 0 : 1;
1446
+ }
1447
+ async setLimitCheckValue(check, value) {
1448
+ const d = this.d;
1449
+ if (!d.LimitChecksEnable[check]) {
1450
+ d.LimitChecksValue[check] = value; // disabled: keep it here
1451
+ return;
1452
+ }
1453
+ if (check === CHECK_SIGNAL_RATE_FINAL_RANGE) {
1454
+ await this.p.wrWord(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, shr(value, 9) & 0xffff);
1455
+ }
1456
+ else if (check === CHECK_SIGNAL_RATE_MSRC || check === CHECK_SIGNAL_RATE_PRE_RANGE) {
1457
+ await this.p.wrWord(PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT, shr(value, 9) & 0xffff);
1458
+ }
1459
+ d.LimitChecksValue[check] = value;
1460
+ }
1461
+ async getLimitCheckValue(check) {
1462
+ if (check === CHECK_SIGNAL_RATE_FINAL_RANGE) {
1463
+ return shl(await this.p.rdWord(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT), 9);
1464
+ }
1465
+ if (check === CHECK_SIGNAL_RATE_MSRC || check === CHECK_SIGNAL_RATE_PRE_RANGE) {
1466
+ return shl(await this.p.rdWord(PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT), 9);
1467
+ }
1468
+ return this.d.LimitChecksValue[check];
1469
+ }
1470
+ }
1471
+ /** Python `//` on BigInt (floor, not truncation). */
1472
+ function pyFloorDiv(a, b) {
1473
+ const q = a / b;
1474
+ return (a % b !== 0n) && ((a < 0n) !== (b < 0n)) ? q - 1n : q;
1475
+ }
1476
+ /**
1477
+ * RangingMeasurementData → the family-wide Measurement. The rates are
1478
+ * FixPoint16.16 Mcps in the ULD and kcps everywhere else (× 1000/65536).
1479
+ */
1480
+ export function l0xAsMeasurement(r) {
1481
+ return measurement({
1482
+ distanceMm: r.distanceMm,
1483
+ status: r.rangeStatus,
1484
+ statusText: l0xStatusText(r.rangeStatus),
1485
+ signalKcps: shr(r.signalRateMcps * 1000, 16),
1486
+ ambientKcps: shr(r.ambientRateMcps * 1000, 16),
1487
+ sigmaMm: r.sigmaMm,
1488
+ spads: r.effectiveSpadRtnCount >> 8, // 8.8
1489
+ extra: { dmax_mm: r.dmaxMm, device_range_status: r.deviceRangeStatus },
1490
+ });
1491
+ }
1492
+ //# sourceMappingURL=l0x.js.map