Close
0%
0%

A Halo For Lucy

It's not what you think.

Public Chat
Similar projects worth following
Lucy is slowly going blind. We bought a halo from Amazon -- it is heavy and clunky and could have been made 100 years ago. It's time for an upgrade to something lighter and better.

Lucy is our seven year-old border collie schipperke mix. We got her as a puppy. 18 months ago she was diagnosed with Progressive Retinal Atrophy -- she is not getting enough blood flow to her retinae and she is slowly going blind. There is no cure.

She can still see in bright light, but has difficulty seeing in dim light or at night. She memorized our house layout and normally doesn't have any difficulty getting around day or night unless something has moved. Occasionally she bumps into something left in the middle of the floor or a drawer left unclosed. 

We purchased a halo. It is a metal hoop that surrounds her head. If she inadvertently gets too close to an object the halo will collide with the object instead of her head. The halo is attached in two locations to her body -- a strap around her neck, and a strap around her chest. The contraption looks uncomfortable to me, but Lucy has tolerated it the few times we have burdened her with it. There must be a better way.

I have begun to investigate an alternative to the halo. Lucy still has all of her other senses. I think that I can design and manufacture an electronic equivalent to the halo that will be smaller, lighter, and function better that what is available now.

The criteria:

  1. Fit -- should be easily applied and removed. Location above the head or below the jaw (TBD). Lucy should be able to sleep with the halo attached.
  2. Light -- Lucy is a small dog, about 25 pounds. The entire unit should weigh just a few ounces.
  3. Range and direction -- should be able to detect object from about 10mm to 1000mm. It should be able to indicate the position and approximate distance of the nearest object within range. Additionally, it should be able to indicate position and range of multiple objects (a stretch goal).
  4. It should perform similarly in brightly lit and poorly lit environments.
  5. It should not degrade Lucy's other senses.
  6. Battery operated -- 170 hrs of operation from a rechargeable lithium battery cell.

My first inclination is to use distance sensors coupled with a microprocessor to detect objects within range. I intend to use some sort of aural feedback to communicate distance and location via stereo speakers.

More to come -- later.

  • Feature Creep

    Bud Bennett20 hours ago 0 comments

    I went to the Adafruit website to order another Arduino Feather M0 Express unit to have as a backup. While meandering around their site I stumbled upon a better, smaller, Arduino unit: the Itsy Bitsy M0 Express. The Itsy Bitsy is quite a bit smaller, and almost half the cost of the Feather (so I bought two of them.) It has nearly all of the functionality of the Feather -- even the CircuitPython stuff -- but lacks the battery charging feature. I'm hoping it draws a bit less current too.

    Getting it to Fit:

    The problem with the Itsy is that the power and battery pins are located at one end of the board and the I2C and digital pins are at the other end. This means that the entire board has to be accommodated within or under the halo motherboard.

    The best solution that I could imagine involved placing the Itsy on top of the motherboard with the USB connector accessible from the right side.

    Not all of the pins are needed -- the analog pins and a lot of other pins related to the battery management or ADC reference. Pins at the corners are required to securely mount the board. I was going to just solder the male headers between the two boards, but tossed that idea when I tried to put mounting holes under the Itsy. Now there are female headers between the boards. The headers raise the Itsy about 0.5 inch -- slightly higher than the VL53L0X daughterboards. Probably still manageable.

    Also, since there is no jack for the battery on the Itsy there are now pads for the battery inputs on the motherboard at the right side. The charge indicator LED is located near the BAT pad.

    I let the layout guide the schematic.

    The ItsyBitsy is now included on the mother board schematic.

    Motion Sensing:

    The halo must turn off when Lucy is not moving or if it is tilted to an extreme position indicating that she is sleeping. Therefore I added a simple accelerometer to the system. The LIS3DH should do the job. I wanted to implement a 6-axis motion sensor, but the only one available in CircuitPython is a bit too expensive. This complicates the motherboard assembly since the accelerometer package is a 14-pin QFN. (At lease it doesn't have an exposed pad.)

    Too Far Too Fast:

    I think I'm getting way way ahead of myself with this latest design. The AliExpress sensors are stuck in U.S. customs, so I haven't even been able to test the multiple-sensor capability yet. I will probably just sit on this while the rest of the project catches up to it.

  • Getting Physical

    Bud Bennett5 days ago 3 comments

    While I'm waiting for the delivery of the VL53L0X sensor boards from AliExpress, I thought I'd take a whack at understanding the PCB requirements for a first prototype of the halo. The Arduino Feather M0 Express that I got from Adafruit has been working flawlessly. The current drain of the Feather coupled with a VL53L0X sensor board was about 40mA, <20mA for just the Feather, so I thought it worthwhile to explore using the Feather with some custom sensor PCBs as a simple solution (since I only need to make one of these systems).

    My first attempt was to just place the AliExpress PCBs into a motherboard that attaches to the Arduino Feather that I ordered from Adafruit. It was huge.  The board would be at least 3 inches in width, which might cause Lucy some problems with the fit. I can do better. Therefore, the AliExpress boards will only be used for prototype purposes -- whatever physical arrangement works to prove the concept. I'm planning on using six-pin dupont connectors and gluing the AliExpress boards to a substrate to get the first prototype working. (Or maybe just desoldering the sensors and migrating them to new PCBs.)

    Going My Own Way:

    By abandoning the AliExpress boards I need to make two sets of PCBs: a daughterboard to contain each VL53L0X sensor and any support circuitry, and a motherboard that attaches to the Feather M0 Express.

    After examining the space requirements of a 0.1" header vs. a 0.05" header, I decided to use the smaller headers to save a lot of space. I ordered a slew of 1.27mm pitch right angle male headers from eBay. Each VL53L0X sensor daughter board only needed the sensor and two capacitors:

    I also decided to use the Arduino 3V3 regulated power supply. This saved many resistors, level translator transistors, and a voltage regulator -- for each daughterboard and the motherboard. The VL53L0X data sheet claims that the part will function without issues with a 3.3V power supply. So be it. The preliminary daughterboard dimensions are 0.35" x 0.4". Pretty small, compared to the AliExpress boards (but not cheaper). The daughterboard attaches to the mother board with 6-pin right-angle headers.

    I'm not entirely convinced that all those resistors are necessary. There are placeholders for R8 and C1 -- CYA components. A lot of the pull-up resistors (R1-R5) may be depopulated after the first prototype. After creating the daughterboards I placed them into the motherboard PCB layout to see how big it would be and what challenges popped up. After a few iterations, I settled on the layout below.

    The motherboard plugs into the Arduino using the 6-pin headers spaced 0.8" apart. I also need the Arduino's 3.3V regulated output, which is not available in the first six pins of the header, but Adafruit did provide it as part of a small breadboard area on the Feather M0 Express. Lastly, the two piezo speakers are mounted at the left/right rear of the motherboard -- pretty close to Lucy's ears. (Here's where a couple of resistors in series with the piezo speakers might have been warranted.)

    I tested the assignment of the GPIO with the motherboard header pins -- no issues. Both the daughter boards and mother boards have been ordered. Awaiting delivery of PCBs fro JLCPCB -- check back in one week.

  • Adding Audio Output and Other Stuff

    Bud Bennett03/11/2019 at 22:47 0 comments

    My recent objective is to just get the piezo speakers to respond to changes in distance measured by the VL53L0X sensor. This is mainly a learning exercise for me -- to gain familiarity with the PWM capabilities of a digital output. I'm also attempting to streamline the code. Here's the latest code snippet:

    # multiple sensors 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
    piezoL = pulseio.PWMOut(board.D0, frequency=3000, duty_cycle=16000, variable_frequency=True)
    piezoR = pulseio.PWMOut(board.D1, frequency=2000, duty_cycle=0, variable_frequency=True)
    
    # turn off all sensors
    for n in range(5):
        shutdown[n].direction = Direction.OUTPUT
        shutdown[n].value = False # low is off
    
    # 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()
    
    # instantiate all sensors and initialize distance array
    VL53L0X = []
    distance = []
    for n in range(5):
        try:
            VL53L0X.append(adafruit_vl53l0x.VL53L0X(i2c=i2c,address=VL53_address[n], io_timeout_s=0))
            distance.append(1001)
        except:
            VL53L0X.append(None)
            distance.append(1000)
        
    print("distance = {}".format(distance))
    
    # 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.
     
    def get_distances():
        global distance, VL53L0X
        for n in range(5):
            if VL53L0X[n]:
                distance[n] = VL53L0X[n].range
            else:
                distance[n] = 1000
    # Main loop will read the range and print it. PWM Frequency increases with the inverse of distance.
    while True:
        
        if (sum(distance) < 5000):
            print("distance1 = {}".format(distance[1]))
            if (distance[1] != 0):
                piezoL.frequency = int(1000/distance[1] * 300)
                piezoL.duty_cycle = 32768
                led.value = not led.value  # blink LED
            else:
                piezoL.duty_cycle = 0
            get_distances()
            piezoL.duty_cycle = 0
            time.sleep(0.01)
        else:
            led.value = False
            piezoL.duty_cycle = 0
            get_distances()
            time.sleep(0.25)
    

    I still only have one VL53L0X sensor to play with.  The main loop just obtains the latest distance measurement from the sensor and changes the PWM output frequency in inverse proportion to the distance. So the piezo speaker frequency increases as the object distance decreases. It works.

    Eventually, I'll mix the amplitude between the two piezo speakers to indicate a direction...

    Other Stuff:

     The other stuff is related to automagically detecting VL53L0X sensors and assigning addresses, and then looping through the sensors to get distance measurements. I'm trying to keep the code relatively compact, so addressing the distance sensors as an array is pretty efficient. If a sensor is not responding, or not...

    Read more »

  • CircuitPython Progress

    Bud Bennett02/28/2019 at 21:15 0 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...
    Read more »

  • A Working Sensor

    Bud Bennett02/27/2019 at 19:21 0 comments

    I ordered a VL51L0X sensor breakout board and an Arduino Feather M0 Express from Adafruit. The Feather is attractive to me because of its inherent CircuitPython capabilities. I probably paid the highest prices for these items, but -- considering the support and the initial development of the sensor interface -- it is worth it. Soldering the pins onto the two boards took no time at all. After that I just plugged them into a half proto-board:

    I got into trouble immediately by not being familiar with the Arduino IDE and methodology. I overwrote the existing CircuitPython data with some C code for the VL53L0X. It took me about an hour to get the Arduino to report distance from the sensor. But after that I would have to learn to program in C to go any further. (One of these days I'll learn C.)

    This morning I discovered that there was an existing VL53L0X python library that ran under CircuitPython. I then reinstalled the CircuitPython capabilities and spent an inordinate amount of trial and error time learning to get the simple sensor interface working. I finally got a stream of data out of a terminal program on the iMac.

    Anecdotal Data:

    The beam width is just as small as the data sheet says -- about ±12°. As the distance to the object increases, the size of the object must increase as well. It would not detect my hand at 800mm but would detect a sheet of 8.5x11 white paper. So it seems that five sensors will be a good starting number to get the required beam width.

    Next steps involve learning the sensor command interface, experimenting with time/accuracy tradeoffs, reassigning the sensor's I2C address, and getting familiar with the CircuitPython libraries for GPIO and PWM. I also need perform some experiments with detecting objects of different reflective properties and the sensors resistance to ambient light. Lots of work left.

  • Thinking Out Loud

    Bud Bennett02/19/2019 at 23:30 2 comments

      Location:

      I've had a couple of days to think about this project. I've also been observing Lucy when she's out and about to determine the best location for the halo. Lucy tends to walk with her head lowered, so it seems the best place to put a smallish electronics package is on her forehead between her eyes and ears. She has about 50mm to work with. This location would not have any obstructions from Lucy's other body parts and could be secured with a simple strap around her head. I think that I'm being overly simplistic here, but my expertise is not with mechanical things. 

      Choosing a Sensor:

      There is a whole universe of distance sensors out there. I've ruled out certain types for size, power consumption, or aesthetic reasons. The favorite so far is ST's VL530X ToF (LIDAR?) distance sensor, for the following reasons:

      1. It's tiny. (4.4x2.4x1.0 mm)
      2. Low power drain: 20mW avg.
      3. Low component count.
      4. Good Range: 5-200cm
      5. More accuracy than needed: < 5mm.
      6. Fast: 33ms acquisition time.
      7. I2C interface. (This is both good and bad...details below.)

      What's not to like:

      1. Narrow field of view: 25°. (Multiple sensors required to cover adequate field of view.)
      2. The data sheet sucks. ST has decided not to include a table of registers addresses for the I2C interface. (Details below.)
      3. Extremely complicated to implement if you're not expert in C or compilers or the jargon of embedded processors.

      Field of view:

      The VL530X has a field of view, per the datasheet, of 25°. This is pretty narrow. I put together a simple diagram to aid in determining the number of sensors required for this application:

      I believe that the minimum number is 5. It's possible that 3 would work, given that Lucy is most likely to collide with something directly in front of her and her head is swiveling to match forward direction. Any comments on this? More sensors mean higher power requirements and shorter battery life (and higher cost -- the sensor is about $6).

      The Interface:

      I like the I2C interface. It's a standard. Some aspects difficult to implement and many components don't comply with all aspects. But the problem here is that ST has not properly documented how to use this sensor. There is not a word about the registers that can be addressed or what they do. ST requires the prospective customer to use their API (written in C) as the only method of communicating with the sensor. This sucks badly.

      I found that Pololu offers a reasonably priced breakout board for this sensor, and also provides a library of commands that work with the Arduino. I've never messed with Arduino, so this is still a bit intimidating. But this is probably the path I'll choose going forward. I've asked a friend who's familiar with Arduino for help.

      Other Features:

      1. Battery and charger. Probably a single cell Li-Ion battery. An 18650 cell contains quite a bit of energy. Charge from USB port.
      2. Power supply: 3.0V switcher. Or LDO with a 3.3V switch mode converter for efficiency.
      3. Accelerometer. To turn off the unit if Lucy is not moving or is laying on her side sleeping. A power saving feature.

      That's all I've got so far. I'm sure a lot of this will change as things progress.

View all 6 project logs

Enjoy this project?

Share

Discussions

NSFW wrote 02/28/2019 at 04:01 point

This is really cool. I'm looking forward to the next update.

  Are you sure? yes | no

Starhawk wrote 02/20/2019 at 03:40 point

Good on you for doing right by your dog. Godspeed to both of you :)

  Are you sure? yes | no

Vije Miller wrote 02/19/2019 at 23:48 point

I once had an 85' Crown Victoria that solved this issue -- giant springy uh.. springs!

  Are you sure? yes | no

Vije Miller wrote 02/27/2019 at 21:43 point

Suggestion to @Benchoff  @Mike Szczys @Aleksandar Bradic for the next HaD contest .. Hack Your Pet(s) .. 

  Are you sure? yes | no

Dan Maloney wrote 02/16/2019 at 19:18 point

Following with interest. I too know the love of a Border Collie - slightly weird, wonky hips, and possibly a touch deaf. But a sweet boy nonetheless. 

Hope you can find a way to help Lucy.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates