BBC micro:bit
Ultrasonic Distance HC-SR04

Introduction

The HC-SR04 is an ultrasonic distance sensor that is widely used with microcontroller projects. You often see them connected to robot vehicles to detect obstacles in the vehicle's path. It has a range from 2cm up to 4.5m although, more often than not, it is used for distances nearer than a couple of metres.

To use the sensor with the micro:bit, you need to look for one that is specifically labelled as working with 3V logic. The one I used came from 4tronix and is very good value for money at just over £2.

micro:bit circuit

The Circuit

Ultrasonic distance sensors work by sending out a high frequency sound on a trigger (transmitter) pin. The sound reflects off any objects in the sensor's path and this reflected sound can be detected by the echo (receiver) pin on the sensor. The time taken for this signal to be received depends on the distance of the object.

You can see 4 pins in the image at the top of the page. For my test program, I connected the pins as follows,

  • VCC - 3V
  • GND - GND
  • ECHO - pin16
  • TRIG - pin12

Programming - Test

The test program was used connected to a laptop with the REPL window open to check the values.

The process starts with setting the trigger pin to LOW for a few microseconds, then a 10 second HIGH signal is set. The time_pulse_us method is used to time how long it takes for a HIGH signal to be detected on the echo pin. This value is used to determine if any signal was received and, if so, how far away the object was.

from microbit import *
import machine
import utime
    
def get_dist():
    pin12.write_digital(0)
    utime.sleep_us(2)
    pin12.write_digital(1)
    utime.sleep_us(10)
    pin12.write_digital(0)             
    d = machine.time_pulse_us(pin16,1,11600)
    if d>0:
        return d/58
    else:
        return d

pin16.set_pull(pin16.NO_PULL)

while True:
    if button_a.was_pressed():
        d = get_dist()
        print(d)
        sleep(150)
    sleep(20)

Programming - With 7-Segment Display

For my next project, I connected an Adafruit HT16K33 7-Segment Display. This is my favourite component for portable sensor projects. I trimmed my code library a little and made some adjustments for my readings. My code meant that my sensor readings were going to be from 0 to 200, with decimal places. I multiplied the reading by 10 and set the last but one decimal point. This truncates the reading after the first decimal place and plays nicely with the code I had previous written for the display.

from microbit import *
import machine
import utime


class backpack:
    NUMS = [0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D,
        0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71]
       
    def __init__(self):
        self.buffer = bytearray([0]*16)
        i2c.write(0x70,b'\x21')
        self.blink_rate(0)
        self.set_brightness(15)
        self.update_display()
    
    def set_brightness(self,b):
        i2c.write(0x70,bytes([0xE0 | b]))       
    
    def blink_rate(self, b):
        i2c.write(0x70,bytes([0x80 | 1 | (b << 1)]))
    
    def write_digit(self, position, digit, dot=False):
        offset = 0 if position < 2 else 1
        pos = offset + position
        self.buffer[pos*2] = self.NUMS[digit] & 0xFF
        if dot:
            self.buffer[pos*2] |= 0x80                    
    
    def update_display(self):
        data = bytearray([0]) + self.buffer
        i2c.write(0x70,data)
    
    def print(self,value):
        if value<0 or value>9999:
            return
        sdig =  '{:04d}'.format(value)
        dts = [int(x) for x in sdig]
        for i,d in enumerate(dts):
            self.write_digit(i,d)
    
    def set_decimal(self, position, dot=True): 
        # skip the colon
        offset = 0 if position < 2 else 1
        pos = offset + position        
        if dot:
            self.buffer[pos*2] |= 0x80
        else:
            self.buffer[pos*2] &= 0x7F
        
    def clear(self):
        self.buffer = bytearray([0]*16)
        self.update_display()

# ultrasound    
def get_dist():
    pin12.write_digital(0)
    utime.sleep_us(2)
    pin12.write_digital(1)
    utime.sleep_us(10)
    pin12.write_digital(0)             
    d = machine.time_pulse_us(pin16,1,11600)
    if d>0:
        return d/58
    else:
        return d

pin16.set_pull(pin16.NO_PULL)
f = backpack()
while True:
    if button_a.was_pressed():        
        d = get_dist()
        d = max(0,d)
        d *= 10
        d = int(d)
        f.print(d)
        f.set_decimal(2,True)
        f.update_display()
        sleep(150)
    sleep(20)

    

With this program flashed, I set off to do some field tests. In this first one, the fake cat is approximately one metre from the sensor.

micro:bit circuit

The real cat showed up at this point. He appears not to be a particularly good reflector of sound, despite being an accomplished emitter of noise.

The next few photographs are at approximately 50cm from a door,

micro:bit circuit

A close-up on the circuit,

micro:bit circuit

Review

This is a really good version of the sensor. Readings were particularly reliable under a metre with some missed readings at longer distances. On a robot car it would be worth taking several readings at once, maybe using an average of several readings or discarding outliers. The sensor would be quite usable for collision avoidance and, with some clever code, maze solving.