ESP32 MicroPython – Interfacing Ultrasonic Sensor HC-SR04

In this blog, we will use MicroPython code to connect the HC-SR04 Ultrasonic Sensor to the ESP32 in this project. Here, we are using Ultrasonic Sensor, ESP32 Board, and a 0.96′′ I2C OLED Display for designing this project. Then, using the uPyCraft IDE, we’ll write MicroPython code. We can upload the firmware to the ESP32 Board directly using the uPyCraft IDE.

You can start by reading this guide: MicroPython on ESP32 with UPyCraft IDE – Getting Started. I strongly advise you to follow this guide because it covers everything from setting up the IDE to writing code and uploading.

Hardware Required

  • ESP32 Board-ESP32 ESP-32S Development Board (ESP-WROOM-32)
  • 0.96″ I2C OLED Display
  • HC-SR04 Ultrasonic Sensor
  • Jumper Wires
  • Breadboard

You can also purchase the MakePython ESP32 Board from Makerfabs if you want a combined product, as seen in the image below. MicroPython is supported by the board, which has an ESP32 chip and a 0.96″ I2C OLED display.

HC-SR04 Ultrasonic Sensor

The HC-SR04 is a distance sensor that uses ultrasonic waves. With a range accuracy of up to 3mm, this inexpensive sensor provides non-contact measurement functionality from 2cm to 400cm. An ultrasonic transmitter, a receiver, and a control circuit are all included in the HC-SR04 module.

Ultrasonic Sensor HC-SR04 uses sonar to measure the distance of the object. It emits an ultrasonic wave at 40 kHz which will go and bounce back after hitting an object. By knowing the time traveled and the speed of the sound, we can calculate the distance (speed=distance/time). Ultrasonic waves are much faster than regular sound and as humans, we won’t be able to hear them. (Some animals and fishes like dolphins and bats are able to use it for communication and seeing in the dark).


  • VCC: +5 V supply
  • TRIG:  Trigger input of the sensor. The microcontroller applies a 10 us trigger pulse to the HC-SR04 ultrasonic module.
  • ECHO: Echo output of the sensor. Microcontroller reads/monitors this pin to detect the obstacle or to find the distance.
  • GND: Ground


  • Sensor Type: Ultrasonic
  • Output: Digital Sensor
  • Voltage: 5VDC
  • Operating Current: 15mA
  • Measure Angle: 15°
  • Ranging Distance: 2cm – 4m
  • Static current: < 2mA
  • Level output: high-5V
  • Output Signal:5V
  • High Precision: Up to 2 mm
  • Input Trigger Signal: 10µs TTL impulse
  • Echo Signal: Output TTL PWL Signal
  • High sensitivity ultrasonic range sensor
  • Works with Arduino board

Interfacing Ultrasonic Sensor HC-SR04 with ESP32 using MicroPython

This is a circuit diagram for utilizing MicroPython code to interface an ultrasonic sensor (HC-SR04) with an ESP32. The TRIG pin is connected to the ESP32 GPIO13 pin and the ECHO pin to the ESP32 GPIO12 pin. VCC is also connected to 5V, and GND is connected to GND.

If you’re using the MakePython ESP32 Board, you can connect it as shown in the diagram below.

Source Code/Program

There are three components to the MicroPython code for using an ultrasonic sensor with an ESP32. This is because the HC-SR04 Ultrasonic Sensor and OLED Display require a library.

The three parts include:




The library required to write to the OLED display is not included in the normal MicroPython library. To begin, we must first download the library to the ESP32 board.

# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
from micropython import const
import time
import framebuf
import sys

import pyb
# 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
self.buffer = bytearray(self.pages * self.width)
self.framebuf = framebuf.FrameBuffer(self.buffer, self.width, self.height, framebuf.MVLSB)
def init_display(self):
for cmd in (
SET_DISP | 0x00, # off
# address setting
SET_MEM_ADDR, 0x00, # horizontal
# resolution and layout
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_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12,
# timing and driving scheme
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
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def contrast(self, 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(self.pages – 1)
def fill(self, 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)
def hline(self, x, y, w, col):
self.framebuf.hline(x, y, w, col)
def vline(self, x, y, h, col):
self.framebuf.vline(x, y, h, col)
def line(self, x1, y1, x2, y2, col):
self.framebuf.line(x1, y1, x2, y2, col)
def rect(self, x, y, w, h, col):
self.framebuf.rect(x, y, w, h, col)
def fill_rect(self, x, y, w, h, col):
self.framebuf.fill_rect(x, y, w, h, col)
def blit(self, fbuf, x, y):
self.framebuf.blit(fbuf, x, y)

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)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
global currentBoard
if currentBoard==”esp8266″ or currentBoard==”esp32″:
self.i2c.writeto(self.addr, self.temp)
elif currentBoard==”pyboard”:

def write_data(self, buf):
self.temp[0] = self.addr << 1
self.temp[1] = 0x40 # Co=0, D/C#=1
global currentBoard
if currentBoard==”esp8266″ or currentBoard==”esp32″:
elif currentBoard==”pyboard”:
def poweron(self):

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
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
global currentBoard
if currentBoard==”esp8266″ or currentBoard==”esp32″:
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
elif currentBoard==”pyboard”:
self.spi.init(mode = pyb.SPI.MASTER,baudrate=self.rate, polarity=0, phase=0)
global currentBoard
if currentBoard==”esp8266″ or currentBoard==”esp32″:
elif currentBoard==”pyboard”:
def write_data(self, buf):
global currentBoard
if currentBoard==”esp8266″ or currentBoard==”esp32″:
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
elif currentBoard==”pyboard”:
self.spi.init(mode = pyb.SPI.MASTER,baudrate=self.rate, polarity=0, phase=0)
global currentBoard
if currentBoard==”esp8266″ or currentBoard==”esp32″:
elif currentBoard==”pyboard”:
def poweron(self):

To begin, open a new file in the uPyCraft IDE and paste the code above. Save it with the name “” Choose the “Download & Run” option. The file will be uploaded to the ESP32 board.

The following message should appear in the uPyCraft console window.

You can now utilise the SSD1306 Library on the Code Part after importing it.


Now open a new tab and paste the code below into it. The code uploads the HC SR04 Ultrasonic Sensor Library to the ESP32 Board.

import machine, time
from machine import Pin

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)

# 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
# Send a 10us pulse.
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

You can now upload the main code after downloading the and HC files to the ESP32 board. To do so, open a new file and paste the code below into it. Name it “” and save it.

from hcsr04 import HCSR04
from machine import Pin,I2C
import ssd1306

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000) #Init i2c

sensor = HCSR04(trigger_pin=13, echo_pin=12,echo_timeout_us=1000000)

while True:
distance = sensor.distance_cm()
except KeyboardInterrupt:

Testing the Ultrasonic Range Finder

The Ultrasonic Sensor will begin showing the distance in cm after the MicroPython code is uploaded to the ESP32 board. You can test the sensor’s functionality by placing something in front of it.

Ultrasonic range finders are interesting tiny distance measuring sensors. They can be used to determine the distance to an object or to detect when something is in close proximity to the sensor, such as a motion detector. They operate just as well in the dark as they do in the day because they utilize sound to measure distance. It has a 3 mm precision and can measure distances from 2 cm to 400 cm.


Ultrasonic sensors are a well-known technology that is still widely used in a variety of industrial and consumer applications. I hope you have understood how to interface ultrasonic Sensor HC-SR04 with ESP32 MicroPython. We MATHA ELECTRONICS will be back soon with more informative blogs.

Leave a Reply

Your email address will not be published.