Learn how to use the HC-SR04 Ultrasonic Sensor with the Raspberry Pi Pico to get the distance to an object using MicroPython firmware. This tutorial covers how to wire the sensor to the board and provides a simple MicroPython script for measuring the distance to an object and displaying it on an OLED display.

New to the Raspberry Pi Pico? Check out our eBook: Learn Raspberry Pi Pico/Pico W with MicroPython.
Table of Contents:
In this guide, we’ll cover the following topics:
- Introducing the HC-SR04 Ultrasonic Sensor
- HC-SR04 Ultrasonic Sensor Technical Data
- HC-SR04 Ultrasonic Sensor Pinout
- How Does the HC-SR04 Ultrasonic Sensor Work?
- RPi Pico with HC-SR04 Ultrasonic Sensor – Wiring Diagram
- HC-SR04 MicroPython Library
- Code – HC-SR04 Ultrasonic Sensor with the RPi Pico
- Display Distance (HCSR04) on OLED Display
Prerequisites – MicroPython Firmware
To follow this tutorial, you need MicroPython firmware installed on your Raspberry Pi Pico board. You also need an IDE to write and upload the code to your board.
The recommended MicroPython IDE for the Raspberry Pi Pico is Thonny IDE. Follow the next tutorial to learn how to install Thonny IDE, flash MicroPython firmware, and upload code to the board.
If you’re still getting started with the Raspberry Pi Pico, follow one these getting-started guides:
- Getting Started with Raspberry Pi Pico 2 and Pico 2 W
- Getting Started with Raspberry Pi Pico (and Pico W)
Introducing the HC-SR04 Ultrasonic Sensor
The HC-SR04 ultrasonic sensor uses sonar to determine the distance to an object. This sensor reads from 2cm to 400cm (0.8inch to 157inch) with an accuracy of 0.3cm (0.1inches), which is good for most hobbyist projects. In addition, this particular module comes with ultrasonic transmitter and receiver modules.
The following picture shows the HC-SR04 ultrasonic sensor.

The next picture shows the other side of the sensor.

Want an alternative for the HC-SR04 ultrasonic sensor? Check out the RCWL-0516 Radar Proximity Sensor:
Where to Buy HC-SR04 Ultrasonic Sensor?
You can check the Ultrasonic Sensor HC-SR04 on Maker Advisor to find the best price:
HC-SR04 Ultrasonic Sensor Technical Data
The following table shows the key features and specs of the HC-SR04 ultrasonic sensor. For more information, you should consult the sensor’s datasheet.
Power Supply | 5V DC |
Working Current | 15 mA |
Working Frequency | 40 kHz |
Maximum Range | 4 meters |
Minimum Range | 2 cm |
Measuring Angle | 15º |
Resolution | 0.3 cm |
Trigger Input Signal | 10uS TTL pulse |
Echo Output Signal | TTL pulse proportional to the distance range |
Dimensions | 45mm x 20mm x 15mm |
HC-SR04 Ultrasonic Sensor Pinout
Here’s the pinout of the HC-SR04 Ultrasonic Sensor.
VCC | Powers the sensor (5V) |
Trig | Trigger Input Pin |
Echo | Echo Output Pin |
GND | Common GND |
How Does the HC-SR04 Ultrasonic Sensor Work?
The ultrasonic sensor uses sonar to determine the distance to an object. Here’s how it works:
- The ultrasound transmitter (trig pin) emits a high-frequency sound (40 kHz).
- The sound travels through the air. If it finds an object, it bounces back to the module.
- The ultrasound receiver (echo pin) receives the reflected sound (echo).

Taking into account the sound’s velocity in the air and the travel time (elapsed time since the transmission and reception of the signal) we can calculate the distance to an object. Here’s the formula:
distance to an object = ((speed of sound in the air)*time)/2
- speed of sound in the air at 20ºC (68ºF) = 343m/s
Parts Required

To complete this tutorial, you need the following parts:
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
RPi Pico with HC-SR04 Ultrasonic Sensor – Wiring Diagram
Wire the HC-SR04 ultrasonic sensor to the RPi Pico board as shown in the following schematic diagram. We’re connecting the Trig pin to GPIO 27 and the Echo pin to GPIO 28, but you can use any other suitable pins.

Ultrasonic Sensor | RPi Pico |
VCC | VBus (5V) |
Trig | GPIO 27 |
Echo | GPIO 28 in series with a 1kOhm resistor |
GND | GND |
Recommended reading: Raspberry Pi Pico and Pico W Pinout Guide: GPIOs Explained.
HC-SR04 MicroPython Library
There are multiple ways to get the distance to an object using the HC-SR04 and RPi Pico board using MicroPython firmware. We’ll use this HC-SR04 MicroPython Library that makes it straightforward to interface the sensor and get measurements.
The library we’ll use isn’t part of the standard MicroPython library by default. So, you need to upload the following library to your RPi Pico board (save it with the name hcsr04.py).
import machine, time
from machine import Pin
__version__ = '0.2.0'
__author__ = 'Roberto Sánchez'
__license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"
class HCSR04:
"""
Driver to use the untrasonic sensor HC-SR04.
The sensor range is between 2cm and 4m.
The timeouts received listening to echo pin are converted to OSError('Out of range')
"""
# echo_timeout_us is based in chip range limit (400cm)
def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
"""
trigger_pin: Output pin to send pulses
echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor
echo_timeout_us: Timeout in microseconds to listen to echo pin.
By default is based in sensor limit range (4m)
"""
self.echo_timeout_us = echo_timeout_us
# Init trigger pin (out)
self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
self.trigger.value(0)
# Init echo pin (in)
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
def _send_pulse_and_wait(self):
"""
Send the pulse to trigger and listen on echo pin.
We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
"""
self.trigger.value(0) # Stabilize the sensor
time.sleep_us(5)
self.trigger.value(1)
# Send a 10us pulse.
time.sleep_us(10)
self.trigger.value(0)
try:
pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us)
return pulse_time
except OSError as ex:
if ex.args[0] == 110: # 110 = ETIMEDOUT
raise OSError('Out of range')
raise ex
def distance_mm(self):
"""
Get the distance in milimeters without floating point operations.
"""
pulse_time = self._send_pulse_and_wait()
# To calculate the distance we get the pulse_time and divide it by 2
# (the pulse walk the distance twice) and by 29.1 becasue
# the sound speed on air (343.2 m/s), that It's equivalent to
# 0.34320 mm/us that is 1mm each 2.91us
# pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582
mm = pulse_time * 100 // 582
return mm
def distance_cm(self):
"""
Get the distance in centimeters with floating point operations.
It returns a float
"""
pulse_time = self._send_pulse_and_wait()
# To calculate the distance we get the pulse_time and divide it by 2
# (the pulse walk the distance twice) and by 29.1 becasue
# the sound speed on air (343.2 m/s), that It's equivalent to
# 0.034320 cm/us that is 1cm each 29.1us
cms = (pulse_time / 2) / 29.1
return cms
Upload HC-SR04 module to the RPi Pico
Follow the next steps to upload the hcsr04.py file to your board.
1. Copy the library code to a new file. The HC-SR04 library code can be found here.
2. Go to File > Save as…

3. Select save to “Raspberry Pi Pico“:

4. Name your file as hcsr04.py and press the OK button:

And that’s it. The library was uploaded to your board. To make sure that it was uploaded successfully, go to File > Save as… and select the Raspberry Pi Pico device. Your file should be listed there:

After uploading the library to your board, you can use the library functionalities in your code by importing the library.
Code – HC-SR04 Ultrasonic Sensor with the RPi Pico
After uploading the library to the RPi Pico board, copy the following code to Thonny IDE. It simply prints the distance to the closest object every second (example adapted from the library page).
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-hc-sr04-micropython/
from machine import Pin
import time
from hcsr04 import HCSR04
# Initialize the HC-SR04 sensor with trigger on GPIO 27 and echo on GPIO 28
sensor = HCSR04(trigger_pin=27, echo_pin=28, echo_timeout_us=30000)
while True:
try:
# Measure distance in centimeters
#distance_cm = sensor.distance_cm()
# Convert distance from centimeters to inches
#distance_inch = distance_cm * 0.393701
#print('Distance: {:.2f} cm'.format(distance_cm))
# Measure distance in millimeters
distance_mm = sensor.distance_mm()
print('Distance: {} mm'.format(distance_mm))
except OSError as e:
print('Error:', e)
# Wait 1 second before the next measurement
time.sleep(1)
How the Code Works
First, you need to import the necessary libraries: import the HCSR04 class from the hcsr04 library. Additionally, you also need to import the time library to add delays to our code.
from hcsr04 import HCSR04
from time import sleep
Then, create an HCSR04 object called sensor that refers to the HCSR04 sensor. Pass as arguments the trigger pin, the echo pin, and the timeout (maximum travel time of the sound wave—when the sensor is probably out of range).
sensor = HCSR04(trigger_pin=27, echo_pin=28, echo_timeout_us=30000)
To get the distance in cm, you just need to call the distance_cm method on the sensor object. Save the result in the distance_cm variable.
distance_cm = sensor.distance_cm()
The library also provides a method to get the distance in millimeters without floating point. You just need to call:
distance_mm = sensor.distance_mm()
Print the distance on the Micropython shell.
print('Distance: {} mm'.format(distance_mm))
In the end, we add a delay of one second (the distance is updated every second):
sleep(1)
We get the distance inside try and except statements, so that the code doesn’t get stuck if we get an error.
try:
# Measure distance in centimeters
#distance_cm = sensor.distance_cm()
#print('Distance: {:.2f} cm'.format(distance_cm))
# Measure distance in millimeters
distance_mm = sensor.distance_mm()
print('Distance: {} mm'.format(distance_mm))
except OSError as e:
print('Error:', e)
Demonstration
After uploading the code to your board, press the RST button to run the code.

The distance to the closest object should be printed on the shell.

Display Distance (HCSR04) on OLED Display
Now that you know how to get the distance to the closest object using an HC-SR04 ultrasonic sensor, we’ll display the sensor readings on an OLED display.

Parts Required
Here’s a list of the parts required to complete this example:
- HC-SR04 Ultrasonic Sensor
- Raspberry Pi Pico (any model)
- 1k Ohm resistor
- 0.96 inch I2C OLED Display SSD1306
- Breadboard
- Jumper wires
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!
Schematic Diagram
Add an I2C OLED display to the previous circuit. We’re connecting SDA to GPIO 4, and SCL to GPIO 5. You can follow the next schematic diagram:

Files
For this example, you need three files:
- hcsr04.py: this is the file that contains all the methods to use the HC-SR04 sensor. That’s the file you’ve uploaded previously.
- ssd1306.py: this is the library for the SSD1306 I2C OLED display. You should upload it to your board to be able to communicate and write on the display.
- main.py: this is the main script to get the distance and display it on the OLED display. You should call it main.py and upload it to your board. When you upload a code called main.py to the RPi Pico filesystem, that file will automatically run when the RPi Pico starts.
ssd1306.py
Create a new file on Thonny IDE called ssd1306.py and copy the following code. Then, upload it to your board (File > Save as… > MicroPython device and call it ssd1306.py).
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces created by Adafruit
import time
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xa4)
SET_NORM_INV = const(0xa6)
SET_DISP = const(0xae)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xa0)
SET_MUX_RATIO = const(0xa8)
SET_COM_OUT_DIR = const(0xc0)
SET_DISP_OFFSET = const(0xd3)
SET_COM_PIN_CFG = const(0xda)
SET_DISP_CLK_DIV = const(0xd5)
SET_PRECHARGE = const(0xd9)
SET_VCOM_DESEL = const(0xdb)
SET_CHARGE_PUMP = const(0x8d)
class SSD1306:
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
# Note the subclass must initialize self.framebuf to a framebuffer.
# This is necessary because the underlying data buffer is different
# between I2C and SPI implementations (I2C needs an extra byte).
self.poweron()
self.init_display()
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR, 0x00, # horizontal
# resolution and layout
SET_DISP_START_LINE | 0x00,
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
SET_MUX_RATIO, self.height - 1,
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
SET_DISP_OFFSET, 0x00,
SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV, 0x80,
SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1,
SET_VCOM_DESEL, 0x30, # 0.83*Vcc
# display
SET_CONTRAST, 0xff, # maximum
SET_ENTIRE_ON, # output follows RAM contents
SET_NORM_INV, # not inverted
# charge pump
SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01): # on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width == 64:
# displays with width of 64 pixels are shifted by 32
x0 += 32
x1 += 32
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_framebuf()
def fill(self, col):
self.framebuf.fill(col)
def pixel(self, x, y, col):
self.framebuf.pixel(x, y, col)
def scroll(self, dx, dy):
self.framebuf.scroll(dx, dy)
def text(self, string, x, y, col=1):
self.framebuf.text(string, x, y, col)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
# Add an extra byte to the data buffer to hold an I2C data/command byte
# to use hardware-compatible I2C transactions. A memoryview of the
# buffer is used to mask this byte from the framebuffer operations
# (without a major memory hit as memoryview doesn't copy to a separate
# buffer).
self.buffer = bytearray(((height // 8) * width) + 1)
self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1
self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_framebuf(self):
# Blast out the frame buffer using a single I2C transaction to support
# hardware I2C interfaces.
self.i2c.writeto(self.addr, self.buffer)
def poweron(self):
pass
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
self.buffer = bytearray((height // 8) * width)
self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs.high()
self.dc.low()
self.cs.low()
self.spi.write(bytearray([cmd]))
self.cs.high()
def write_framebuf(self):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs.high()
self.dc.high()
self.cs.low()
self.spi.write(self.buffer)
self.cs.high()
def poweron(self):
self.res.high()
time.sleep_ms(1)
self.res.low()
time.sleep_ms(10)
self.res.high()
hcsr04.py
Upload the hcsr04.py to your board if you haven’t already (File > Save as… > MicroPython device and call it hcsr04.py).
import machine, time
from machine import Pin
__version__ = '0.2.0'
__author__ = 'Roberto Sánchez'
__license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0"
class HCSR04:
"""
Driver to use the untrasonic sensor HC-SR04.
The sensor range is between 2cm and 4m.
The timeouts received listening to echo pin are converted to OSError('Out of range')
"""
# echo_timeout_us is based in chip range limit (400cm)
def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30):
"""
trigger_pin: Output pin to send pulses
echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor
echo_timeout_us: Timeout in microseconds to listen to echo pin.
By default is based in sensor limit range (4m)
"""
self.echo_timeout_us = echo_timeout_us
# Init trigger pin (out)
self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None)
self.trigger.value(0)
# Init echo pin (in)
self.echo = Pin(echo_pin, mode=Pin.IN, pull=None)
def _send_pulse_and_wait(self):
"""
Send the pulse to trigger and listen on echo pin.
We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received.
"""
self.trigger.value(0) # Stabilize the sensor
time.sleep_us(5)
self.trigger.value(1)
# Send a 10us pulse.
time.sleep_us(10)
self.trigger.value(0)
try:
pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us)
return pulse_time
except OSError as ex:
if ex.args[0] == 110: # 110 = ETIMEDOUT
raise OSError('Out of range')
raise ex
def distance_mm(self):
"""
Get the distance in milimeters without floating point operations.
"""
pulse_time = self._send_pulse_and_wait()
# To calculate the distance we get the pulse_time and divide it by 2
# (the pulse walk the distance twice) and by 29.1 becasue
# the sound speed on air (343.2 m/s), that It's equivalent to
# 0.34320 mm/us that is 1mm each 2.91us
# pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582
mm = pulse_time * 100 // 582
return mm
def distance_cm(self):
"""
Get the distance in centimeters with floating point operations.
It returns a float
"""
pulse_time = self._send_pulse_and_wait()
# To calculate the distance we get the pulse_time and divide it by 2
# (the pulse walk the distance twice) and by 29.1 becasue
# the sound speed on air (343.2 m/s), that It's equivalent to
# 0.034320 cm/us that is 1cm each 29.1us
cms = (pulse_time / 2) / 29.1
return cms
main.py
In the main.py file is where we’ll get the distance and display it on the OLED display. Upload it to your board with the name main.py.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-hc-sr04-micropython/
from machine import Pin, I2C
import ssd1306
from hcsr04 import HCSR04
from time import sleep
# Initialize I2C for SSD1306 OLED (Pico: SCL = GPIO 5, SDA = GPIO 4 for I2C1)
i2c = I2C(scl=Pin(5), sda=Pin(4))
# Initialize HC-SR04 ultrasonic sensor (trigger = GPIO 27, echo = GPIO 28)
sensor = HCSR04(trigger_pin=27, echo_pin=28, echo_timeout_us=10000)
# Initialize SSD1306 OLED (128x64)
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
while True:
try:
# Measure distance in centimeters
distance_cm = sensor.distance_cm()
# Convert distance from centimeters to inches
#distance_inch = distance_cm * 0.393701
# Print to shell
print('Distance: {:.2f} cm'.format(distance_cm))
# Clear OLED and display distance in cm
oled.fill(0)
oled.text("Distance:", 0, 10)
oled.text("{:.2f} cm".format(distance_cm), 0, 30)
oled.show()
except OSError as e:
# Handle sensor errors (e.g., out of range)
print('Error:', e)
oled.fill(0)
oled.text("Error:", 0, 10)
oled.text("Out of range", 0, 30)
oled.show()
# Wait 1 second before next measurement
sleep(1)
The code is straightforward to understand. To learn more about using the OLED display with the RPi Pico using MicroPython, refer to the next tutorial:
The code starts by importing the required libraries.
from machine import Pin, I2C
import ssd1306
from hcsr04 import HCSR04
from time import sleep
Set the pins for the OLED display and ultrasonic sensor.
i2c = I2C(scl=Pin(5), sda=Pin(4))
sensor = HCSR04(trigger_pin=27, echo_pin=28, echo_timeout_us=10000)
Define the OLED width and height and initialize the OLED display.
oled_width = 128
oled_height = 64
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
The while loop is where we’ll get the distance and display it on the OLED. Like in the previous code, we use try and except statements to catch any errors that might occur during the execution of the program.
Get the distance in cm and save it in the distance_cm variable.
distance = sensor.distance_cm()
Print the distance in the console.
print('Distance:', distance, 'mm')
Clear the display in each iteration with oled.fill(0).
oled.fill(0)
Display the distance in the display.
oled.fill(0)
oled.text("Distance:", 0, 10)
oled.text("{:.2f} cm".format(distance_cm), 0, 30)
oled.show()
Finally, call oled.show() to actually display the text.
oled.show()
The distance is updated every second.
sleep(1)
In case there are any errors, they will be handled in the except statement.
except OSError as e:
# Handle sensor errors
print('Error:', e)
oled.fill(0)
oled.text("Error:", 0, 10)
oled.text("Out of range", 0, 30)
oled.show()
Demonstration
Upload all the previous files to your RPi Pico board in the following order as explained previously:
- ssd1306.py
- hcsr04.py
- main.py
After uploading the code, it should start running, and it will display the distance in mm on the OLED display, as shown in the following picture.

Wrapping Up
We hope you’ve found this tutorial useful. We have more projects and tutorials for the Raspberry Pi Pico with MicroPython with other popular sensors that you may like:
- Raspberry Pi Pico: DS3231 RTC (Real-Time Clock) – Get Time and Set Alarms (MicroPython)
- Raspberry Pi Pico: BME680 Environmental Sensor (MicroPython)
- Raspberry Pi Pico: Detect Motion using a PIR Sensor (MicroPython)
- Raspberry Pi Pico: RCWL-0516 Microwave Radar Proximity Sensor (MicroPython)
You can check all our Raspberry Pi Pico projects and tutorials here.
If you want to learn more about the Raspberry Pi Pico and MicroPython, check out our eBook: Learn Raspberry Pi Pico/Pico W with MicroPython (compatible with RPi Pico version 1 and 2).
Thanks for reading.