HC-SR04 Ultrasonic Distance Sensor with Raspberry Pi Pico and SSD1306 OLED: Displaying Measurement
Introduction:
HC-SR04 Ultrasonic Distance Sensor with Raspberry Pi Pico and SSD1306 OLED: Displaying Measurement – Are you looking for a reliable and accurate way to measure distances using your Raspberry Pi Pico? Look no further! we will explore how to connect and use the HC-SR04 Ultrasonic Distance Sensor with the Raspberry Pi Pico and display the distance measurements on an SSD1306 OLED display. This sensor is renowned for its accuracy in distance measurement, making it an ideal choice for a wide range of applications, from robotics to automated systems.
We will begin by introducing the HC-SR04 Ultrasonic Distance Sensor, discussing its working principles, and why it stands out as a choice for distance measurement. Understanding how this sensor uses sound waves to determine distance will give you insight into its capabilities and limitations.
Next, we will guide you through the step-by-step process of connecting the HC-SR04 to the Raspberry Pi Pico. This will include a detailed wiring diagram to ensure a hassle-free setup.
By the end of this article, you will not only have learned how to measure distances using the HC-SR04 Ultrasonic Distance Sensor with your Raspberry Pi Pico, but also how to display these measurements effectively on an SSD1306 OLED display. This knowledge will open up new possibilities for your projects, enhancing their functionality and interactivity.
Amazon links:
Hc-SR04 Ultrasonic Distance Sensor
*Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!
HC-SR04 Ultrasonic Distance Sensor working:
The HC-SR04 is a device commonly used in robotics and automation for measuring distances without direct contact. The sensor operates by emitting a burst of ultrasonic sound waves from its transmitter module (the component labeled “T”). These waves travel through the air and, upon encountering an object, bounce back as an echo to the receiver module (labeled “R”). The sensor measures the time interval between sending the wave and receiving the echo. This time interval is used to calculate the distance to the object, applying the formula that takes into account the known speed of sound in air. In the illustration, distances are displayed in both inches and centimeters, showing the versatility of the sensor in providing measurements in different units of length.
Distance Calculation:
To calculate the distance to an object using the HC-SR04 Ultrasonic Distance Sensor, the speed of sound in air is utilized. The speed of sound is approximately 343 meters per second at a standard temperature of 20 degrees Celsius. When the sensor triggers an ultrasonic pulse, it measures the time it takes for the echo to return after bouncing off the object. To find the distance, the following formula is applied:
Distance= Time × Speed of Sound / 2
​This equation considers the fact that the ultrasonic sound wave has to travel to the object and then back to the sensor, hence the time measured corresponds to the round trip. By dividing the product of the time and the speed of sound by two, we obtain the one-way distance from the sensor to the object, which is the actual measurement we seek. This simple yet effective method allows the HC-SR04 sensor to accurately determine how far away an object is without making any physical contact with it.
Specifications:
Operating Voltage: Typically 5V DC.
Current Consumption: Around 15 mA while measuring, less when idle.
Ultrasonic Frequency: 40 kHz.
Max Range: Up to 4 meters (400 cm).
Min Range: 2 centimeters (cm).
Resolution: About 3 mm (0.3 cm).
Measuring Angle: Approximately 15 degrees.
Trigger Input Signal: 10 µs TTL pulse.
Echo Output Signal: Proportional to the distance, TTL level signal with the duration of the echo time.
Measurement Duration: Up to 25 ms (a single measurement cycle).
Pinout:
VCC (Power): This pin is connected to the positive side of the power supply, typically requiring 5 volts of DC power. It’s the main power source for the sensor’s operation.
TRIG (Trigger): The TRIG pin is used to trigger the ultrasonic pulse. A control signal is sent to this pin, usually a short digital pulse (a TTL pulse) lasting at least 10 microseconds, which instructs the sensor to emit an ultrasonic burst.
ECHO (Echo): The ECHO pin outputs a signal in response to the incoming ultrasonic waves that have bounced off an object and returned to the sensor. The duration of the ECHO pulse is directly proportional to the distance measured, with the pulse width representing the time it took for the ultrasonic sound to travel to the object and back to the sensor.
GND (Ground): This pin is connected to the ground of the power supply and the common ground of the microcontroller or the control circuit. It completes the circuit’s power loop.
Key Features of Raspberry Pi Pico:
Processor: The RP2040 chip at its heart features a dual-core ARM Cortex-M0+ processor clocked at 133 MHz.
Memory: It comes with 264 KB of SRAM, and 2 MB of on-board flash memory for storing code and data.
I/O Options: The board includes a comprehensive set of I/O options, including 26 multi-function GPIO pins, two SPI, two I2C, two UARTs, three 12-bit ADC channels, and 16 controllable PWM channels.
Programming: The Pico can be programmed using C/C++ SDK or the MicroPython, which is a Python implementation for microcontrollers, making it accessible for beginners and also suitable for rapid prototyping.
Power: It can be powered via the USB connection or an external power source such as a battery, and it also features a programmable voltage regulator allowing the user to turn off power to external hardware.
Key Features of the SSD1306 OLED Display:
Resolution: Common resolutions for SSD1306 displays are 128×64 or 128×32 pixels.
Color: It displays monochrome graphics and text, typically in blue or white.
Interface: The display supports multiple communication interfaces, including I2C and SPI, making it versatile for different microcontroller connections.
Driver IC: The SSD1306 driver chip controls the OLED matrix which requires no backlight, each pixel serves as its own light source.
Size: The display area is usually around 0.96 inches diagonally, suitable for compact applications.
Versatility: It can be used to display everything from simple text to complex graphical content, and it’s often found in DIY projects, wearable devices, and small gadgets.
Raspberry Pi Pico Pins Diagram:
Ultrasonic Sensor and SSD1306 OLED with Raspberry Pi Pico:
Raspberry Pi Pico to SSD1306 OLED Display:
GND (Ground): the gnd pin of the SSD1306 oled is connected with raspberry pi pico gnd.
3V3 (3.3 Volts): the 3.3v pin of the SSD1306 oled is connected with raspberry pi pico 3.3v.
SCL (Serial Clock Line) is Connected to GP17 on the Raspberry Pi Pico.
SDA (Serial Data Line) is Connected to GP16 on the Raspberry Pi Pico.
Raspberry Pi Pico to HC-SR04 Ultrasonic Distance Sensor:
VCC (Power): The sensor is powered by connecting its VCC pin to pin 40 on the Raspberry Pi Pico, which presumably is configured to supply 5V as the HC-SR04 typically operates at this voltage.
Trig (Trigger): The trigger pin of the HC-SR04 is connected to GP3 on the Raspberry Pi Pico.
Echo (Echo Pulse Signal): The echo pin of the HC-SR04 is connected to GP2 on the Raspberry Pi Pico.
GND (Ground): the gnd pin of the HC-SR04 is connected with raspberry pi pico gnd.
Programming:
To run this project, we need two programming scripts. The first one is ‘ssd1306.py‘ and the second is ‘main.py‘. The ‘ssd1306.py‘ file contains the driver code for SSD1306; without this code, the OLED display does not work. And in the ‘main.py’ file, we have our actual project code. However, if you don’t understand, I recommend visiting my previous article where I have explained this in great detail.
ssd1306.py:
Create a new file in Thonny IDE or any other IDE of your choice, paste the code below into it, and save the file with the name ‘ssd1306.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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces from micropython import const 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) # Subclassing FrameBuffer provides support for graphics primitives # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html class SSD1306(framebuf.FrameBuffer): 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) super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 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.width > 2 * self.height 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 poweron(self): self.write_cmd(SET_DISP | 0x01) 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_data(self.buffer) 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) self.write_list = [b"\x40", None] # Co=0, D/C#=1 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_data(self, buf): self.write_list[1] = buf self.i2c.writevto(self.addr, self.write_list) 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 import time self.res(1) time.sleep_ms(1) self.res(0) time.sleep_ms(10) self.res(1) super().__init__(width, height, external_vcc) def write_cmd(self, cmd): self.spi.init(baudrate=self.rate, polarity=0, phase=0) self.cs(1) self.dc(0) self.cs(0) self.spi.write(bytearray([cmd])) self.cs(1) def write_data(self, buf): self.spi.init(baudrate=self.rate, polarity=0, phase=0) self.cs(1) self.dc(1) self.cs(0) self.spi.write(buf) self.cs(1) |
main.py:
Now open another tab in Thonny IDE, paste the below code into it, and save it with the name ‘main.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 |
from machine import Pin, I2C from ssd1306 import SSD1306_I2C import utime WIDTH = 128 HEIGHT = 64 i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=200000) oled = SSD1306_I2C(WIDTH, HEIGHT, i2c) trigger = Pin(2, Pin.OUT) echo = Pin(3, Pin.IN) def distance(): timepassed=0 trigger.low() utime.sleep_us(2) trigger.high() utime.sleep_us(5) trigger.low() while echo.value() == 0: signaloff = utime.ticks_us() while echo.value() == 1: signalon = utime.ticks_us() timepassed = signalon - signaloff return timepassed while True: oled.fill(0) measured_time = distance() distance_cm = (measured_time * 0.0343) / 2 oled.fill(0) oled.text("Distance: ", 0, 10) oled.text(str(round(distance_cm,1))+" cm",0,25) oled.show() |
Once both codes are saved, then simply press the ‘Run’ button or press F5 on the keyboard.
Output:
As you can see, our project has been successfully run, and I have placed a box in front of it which is at a distance of 2.5 cm as you can see.
And when I moved the box, the distance changed from 2.5 cm to 16.2 cm, which you can see on the SSD1306 OLED display module.
Code Explanation:
1 2 3 4 5 |
from machine import Pin, I2C from ssd1306 import SSD1306_I2C import utime |
Firstly, the statement from machine import Pin, I2C is used to import the Pin and I2C classes from the machine module. This is a crucial step in the setup as these classes enable the microcontroller to interact with the General-Purpose Input/Output (GPIO) pins and utilize the I2C communication protocol, which are fundamental for controlling various hardware peripherals. Following this, from ssd1306 import SSD1306_I2C is used to import the SSD1306_I2C class from the ssd1306 module. This particular class provides the necessary functions to control an SSD1306 OLED display through the I2C protocol, making it possible to display text, graphics, or data readings on the screen. Lastly, the code includes import utime, which brings in the utime module. This module is essential for managing time-related functions such as delays or time measurements, which are often required in hardware interfacing and control tasks. Together, these lines form the backbone of a script designed for sophisticated hardware control and data display on a microcontroller using MicroPython.
1 2 3 4 5 6 7 8 9 10 11 |
WIDTHÂ = 128Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â HEIGHT = 64 i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=200000)Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â oled = SSD1306_I2C(WIDTH, HEIGHT, i2c) trigger = Pin(2, Pin.OUT) echo = Pin(3, Pin.IN) |
The code starts by setting the width and height for an OLED display, specifically a 128×64 pixel display. This indicates that the display has a resolution of 128 pixels in width and 64 pixels in height, which is a standard size for many OLED screens used in DIY electronics.
Following this, the code initializes the I2C communication protocol with i2c = I2C(0, scl=Pin(17), sda=Pin(16), freq=200000). The I2C protocol is crucial for communication with devices like the OLED display. The ‘0’ in this line represents the I2C instance, as some microcontrollers can support multiple I2C instances. The scl and sda parameters, assigned to GPIO pins 17 and 16 respectively, define the clock (SCL) and data (SDA) lines for I2C communication, while freq=200000 sets the communication frequency to 200,000 Hz (200 kHz).
Then, an instance of the SSD1306 OLED display driver is created with oled = SSD1306_I2C(WIDTH, HEIGHT, i2c), which passes the display’s dimensions and the I2C instance as arguments. This prepares the OLED display for interaction with the microcontroller. In addition, two GPIO pins are configured for the ultrasonic sensor: trigger = Pin(2, Pin.OUT) sets up a GPIO pin as an output to act as the trigger for the sensor, sending ultrasonic pulses, and echo = Pin(3, Pin.IN) sets up another pin as an input to receive the echo or reflected pulses. These configurations are key for enabling the ultrasonic sensor to measure distances accurately.
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 |
def distance():    timepassed=0    trigger.low()    utime.sleep_us(2)    trigger.high()    utime.sleep_us(5)    trigger.low()    while echo.value() == 0:        signaloff = utime.ticks_us()    while echo.value() == 1:        signalon = utime.ticks_us()    timepassed = signalon - signaloff    return timepassed |
this code is used to measure the time taken by an ultrasonic pulse to travel from a sensor, hit an object, and return. Initially, it sets the variable timepassed to zero, which will later store the duration of the ultrasonic pulse’s round trip. The process begins by setting the trigger pin to low, a standard practice to ensure accurate pulse emission. A brief pause of 2 microseconds, achieved using utime.sleep_us(2), ensures that the trigger pin remains low long enough to be recognized by the sensor.
Following this, the trigger pin is set to high, which sends out the ultrasonic pulse. The code then waits for another 5 microseconds, a typical duration for ultrasonic pulse emission, before setting the trigger pin back to low. At this point, the code enters a loop, waiting for the echo pin to register a high signal, indicating that the pulse has been sent. The moment the pulse is sent, the code starts timing with utime.ticks_us(), and signaloff captures the timestamp just before the echo is received.
The next loop starts when the echo pin goes high, suggesting the pulse has hit an object and is returning. This loop continues until the echo pin turns low again, at which point signalon records the timestamp, marking the end of the pulse’s journey. The code then calculates the total time elapsed from sending the ultrasonic pulse to receiving its echo by subtracting signaloff from signalon.
Finally, the function concludes by returning the calculated duration timepassed. This measure of time is crucial as it can be used to calculate the distance to the object based on the speed of sound, providing a fundamental functionality for distance measuring devices using ultrasonic sensors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
while True: Â Â Â oled.fill(0) Â Â Â measured_time = distance()Â Â Â Â Â Â Â distance_cm = (measured_time * 0.0343) / 2 Â Â Â Â Â Â oled.fill(0) Â Â Â Â Â Â oled.text("Distance: ", 0, 10) Â Â Â oled.text(str(round(distance_cm,1))+" cm",0,25) Â Â Â Â Â Â oled.show() |
This code continuously measure and display distance using an ultrasonic sensor and an OLED display. It operates within an infinite loop, denoted by while True:, ensuring the code inside it runs repeatedly without interruption. The loop begins with oled.fill(0), a command that clears the OLED display by turning all pixels off, thereby removing any previous text or graphics. Next, the distance() function is called, which calculates the time taken for an ultrasonic pulse to travel to an object and return. This duration is stored in the variable measured_time.
The actual distance in centimeters is calculated using the formula distance_cm = (measured_time * 0.0343) / 2. This formula considers the speed of sound in air (approximated as 343 meters per second, hence 0.0343 cm/µs) and divides the round trip time by 2 since we need the distance to the object, not the total travel distance of the pulse.
The OLED display is cleared again with oled.fill(0), which might appear redundant but ensures that the new distance value is displayed cleanly. Following this, the calculated distance is displayed on the screen. The oled.text(“Distance: “, 0, 10) command displays the text “Distance: ” at a specific position on the screen, and oled.text(str(round(distance_cm,1))+” cm”,0,25) shows the measured distance, rounded to one decimal place, in centimeters.
Finally, oled.show() updates the display with the new information. This command is crucial as it refreshes the OLED screen to show the current distance measurement, allowing for real-time monitoring of the distance to an object as detected by the ultrasonic sensor. This setup is commonly used in various applications, including robotics and proximity sensing.
Conclusion
In conclusion, the HC-SR04 Ultrasonic Distance Measurement project with Raspberry Pi Pico and SSD1306 OLED display has demonstrated a practical and efficient way to measure distances using ultrasonic waves. The integration of the HC-SR04 sensor with the Raspberry Pi Pico microcontroller provided a reliable method for capturing distance measurements, while the SSD1306 OLED display offered a clear and concise way to visualize these measurements in real time.
Throughout this project, we observed the precision and reliability of the HC-SR04 sensor in detecting distances, highlighting its suitability for a wide range of applications, from robotics to proximity sensors. The Raspberry Pi Pico, with its ease of programming and robust GPIO interface, proved to be an excellent platform for this type of sensor integration. Additionally, the use of the SSD1306 OLED display enhanced the user experience by providing immediate visual feedback.
Related Articles:
Interfacing SSD1306 OLED Display with Raspberry Pi Pico
Display Analog Value of Potentiometer on SSD1306 OLED using Raspberry Pi Pico
Interfacing DHT11 Temperature and Humidity sensor with Raspberry Pi Pico and SSD1306 OLED
Very nice explanation. I think the sensor is a 5V device with 5V output to the Pico. Perhaps a voltage divider between the output pin and the Pico GPIO so that only 3.3V is applied to the pin?