Before you try porting or creating a library for hardware you'll need to get very familiar with how the hardware works.  Typically you'll need to download the device's datasheet which is a document that describes in very great detail how the hardware works.  Grab the VL6180X datasheet below:

Datasheets can be somewhat intimidating, but don't worry you don't usually need to read or understand the entire document in excruciating detail (but it can't hurt!).  First start by skimming the document and you'll see high level sections that most datasheets contain:

  • Overview / Summary - Typically at the start datasheets will have a high level description of the device, important specifications, and how it communicates.  For this device a very important thing to note in the overview is that this device uses the I2C protocol for communication.  As a driver creator you'll need to understand what protocol is used to communicate with the device--typically I2C, SPI, serial, or even an analog or digital I/O signal.  For this guide and example we'll focus on I2C as it's a very common protocol for sensors.
  • Description - After the overview there's usually a deeper description of how the device functions.  This is important to read as it helps you understand the capabilities and behavior of the device.
  • Performance - Most sensors and devices have certain physical characteristics, like the precision and accuracy of their measurements or limits on environmental factors like temperature.  This is an important section to read if you're really getting deep into the usage of a device.  However if you're just porting existing code for a board that's already built you might not need to focus on these details.
  • I2C / Protocol Specification - Typically there's a section in the datasheet that describes the communication protocol for the device.  For this sensor the I2C section describes how to talk to the device using the I2C protocol.  Other devices might describe different protocols like SPI, serial, etc.  For an I2C device be sure to watch for uncommon behavior, like if the device mentions it needs clock stretching or repeated stop bits.  Some hardware doesn't support these uncommon I2C protocol behaviors and can be more difficult to port or work with.  Save yourself some future headaches by looking for these characteristics from the start!  In addition be sure to look for the I2C address of a device as you'll need to know it to talk to it later.  For the VL6180X its datasheet shows the I2C communication is fairly typical and doesn't use clock stretching, repeated stops, or other uncommon behavior.  In addition the address of the device is 0x29.
  • Electrical Characteristics - This section describes electrical characteristics and limits for the device.  It's good to skim this section and look for potential areas of concern like if the device works with 3.3V or 5V, how it behaves across a range of voltage inputs, etc.  As a driver writer you're probably less interested in this information, but it's good to skim for any warnings or gotchas.
  • Package Information - This is the least useful section for you as a driver writer.  Typically the mechanical and physical descriptions of the device's packages are found here.  Things like CAD and mechanical drawings of the footprint, layout, etc. of the hardware.  Since you're probably using a breakout that was already designed to hold the board you're less interested in this information and can skim or skip it.

I2C Registers

For an I2C device like the VL6180X you'll typically find the device exposes information through registers.  These are just like registers in your computer's processor--they're locations in memory that store data, like an integer or fractional number.  Most datasheets have an entire table or section devoted to describing the registers of the device, for example the VL6180X has:

You can see this table lists the offset, or address, of each register and then a description and link to more details about it.  A simple register like the IDENTIFICATION_MODEL_ID at the top has an offset of 0x000 (be careful to note these addresses are 16-bit and not just 8-bit!) and links to more details about its contents:

By reading the register description you can understand the data exposed by the chip.  In this case the model ID register contains a byte of data (8 bits) with a model identification number inside.  According to the datasheet this ID should be the value 0xB4. 

Accessing Registers

As a quick test let's try reading the register to confirm we see this value.  Before you get started be sure to read the CircuitPython I2C guide for details on using the I2C protocol with CircuitPython.  Then with the VL6180X wired to your connect to your board's serial REPL and initialize the I2C bus with code:

import board
import busio as io
i2c = io.I2C(board.SCL, board.SDA)

Note if your board doesn't support hardware I2C (like the ESP8266) you'll need to use the bitbangio module instead:

import board
import bitbangio as io
i2c = io.I2C(board.SCL, board.SDA)

Next let's scan for device addresses to make sure we see the VL6180X at the expected address (0x29).  Like the I2C guide mentions you can run code like this to scan for devices and print their address in hex:

while not i2c.try_lock():
[hex(x) for x in i2c.scan()]

If everything is connected and working correctly you should see the expected device address printed, 0x29.  If you don't see the address or see a different value carefully check your wiring and solder connections!

Now let's read the model ID register, again following the code from the I2C guide but modifying it to specify register address 0x000 (from the register table above):

i2c.writeto(0x29, bytes([0x00, 0x00]), stop=False)
result = bytearray(1)
i2c.readfrom_into(0x29, result)

Success! Notice the value 0xb4 is printed and matches the value we expect to see from the datasheet.  If you notice in the code that read the register value the i2c.writeto function is used to request the register to read--in this case the device with address 0x29 (the VL6180X sensor) and register 0x000.  Be careful to note for this device register addresses take two bytes since they are 16-bit values, hence the list [0x00, 0x00] to represent the 2 bytes of register address 0x000. The number of bytes to read from the register are specified by the size of the buffer that will hold results, in this case a bytearray of size 1 (i.e. one byte).

Finally like the I2C guide mentions there's an easier way to talk to I2C devices using the Adafruit bus device module and its I2CDevice class.  Follow the I2C guide to see how to install this module on your board (it does not currently come built in to CircuitPython builds and must be installed separately).  Then connect to the board and initialize I2C as before, but now run this to create an I2CDevice instance for the VL6180X:

from adafruit_bus_device.i2c_device import I2CDevice
device = I2CDevice(i2c, 0x29)

Now you can use the device class in a context manager to simplify the locking, unlocking and access to registers.  For example to read the model ID register again you can run:

with device:
    device.write(bytes([0x00, 0x00]), stop=False)
    result = bytearray(1)

Again you should see the model ID 0xb4 printed, but this time notice how the code to talk to the device is simpler.  Instead of sending the device address and locking/unlocking the I2C bus you can use the I2CDevice class to do this automatically.  When you port over a library it will greatly help to use the bus device module and its I2C and SPI device classes!

Now that you've seen how to talk to the device with CircuitPython continue on to learn about using a template to create some of the boilerplate code for a module.

This guide was first published on Oct 16, 2017. It was last updated on Mar 08, 2024.

This page (Datasheet & Registers) was last updated on Oct 12, 2017.

Text editor powered by tinymce.