Close

CircuitPython Progress

A project log for A Halo For Lucy

It's not what you think.

bud-bennettBud Bennett 02/28/2019 at 21:150 Comments

The coding is coming along faster than anticipated. In order to manage the input and output requirements of the sensors and transducers there has to be an understanding of the Arduino's basic I/O capabilities. At a minimum there has to be one I2C interface (2 pins), 5 digital outputs to allow changing of the VL53L0X I2C addresses -- enabling multiple sensors on a single I2C bus, and two PWM digital outputs to generate the audible feedback containing direction and distance.

I tend to write large pieces of code and then get bogged down trying to comprehend what exactly went wrong. This time I just wrote code snippets to get familiar with CircuitPython and the Arduino hardware platform. I'm not using the Arduino IDE -- it is not compatible with CIrcuitPython.

The first objective was to just wiggle the correct digital pins, so I wrote and debugged this code snippet:

import time
import board
from digitalio import DigitalInOut, Direction

# assign pins to VL53L0X shutdown inputs
shutdown = []
shutdown.append(DigitalInOut(board.D5))
shutdown.append(DigitalInOut(board.D6))
shutdown.append(DigitalInOut(board.D9))
shutdown.append(DigitalInOut(board.D10))
shutdown.append(DigitalInOut(board.D11))

for n in range(5):
    shutdown[n].direction = Direction.OUTPUT
    shutdown[n].value = False

while True:
    for n in range(5):
        shutdown[n].value = not shutdown[n].value
    time.sleep(5)

All that this does is wiggle the digital I/O pin on/off every 5 seconds. I was able to put a DVM on the pin and verify that it was behaving correctly. 

I then got the PWM outputs to function with this code the generates a 10 Hz square wave on D0 and D1:

import board
import pulseio

# assign PWM pins
oudio_l = pulseio.PWMOut(board.D0, frequency=10, duty_cycle=32768, variable_frequency=True)
audio_r = pulseio.PWMOut(board.D1, frequency=10, duty_cycle=32768, variable_frequency=True)

I got errors trying to assign the PWM outputs to D14 and D15. I took the path of least resistance with D0, and D1. I got correct DC and AC values on both pins with the DVM.

Multiple Sensors

After this initial success, I attempted to implement a method to assign a different I2C address to each VL53L0X sensor. I only have one VL53L0X at this point but that didn't stop me. 

This is the algorithm in english:

  1. Assign pins to I/O.
  2. Assert the shutdown pins (active low) on all VL53L0X sensors to disable them from communicating on the I2C bus.
  3. instantiate a generic I2C interface just to assign new addresses to the sensors.
  4. for each sensor:
    1. de-assert its shutdown pin to activate the sensor
    2. send an I2C command over the generic interface to change the I2C address to a unique one.
    3. read back the I2C address of the sensor as a check.
    4. leave the sensor active at this point.
  5. Instantiate the five sensors with unique names and addresses. This will call the calibration procedure and initialize each sensor properly. I commented out all of these instances except the second one in the sequence -- since I have only one sensor.
  6. Run the main loop using the single sensor. This loop blinks an LED when the distance sensor detects an object. The blink rate starts at 0.5 Hz for distant objects increasing to 10Hz or so for near objects.

Here's the final code that puts all of the above into place.

# multiple sensor simple sensor with PWM speaker outputs
import time
import board
from digitalio import DigitalInOut, Direction
import busio
import pulseio
import adafruit_vl53l0x

# assign pins to VL53L0X shutdown inputs
shutdown = []
shutdown.append(DigitalInOut(board.D5))
shutdown.append(DigitalInOut(board.D6))
shutdown.append(DigitalInOut(board.D9))
shutdown.append(DigitalInOut(board.D10))
shutdown.append(DigitalInOut(board.D11))
  
# assign PWM pins
oudio_l = pulseio.PWMOut(board.D0, frequency=10, duty_cycle=32768, variable_frequency=True)
audio_r = pulseio.PWMOut(board.D1, frequency=10, duty_cycle=32768, variable_frequency=True)

# turn off all sensors and intialize distance array
distance = []
for n in range(5):
    shutdown[n].direction = Direction.OUTPUT
    shutdown[n].value = False # low is shutdown
    distance.append(1000)

# Initialize I2C bus and sensors.
i2c = busio.I2C(board.SCL, board.SDA)

# initialize led
led = DigitalInOut(board.D13)
led.direction = Direction.OUTPUT

# setup multiple VL53L0X sensors
VL53_address =[0x29, 0x2A, 0x2B, 0x2C, 0x2D]
for n in range(5):
    shutdown[n].value = True # turn on sensor
    time.sleep(0.1)
    print("Address = {}".format(VL53_address[n]))
    try:
        while not i2c.try_lock():
            pass
        result = bytearray(1)
        #set new address
        i2c.writeto(0x29, bytes([0x8A, VL53_address[n]]), stop=False)
        time.sleep(0.1)
        # verity new address
        i2c.writeto(VL53_address[n], bytes([0x8A]))
        i2c.readfrom_into(VL53_address[n],result)
        print("device address = {}".format(int.from_bytes(result,'big')))
    except:
        i2c.unlock()
    finally:
        i2c.unlock()

#VL53L0X_0 = adafruit_vl53l0x.VL53L0X(i2c=i2c,address=VL53_address[0], io_timeout_s=0)
VL53L0X_1 = adafruit_vl53l0x.VL53L0X(i2c=i2c,address=VL53_address[1], io_timeout_s=0)
#VL53L0X_2 = adafruit_vl53l0x.VL53L0X(i2c=i2c,address=VL53_address[2], io_timeout_s=0)
#VL53L0X_3 = adafruit_vl53l0x.VL53L0X(i2c=i2c,address=VL53_address[3], io_timeout_s=0)
#VL53L0X_4 = adafruit_vl53l0x.VL53L0X(i2c=i2c,address=VL53_address[4], io_timeout_s=0)


# Optionally adjust the measurement timing budget to change speed and accuracy.
# See the example here for more details:
#   https://github.com/pololu/vl53l0x-arduino/blob/master/examples/Single/Single.ino
# For example a higher speed but less accurate timing budget of 20ms:
#vl53.measurement_timing_budget = 20000
# Or a slower but more accurate timing budget of 200ms:
#vl53.measurement_timing_budget = 200000
# The default timing budget is 33ms, a good compromise of speed and accuracy.

# Main loop will read the range and blink LED at rate proportional to distance.
while True:
    distance = VL53L0X_1.range
    if (distance < 1000):
        if (led.value):
            led.value = False
        else:
            led.value = True
    else:
        distance = 1000
        led.value = False
    time.sleep(distance/1000)

Debugging with a single sensor was difficult and led down a few dead ends. It took a bit of trial and error to understand what was happening and how to interpret the result. 

The second sensor (the only sensor) has its shutdown input connected to D6. Therefore it only responds the second time through the address change loop and has its address changed from 0x29 to 0x2A. After that it stops responding to any address changes since doesn't have the correct address anymore. The sensor can be instantiated as VL53L0X_1 and will operate correctly with the assigned address. This is proven by the blinking LED which changes its blink rate as objects get closer.

Some Griping:

I'm not entirely happy with the reliability of the USB interface between my iMac and the Arduino board. For no apparent reason the CIRCUITPY drive icon disappears from my iMac's desktop and the Arduino board reboots and reloads the CircuitPython code. Flakey.

Onward:

My swelling confidence that things were working out led me to make a couple more purchases:

Some piezo speakers from Amazon. 

Six more VL53L0X distance sensors this time from AliExpress (sorry Adafruit, my loyalty has limits.) These 6 boards cost less, including shipping, than the single board from Adafruit. In fact, the board is less than 1/2 the price of a bare VL53L0X sensor from Digikey. Go figure.

Discussions