Once you've finished setting up your QT Py RP2040 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.

To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.

# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import array
import math
import board
import audiobusio
import simpleio
import neopixel

#  neopixel setup
pixel_pin = board.A0

pixel_num = 16

pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.1, auto_write=False)

#  function to average mic levels
def mean(values):
    return sum(values) / len(values)

#  function to return mic level
def normalized_rms(values):
    minbuf = int(mean(values))
    samples_sum = sum(
        float(sample - minbuf) * (sample - minbuf)
        for sample in values
    )

    return math.sqrt(samples_sum / len(values))

#  mic setup
mic = audiobusio.PDMIn(board.TX,
                       board.A1, sample_rate=16000, bit_depth=16)
samples = array.array('H', [0] * 160)

#  variable to hold previous mic level
last_input = 0

#  neopixel colors
GREEN = (0, 127, 0)
RED = (127, 0, 0)
YELLOW = (127, 127, 0)
OFF = (0, 0, 0)

#  array of colors for VU meter
colors = [GREEN, GREEN, GREEN, GREEN, GREEN, GREEN, GREEN, GREEN,
          GREEN, YELLOW, YELLOW, YELLOW, YELLOW, YELLOW, RED, RED]

#  begin with pixels off
pixels.fill(OFF)
pixels.show()

while True:
    #  take in audio
    mic.record(samples, len(samples))
    #  magnitude holds the value of the mic level
    magnitude = normalized_rms(samples)
    #  uncomment to see the levels in the REPL
    #  print((magnitude,))

    #  mapping the volume range (125-500) to the 16 neopixels
    #  volume range is optimized for music. you may want to adjust the range
    #  based on the type of audio that you're trying to show
    mapped_value = simpleio.map_range(magnitude, 125, 500, 0, 16)
    #  converting the mapped range to an integer
    input_val = int(mapped_value)

    #  if the mic input has changed from the last input value...
    if last_input != input_val:
        for i in range(input_val):
            #  if the level is lower...
            if last_input > input_val:
                for z in range(last_input):
                    #  turn those pixels off
                    pixels[z] = (OFF)
            #  turn on pixels using the colors array
            pixels[i] = (colors[i])
            pixels.show()
            #  update last_input
            last_input = input_val

    time.sleep(0.01)

Upload the Code and Libraries to the QT Py RP2040

After downloading the Project Bundle, plug your QT Py RP2040 into the computer's USB port. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the QT Py RP2040's CIRCUITPY drive. 

  • lib folder
  • code.py

Your QT Py RP2040 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.

How the CircuitPython Code Works

First, the strip of 16 NeoPixels are setup.

#  neopixel setup
pixel_pin = board.A0

pixel_num = 16

pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.1, auto_write=False)

PDM Mic Setup

Then the PDM mic is setup, along with two functions that assist with returning usable data from the PDM mic.

#  function to average mic levels
def mean(values):
    return sum(values) / len(values)

#  function to return mic level
def normalized_rms(values):
    minbuf = int(mean(values))
    samples_sum = sum(
        float(sample - minbuf) * (sample - minbuf)
        for sample in values
    )

    return math.sqrt(samples_sum / len(values))

#  mic setup
mic = audiobusio.PDMIn(board.TX,
                       board.A1, sample_rate=16000, bit_depth=16)
samples = array.array('H', [0] * 160)

Defining the NeoPixel Colors

Four colors for the NeoPixels are setup. The colors array holds the colors in the order that they will appear on the VU meter.

#  neopixel colors
GREEN = (0, 127, 0)
RED = (127, 0, 0)
YELLOW = (127, 127, 0)
OFF = (0, 0, 0)

#  array of colors for VU meter
colors = [GREEN, GREEN, GREEN, GREEN, GREEN, GREEN, GREEN, GREEN,
          GREEN, YELLOW, YELLOW, YELLOW, YELLOW, YELLOW, RED, RED]

#  begin with pixels off
pixels.fill(OFF)
pixels.show()

The Loop

In the loop, data is received by the PDM mic and is held in the variable magnitude. magnitude is scaled to fit into the range of 0 to 16 using map_range(). This allows the 16 NeoPixels to represent the range of data coming from the PDM mic.

#  take in audio
    mic.record(samples, len(samples))
    #  magnitude holds the value of the mic level
    magnitude = normalized_rms(samples)
    #  uncomment to see the levels in the REPL
    #  print((magnitude,))

    #  mapping the volume range (125-500) to the 16 neopixels
    #  volume range is optimized for music. you may want to adjust the range
    #  based on the type of audio that you're trying to show
    mapped_value = simpleio.map_range(magnitude, 125, 500, 0, 16)
    #  converting the mapped range to an integer
    input_val = int(mapped_value)

Updating the NeoPixels

The NeoPixels are updated if the data from the PDM mic changes. This is checked by comparing last_input and input_val.

A for statement is used to iterate through the range of the current input data from the PDM mic. The index, represented with i, is used to turn on the correct number of pixels and the defined colors from the colors array.

#  if the mic input has changed from the last input value...
    if last_input != input_val:
        for i in range(input_val):
            #  if the level is lower...
            if last_input > input_val:
                for z in range(last_input):
                    #  turn those pixels off
                    pixels[z] = (OFF)
            #  turn on pixels using the colors array
            pixels[i] = (colors[i])
            pixels.show()
            #  update last_input
            last_input = input_val

    time.sleep(0.01)

This guide was first published on Feb 01, 2022. It was last updated on Jan 25, 2022.

This page (Coding the NeoPixel Mini VU Meter) was last updated on Jun 08, 2023.

Text editor powered by tinymce.