Metronome Maker Pi Pico, programmed with CircuitPython

Introduction

A metronome can help you keep a consistent tempo so that you don’t inadvertently speed up or slow down. It provides a steady click marking a musical interval – masterclass.com. Let’s built our own metronome! 😊

Video

Hardware Preparation

This is the list of items used in the video.

Sample Program

This is CircuitPython sample program and have been tested with CircuitPython 6.3.0. Before that, please include the following additional libraries:

  • simpleio.mpy
  • neopixel.mpy

You may download the CircuitPython Libraries Bundle 👉 here.

#
# Metronome Maker Pi Pico, programmed with CircuitPython
#
# Reference:
# – https://learn.adafruit.com/metronome-clue
#
# Tutorial
# – https://tutorial.cytron.io/
#
# Raspberry Pi Pico
# – Maker Pi Pico https://my.cytron.io/p-maker-pi-pico?tracking=idris
# – Grove 16×2 LCD https://my.cytron.io/p-grove-16-x-2-lcd-white-on-blue?tracking=idris
#
# Additional Libraries
# – simpleio.mpy
# – neopixel.mpy
# Download CircuitPython Libraries Bundle – https://circuitpython.org/libraries
#
# Update:
# 3 Aug 2021 – Tested with CircuitPython Pico 6.3.0
#
import time
import board
import digitalio
import simpleio
import neopixel
import busio
from grove_lcd_i2c import Grove_LCD_I2C
button1 = digitalio.DigitalInOut(board.GP20)
button1.direction = digitalio.Direction.INPUT
button2 = digitalio.DigitalInOut(board.GP21)
button2.direction = digitalio.Direction.INPUT
button3 = digitalio.DigitalInOut(board.GP22)
button3.direction = digitalio.Direction.INPUT
NONE = (0, 0, 0)
RED = (100, 0, 0)
GREEN = (0, 100, 0)
pixels = neopixel.NeoPixel(board.GP28, 1)
pixels[0] = NONE
LCD_SDA = board.GP4
LCD_SCL = board.GP5
LCD_ADDR = 0x3E
i2c = busio.I2C(scl=LCD_SCL, sda=LCD_SDA)
lcd = Grove_LCD_I2C(i2c, LCD_ADDR)
lcd.home()
lcd.print("Metronome on\nMaker Pi Pico")
tempo = 120 # in bpm
print("BPM: {}".format(tempo))
time_signature = 4 # Beats per measure
BEEP_DURATION = 0.05
delay = 60 / tempo
def metronome(accent): # Play metronome sound and flash display
if accent == 1: # Put emphasis on downbeat
pixels[0] = GREEN
simpleio.tone(board.GP18, 1800, BEEP_DURATION)
else:
pixels[0] = RED
simpleio.tone(board.GP18, 1200, BEEP_DURATION)
pixels[0] = NONE
time.sleep(0.2)
tempo_increment = 1 # increment for tempo value setting
feedback_mode = 0 # 0 is sound and visual, 1 is sound only, 2 is visual only
running = True
beat = 1
time.sleep(2)
lcd.cursor_position(0, 0)
lcd.print("Tempo: {} \nTSign: {}/4 ".format(tempo, time_signature))
t0 = time.monotonic() # set start time
while True:
if button1.value == False: # Button 1 is pressed
if tempo > 40:
tempo = tempo tempo_increment
delay = 60 / tempo
lcd.cursor_position(0, 0)
lcd.print("Tempo: {} \nTSign: {}/4 ".format(tempo, time_signature))
time.sleep(0.2) # debounce
elif button2.value == False: # Button 2 is pressed
if tempo < 330:
tempo = tempo + tempo_increment
delay = 60 / tempo
lcd.cursor_position(0, 0)
lcd.print("Tempo: {} \nTSign: {}/4 ".format(tempo, time_signature))
time.sleep(0.2) # debounce
elif button3.value == False: # Button 3 is pressed
print("sig change")
if time_signature == 4:
time_signature = 3
else:
time_signature = 4
lcd.cursor_position(0, 0)
lcd.print("Tempo: {} \nTSign: {}/4 ".format(tempo, time_signature))
time.sleep(0.4)
beat = 1 # start with downbeat
elif time.monotonic() t0 >= delay:
t0 = time.monotonic() # reset time before click to maintain accuracy
metronome(beat)
beat = beat 1
if beat == 0: # if the downbeat was just played, start at top of measure
beat = time_signature

view raw
code.py
hosted with ❤ by GitHub

from board import *
import digitalio
import busio
import time
from adafruit_bus_device.i2c_device import I2CDevice
# Command values
LCD_CLEAR_DISPLAY = 0x01
LCD_RETURN_HOME = 0x02
LCD_ENTRY_MODE_SET = 0x04
LCD_DISPLAY_CONTROL = 0x08
LCD_CURSOR_SHIFT = 0x10
LCD_FUNCTION_SET = 0x20
LCD_SET_CG_RAM_ADDR = 0x40
LCD_SET_DD_RAM_ADDR = 0x80
# Flags for display entry mode
LCD_ENTRY_RIGHT = 0x00
LCD_ENTRY_LEFT = 0x02
LCD_ENTRY_SHIFT_INCREMENT = 0x01
LCD_ENTRY_SHIFT_DECREMENT = 0x00
# Flags for display on/off control
LCD_DISPLAY_ON = 0x04
LCD_DISPLAY_OFF = 0x00
LCD_CURSOR_ON = 0x02
LCD_CURSOR_OFF = 0x00
LCD_BLINK_ON = 0x01
LCD_BLINK_OFF = 0x00
# Flags for display/cursor shift
LCD_DISPLAY_MOVE = 0x08
LCD_CURSOR_MOVE = 0x00
LCD_MOVE_RIGHT = 0x04
LCD_MOVE_LEFT = 0x00
# Flags for function set
LCD_8_BIT_MODE = 0x10
LCD_4_BIT_MODE = 0x00
LCD_2_LINE = 0x08
LCD_1_LINE = 0x00
LCD_5x10_DOTS = 0x04
LCD_5x8_DOTS = 0x00
class Grove_LCD_I2C:
def __init__(self, i2c_bus, lcd_address, cols=16, lines=2, dotsize=LCD_5x8_DOTS):
self.lcd = I2CDevice(i2c_bus, lcd_address)
self._displayfunction = 0
self._displaycontrol = 0
self._displaymode = 0
self._initialized = 0
self._numlines = lines
self._currline = 0
if lines > 1:
self._displayfunction |= LCD_2_LINE
if (not dotsize == 0) and lines == 1:
self._displayfunction |= LCD_5x10_DOTS
time.sleep(0.05)
self.command(LCD_FUNCTION_SET | self._displayfunction)
time.sleep(0.0045) # wait more than 4.1 ms
self.command(LCD_FUNCTION_SET | self._displayfunction)
time.sleep(0.00015)
self.command(LCD_FUNCTION_SET | self._displayfunction)
self.command(LCD_FUNCTION_SET | self._displayfunction)
self._displaycontrol = LCD_DISPLAY_ON | LCD_CURSOR_OFF | LCD_BLINK_OFF
self.display()
self.clear()
self._displaymode = LCD_ENTRY_LEFT | LCD_ENTRY_SHIFT_DECREMENT
self.command(LCD_ENTRY_MODE_SET | self._displaymode)
def clear(self):
self.command(LCD_CLEAR_DISPLAY)
time.sleep(0.002)
def home(self):
self.command(LCD_RETURN_HOME)
time.sleep(0.002)
def cursor_position(self, col, row):
position = col | 0x80 if row == 0 else col | 0xC0
data = bytearray(2)
data[0] = 0x80
data[1] = position
# print(data)
self.i2c_send_bytes(data)
def noDisplay(self):
self._displaycontrol &= 0xFF LCD_DISPLAY_ON
self.command(LCD_DISPLAY_CONTROL | self._displaycontrol)
def display(self):
self._displaycontrol |= LCD_DISPLAY_ON
self.command(LCD_DISPLAY_CONTROL | self._displaycontrol)
def noCursor(self):
self._displaycontrol &= 0xFF LCD_CURSOR_ON
self.command(LCD_DISPLAY_CONTROL | self._displaycontrol)
def cursor(self):
self._displaycontrol &= 0xFF LCD_CURSOR_ON
self.command(LCD_DISPLAY_CONTROL | self._displaycontrol)
def noBlink(self):
self._displaycontrol |= LCD_CURSOR_ON
self.command(LCD_DISPLAY_CONTROL | self._displaycontrol)
def blink(self):
self._displaycontrol |= LCD_BLINK_ON
self.command(LCD_DISPLAY_CONTROL | self._displaycontrol)
def scrollDisplayLeft(self):
self.command(LCD_CURSOR_SHIFT | LCD_DISPLAY_MOVE | LCD_MOVE_LEFT)
def scrollDisplayRight(self):
self.command(LCD_CURSOR_SHIFT | LCD_DISPLAY_MOVE | LCD_MOVE_RIGHT)
def rightToLeft(self):
self._displaymode |= LCD_ENTRY_LEFT
self.command(LCD_ENTRY_MODE_SET | self._displaymode)
def autoscroll(self):
self._displaymode |= LCD_ENTRY_SHIFT_INCREMENT
self.command(LCD_ENTRY_MODE_SET | self._displaymode)
def noAutoscroll(self):
self._displaymode &= 0xFF LCD_ENTRY_SHIFT_INCREMENT
self.command(LCD_ENTRY_MODE_SET | self._displaymode)
def createChar(self, location, charmap):
location &= 0x7
self.command(LCD_SET_CG_RAM_ADDR | (location << 3))
data = bytearray(9)
data[0] = 0x40
for i in range(8):
data[i + 1] = charmap[i]
self.i2c_send_bytes(data)
def command(self, value):
data = bytearray(2)
data[0] = 0x80 # command register address
data[1] = value # command byte
self.i2c_send_bytes(data)
def write(self, value):
data = bytearray(2)
data[0] = 0x40
data[1] = value
self.i2c_send_bytes(data)
return 1
def i2c_send_bytes(self, data):
with self.lcd as wire:
wire.write(data)
def print(self, text):
string = str(text)
for char in string:
if char == "\n":
self.cursor_position(0, 1)
else:
self.write(ord(char))

view raw
grove_lcd_i2c.py
hosted with ❤ by GitHub

Thank You

References:

Thanks for reading this tutorial. If you have any technical inquiries, please post at Cytron Technical Forum.

Please be reminded, this tutorial is prepared for you to try and learn.
You are encouraged to improve the code for a better application.

Leave a Comment

Your email address will not be published.

Share this Tutorial

Share on facebook
Share on whatsapp
Share on email
Share on print
Share on twitter
Share on pinterest
Share on facebook
Share on whatsapp
Share on email
Share on print
Share on twitter
Share on pinterest

Latest Tutorial

Low Profile Aluminum Case and Icon Case Stress Test
In-Home Security System using Maker Uno.
Learn to program Arduino using Tinkercad Circuit.
DIY Digital Alarm Clock Using REKA:BIT With Micro:bit
Display Internet Time (NTP) on micro:bit
Tutorials of Cytron Technologies Scroll to Top