Advertisement
DeaD_EyE

VL6180X for Micropython 1.20+

Jul 4th, 2023 (edited)
811
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.17 KB | None | 0 0
  1. # SPDX-FileCopyrightText: 2023 Andre Müller
  2. # SPDX-License-Identifier: MIT
  3.  
  4. """
  5. `adafruit_vl6180x`
  6. ====================================================
  7.  
  8. CircuitPython module for the VL6180X distance sensor.  See
  9. examples/simpletest.py for a demo of the usage.
  10.  
  11. * Author(s): Tony DiCola, Jonas Schatz
  12.  
  13. Implementation Notes
  14. --------------------
  15.  
  16. **Hardware:**
  17.  
  18. * Adafruit `VL6180X Time of Flight Distance Ranging Sensor (VL6180)
  19.  <https://www.adafruit.com/product/3316>`_ (Product ID: 3316)
  20.  
  21. **Software and Dependencies:**
  22.  
  23. * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
  24.  https://github.com/adafruit/circuitpython/releases
  25. * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
  26.  
  27. **Update for Micropython:**
  28. This module was updated and works with ESP32 and Micropython 1.20+
  29.  
  30. **Bugs**
  31. read_lux does not work with my VL6180X distance sensor.
  32. I guess it's a bug.
  33. """
  34.  
  35. import struct
  36. import time
  37.  
  38. from micropython import const
  39.  
  40. # Registers
  41. _VL6180X_REG_IDENTIFICATION_MODEL_ID = const(0x000)
  42.  
  43. _VL6180X_REG_SYSTEM_HISTORY_CTRL = const(0x012)
  44. _VL6180X_REG_SYSTEM_INTERRUPT_CONFIG = const(0x014)
  45. _VL6180X_REG_SYSTEM_INTERRUPT_CLEAR = const(0x015)
  46. _VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET = const(0x016)
  47.  
  48. _VL6180X_REG_SYSRANGE_START = const(0x018)
  49. _VL6180X_REG_SYSRANGE_INTERMEASUREMENT_PERIOD = const(0x01B)
  50. _VL6180X_REG_SYSRANGE_PART_TO_PART_RANGE_OFFSET = const(0x024)
  51.  
  52. _VL6180X_REG_SYSALS_START = const(0x038)
  53. _VL6180X_REG_SYSALS_ANALOGUE_GAIN = const(0x03F)
  54. _VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI = const(0x040)
  55. _VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO = const(0x041)
  56.  
  57. _VL6180X_REG_RESULT_RANGE_STATUS = const(0x04D)
  58. _VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO = const(0x04F)
  59. _VL6180X_REG_RESULT_ALS_VAL = const(0x050)
  60. _VL6180X_REG_RESULT_HISTORY_BUFFER_0 = const(0x052)
  61. _VL6180X_REG_RESULT_RANGE_VAL = const(0x062)
  62.  
  63. # Internal constants:
  64. _VL6180X_DEFAULT_I2C_ADDR = const(0x29)
  65.  
  66. # User-facing constants:
  67. ALS_GAIN_1 = const(0x06)
  68. ALS_GAIN_1_25 = const(0x05)
  69. ALS_GAIN_1_67 = const(0x04)
  70. ALS_GAIN_2_5 = const(0x03)
  71. ALS_GAIN_5 = const(0x02)
  72. ALS_GAIN_10 = const(0x01)
  73. ALS_GAIN_20 = const(0x00)
  74. ALS_GAIN_40 = const(0x07)
  75.  
  76. ERROR_NONE = const(0)
  77. ERROR_SYSERR_1 = const(1)
  78. ERROR_SYSERR_5 = const(5)
  79. ERROR_ECEFAIL = const(6)
  80. ERROR_NOCONVERGE = const(7)
  81. ERROR_RANGEIGNORE = const(8)
  82. ERROR_SNR = const(11)
  83. ERROR_RAWUFLOW = const(12)
  84. ERROR_RAWOFLOW = const(13)
  85. ERROR_RANGEUFLOW = const(14)
  86. ERROR_RANGEOFLOW = const(15)
  87.  
  88.  
  89. class VL6180X:
  90.     """
  91.    Create an instance of the VL6180X distance sensor. You must pass in
  92.    the following parameters:
  93.  
  94.    :param ~I2C i2c: An instance of the I2C bus connected to the sensor.
  95.  
  96.    Optionally you can specify:
  97.  
  98.    :param int address: The I2C address of the sensor.  If not specified the sensor's
  99.                    default value will be assumed.
  100.    :param int offset: The offset to be applied to measurements, in mm
  101.    """
  102.  
  103.     def __init__(
  104.         self, i2c: I2C, address: int = _VL6180X_DEFAULT_I2C_ADDR, offset: int = 0
  105.     ) -> None:
  106.         self._device = i2c
  107.         self._address = address
  108.         if self._read_8(_VL6180X_REG_IDENTIFICATION_MODEL_ID) != 0xB4:
  109.             raise RuntimeError("Could not find VL6180X, is it connected and powered?")
  110.         self._load_settings()
  111.         self._write_8(_VL6180X_REG_SYSTEM_FRESH_OUT_OF_RESET, 0x00)
  112.         self.offset = offset
  113.  
  114.         # Reset a sensor that crashed while in continuous mode
  115.         if self.continuous_mode_enabled:
  116.             self.stop_range_continuous()
  117.             time.sleep(0.1)
  118.  
  119.         # Activate history buffer for range measurement
  120.         self._write_8(_VL6180X_REG_SYSTEM_HISTORY_CTRL, 0x01)
  121.  
  122.     @property
  123.     def range(self) -> int:
  124.         """
  125.        Read the range of an object in front of sensor and return it in mm.
  126.        """
  127.         if self.continuous_mode_enabled:
  128.             return self._read_range_continuous()
  129.         return self._read_range_single()
  130.  
  131.     @property
  132.     def range_from_history(self) -> Optional[int]:
  133.         """
  134.        Read the latest range data from history
  135.        To do so, you don't have to wait for a complete measurement.
  136.        """
  137.  
  138.         if not self.range_history_enabled:
  139.             return None
  140.  
  141.         return self._read_8(_VL6180X_REG_RESULT_HISTORY_BUFFER_0)
  142.  
  143.     @property
  144.     def ranges_from_history(self) -> Optional[List[int]]:
  145.         """
  146.        Read the last 16 range measurements from history
  147.        """
  148.  
  149.         if not self.range_history_enabled:
  150.             return None
  151.  
  152.         return [
  153.             self._read_8(_VL6180X_REG_RESULT_HISTORY_BUFFER_0 + age)
  154.             for age in range(16)
  155.         ]
  156.  
  157.     @property
  158.     def range_history_enabled(self) -> bool:
  159.         """
  160.        Checks if history buffer stores range data
  161.        """
  162.  
  163.         history_ctrl: int = self._read_8(_VL6180X_REG_SYSTEM_HISTORY_CTRL)
  164.  
  165.         if history_ctrl & 0x0:
  166.             print("History buffering not enabled")
  167.             return False
  168.  
  169.         if (history_ctrl > 1) & 0x1:
  170.             print("History buffer stores ALS data, not range")
  171.             return False
  172.  
  173.         return True
  174.  
  175.     def start_range_continuous(self, period: int = 100) -> None:
  176.         """
  177.        Start continuous range mode
  178.  
  179.        :param int period: Time delay between measurements, in milliseconds; the value you
  180.            will be floored to the nearest 10 milliseconds (setting to 157 ms sets it to 150
  181.            ms). Range is 10 - 2550 ms.
  182.        """
  183.         # Set range between measurements
  184.         if not 10 <= period <= 2550:
  185.             raise ValueError(
  186.                 "Delay must be in 10 millisecond increments between 10 and 2550 milliseconds"
  187.             )
  188.  
  189.         period_reg = (period // 10) - 1
  190.         self._write_8(_VL6180X_REG_SYSRANGE_INTERMEASUREMENT_PERIOD, period_reg)
  191.  
  192.         # Start continuous range measurement
  193.         self._write_8(_VL6180X_REG_SYSRANGE_START, 0x03)
  194.  
  195.     def stop_range_continuous(self) -> None:
  196.         """
  197.        Stop continuous range mode. It is advised to wait for about 0.3s
  198.        afterwards to avoid issues with the interrupt flags
  199.        """
  200.         if self.continuous_mode_enabled:
  201.             self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01)
  202.  
  203.     @property
  204.     def continuous_mode_enabled(self) -> bool:
  205.         """
  206.        Checks if continuous mode is enabled
  207.        """
  208.         return self._read_8(_VL6180X_REG_SYSRANGE_START) > 1 & 0x1
  209.  
  210.     @property
  211.     def offset(self) -> int:
  212.         """
  213.        Read and sets the manual offset for the sensor, in millimeters
  214.        """
  215.         return self._offset
  216.  
  217.     @offset.setter
  218.     def offset(self, offset: int) -> None:
  219.         self._write_8(
  220.             _VL6180X_REG_SYSRANGE_PART_TO_PART_RANGE_OFFSET, struct.pack("b", offset)[0]
  221.         )
  222.         self._offset = offset
  223.  
  224.     def _read_range_single(self) -> int:
  225.         """
  226.        Read the range when in single-shot mode
  227.        """
  228.         while not self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) & 0x01:
  229.             pass
  230.         self._write_8(_VL6180X_REG_SYSRANGE_START, 0x01)
  231.         return self._read_range_continuous()
  232.  
  233.     def _read_range_continuous(self) -> int:
  234.         """
  235.        Read the range when in continuous mode
  236.        """
  237.  
  238.         # Poll until bit 2 is set
  239.         while not self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) & 0x04:
  240.             pass
  241.  
  242.         # read range in mm
  243.         range_ = self._read_8(_VL6180X_REG_RESULT_RANGE_VAL)
  244.  
  245.         # clear interrupt
  246.         self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07)
  247.  
  248.         return range_
  249.  
  250.     def read_lux(self, gain: int) -> float:
  251.         """
  252.        Read the lux (light value) from the sensor and return it.  Must
  253.        specify the gain value to use for the lux reading:
  254.  
  255.        =================  =====
  256.             Setting       Value
  257.        =================  =====
  258.        ``ALS_GAIN_1``     1x
  259.        ``ALS_GAIN_1_25``  1.25x
  260.        ``ALS_GAIN_1_67``  1.67x
  261.        ``ALS_GAIN_2_5``   2.5x
  262.        ``ALS_GAIN_5``     5x
  263.        ``ALS_GAIN_10``    10x
  264.        ``ALS_GAIN_20``    20x
  265.        ``ALS_GAIN_40``    40x
  266.        =================  =====
  267.  
  268.        :param int gain: The gain value to use
  269.  
  270.        """
  271.         reg = self._read_8(_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG)
  272.         reg &= ~0x38
  273.         reg |= 0x4 << 3  # IRQ on ALS ready
  274.         self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CONFIG, reg)
  275.         # 100 ms integration period
  276.         self._write_8(_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_HI, 0)
  277.         self._write_8(_VL6180X_REG_SYSALS_INTEGRATION_PERIOD_LO, 100)
  278.         # analog gain
  279.         gain = min(gain, ALS_GAIN_40)
  280.         self._write_8(_VL6180X_REG_SYSALS_ANALOGUE_GAIN, 0x40 | gain)
  281.         # start ALS
  282.         self._write_8(_VL6180X_REG_SYSALS_START, 0x1)
  283.         # Poll until "New Sample Ready threshold event" is set
  284.         while (
  285.             (self._read_8(_VL6180X_REG_RESULT_INTERRUPT_STATUS_GPIO) >> 3) & 0x7
  286.         ) != 4:
  287.             pass
  288.         # read lux!
  289.         lux = self._read_16(_VL6180X_REG_RESULT_ALS_VAL)
  290.         # clear interrupt
  291.         self._write_8(_VL6180X_REG_SYSTEM_INTERRUPT_CLEAR, 0x07)
  292.         lux *= 0.32  # calibrated count/lux
  293.         if gain == ALS_GAIN_1:
  294.             pass
  295.         elif gain == ALS_GAIN_1_25:
  296.             lux /= 1.25
  297.         elif gain == ALS_GAIN_1_67:
  298.             lux /= 1.67
  299.         elif gain == ALS_GAIN_2_5:
  300.             lux /= 2.5
  301.         elif gain == ALS_GAIN_5:
  302.             lux /= 5
  303.         elif gain == ALS_GAIN_10:
  304.             lux /= 10
  305.         elif gain == ALS_GAIN_20:
  306.             lux /= 20
  307.         elif gain == ALS_GAIN_40:
  308.             lux /= 40
  309.         lux *= 100
  310.         lux /= 100  # integration time in ms
  311.         return lux
  312.  
  313.     @property
  314.     def range_status(self) -> int:
  315.         """
  316.        Retrieve the status/error from a previous range read.  This will
  317.        return a constant value such as:
  318.  
  319.        =====================  ==============================
  320.                Error                   Description
  321.        =====================  ==============================
  322.        ``ERROR_NONE``         No error
  323.        ``ERROR_SYSERR_1``     System error 1 (see datasheet)
  324.        ``ERROR_SYSERR_5``     System error 5 (see datasheet)
  325.        ``ERROR_ECEFAIL``      ECE failure
  326.        ``ERROR_NOCONVERGE``   No convergence
  327.        ``ERROR_RANGEIGNORE``  Outside range ignored
  328.        ``ERROR_SNR``          Too much noise
  329.        ``ERROR_RAWUFLOW``     Raw value underflow
  330.        ``ERROR_RAWOFLOW``     Raw value overflow
  331.        ``ERROR_RANGEUFLOW``   Range underflow
  332.        ``ERROR_RANGEOFLOW``   Range overflow
  333.        =====================  ==============================
  334.  
  335.        """
  336.         return self._read_8(_VL6180X_REG_RESULT_RANGE_STATUS) >> 4
  337.  
  338.     def _load_settings(self) -> None:
  339.         """
  340.        private settings from page 24 of app note
  341.        """
  342.         self._write_8(0x0207, 0x01)
  343.         self._write_8(0x0208, 0x01)
  344.         self._write_8(0x0096, 0x00)
  345.         self._write_8(0x0097, 0xFD)
  346.         self._write_8(0x00E3, 0x00)
  347.         self._write_8(0x00E4, 0x04)
  348.         self._write_8(0x00E5, 0x02)
  349.         self._write_8(0x00E6, 0x01)
  350.         self._write_8(0x00E7, 0x03)
  351.         self._write_8(0x00F5, 0x02)
  352.         self._write_8(0x00D9, 0x05)
  353.         self._write_8(0x00DB, 0xCE)
  354.         self._write_8(0x00DC, 0x03)
  355.         self._write_8(0x00DD, 0xF8)
  356.         self._write_8(0x009F, 0x00)
  357.         self._write_8(0x00A3, 0x3C)
  358.         self._write_8(0x00B7, 0x00)
  359.         self._write_8(0x00BB, 0x3C)
  360.         self._write_8(0x00B2, 0x09)
  361.         self._write_8(0x00CA, 0x09)
  362.         self._write_8(0x0198, 0x01)
  363.         self._write_8(0x01B0, 0x17)
  364.         self._write_8(0x01AD, 0x00)
  365.         self._write_8(0x00FF, 0x05)
  366.         self._write_8(0x0100, 0x05)
  367.         self._write_8(0x0199, 0x05)
  368.         self._write_8(0x01A6, 0x1B)
  369.         self._write_8(0x01AC, 0x3E)
  370.         self._write_8(0x01A7, 0x1F)
  371.         self._write_8(0x0030, 0x00)
  372.         # Recommended : Public registers - See data sheet for more detail
  373.         self._write_8(0x0011, 0x10)  # Enables polling for 'New Sample ready'
  374.         # when measurement completes
  375.         self._write_8(0x010A, 0x30)  # Set the averaging sample period
  376.         # (compromise between lower noise and
  377.         # increased execution time)
  378.         self._write_8(0x003F, 0x46)  # Sets the light and dark gain (upper
  379.         # nibble). Dark gain should not be
  380.         # changed.
  381.         self._write_8(0x0031, 0xFF)  # sets the # of range measurements after
  382.         # which auto calibration of system is
  383.         # performed
  384.         self._write_8(0x0040, 0x63)  # Set ALS integration time to 100ms
  385.         self._write_8(0x002E, 0x01)  # perform a single temperature calibration
  386.         # of the ranging sensor
  387.  
  388.         # Optional: Public registers - See data sheet for more detail
  389.         self._write_8(0x001B, 0x09)  # Set default ranging inter-measurement
  390.         # period to 100ms
  391.         self._write_8(0x003E, 0x31)  # Set default ALS inter-measurement period
  392.         # to 500ms
  393.         self._write_8(0x0014, 0x24)  # Configures interrupt on 'New Sample
  394.         # Ready threshold event'
  395.    
  396.     def _write_register(self, register: int) -> None:
  397.         """
  398.        Write 0 byte of data from the specified 16-bit register address.
  399.        """
  400.         self._device.writeto(
  401.             self._address,
  402.             struct.pack(">H", register),
  403.         )
  404.  
  405.     def _write_8(self, register: int, data: int) -> None:
  406.         """
  407.        Write 1 byte of data from the specified 16-bit register address.
  408.        """
  409.         self._device.writeto(
  410.             self._address,
  411.             struct.pack(">HB", register, data),
  412.         )
  413.  
  414.     def _write_16(self, register: int, data: int) -> None:
  415.         """
  416.        Write a 16-bit big endian value to the specified 16-bit register
  417.        address.
  418.        """
  419.         self._device.writeto(
  420.             self._address,
  421.             struct.pack(">HH", register, data),
  422.         )
  423.  
  424.     def _read_8(self, register: int) -> int:
  425.         """
  426.        Read and return a byte from the specified 16-bit register address.    
  427.        """
  428.         result = bytearray(1)
  429.         self._write_register(register)
  430.         self._device.readfrom_into(self._address, result)
  431.         return result[0]
  432.  
  433.     def _read_16(self, register: int) -> int:
  434.         """
  435.        Read and return a 16-bit unsigned big endian value read from the
  436.        specified 16-bit register address.
  437.        """
  438.         result = bytearray(2)
  439.         self._write_register(register)
  440.         self._device.readfrom_into(self._address, result)
  441.         return struct.unpack_from(">H", result)[0]
  442.  
  443.  
  444. if __name__ == "__main__":
  445.     from machine import I2C, Pin, PWM
  446.    
  447.     led = PWM(Pin(13), duty_u16=0, freq=300)
  448.    
  449.    
  450.     i2c = I2C(0, freq=400_000)
  451.     sensor = VL6180X(i2c)
  452.     # sensor.offset = 20
  453.     sensor.start_range_continuous(50)
  454.     while True:
  455.         if (srange := sensor.range) != 255:
  456.             led.duty_u16(int((255 - srange) * 65535 / 255))
  457.             # time.sleep_ms(500)
  458.             print(srange, "mm")
  459.         else:
  460.             led.duty_u16(0)
  461.  
  462.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement