Build a Portable Decibel Meter with Raspberry Pi Pico

Build a Portable Decibel Meter with Raspberry Pi Pico

10 min read
Quick Navigation
Build a portable decibel meter with Raspberry Pi Pico. Use MicroPython and 3D printing for real-time sound monitoring.

Greetings everyone, and welcome to my article tutorial. Today, I'll guide you through the process of creating a portable decibel meter using Raspberry Pi Pico and MicroPython.

Project Overview:

I build a portable decibel meter using Raspberry Pi Pico and a high-precision INMP441 I2S MEMS microphone. The device measures real-time sound levels, displays them on an OLED screen, and runs completely standalone with a battery-powered design.

This project covers:

  1. Real-time sound level measurement (dB)
  2. I2S microphone interfacing with Raspberry Pi Pico
  3. OLED visualization
  4. Custom 3D-printed enclosure made using Fusion 360
  5. PCB-based final assembly for a professional finish

This is a practical electronics and embedded systems project, perfect for IoT, environmental monitoring, noise analysis, and learning MicroPython with Raspberry Pi Pico.

Before beginning, a huge shoutout to JLCMC for sponsoring.

Now, let's get started with our project!

Supplies

Image

Electronic Components Required:

  1. Raspberry Pi Pico
  2. INMP441 MEMS I2S Microphone
  3. 0.98" OLED Display
  4. 3.7V Li-Po Battery
  5. TP4056 Battery Charging Module
  6. Slider Switch

Additional Components:

  1. Custom PCB
  2. 3D-Printed Enclosure
  3. Hot Glue
  4. Cutter
  5. Soldering Iron
  6. PLA Filament

Software:

  1. Thonny IDE

Step 1: Breadboard Testing (Microphone + Pico)

Image Image

Follow the steps:

  1. Place the Raspberry Pi Pico and the INMP441 microphone module on the breadboard, and make the connections using jumper wires, exactly as shown in the circuit diagram.
  2. Once the connections are done, connect the Raspberry Pi Pico to my computer using a USB cable.
  3. Now, inside the** Thonny IDE**, I’ll paste the code, save it as main.py, and run it.
from machine import I2S, Pin
import math
import time
import array

# --- CONFIGURATION ---
SCK_PIN = 16
WS_PIN = 17
SD_PIN = 18

# I2S Config
I2S_ID = 0
SAMPLE_RATE = 16000
BITS_PER_SAMPLE = 32
BUFFER_LENGTH = 64 

# --- CALIBRATION VALUE ---
DB_OFFSET = -46.72

# Initialize I2S
audio_in = I2S(
    I2S_ID,
    sck=Pin(SCK_PIN),
    ws=Pin(WS_PIN),
    sd=Pin(SD_PIN),
    mode=I2S.RX,
    bits=BITS_PER_SAMPLE,
    format=I2S.MONO,
    rate=SAMPLE_RATE,
    ibuf=2048 # Internal buffer size
)

# Create a buffer to store the raw bytes read from the I2S
read_buffer = bytearray(BUFFER_LENGTH * 4)

print("Starting Decibel Meter...")

while True:
    # Read data from the INMP441 into the buffer
    num_bytes_read = audio_in.readinto(read_buffer)
    
    # Determine how many samples we actually read
    samples_read = num_bytes_read // 4
    
    if samples_read > 0:
        # **FIXED: Use array.array to cast bytes to 32-bit signed integers ('i')**
        mic_samples = array.array('i', read_buffer)
        
        sum_squares = 0.0
        
        for i in range(samples_read):
            # Shift right by 8 to get the correct 24-bit integer value
            processed_sample = mic_samples[i] >> 8
            
            # Accumulate sum of squares
            sum_squares += processed_sample * processed_sample
            
        # Calculate RMS
        rms = math.sqrt(sum_squares / samples_read)
        
        # Avoid log(0) error
        if rms <= 0:
            rms = 1
            
        # Calculate dB
        db = 20.0 * math.log10(rms)
        
        # Apply calibration
        final_db = db + DB_OFFSET
        
        # Print to Serial
        print(f"Raw dB: {db:.2f} | Final dB: {final_db:.2f}")
    
    time.sleep(0.05)

As you can see in the shell, the sound level values in decibels are updating in real time.

Step 2: Elevate Your Electronic Projects - JLCMC

Image Image Image

JLCMC is your one-stop shop for all electronic manufacturing needs, offering an extensive catalog of nearly 600,000 SKUs that cover hardware, mechanical, electronic, and automation components. Their commitment to guaranteeing genuine products, rapid shipping (with most in-stock items dispatched within 24 hours), and competitive pricing truly sets them apart. In addition, their exceptional customer service ensures you always get exactly what you need to bring your projects to life.

For my next project, I’m planning to buy a timing belt from their Transmission Components section.

What I really like is how easy it is to customize the part. On the left side, you can select all the required options, and just below that, you get the complete specification and documentation, so you know exactly what you’re ordering.

JLCMC has recently** upgraded their new-user registration benefits,** increasing the value of the welcome coupon package to $123 in discount coupons. Whether you’re building DIY electronics, robotics, or mechanical projects, JLCMC has you covered with quality parts and fast delivery. Don’t miss out—visit https://jlcmc.com/?from=RL2 to explore their amazing range of products and grab your discount coupon today!

Step 3: Adding OLED Display

Image Image

Now let’s add the OLED display to the breadboard.

  • Mount the OLED on the breadboard and connect it to the Raspberry Pi Pico using I2C, following this circuit diagram.
  • After that, connect the USB cable again and open Thonny IDE.
  • First, save the ssd1306.py library file on the Raspberry Pi Pico.
# 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)
  • Then open a new file, paste the main code, save it as main.py, and run it.
#main.py
from machine import I2S, Pin, I2C
from ssd1306 import SSD1306_I2C
import math
import time
import array

# --- I2S CONFIGURATION (Microphone) ---
SCK_PIN = 16
WS_PIN = 17
SD_PIN = 18
SAMPLE_RATE = 16000
BITS_PER_SAMPLE = 32
BUFFER_LENGTH = 64
DB_OFFSET = -46.72

audio_in = I2S(
0, sck=Pin(SCK_PIN), ws=Pin(WS_PIN), sd=Pin(SD_PIN),
mode=I2S.RX, bits=BITS_PER_SAMPLE, format=I2S.MONO,
rate=SAMPLE_RATE, ibuf=2048
)
read_buffer = bytearray(BUFFER_LENGTH * 4)

# --- OLED CONFIGURATION (Display) ---
OLED_WIDTH = 128
OLED_HEIGHT = 64
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
oled = SSD1306_I2C(OLED_WIDTH, OLED_HEIGHT, i2c)
SMOOTHING_FACTOR = 0.15
smoothed_db = 0.0
is_first_reading = True

# Peak hold for visual effect
peak_db = 0.0
peak_hold_time = 0
PEAK_HOLD_DURATION = 20

print("Starting Professional Decibel Meter...")

def draw_meter_bar(oled, db_value):
"""Draw a professional-looking meter bar with segments"""
# Map dB range (30-90 dB) to bar width (0-100 pixels)
db_min = 30.0
db_max = 90.0

# Clamp value
db_clamped = max(db_min, min(db_max, db_value))

# Calculate bar width (100 pixels max)
bar_width = int((db_clamped - db_min) / (db_max - db_min) * 100)

# Draw border for meter
oled.rect(0, 28, 102, 14, 1)

# Draw segmented bar (10 segments)
for seg in range(10):
seg_start = seg * 10 + 1
seg_end = seg_start + 8

if bar_width > seg_start:
# Fill this segment
fill_width = min(8, bar_width - seg_start)
oled.fill_rect(seg_start + 1, 30, fill_width, 10, 1)

# Draw level indicators
for i in range(0, 101, 20):
oled.vline(i + 1, 42, 3, 1)

# Draw labels
oled.text("30", 0, 46, 1)
oled.text("60", 44, 46, 1)
oled.text("90", 88, 46, 1)

def draw_peak_indicator(oled, peak_value):
"""Draw a small peak hold indicator"""
db_min = 30.0
db_max = 90.0
peak_clamped = max(db_min, min(db_max, peak_value))
peak_pos = int((peak_clamped - db_min) / (db_max - db_min) * 100)

if peak_pos > 0 and peak_pos <= 100:
# Draw peak marker
oled.vline(peak_pos + 1, 29, 12, 1)

while True:
num_bytes_read = audio_in.readinto(read_buffer)
samples_read = num_bytes_read // 4

if samples_read > 0:
mic_samples = array.array('i', read_buffer)
sum_squares = 0.0

for i in range(samples_read):
processed_sample = mic_samples[i] >> 8
sum_squares += processed_sample * processed_sample

rms = math.sqrt(sum_squares / samples_read)
if rms <= 0:
rms = 1

db = 20.0 * math.log10(rms)
final_db = db + DB_OFFSET

if is_first_reading:
smoothed_db = final_db
is_first_reading = False
else:
smoothed_db = (SMOOTHING_FACTOR * final_db) + ((1 - SMOOTHING_FACTOR) * smoothed_db)

# Peak detection with hold
if smoothed_db > peak_db:
peak_db = smoothed_db
peak_hold_time = PEAK_HOLD_DURATION
else:
peak_hold_time -= 1
if peak_hold_time <= 0:
# Slowly decay peak
peak_db = peak_db * 0.95

# Print to Serial Monitor
print(f"Raw: {final_db:.2f} | Smoothed: {smoothed_db:.2f} | Peak: {peak_db:.2f}")


oled.fill(0)

# Title with box
oled.rect(0, 0, 128, 12, 1)
oled.text("dB METER", 35, 2, 1)

# Large dB value display
db_str = f"{smoothed_db:.1f}"
oled.text(db_str, 30, 15, 1)
oled.text("dB", 75, 15, 1)

# Draw meter bar with segments
draw_meter_bar(oled, smoothed_db)

# Draw peak hold indicator
draw_peak_indicator(oled, peak_db)

# Status indicator (small dot that blinks)
if int(time.ticks_ms() / 500) % 2:
oled.fill_rect(122, 2, 4, 4, 1)

oled.show() # Update display

time.sleep(0.05)

And there you go, the decibel values are now displayed live on the OLED screen, as seen in the video.

Step 4: 3D Printed Enclosure

To give this project a proper handheld form, I designed a custom enclosure in Fusion 360.

I’m still a beginner in 3D modeling, but I wanted this project to feel like a real device instead of just a breadboard setup.

After designing the enclosure, I 3D-printed it, and the final result looks clean, compact, and portable.

Step 5: PCB & Power Circuit

Image Image Image

To make the project more reliable and professional, I moved the circuit to a PCB board.

I mounted header pins and soldered the Raspberry Pi Pico onto the PCB.

Then, following the circuit diagram, I soldered the header pin connections for the microphone, OLED display, and other components.

For your ease I have also made the PCB, and here is the Gerber File Link: https://github.com/ShahbazCoder1/Portable-Decibel-Meter-Using-Raspberry-Pi-Pico-and-Micro-Python/tree/main/PCB%20Gerber%20File

Finally, I assembled the power supply unit using the battery and charging module, making the device fully portable.

Step 6: Final Assembly

Image

Follow the steps below to assemble all the components inside the enclosure and complete the portable decibel meter.

1. Mount the Raspberry Pi Pico

  • Place the Raspberry Pi Pico onto the PCB board.
  • Once aligned properly, use double-sided tape to fix the PCB securely to the bottom of the enclosure, as shown in Image 1 & 2.

2. Fix the PCB Inside the Enclosure

  • Carefully position the PCB board inside the box so that the Pico is centered and properly aligned.
  • Press it gently to ensure the tape holds firmly (Image 2).

3. Place the Battery

  • Using double-sided tape, place the Li-Po battery beside the PCB board, as shown in Image 3.
  • Make sure the battery wires are accessible and not pinched.

4. Install the Charging Module and Switch

  • Insert the TP4056 battery charging module into the designated slot in the enclosure.
  • If it feels loose, secure it using hot glue.
  • Similarly, mount the slider switch into the provided slot on the enclosure (Image 4).

5. Mount Display and Microphone

  • Insert the OLED display and the INMP441 I2S microphone module into their respective cutouts on the front panel.
  • Once aligned, use hot glue to fix them securely in place (Image 5 & 6).
  • Ensure the glue does not block the microphone hole or display area.

6. Connect Jumper Wires

  • Attach the male-to-female jumper wires to the Pico pins according to the circuit connections (Image 6).

7. Power Connections

  • Connect the VCC and GND pins from the battery charging circuit to the PCB board to complete the power supply wiring (Image 7).

8. Final Wiring

Now connect the jumper wires from:

  1. The OLED display
  2. The INMP441 I2S microphone

to their corresponding pins on the PCB board, as shown in Image 8.

Double-check all connections before proceeding.

9. Close the Enclosure

  • Once everything is properly connected and secured, place the lid onto the enclosure and close it carefully (Image 9).

Your portable Raspberry Pi Pico–based Decibel Meter is now fully assembled and ready to use!

Step 7: Working Video and Tutorial

Congratulations! You’ve successfully built your Portable Decibel Meter Using Raspberry Pi Pico and Micro Python. A demonstration video of this project can be viewed here: Watch Now

Thank you for your interest in this project. If you have any questions or suggestions for future projects, please leave a comment, and I will do my best to assist you.

For business or promotional inquiries, please contact me via email at Email.

I will continue to update this article with new information. Don’t forget to follow me for updates on new projects and subscribe to my YouTube channel (YouTube: roboattic Lab) for more content. Thank you for your support.

Related Topics:raspberry pi project

Related Articles

Build a GPS Vehicle Tracker with RPi Pico & MicroPythonraspberry pi project
October 21, 2025

Build a GPS Vehicle Tracker with RPi Pico & MicroPython

Build a professional GPS tracker with Raspberry Pi Pico, SIM800L, and Neo-6M. Track real-time locations via SMS and Google Maps using MicroPython.

Read the full raspberry pi project tutorial: See Project Details
How to Make a Raspberry Pi Pico Bluetooth Control Carraspberry pi project
July 05, 2023

How to Make a Raspberry Pi Pico Bluetooth Control Car

In this project, we will utilize a Bluetooth module receiver to receive commands from mobile. Based on the received commands, the car will perform corresponding actions.

Read the full raspberry pi project tutorial: Follow Tutorial
How to Make a Target Chasing Robot Car Using Raspberry Pi Picoraspberry pi project
January 31, 2025

How to Make a Target Chasing Robot Car Using Raspberry Pi Pico

This project showcases a target chasing robot car built with a Raspberry Pi Pico and an L298N motor driver, combined with mecanum wheels that allow for agile and multidirectional movement.

Read the full raspberry pi project tutorial: Follow Tutorial