Portable Decibel Meter Using Raspberry Pi Pico, Micro Python and Fusion360

Greetings everyone, and welcome to my Instructables tutorial. Today, I'll guide you through the process of creating a Portable Decibel Meter Using Raspberry Pi Pico and Micro Python.

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:

• Real-time sound level measurement (dB)
• I2S microphone interfacing with Raspberry Pi Pico
• OLED visualization
• Custom 3D-printed enclosure made using fusion360
• PCB-based final assembly for a professional finish

This is a practical electronics + 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

Electronic Components Required:

• Raspberry Pi Pico
• INMP441 MEMS I2S Microphone
• 0.98" OLED Display
• 3.7V Li-Po Battery
• TP4056 Battery Charging Module
• Slider Switch
Additional Components:

• Custom PCB
• 3D-Printed Enclosure
• Hot Glue
• Cutter
• Soldering Iron
• PLA Filament

Software:
• Thonny IDE

STEP 1
Breadboard Testing (Microphone + Pico)

Follow the steps:
• 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.
• Once the connections are done, connect the Raspberry Pi Pico to my computer using a USB cable.
• Now, inside the Thonny IDE, I’ll paste the code, save it as main.py, and run it.

CODE
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

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

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.

CODE
# 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.

CODE
#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.

icon Decibel Meter .stl file.zip 57KB Download(0)
STEP 5
PCB & Power Circuit

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.

icon Gerber_Decibel-Meter_PCB_Decibel-Meter_2_2026-01-19.zip 37KB Download(0)
STEP 6
Troubleshooting

If your decibel meter does not work as expected, don’t worry. Most issues are related to wiring, power, or software configuration. Go through the checks below step by step.

1. Device Does Not Power ON

Possible causes:

  1. Battery not charged
  2. Incorrect wiring to the TP4056 module
  3. Power switch wiring issue

Solutions:

  1. Fully charge the Li-Po battery using the TP4056 module.
  2. Double-check B+ / B- connections on the charging module.
  3. Ensure the VCC and GND connections are correctly wired to the PCB.
  4. Verify that the slider switch is wired in series with the power line.

2. OLED Display Not Turning ON

Possible causes:

  1. Incorrect I2C wiring
  2. Wrong I2C address
  3. Missing or incorrect driver file

Solutions:

  1. Check that SDA and SCL pins are connected correctly to the Raspberry Pi Pico.
  2. Confirm the OLED I2C address (usually 0x3C).
  3. Make sure the ssd1306.py file is uploaded to the Pico.
  4. Ensure the display is receiving 3.3V, not 5V.

3. OLED Turns ON but Shows Nothing

Possible causes:

  1. Code not running
  2. Display initialization failed
  3. Contrast issue

Solutions:

  1. Re-upload the main.py file and restart the Pico.
  2. Check the display initialization code.
  3. Ensure the screen is not damaged or glued too tightly.

4. No Sound Readings / dB Value Not Changing

Possible causes:

  1. INMP441 microphone wiring issue
  2. Incorrect I2S pin configuration
  3. Microphone power issue

Solutions:

  1. Verify the I2S pins (BCLK, WS, SD) match the pins defined in the code.
  2. Check that the microphone is powered with 3.3V.
  3. Make sure the microphone hole is not blocked by glue.
  4. Try speaking or clapping near the microphone to test response.

5. Random or Incorrect dB Values

Possible causes:

  1. Loose jumper connections
  2. Electrical noise
  3. Unstable power supply

Solutions:

  1. Secure all jumper wires properly.
  2. Avoid long or loose wires near the microphone.
  3. Ensure the battery and power connections are stable.
  4. Restart the device after assembly.

6. Code Upload Errors in Thonny

Possible causes:

  1. Pico not in boot mode
  2. Wrong interpreter selected
  3. File not saved correctly

Solutions:

  1. Press and hold the BOOTSEL button while connecting the Pico.
  2. Select MicroPython (Raspberry Pi Pico) in Thonny.
  3. Save the main file as main.py on the Pico.
  4. Ensure all required files are uploaded.

7. Device Works on USB but Not on Battery

Possible causes:

  1. Battery voltage too low
  2. Charging module wiring issue
  3. Power switch problem

Solutions:

  1. Fully charge the battery before testing.
  2. Check connections between TP4056 output and Pico power pins.
  3. Test continuity of the power switch.

8. Device Stops Working After Closing the Enclosure

Possible causes:

  1. Wires getting pinched
  2. Loose connections inside the box

Solutions:

  1. Reopen the enclosure and inspect all wiring.
  2. Ensure no wires are under pressure when the lid is closed.
  3. Secure loose components with hot glue or tape.

9. General Debugging Tips

  1. Always test the circuit on a breadboard first.
  2. Upload and test code before final enclosure assembly.
  3. Test each module individually:
  4. Pico
  5. OLED
  6. INMP441 microphone
  7. Keep a USB cable connected for quick debugging.

Following these steps should help you resolve most common issues and get your portable Raspberry Pi Pico Decibel Meter working correctly.

⭐ Pro Tip

If you modify pin assignments or hardware layout, update the code accordingly to avoid conflicts.

STEP 7
Final Assembly

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:

• The OLED display
• 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 8
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 instructable 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.

License
All Rights
Reserved
licensBg
0