Ultrasonic Ranger Examples - Raspberry Pi Pico¶
The following short Python programs will demonstrate essential operation of the
Raspberry Pi Pico board. These assume one or more binary input or output circuits are
externally attached. Each can be run by copying the program into code.py
on
the CIRCUITPY drive offered by the board. The text can be pasted directly from
this page, or each file can be downloaded from the CircuitPython sample code
folder on this site.
Related Pages
Contents
Sample Ultrasonic Ranger Circuit¶
Sonar Demo¶
Direct download: sonar_demo.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | # sonar_demo.py
#
# Raspberry Pi Pico - HC-04 sonar module demo
# This module provides a class for measuring distance using an HC-04 ultrasonic
# ranger.
# The device requires one digital trigger output and one digital echo input.
# It is a 5V device, so echo needs to be level-shifted to 3.3V logic levels. One option
# is a pair of resistors as a voltage divider.
# This implementation uses bit-banged digital I/O to measure the echo pulse
# width. But the timing precision is limited by CircuitPython execution and
# clock resolution, so the result is only good for rough proximity detection.
# Likely a better long-term solution will be to use the RP2040 programmable IO
# peripheral (PIO) to precisely measure the echo duration.
################################################################
# CircuitPython module documentation:
# time https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/index.html
# board https://circuitpython.readthedocs.io/en/latest/shared-bindings/board/index.html
# digitalio https://circuitpython.readthedocs.io/en/latest/shared-bindings/digitalio/index.html
#
# Driver lifecycle documentation:
# https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html#lifetime-and-contextmanagers
#
################################################################################
# load standard Python modules
import time
# load the CircuitPython hardware definition module for pin definitions
import board
# load the CircuitPython GPIO support
import digitalio
#--------------------------------------------------------------------------------
class Sonar:
def __init__(self, ECHO=board.GP20, TRIG=board.GP21):
"""This class represents an HC-04 ultraonic ranger module. It uses one output
pin for trigger and one input pin for echo pulse measurement. N.B. the
device is 5V so the ECHO pin will need level-shifting to 3.3V logic
levels."""
self._trig = digitalio.DigitalInOut(TRIG)
self._trig.direction = digitalio.Direction.OUTPUT
self._trig.value = False
self._echo = digitalio.DigitalInOut(ECHO)
self._echo.direction = digitalio.Direction.INPUT
def ping_sync(self):
"""Measure the round-trip echo time, returning either a value in seconds or None
if the measurement times out. N.B. this function will not return until
the cycle is done, so it is not compatible with asynchronous event
loops.
"""
# trigger the ultrasonic pulse
self._trig.value = True
time.sleep(1e-5)
self._trig.value = False
start = time.monotonic_ns()
timeout = start + int(0.1 * 1e9)
# wait for echo to go high
while self._echo.value is False:
now = time.monotonic_ns()
if now > timeout:
# print("Error: ECHO never went high.")
return None
# wait for echo to go low
while True:
now = time.monotonic_ns()
if self._echo.value is False:
return 1e-9 * (now - start)
if now >= timeout:
return None
def deinit(self):
"""Manage resource release as part of object lifecycle."""
self._trig.deinit()
self._echo.deinit()
self._trig = None
self._echo = None
def __enter__(self):
return self
def __exit__(self):
# Automatically deinitializes the hardware when exiting a context.
self.deinit()
#--------------------------------------------------------------------------------
# Ultrasonic ranger demonstration.
sonar = Sonar()
print("Starting sonar test.")
while True:
range = sonar.ping_sync()
print(f"Range: {range} seconds round-trip time.")
time.sleep(0.1)
|
Sonar PulseIO Demo¶
An alternate implementation using the pulseio library. So far, this actually appears to perform worse than bit-banging on the Pico.
Direct download: sonar_pulseio_demo.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | # sonar_demo.py
#
# Raspberry Pi Pico - HC-04 sonar module demo
# This module provides a class for measuring distance using an HC-04 ultrasonic
# ranger.
# The device requires one digital trigger output and one digital echo input.
# It is a 5V device, so echo needs to be level-shifted to 3.3V logic levels. One option
# is a pair of resistors as a voltage divider.
# This implementation uses the pulseio module to measure the echo pulse width.
# But the precision appears to be low, so the result is only suitable for binary
# proximity detection.
# Likely a better long-term solution will be to use the RP2040 programmable IO
# peripheral (PIO) to precisely measure the echo duration.
################################################################
# CircuitPython module documentation:
# time https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/index.html
# board https://circuitpython.readthedocs.io/en/latest/shared-bindings/board/index.html
# digitalio https://circuitpython.readthedocs.io/en/latest/shared-bindings/digitalio/index.html
# pulseio https://circuitpython.readthedocs.io/en/latest/shared-bindings/pulseio/index.html
#
# Driver lifecycle documentation:
# https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html#lifetime-and-contextmanagers
#
################################################################################
# load standard Python modules
import time
# load the CircuitPython hardware definition module for pin definitions
import board
# load the CircuitPython GPIO support
import digitalio
# load the CircuitPython pulse measurement support
import pulseio
#--------------------------------------------------------------------------------
class Sonar:
def __init__(self, ECHO=board.GP20, TRIG=board.GP21):
"""This class represents an HC-04 ultraonic ranger module. It uses one output
pin for trigger and one input pin for echo pulse measurement. N.B. the
device is 5V so the ECHO pin will need level-shifting to 3.3V logic
levels."""
self._trig = digitalio.DigitalInOut(TRIG)
self._trig.direction = digitalio.Direction.OUTPUT
self._trig.value = False
self._echo = pulseio.PulseIn(ECHO)
self._echo.pause()
def ping_sync(self):
"""Measure the round-trip echo time, returning either a value in seconds or None
if the measurement times out. N.B. this function will not return until
the cycle is done, so it is not compatible with asynchronous event
loops.
"""
self._echo.clear()
# trigger the ultrasonic pulse
self._trig.value = True
time.sleep(1e-5)
self._trig.value = False
self._echo.resume()
start = time.monotonic_ns()
timeout = start + int(0.5 * 1e9)
# wait for the measurement to complete or time out
while len(self._echo) < 1:
now = time.monotonic_ns()
if now > timeout:
self._echo.pause()
return None
self._echo.pause()
return self._echo[0] * 1e-6
def deinit(self):
"""Manage resource release as part of object lifecycle."""
self._trig.deinit()
self._echo.deinit()
self._trig = None
self._echo = None
def __enter__(self):
return self
def __exit__(self):
# Automatically deinitializes the hardware when exiting a context.
self.deinit()
#--------------------------------------------------------------------------------
# Ultrasonic ranger demonstration.
sonar = Sonar()
print("Starting sonar test.")
while True:
range = sonar.ping_sync()
print(f"Range: {range} seconds round-trip time.")
time.sleep(0.1)
|