Pico W RFID music player with a WS2812 strip
MicroPython on a Pico W: lay an RFID card on an MFRC522 and the notes stored on it play on a passive buzzer while a WS2812 strip flashes a color per note. The whole build, a Raspberry Pi Pico W and 5 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Advanced About 40 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $12.55. On Mokxi they're free.
What you'll learn
- Reading data stored on an RFID card over SPI
- Turning note letters into frequencies with a lookup table
- Making tones on a passive buzzer with PWM
- Driving a buzzer through a transistor
- Setting colors on a WS2812 strip with NeoPixel
- Difficulty: Advanced
- Time: About 40 minutes
- Board: Raspberry Pi Pico W
- Parts: 9
How it works
An MFRC522 reader sits on the Pico W's SPI pins (SCK GP18, MOSI GP19, MISO GP16, SDA GP17, RST GP9). The main loop keeps asking it whether a card is present. When one is, the program reads block 8 of the card, which holds the tune as note letters such as C D E F G A B.
Each letter plays on a passive buzzer on GP15. The Pico drives the buzzer through a 1k resistor and an NPN transistor. A strip of 8 addressable LEDs on GP0 flashes a random color for every note.
If the card has nothing stored, the player falls back to the tune CDEC. Then it waits until you take the card away, so one tap plays one tune.
Parts list
9 parts, plus the jumper wires. They retail for about $12.55; on Mokxi they're free.
- 1 × Raspberry Pi Pico W
- 1 × RFID reader, MFRC522In Escape Box Kit.
- 1 × Addressable RGB LEDsIn Claw Machine Kit, Halloween Prop Kit, Christmas Lights Kit.
- 1 × Piezo speakerIn Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Transistor (NPN or PNP)In LED Cube Kit.
- 1 × Resistor, 1k ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 2 × Power, 3.3 V
- 1 × Power, 5 V
Kits that can build it
This starter kit has every part in the circuit. Each kit's page lists what else it can build.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Addressable RGB LEDs pin DIN | Raspberry Pi Pico W pin GP0 |
| RFID reader, MFRC522 pin RST | Raspberry Pi Pico W pin GP9 |
| Resistor, 1k Ω pin 1 | Raspberry Pi Pico W pin GP15 |
| RFID reader, MFRC522 pin MOSI | Raspberry Pi Pico W pin GP19 |
| RFID reader, MFRC522 pin SCK | Raspberry Pi Pico W pin GP18 |
| RFID reader, MFRC522 pin SDA | Raspberry Pi Pico W pin GP17 |
| RFID reader, MFRC522 pin MISO | Raspberry Pi Pico W pin GP16 |
| RFID reader, MFRC522 pin GND | Ground |
| RFID reader, MFRC522 pin 3.3V | 3.3 V |
| Addressable RGB LEDs pin VDD | 5 V |
| Addressable RGB LEDs pin VSS | Ground |
| Piezo speaker pin 1 | 3.3 V |
| Transistor (NPN or PNP) pin C | Piezo speaker pin 2 |
| Resistor, 1k Ω pin 2 | Transistor (NPN or PNP) pin B |
| Transistor (NPN or PNP) pin E | Ground |
The code
The MicroPython code exactly as the editor opens it. Change a line there and press Run: the board runs it on the official MicroPython firmware, with nothing to compile.
# RFID music player: a card carries a tune as note letters (C D E F G A B),
# stored with lesson 6.5's program. Lay it on the reader: each note plays
# on the buzzer (GP15) and the strip (GP0) flashes a color for it.
from machine import Pin, PWM, SPI
from mfrc522 import MFRC522
import neopixel
import random
import time
NOTES = {"C": 262, "D": 294, "E": 330, "F": 349, "G": 392, "A": 440, "B": 494}
spi = SPI(0, baudrate=1000000, sck=Pin(18), mosi=Pin(19), miso=Pin(16))
reader = MFRC522(spi, Pin(17), Pin(9))
strip = neopixel.NeoPixel(Pin(0), 8)
buzzer = PWM(Pin(15))
buzzer.duty_u16(0)
def play(tune):
for letter in tune:
hz = NOTES.get(letter.upper())
if not hz:
continue
color = (random.randint(0, 64), random.randint(0, 64), random.randint(0, 64))
strip.fill(color)
strip.write()
buzzer.freq(hz)
buzzer.duty_u16(30000)
print("note", letter, hz)
time.sleep_ms(250)
buzzer.duty_u16(0)
time.sleep_ms(50)
strip.fill((0, 0, 0))
strip.write()
print("Lay a card with a tune on it")
while True:
uid = reader.uid()
if uid:
data = reader.read(8, uid)
reader.done()
tune = data.rstrip(b"\x00").decode() if data else ""
print("tune:", tune or "(nothing stored)")
play(tune or "CDEC")
while reader.uid():
reader.done()
time.sleep_ms(100)
time.sleep_ms(100)
# mfrc522.py: the MFRC522 reader on SPI. Enough to find a MIFARE Classic
# card, read its UID, and read or write a 16-byte block behind the
# factory key, which is what a lesson that stores text on a card needs.
from machine import Pin, SPI
import time
OK, NOTAG, ERR = 0, 1, 2
class MFRC522:
def __init__(self, spi, cs, rst):
self.spi = spi
self.cs = cs
self.rst = rst
cs.init(Pin.OUT, value=1)
rst.init(Pin.OUT, value=0)
time.sleep_ms(10)
rst.value(1)
time.sleep_ms(10)
self._w(0x01, 0x0F) # soft reset
time.sleep_ms(10)
self._w(0x2A, 0x8D) # timer: about 25 ms before a card that
self._w(0x2B, 0x3E) # does not answer is given up on
self._w(0x2D, 30)
self._w(0x2C, 0)
self._w(0x15, 0x40) # 100 % ASK
self._w(0x11, 0x3D) # CRC preset 0x6363
self._w(0x14, self._r(0x14) | 0x03) # antenna on
def _w(self, reg, value):
self.cs.value(0)
self.spi.write(bytes([(reg << 1) & 0x7E, value]))
self.cs.value(1)
def _r(self, reg):
self.cs.value(0)
self.spi.write(bytes([((reg << 1) & 0x7E) | 0x80]))
value = self.spi.read(1)[0]
self.cs.value(1)
return value
def _card(self, command, send):
irq_wait = 0x12 if command == 0x0E else 0x30
self._w(0x02, 0x80 | (0x12 if command == 0x0E else 0x77))
self._w(0x04, 0x7F) # clear the interrupt bits
self._w(0x0A, 0x80) # flush the FIFO
self._w(0x01, 0x00) # idle
for b in send:
self._w(0x09, b)
self._w(0x01, command)
if command == 0x0C:
self._w(0x0D, self._r(0x0D) | 0x80) # start sending
for _ in range(2000):
n = self._r(0x04)
if n & irq_wait or n & 0x01:
break
self._w(0x0D, self._r(0x0D) & 0x7F)
if not (n & irq_wait):
return NOTAG, [], 0
if self._r(0x06) & 0x1B:
return ERR, [], 0
back, bits = [], 0
if command == 0x0C:
count = self._r(0x0A)
last = self._r(0x0C) & 0x07
bits = (count - 1) * 8 + last if last else count * 8
back = [self._r(0x09) for _ in range(count)]
return OK, back, bits
def _crc(self, data):
self._w(0x05, 0x04)
self._w(0x0A, 0x80)
for b in data:
self._w(0x09, b)
self._w(0x01, 0x03)
for _ in range(255):
if self._r(0x05) & 0x04:
break
return [self._r(0x22), self._r(0x21)]
def uid(self):
"""The UID of the card on the reader, selected, or None."""
self._w(0x0D, 0x07)
stat, back, bits = self._card(0x0C, [0x26]) # REQA
if stat != OK or bits != 0x10:
return None
self._w(0x0D, 0x00)
stat, back, _ = self._card(0x0C, [0x93, 0x20]) # anticollision
if stat != OK or len(back) != 5:
return None
frame = [0x93, 0x70] + back
stat, _, _ = self._card(0x0C, frame + self._crc(frame)) # select
return back[:4] if stat == OK else None
def _auth(self, block, uid):
key = [0xFF] * 6
stat, _, _ = self._card(0x0E, [0x60, block] + key + uid)
return stat == OK and self._r(0x08) & 0x08
def read(self, block, uid):
if not self._auth(block, uid):
return None
frame = [0x30, block]
stat, back, _ = self._card(0x0C, frame + self._crc(frame))
return bytes(back[:16]) if stat == OK else None
def write(self, block, uid, data):
if not self._auth(block, uid):
return False
frame = [0xA0, block]
stat, back, _ = self._card(0x0C, frame + self._crc(frame))
if stat != OK:
return False
data = list(data[:16]) + [0] * (16 - len(data[:16]))
stat, back, _ = self._card(0x0C, data + self._crc(data))
return stat == OK
def done(self):
self._w(0x08, self._r(0x08) & 0xF7) # stop the encryption
Code walkthrough
The key functions of the sketch, and what each one does.
play main.py
This plays a tune, one letter at a time. It looks each letter up in NOTES to get a frequency and skips any letter it does not know. For a known note it fills the strip with a random color, sets the buzzer to that frequency, and sounds it for 250 ms. Then it pauses 50 ms and moves on. When the tune ends, the strip is turned off.
def play(tune):
for letter in tune:
hz = NOTES.get(letter.upper())
if not hz:
continue
color = (random.randint(0, 64), random.randint(0, 64), random.randint(0, 64))
strip.fill(color)
strip.write()
buzzer.freq(hz)
buzzer.duty_u16(30000)
print("note", letter, hz)
time.sleep_ms(250)
buzzer.duty_u16(0)
time.sleep_ms(50)
strip.fill((0, 0, 0))
strip.write()Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- No sound, but the strip flashes This project
Check the buzzer circuit. GP15 goes through the 1k resistor to the transistor base, the buzzer sits between 3.3 V and the collector, and the emitter goes to ground. - The card is never read This project
Check the four SPI wires and the RST pin. SDA goes to GP17 and RST to GP9. Also make sure the reader is powered from 3.3 V, not 5 V. - The strip stays dark This project
Its data wire must go to GP0, and it needs 5 V on VDD and ground on VSS. - The reader is damaged or behaves oddly on 5 V power. RFID reader
The MFRC522 runs on 3.3 V only. Power it from the 3.3 V pin, never 5 V. Ideally divide 5 V signals from an Uno down for SCK, MOSI and SS. - No card is detected. RFID reader
Check SDA (SS), RST, SCK, MOSI and MISO against your board's SPI pins and the constructor in your code. It reads 13.56 MHz cards only, not 125 kHz key fobs. - The first LED flickers, shows wrong colors, or the whole strip glitches. Addressable RGB LEDs
Join the grounds of the strip and the controller. Add a 300 to 500 ohm resistor in series with the data line. Put a 1000 uF capacitor across the strip's 5 V and GND. - The strip works at low brightness but dims, turns yellow, or resets when many LEDs go white. Addressable RGB LEDs
Each LED can draw about 60 mA at full white. Use a 5 V supply sized for the count, feed power in at several points, and lower the brightness in code. - A passive piezo stays silent when you just set the pin HIGH. Piezo speaker
A passive piezo needs a changing signal. Use tone(pin, frequency) or a PWM wave. Around 2 to 4 kHz is loudest. A buzzer with a built in oscillator makes sound from plain DC instead.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.