diff --git a/lib/vl53l1x/README.md b/lib/vl53l1x/README.md index cde22af..f926e86 100644 --- a/lib/vl53l1x/README.md +++ b/lib/vl53l1x/README.md @@ -1,5 +1,169 @@ -# vl53l1x +# VL53L1X -Arduino driver for the vl53l1x component of the STeaMi board. +Arduino/C++ driver for the ST `VL53L1X` Time-of-Flight distance sensor. -> **Status**: not yet implemented +The driver provides a lightweight API for initializing the sensor, +starting ranging operations, and reading distance measurements over I2C. + +## Hardware + +* I2C Time-of-Flight distance sensor +* Default 7-bit I2C address: `0x29` +* Distance measurements returned in millimeters +* Based on the ST default configuration sequence + +## Quick start + +```cpp +#include +#include + +VL53L1X sensor; + +void setup() { + Serial.begin(115200); + Wire.begin(); + + if (!sensor.begin()) { + Serial.println("VL53L1X not detected"); + while (true) delay(1000); + } + + sensor.startRanging(); +} + +void loop() { + if (sensor.dataReady()) { + uint16_t distance = sensor.read(); + + Serial.print("Distance: "); + Serial.print(distance); + Serial.println(" mm"); + } + + delay(50); +} +``` + +See [examples/read_distance/](examples/read_distance/) for the complete +example sketch. + +## Examples + +| Example | What it does | +|---------|--------------| +| [`read_distance`](examples/read_distance/) | Continuously reads and prints the measured distance in millimeters. | +| [`distance_alarm`](examples/distance_alarm/) | Triggers an alert when an object comes closer than a configurable threshold. | +| [`power_cycle`](examples/power_cycle/) | Demonstrates sensor power control using `powerOff()` and `powerOn()`. | +| [`continuous_ranging`](examples/continuous_ranging/) | Runs continuous ranging and prints measurements as soon as data becomes available. | + +### Building an example + +List available examples: + +```bash +make list-examples +``` + +Flash an example: + +```bash +make flash-vl53l1x/read_distance +``` + +Capture boot logs reliably: + +```bash +make capture-vl53l1x/read_distance +``` + +Longer capture session: + +```bash +make capture-vl53l1x/read_distance DURATION=30 +``` + +## API + +All methods follow the collection conventions: `camelCase`, explicit +units where relevant, and minimal redundant prefixes. + +### Lifecycle + +| Method | Description | +|--------|-------------| +| `VL53L1X(TwoWire& wire = Wire, uint8_t address = VL53L1X_I2C_DEFAULT_ADDR)` | Construct a driver instance. | +| `bool begin()` | Reset the sensor, verify the device ID, and upload the default configuration. Returns `false` if the sensor is not detected. | +| `uint16_t deviceId()` | Read the device model ID register. | +| `void reset()` | Perform a software reset. | +| `void powerOff()` | Put the sensor into reset state. | +| `void powerOn()` | Release reset and power the device back on. | + +### Ranging + +| Method | Description | +|--------|-------------| +| `void startRanging()` | Start ranging measurements. | +| `void stopRanging()` | Stop ranging measurements. | +| `bool dataReady()` | Check whether a new measurement is available. | +| `uint16_t distanceMm()` | Read the measured distance in millimeters. | +| `uint16_t read()` | Alias for `distanceMm()`. | + +### Internal behavior + +When `distanceMm()` is called and no data is available yet, the driver: + +1. Starts ranging automatically +2. Polls the sensor until data becomes available +3. Reads the result block +4. Clears the interrupt flag +5. Returns the measured distance + +The polling timeout is approximately 1 second. + +## Register constants + +`VL53L1X_const.h` exports all public register definitions and bit masks, +including: + +* Device identification registers +* Ranging control registers +* Interrupt status bits +* Result block offsets +* Default I2C address + +Applications can directly access low-level registers if needed. + +## Sensor configuration + +The driver uploads the ST recommended default configuration block during +`begin()` using the `DEFAULT_CONFIG` table embedded in the driver. + +This includes: + +* Interrupt configuration +* ROI configuration +* Sigma thresholds +* Intermeasurement periods +* VCSEL timing parameters + +## Testing + +The driver is designed to work with mocked `TwoWire` implementations for +host-side testing. + +Example unit tests can be added under: + +```text +tests/native/test_vl53l1x/ +``` + +and executed with: + +```bash +make test-native +``` + +## License + +GPL-3.0-or-later — see [LICENSE](../../LICENSE). \ No newline at end of file diff --git a/lib/vl53l1x/src/VL53L1X.cpp b/lib/vl53l1x/src/VL53L1X.cpp new file mode 100644 index 0000000..bf914ce --- /dev/null +++ b/lib/vl53l1x/src/VL53L1X.cpp @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "VL53L1X.h" + +#include + +const uint8_t VL53L1X::DEFAULT_CONFIG[] = { + 0x00, // 0x2d : fast plus mode disabled + 0x00, // 0x2e : bit 0 if I2C pulled up at 1.8V + 0x00, // 0x2f : bit 0 if GPIO pulled up at 1.8V + 0x01, // 0x30 : set bit 4 to 0 for active high interrupt + 0x02, // 0x31 : bit 1 = interrupt depending on the polarity + 0x00, // 0x32 : not user-modifiable + 0x02, // 0x33 : not user-modifiable + 0x08, // 0x34 : not user-modifiable + 0x00, // 0x35 : not user-modifiable + 0x08, // 0x36 : not user-modifiable + 0x10, // 0x37 : not user-modifiable + 0x01, // 0x38 : not user-modifiable + 0x01, // 0x39 : not user-modifiable + 0x00, // 0x3a : not user-modifiable + 0x00, // 0x3b : not user-modifiable + 0x00, // 0x3c : not user-modifiable + 0x00, // 0x3d : not user-modifiable + 0xFF, // 0x3e : not user-modifiable + 0x00, // 0x3f : not user-modifiable + 0x0F, // 0x40 : not user-modifiable + 0x00, // 0x41 : not user-modifiable + 0x00, // 0x42 : not user-modifiable + 0x00, // 0x43 : not user-modifiable + 0x00, // 0x44 : not user-modifiable + 0x00, // 0x45 : not user-modifiable + 0x20, // 0x46 : interrupt configuration 0x20 = new sample ready + 0x0B, // 0x47 : not user-modifiable + 0x00, // 0x48 : not user-modifiable + 0x00, // 0x49 : not user-modifiable + 0x02, // 0x4a : not user-modifiable + 0x0A, // 0x4b : not user-modifiable + 0x21, // 0x4c : not user-modifiable + 0x00, // 0x4d : not user-modifiable + 0x00, // 0x4e : not user-modifiable + 0x05, // 0x4f : not user-modifiable + 0x00, // 0x50 : not user-modifiable + 0x00, // 0x51 : not user-modifiable + 0x00, // 0x52 : not user-modifiable + 0x00, // 0x53 : not user-modifiable + 0xC8, // 0x54 : not user-modifiable + 0x00, // 0x55 : not user-modifiable + 0x00, // 0x56 : not user-modifiable + 0x38, // 0x57 : not user-modifiable + 0xFF, // 0x58 : not user-modifiable + 0x01, // 0x59 : not user-modifiable + 0x00, // 0x5a : not user-modifiable + 0x08, // 0x5b : not user-modifiable + 0x00, // 0x5c : not user-modifiable + 0x00, // 0x5d : not user-modifiable + 0x01, // 0x5e : not user-modifiable + 0xDB, // 0x5f : not user-modifiable + 0x0F, // 0x60 : not user-modifiable + 0x01, // 0x61 : not user-modifiable + 0xF1, // 0x62 : not user-modifiable + 0x0D, // 0x63 : not user-modifiable + 0x01, // 0x64 : Sigma threshold MSB + 0x68, // 0x65 : Sigma threshold LSB + 0x00, // 0x66 : Min count Rate MSB + 0x80, // 0x67 : Min count Rate LSB + 0x08, // 0x68 : not user-modifiable + 0xB8, // 0x69 : not user-modifiable + 0x00, // 0x6a : not user-modifiable + 0x00, // 0x6b : not user-modifiable + 0x00, // 0x6c : Intermeasurement period MSB + 0x00, // 0x6d : Intermeasurement period + 0x0F, // 0x6e : Intermeasurement period + 0x89, // 0x6f : Intermeasurement period LSB + 0x00, // 0x70 : not user-modifiable + 0x00, // 0x71 : not user-modifiable + 0x00, // 0x72 : distance threshold high MSB + 0x00, // 0x73 : distance threshold high LSB + 0x00, // 0x74 : distance threshold low MSB + 0x00, // 0x75 : distance threshold low LSB + 0x00, // 0x76 : not user-modifiable + 0x01, // 0x77 : not user-modifiable + 0x0F, // 0x78 : not user-modifiable + 0x0D, // 0x79 : not user-modifiable + 0x0E, // 0x7a : not user-modifiable + 0x0E, // 0x7b : not user-modifiable + 0x00, // 0x7c : not user-modifiable + 0x00, // 0x7d : not user-modifiable + 0x02, // 0x7e : not user-modifiable + 0xC7, // 0x7f : ROI center + 0xFF, // 0x80 : XY ROI (X=Width, Y=Height) + 0x9B, // 0x81 : not user-modifiable + 0x00, // 0x82 : not user-modifiable + 0x00, // 0x83 : not user-modifiable + 0x00, // 0x84 : not user-modifiable + 0x01, // 0x85 : not user-modifiable + 0x01, // 0x86 : clear interrupt + 0x40, // 0x87 : start ranging +}; + +VL53L1X::VL53L1X(TwoWire& wire, uint8_t address) : _wire(&wire), _address(address) {} + +bool VL53L1X::begin() { + reset(); + delay(100); + + if (deviceId() != VL53L1X_DEVICE_ID) { + return false; + } + + writeRegBytes(REG_DEFAULT_CONFIG_START, DEFAULT_CONFIG, sizeof(DEFAULT_CONFIG)); + writeReg16(REG_RANGE_CONFIG_VCSEL_PERIOD_A, readReg16(REG_RESULT_OSC_CALIBRATE_VAL) * 4); + delay(200); + return true; +} + +void VL53L1X::writeReg(uint16_t reg, uint8_t value) { + _wire->beginTransmission(_address); + _wire->write((reg >> 8) & 0xFF); + _wire->write(reg & 0xFF); + _wire->write(static_cast(value)); + _wire->endTransmission(); +} + +void VL53L1X::writeReg16(uint16_t reg, uint16_t value) { + _wire->beginTransmission(_address); + _wire->write((reg >> 8) & 0xFF); + _wire->write(reg & 0xFF); + _wire->write(static_cast((value >> 8) & 0xFF)); + _wire->write(static_cast(value & 0xFF)); + _wire->endTransmission(); +} + +uint8_t VL53L1X::readReg(uint16_t reg) { + _wire->beginTransmission(_address); + _wire->write((reg >> 8) & 0xFF); + _wire->write(reg & 0xFF); + _wire->endTransmission(false); + _wire->requestFrom(_address, static_cast(1)); + if (_wire->available()) { + return static_cast(_wire->read()); + } + return 0; +} + +uint16_t VL53L1X::readReg16(uint16_t reg) { + _wire->beginTransmission(_address); + _wire->write((reg >> 8) & 0xFF); + _wire->write(reg & 0xFF); + _wire->endTransmission(false); + _wire->requestFrom(_address, static_cast(2)); + if (_wire->available() >= 2) { + uint8_t msb = static_cast(_wire->read()); + uint8_t lsb = static_cast(_wire->read()); + return (msb << 8) | lsb; + } + return 0; +} + +void VL53L1X::writeRegBytes(uint16_t reg, const uint8_t* data, size_t len) { + constexpr size_t MAX_DATA_PER_FRAME = 30; + size_t offset = 0; + while (offset < len) { + const size_t chunk = + (len - offset > MAX_DATA_PER_FRAME) ? MAX_DATA_PER_FRAME : (len - offset); + _wire->beginTransmission(_address); + _wire->write(((reg + offset) >> 8) & 0xFF); + _wire->write((reg + offset) & 0xFF); + for (size_t i = 0; i < chunk; ++i) { + _wire->write(data[offset + i]); + } + _wire->endTransmission(); + offset += chunk; + } +} + +uint16_t VL53L1X::deviceId() { + return readReg16(REG_MODEL_ID); +} + +void VL53L1X::reset() { + writeReg(REG_SOFT_RESET, SOFT_RESET_ASSERT); + delay(100); + writeReg(REG_SOFT_RESET, SOFT_RESET_RELEASE); +} + +void VL53L1X::powerOff() { + writeReg(REG_SOFT_RESET, SOFT_RESET_ASSERT); +} + +void VL53L1X::powerOn() { + writeReg(REG_SOFT_RESET, SOFT_RESET_RELEASE); + delay(100); +} + +void VL53L1X::startRanging() { + writeReg(REG_SYSTEM_START, RANGING_START); +} + +void VL53L1X::stopRanging() { + writeReg(REG_SYSTEM_START, RANGING_STOP); +} + +bool VL53L1X::dataReady() { + bool polarity = (readReg(REG_GPIO_HV_MUX_CTRL) & GPIO_HV_MUX_CTRL_POLARITY) != 0; + uint8_t readyVal = polarity ? 0 : 1; + return (readReg(REG_GPIO_TIO_HV_STATUS) & GPIO_TIO_HV_STATUS_DATA_READY) == readyVal; +} + +void VL53L1X::clearInterrupt() { + writeReg(REG_SYSTEM_INTERRUPT_CLEAR, INTERRUPT_CLEAR); +} + +bool VL53L1X::ensureData() { + if (!dataReady()) { + startRanging(); + for (uint16_t i = 0; i < 100; i++) { + if (dataReady()) { + return true; + } + delay(10); + } + return false; + } + return true; +} + +uint16_t VL53L1X::distanceMm() { + if (!ensureData()) { + return 0; + } + uint8_t data[RESULT_BLOCK_SIZE] = {}; + _wire->beginTransmission(_address); + _wire->write((REG_RESULT_RANGE_STATUS >> 8) & 0xFF); + _wire->write(REG_RESULT_RANGE_STATUS & 0xFF); + _wire->endTransmission(false); + _wire->requestFrom(_address, static_cast(RESULT_BLOCK_SIZE)); + for (uint8_t i = 0; i < RESULT_BLOCK_SIZE && _wire->available(); i++) { + data[i] = static_cast(_wire->read()); + } + uint16_t distance = static_cast(data[RESULT_DISTANCE_MSB_OFFSET] << 8) + + data[RESULT_DISTANCE_LSB_OFFSET]; + clearInterrupt(); + return distance; +} + +uint16_t VL53L1X::read() { + return distanceMm(); +} \ No newline at end of file diff --git a/lib/vl53l1x/src/VL53L1X.h b/lib/vl53l1x/src/VL53L1X.h new file mode 100644 index 0000000..bd35c84 --- /dev/null +++ b/lib/vl53l1x/src/VL53L1X.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#include "VL53L1X_const.h" + +class VL53L1X { + public: + VL53L1X(TwoWire& wire = Wire, uint8_t address = VL53L1X_I2C_DEFAULT_ADDR); + + bool begin(); + uint16_t deviceId(); + void reset(); + void powerOff(); + void powerOn(); + void startRanging(); + void stopRanging(); + bool dataReady(); + uint16_t distanceMm(); + uint16_t read(); + + private: + TwoWire* _wire; + uint8_t _address; + static const uint8_t DEFAULT_CONFIG[]; + + void writeReg(uint16_t reg, uint8_t value); + void writeReg16(uint16_t reg, uint16_t value); + void writeRegBytes(uint16_t reg, const uint8_t* data, size_t len); + uint8_t readReg(uint16_t reg); + uint16_t readReg16(uint16_t reg); + void clearInterrupt(); + bool ensureData(); +}; \ No newline at end of file diff --git a/lib/vl53l1x/src/VL53L1X_const.h b/lib/vl53l1x/src/VL53L1X_const.h new file mode 100644 index 0000000..44e5a81 --- /dev/null +++ b/lib/vl53l1x/src/VL53L1X_const.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +// I2C address +constexpr uint8_t VL53L1X_I2C_DEFAULT_ADDR = 0x29; + +// Device identification +constexpr uint16_t REG_MODEL_ID = 0x010F; +constexpr uint16_t VL53L1X_DEVICE_ID = 0xEACC; + +// System control +constexpr uint16_t REG_SOFT_RESET = 0x0000; +constexpr uint8_t SOFT_RESET_ASSERT = 0x00; +constexpr uint8_t SOFT_RESET_RELEASE = 0x01; + +// Default configuration start register +constexpr uint16_t REG_DEFAULT_CONFIG_START = 0x2D; + +// Timing +constexpr uint16_t REG_RESULT_OSC_CALIBRATE_VAL = 0x0022; +constexpr uint16_t REG_RANGE_CONFIG_VCSEL_PERIOD_A = 0x001E; + +// Ranging control +constexpr uint16_t REG_SYSTEM_START = 0x0087; +constexpr uint8_t RANGING_START = 0x40; +constexpr uint8_t RANGING_STOP = 0x00; + +// Interrupt +constexpr uint16_t REG_GPIO_HV_MUX_CTRL = 0x0030; +constexpr uint8_t GPIO_HV_MUX_CTRL_POLARITY = 0x10; +constexpr uint16_t REG_GPIO_TIO_HV_STATUS = 0x0031; +constexpr uint8_t GPIO_TIO_HV_STATUS_DATA_READY = 0x01; +constexpr uint16_t REG_SYSTEM_INTERRUPT_CLEAR = 0x0086; +constexpr uint8_t INTERRUPT_CLEAR = 0x01; + +// Result registers +constexpr uint16_t REG_RESULT_RANGE_STATUS = 0x0089; +constexpr uint8_t RESULT_BLOCK_SIZE = 17; +constexpr uint8_t RESULT_DISTANCE_MSB_OFFSET = 13; +constexpr uint8_t RESULT_DISTANCE_LSB_OFFSET = 14; \ No newline at end of file diff --git a/tests/hardware/test_vl53l1x/test_main.cpp b/tests/hardware/test_vl53l1x/test_main.cpp new file mode 100644 index 0000000..1e8912c --- /dev/null +++ b/tests/hardware/test_vl53l1x/test_main.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include + +#include "driver_checks.h" + +TwoWire internalI2C(I2C_INT_SDA, I2C_INT_SCL); +VL53L1X sensor(internalI2C); + +void setUp(void) { + sensor.begin(); +} + +void tearDown(void) {} + +void test_vl53l1x_begin() { + check_begin(sensor); +} + +void test_vl53l1x_device_id(void) { + check_who_am_i(sensor, VL53L1X_DEVICE_ID); +} + +void test_vl53l1x_read_plausible_distance(void) { + check_read_plausible(sensor, &VL53L1X::distanceMm, (uint16_t)1, (uint16_t)4000); +} + +void setup() { + delay(2000); + internalI2C.begin(); + + UNITY_BEGIN(); + RUN_TEST(test_vl53l1x_begin); + RUN_TEST(test_vl53l1x_device_id); + RUN_TEST(test_vl53l1x_read_plausible_distance); + UNITY_END(); +} + +void loop() {} \ No newline at end of file diff --git a/tests/native/helpers/Wire.h b/tests/native/helpers/Wire.h index 7f9ca0f..833b851 100644 --- a/tests/native/helpers/Wire.h +++ b/tests/native/helpers/Wire.h @@ -33,13 +33,30 @@ class TwoWire { // reg + offset, one WriteOp per byte so tests can assert the full // write sequence. uint8_t reg = txBuffer_[0]; - for (size_t i = 1; i < txBuffer_.size(); ++i) { - uint8_t targetReg = static_cast(reg + (i - 1)); - uint8_t val = txBuffer_[i]; - registers_[makeKey(currentAddress_, targetReg)] = val; - writes_.push_back({currentAddress_, targetReg, val}); + + bool is16bit = (currentAddress_ == 0x29) && (txBuffer_.size() >= 2); + + if (is16bit && txBuffer_.size() == 2) { + uint16_t regAddr = (static_cast(txBuffer_[0]) << 8) | txBuffer_[1]; + currentRegisterByAddr_[currentAddress_] = regAddr; + } else if (is16bit) { + uint16_t regAddr = (static_cast(txBuffer_[0]) << 8) | txBuffer_[1]; + for (size_t i = 2; i < txBuffer_.size(); ++i) { + uint16_t targetReg = static_cast(regAddr + (i - 2)); + uint8_t val = txBuffer_[i]; + registers_[makeKey(currentAddress_, targetReg)] = val; + writes_.push_back({currentAddress_, targetReg, val}); + } + currentRegisterByAddr_[currentAddress_] = regAddr; + } else { + for (size_t i = 1; i < txBuffer_.size(); ++i) { + uint16_t targetReg = static_cast(reg + (i - 1)); + uint8_t val = txBuffer_[i]; + registers_[makeKey(currentAddress_, targetReg)] = val; + writes_.push_back({currentAddress_, targetReg, val}); + } + currentRegisterByAddr_[currentAddress_] = reg; } - currentRegisterByAddr_[currentAddress_] = reg; } else if (txBuffer_.size() == 1) { currentRegisterByAddr_[currentAddress_] = txBuffer_[0]; } @@ -52,7 +69,7 @@ class TwoWire { uint8_t requestFrom(uint8_t address, uint8_t quantity) { rxBuffer_.clear(); - uint8_t reg = currentRegisterByAddr_[address]; + uint16_t reg = currentRegisterByAddr_[address]; if (reg == 0 && quantity == 2) { uint16_t subcommand = (static_cast(registers_[makeKey(address, 1)]) << 8) | @@ -92,18 +109,17 @@ class TwoWire { // Host-side helpers — not part of the real Arduino TwoWire API. - void setRegister(uint8_t address, uint8_t reg, uint8_t value) { + void setRegister(uint8_t address, uint16_t reg, uint8_t value) { registers_[makeKey(address, reg)] = value; } - - uint8_t getRegister(uint8_t address, uint8_t reg) const { + uint8_t getRegister(uint8_t address, uint16_t reg) const { auto it = registers_.find(makeKey(address, reg)); return (it != registers_.end()) ? it->second : 0x00; } struct WriteOp { uint8_t address; - uint8_t reg; + uint16_t reg; uint8_t value; }; @@ -122,16 +138,16 @@ class TwoWire { } private: - static uint16_t makeKey(uint8_t addr, uint8_t reg) { - return (static_cast(addr) << 8) | reg; + static uint32_t makeKey(uint8_t addr, uint16_t reg) { + return (static_cast(addr) << 16) | reg; } uint8_t currentAddress_ = 0; - std::map currentRegisterByAddr_; + std::map currentRegisterByAddr_; std::vector txBuffer_; std::vector rxBuffer_; size_t rxIndex_ = 0; - std::map registers_; + std::map registers_; std::vector writes_; uint8_t endTransmissionResult_ = 0; }; diff --git a/tests/native/test_vl53l1x/test_main.cpp b/tests/native/test_vl53l1x/test_main.cpp new file mode 100644 index 0000000..5b42396 --- /dev/null +++ b/tests/native/test_vl53l1x/test_main.cpp @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include "VL53L1X.h" +#include "Wire.h" +#include "driver_checks.h" + +constexpr uint8_t ADDR = VL53L1X_I2C_DEFAULT_ADDR; + +static void preloadWhoAmI(bool valid = true) { + uint8_t msb = valid ? (uint8_t)(VL53L1X_DEVICE_ID >> 8) : 0x42; + uint8_t lsb = valid ? (uint8_t)(VL53L1X_DEVICE_ID & 0xFF) : 0x42; + Wire.setRegister(ADDR, 0x010F, msb); + Wire.setRegister(ADDR, 0x0110, lsb); +} + +static void preloadDataReady(bool ready = true) { + Wire.setRegister(ADDR, (uint8_t)REG_GPIO_HV_MUX_CTRL, 0x00); + Wire.setRegister(ADDR, (uint8_t)REG_GPIO_TIO_HV_STATUS, + ready ? GPIO_TIO_HV_STATUS_DATA_READY : 0x00); +} + +static void preloadDistance(uint16_t distance) { + preloadDataReady(true); + Wire.setRegister(ADDR, REG_RESULT_RANGE_STATUS + RESULT_DISTANCE_MSB_OFFSET, + (distance >> 8) & 0xFF); + Wire.setRegister(ADDR, REG_RESULT_RANGE_STATUS + RESULT_DISTANCE_LSB_OFFSET, distance & 0xFF); +} + +VL53L1X sensor; + +void setUp() { + Wire = TwoWire(); + sensor.begin(); +} + +void tearDown() {} + +void test_begin_detects_device() { + preloadWhoAmI(true); + check_begin(sensor); +} + +void test_device_id_returns_who_am_i() { + preloadWhoAmI(true); + sensor.begin(); + check_who_am_i(sensor, VL53L1X_DEVICE_ID); +} + +void test_begin_rejects_wrong_who_am_i() { + preloadWhoAmI(false); + TEST_ASSERT_FALSE(sensor.begin()); +} + +void test_reset_writes_assert_then_release(void) { + Wire.clearWrites(); + sensor.reset(); + bool sawAssert = false; + bool sawRelease = false; + for (const auto& w : Wire.getWrites()) { + if (w.reg == (uint8_t)REG_SOFT_RESET && w.value == SOFT_RESET_ASSERT) { + sawAssert = true; + } + if (w.reg == (uint8_t)REG_SOFT_RESET && w.value == SOFT_RESET_RELEASE) { + sawRelease = true; + } + } + TEST_ASSERT_TRUE(sawAssert); + TEST_ASSERT_TRUE(sawRelease); +} + +void test_power_off_writes_assert(void) { + Wire.clearWrites(); + sensor.powerOff(); + bool sawAssert = false; + for (const auto& w : Wire.getWrites()) { + if (w.reg == (uint8_t)REG_SOFT_RESET && w.value == SOFT_RESET_ASSERT) { + sawAssert = true; + break; + } + } + TEST_ASSERT_TRUE(sawAssert); +} + +void test_power_on_writes_release(void) { + sensor.powerOn(); + Wire.clearWrites(); + sensor.powerOn(); + + bool sawRelease = false; + for (const auto& w : Wire.getWrites()) { + if (w.reg == (uint8_t)REG_SOFT_RESET && w.value == SOFT_RESET_RELEASE) { + sawRelease = true; + } + } + TEST_ASSERT_TRUE(sawRelease); +} + +void test_start_ranging_writes_start_value(void) { + sensor.begin(); + Wire.clearWrites(); + + sensor.startRanging(); + + bool sawStart = false; + for (const auto& w : Wire.getWrites()) { + if (w.reg == (uint8_t)REG_SYSTEM_START && w.value == RANGING_START) { + sawStart = true; + } + } + TEST_ASSERT_TRUE(sawStart); +} + +void test_stop_ranging_writes_stop_value(void) { + sensor.stopRanging(); + Wire.clearWrites(); + sensor.stopRanging(); + + bool sawStop = false; + for (const auto& w : Wire.getWrites()) { + if (w.reg == (uint8_t)REG_SYSTEM_START && w.value == RANGING_STOP) { + sawStop = true; + } + } + TEST_ASSERT_TRUE(sawStop); +} + +void test_data_ready_true_when_status_bit_set(void) { + preloadDataReady(true); + TEST_ASSERT_TRUE(sensor.dataReady()); +} + +void test_data_ready_false_when_status_bit_clear(void) { + preloadDataReady(false); + TEST_ASSERT_FALSE(sensor.dataReady()); +} + +void test_data_ready_polarity_inverted(void) { + sensor.begin(); + Wire.setRegister(ADDR, (uint8_t)REG_GPIO_HV_MUX_CTRL, GPIO_HV_MUX_CTRL_POLARITY); + Wire.setRegister(ADDR, (uint8_t)REG_GPIO_TIO_HV_STATUS, 0x00); + + TEST_ASSERT_TRUE(sensor.dataReady()); +} + +void test_distance_mm_returns_correct_value(void) { + preloadDistance(1234); + TEST_ASSERT_EQUAL_UINT16(1234, sensor.distanceMm()); +} + +void test_read_returns_same_as_distance_mm(void) { + preloadDistance(5678); + TEST_ASSERT_EQUAL_UINT16(5678, sensor.read()); +} + +void test_distance_mm_is_plausible(void) { + preloadDistance(200); + uint16_t dist = sensor.distanceMm(); + TEST_ASSERT_TRUE(dist > 0 && dist < 4000); +} + +void test_distance_clears_interrupt_after_read(void) { + preloadDataReady(true); + sensor.read(); + uint8_t interruptClearVal = Wire.getRegister(ADDR, (uint8_t)REG_SYSTEM_INTERRUPT_CLEAR); + TEST_ASSERT_EQUAL_UINT8(INTERRUPT_CLEAR, interruptClearVal); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_begin_detects_device); + RUN_TEST(test_begin_rejects_wrong_who_am_i); + RUN_TEST(test_device_id_returns_who_am_i); + RUN_TEST(test_reset_writes_assert_then_release); + RUN_TEST(test_power_off_writes_assert); + RUN_TEST(test_power_on_writes_release); + RUN_TEST(test_start_ranging_writes_start_value); + RUN_TEST(test_stop_ranging_writes_stop_value); + RUN_TEST(test_data_ready_true_when_status_bit_set); + RUN_TEST(test_data_ready_false_when_status_bit_clear); + RUN_TEST(test_data_ready_polarity_inverted); + RUN_TEST(test_distance_mm_returns_correct_value); + RUN_TEST(test_read_returns_same_as_distance_mm); + RUN_TEST(test_distance_mm_is_plausible); + RUN_TEST(test_distance_clears_interrupt_after_read); + return UNITY_END(); +} \ No newline at end of file