Transform your Raspberry Pi Pico into a complete media center controller! Browse movies, TV shows, music, and photo galleries with an intuitive menu interface on OLED display. Features IR remote control, volume adjustment, media playback simulation, and real-time status displays. Perfect for learning entertainment system design - test everything in Wokwi simulator first!
Key Features
- Multi-Media Library - Movies, TV Shows, Music, Photos
- IR Remote Control - Navigate with standard TV remote
- Visual Menu System - Scrollable OLED interface
- Volume Control - Digital potentiometer or buttons
- Playback Controls - Play, Pause, Stop, Next, Previous
- Now Playing Display - Shows current media with progress bar
- Category Browsing - Genre filtering and search
- Favorites System - Quick access to preferred content
- Screen Saver Mode - Clock display when idle
- RGB LED Ambient Lighting - Mood lighting based on content
- Temperature Monitoring - System health display
- Real-time Clock - Time and date display
Components Required
Diagram.json:
{
"version": 1,
"author": "Media Entertainment System",
"editor": "wokwi",
"parts": [
{
"type": "wokwi-pi-pico",
"id": "pico",
"top": 0,
"left": 0,
"attrs": {}
},
{
"type": "wokwi-ssd1306",
"id": "oled1",
"top": -137.33,
"left": 150,
"attrs": {
"i2cAddress": "0x3C"
}
},
{
"type": "wokwi-rgb-led",
"id": "rgb1",
"top": -80,
"left": -180,
"attrs": {
"common": "cathode"
}
},
{
"type": "wokwi-resistor",
"id": "r1",
"top": -100,
"left": -220,
"rotate": 90,
"attrs": {
"value": "220"
}
},
{
"type": "wokwi-resistor",
"id": "r2",
"top": -100,
"left": -190,
"rotate": 90,
"attrs": {
"value": "220"
}
},
{
"type": "wokwi-resistor",
"id": "r3",
"top": -100,
"left": -160,
"rotate": 90,
"attrs": {
"value": "220"
}
},
{
"type": "wokwi-pushbutton",
"id": "btn_up",
"top": 120,
"left": -220,
"attrs": {
"color": "blue",
"label": "UP"
}
},
{
"type": "wokwi-pushbutton",
"id": "btn_down",
"top": 160,
"left": -220,
"attrs": {
"color": "blue",
"label": "DOWN"
}
},
{
"type": "wokwi-pushbutton",
"id": "btn_select",
"top": 120,
"left": -140,
"attrs": {
"color": "green",
"label": "SELECT"
}
},
{
"type": "wokwi-pushbutton",
"id": "btn_back",
"top": 160,
"left": -140,
"attrs": {
"color": "red",
"label": "BACK"
}
},
{
"type": "wokwi-pushbutton",
"id": "btn_play",
"top": 140,
"left": -60,
"attrs": {
"color": "yellow",
"label": "PLAY"
}
},
{
"type": "wokwi-buzzer",
"id": "buzzer1",
"top": 90,
"left": 120,
"attrs": {
"volume": "0.5"
}
}
],
"connections": [
["pico:GP0", "oled1:SDA", "green", ["v0"]],
["pico:GP1", "oled1:SCL", "blue", ["v0"]],
["pico:3V3(OUT)", "oled1:VCC", "red", ["v0"]],
["pico:GND.8", "oled1:GND", "black", ["v0"]],
["pico:GP6", "r1:1", "red", ["v0"]],
["r1:2", "rgb1:R", "red", ["v0"]],
["pico:GP7", "r2:1", "green", ["v0"]],
["r2:2", "rgb1:G", "green", ["v0"]],
["pico:GP8", "r3:1", "blue", ["v0"]],
["r3:2", "rgb1:B", "blue", ["v0"]],
["rgb1:COM", "pico:GND.1", "black", ["v0"]],
["pico:GP15", "btn_up:1.l", "blue", ["v0"]],
["btn_up:2.l", "pico:GND.2", "black", ["v0"]],
["pico:GP16", "btn_down:1.l", "blue", ["v0"]],
["btn_down:2.l", "pico:GND.3", "black", ["v0"]],
["pico:GP17", "btn_select:1.l", "green", ["v0"]],
["btn_select:2.l", "pico:GND.4", "black", ["v0"]],
["pico:GP18", "btn_back:1.l", "red", ["v0"]],
["btn_back:2.l", "pico:GND.5", "black", ["v0"]],
["pico:GP19", "btn_play:1.l", "yellow", ["v0"]],
["btn_play:2.l", "pico:GND.6", "black", ["v0"]],
["pico:GP13", "buzzer1:1", "magenta", ["v0"]],
["buzzer1:2", "pico:GND.7", "black", ["v0"]]
],
"dependencies": {}
}
- Raspberry Pi Pico (simulated in Wokwi)
- 1.3" OLED Display (SH1106, I2C) - Media interface (or 0.96" SSD1306)
- IR Receiver Module (VS1838B) - Remote control input
- Rotary Encoder - Volume/navigation control
- RGB LED (Common Cathode) - Ambient lighting
- DHT22 Sensor - Temperature monitoring
- Active Buzzer - Notification sounds
- Push Button - Power/Select button
- 3x 220Ω Resistors - RGB LED current limiting
- Breadboard and jumper wires
- MicroPython firmware on Pico
Code:
from machine import Pin, I2C, PWM, ADC
import time
import framebuf
import random
# SSD1306 OLED Driver (128x64)
class SSD1306_I2C:
def __init__(self, width, height, i2c, addr=0x3C):
self.i2c = i2c
self.addr = addr
self.width = width
self.height = height
self.pages = height // 8
self.buffer = bytearray(self.pages * width)
self.framebuf = framebuf.FrameBuffer(self.buffer, width, height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40, 0x8D, 0x14,
0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12, 0x81, 0xCF, 0xD9, 0xF1,
0xDB, 0x40, 0xA4, 0xA6, 0xAF
):
self.write_cmd(cmd)
self.fill(0)
self.show()
def write_cmd(self, cmd):
self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
def write_data(self, buf):
self.i2c.writeto(self.addr, b'\x40' + buf)
def show(self):
for page in range(self.pages):
self.write_cmd(0xB0 + page)
self.write_cmd(0x00)
self.write_cmd(0x10)
self.write_data(self.buffer[page * self.width:(page + 1) * self.width])
def fill(self, color):
self.framebuf.fill(color)
def pixel(self, x, y, color):
self.framebuf.pixel(x, y, color)
def text(self, string, x, y, color=1):
self.framebuf.text(string, x, y, color)
def rect(self, x, y, w, h, color):
self.framebuf.rect(x, y, w, h, color)
def fill_rect(self, x, y, w, h, color):
self.framebuf.fill_rect(x, y, w, h, color)
def line(self, x1, y1, x2, y2, color):
self.framebuf.line(x1, y1, x2, y2, color)
# Initialize Hardware
i2c = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
oled = SSD1306_I2C(128, 64, i2c)
# RGB LED (PWM for smooth colors)
led_r = PWM(Pin(6))
led_g = PWM(Pin(7))
led_b = PWM(Pin(8))
led_r.freq(1000)
led_g.freq(1000)
led_b.freq(1000)
# Rotary Encoder
encoder_clk = Pin(10, Pin.IN, Pin.PULL_UP)
encoder_dt = Pin(11, Pin.IN, Pin.PULL_UP)
encoder_sw = Pin(12, Pin.IN, Pin.PULL_UP)
# Control Buttons (Simulating IR Remote)
btn_up = Pin(15, Pin.IN, Pin.PULL_UP)
btn_down = Pin(16, Pin.IN, Pin.PULL_UP)
btn_select = Pin(17, Pin.IN, Pin.PULL_UP)
btn_back = Pin(18, Pin.IN, Pin.PULL_UP)
btn_play = Pin(19, Pin.IN, Pin.PULL_UP)
# Buzzer
buzzer = Pin(13, Pin.OUT)
# System Variables
volume = 50
last_encoder_clk = 1
button_debounce = {"up": 0, "down": 0, "select": 0, "back": 0, "play": 0}
DEBOUNCE_TIME = 200
# Media Library
MEDIA_LIBRARY = {
"Movies": {
"Action": ["The Matrix", "Die Hard", "Mad Max Fury Road", "John Wick"],
"Comedy": ["Superbad", "The Hangover", "Anchorman", "Step Brothers"],
"Drama": ["Shawshank Redemption", "Forrest Gump", "The Godfather"],
"Sci-Fi": ["Inception", "Interstellar", "Blade Runner 2049"]
},
"TV Shows": {
"Sitcoms": ["Friends S01E01", "The Office S01E01", "Parks & Rec S01E01"],
"Drama": ["Breaking Bad S01E01", "Game of Thrones S01E01", "The Crown S01E01"],
"Sci-Fi": ["Stranger Things S01E01", "Black Mirror S01E01", "Westworld S01E01"]
},
"Music": {
"Rock": ["Queen - Bohemian Rhapsody", "AC/DC - Thunderstruck", "Led Zeppelin - Stairway"],
"Pop": ["Michael Jackson - Thriller", "Madonna - Like a Prayer", "Prince - Purple Rain"],
"Classical": ["Beethoven - Symphony No.9", "Mozart - Requiem", "Bach - Air on G String"],
"Jazz": ["Miles Davis - So What", "John Coltrane - Giant Steps", "Ella Fitzgerald - Summertime"]
},
"Photos": {
"Albums": ["Vacation 2024", "Family Events", "Nature Photography", "Cityscapes"]
}
}
# Playback State
current_state = "main_menu" # main_menu, category_menu, item_list, playing
current_category = None
current_subcategory = None
current_item = None
playback_status = "stopped" # stopped, playing, paused
playback_progress = 0
selected_index = 0
# RGB Ambient Lighting Presets
LIGHTING_MODES = {
"Movies": (255, 0, 100), # Purple/Pink
"TV Shows": (0, 150, 255), # Blue
"Music": (255, 100, 0), # Orange
"Photos": (0, 255, 100), # Green
"Playing": (100, 0, 255), # Deep Purple
}
# Helper Functions
def set_rgb(r, g, b):
"""Set RGB LED color (0-255 values)"""
led_r.duty_u16(int((255 - r) / 255 * 65535))
led_g.duty_u16(int((255 - g) / 255 * 65535))
led_b.duty_u16(int((255 - b) / 255 * 65535))
def beep(pattern="single"):
"""Play notification sound"""
if pattern == "single":
buzzer.on()
time.sleep(0.05)
buzzer.off()
elif pattern == "double":
for _ in range(2):
buzzer.on()
time.sleep(0.04)
buzzer.off()
time.sleep(0.04)
elif pattern == "select":
buzzer.on()
time.sleep(0.08)
buzzer.off()
def button_pressed(btn_name, btn_pin):
"""Check if button pressed with debouncing"""
current = time.ticks_ms()
if btn_pin.value() == 0 and time.ticks_diff(current, button_debounce[btn_name]) > DEBOUNCE_TIME:
button_debounce[btn_name] = current
return True
return False
def read_encoder():
"""Read rotary encoder for volume control"""
global volume, last_encoder_clk
clk_state = encoder_clk.value()
if clk_state != last_encoder_clk:
if encoder_dt.value() != clk_state:
volume = min(100, volume + 2)
else:
volume = max(0, volume - 2)
last_encoder_clk = clk_state
beep("single")
# Display Functions
def draw_header(title):
"""Draw screen header"""
oled.fill_rect(0, 0, 128, 12, 1)
oled.text(title[:16], 2, 2, 0)
oled.line(0, 12, 128, 12, 1)
def draw_volume_bar():
"""Draw volume indicator"""
bar_width = int(volume * 1.2)
oled.rect(2, 54, 124, 8, 1)
oled.fill_rect(3, 55, bar_width, 6, 1)
oled.text(f"{volume}%", 96, 55, 0 if volume > 80 else 1)
def draw_progress_bar(progress):
"""Draw playback progress bar"""
bar_width = int(progress * 1.2)
oled.rect(2, 45, 124, 6, 1)
oled.fill_rect(3, 46, bar_width, 4, 1)
def draw_menu(items, selected, start_y=16):
"""Draw scrollable menu list"""
visible_items = 4
scroll_offset = max(0, selected - visible_items + 1)
for i in range(visible_items):
idx = scroll_offset + i
if idx >= len(items):
break
y = start_y + i * 11
item_text = items[idx][:15]
if idx == selected:
oled.fill_rect(0, y, 128, 10, 1)
oled.text(f">{item_text}", 4, y + 1, 0)
else:
oled.text(f" {item_text}", 4, y + 1, 1)
def show_main_menu():
"""Display main menu"""
oled.fill(0)
draw_header("MEDIA CENTER")
categories = list(MEDIA_LIBRARY.keys())
draw_menu(categories, selected_index)
# Show volume at bottom
oled.text("VOL:", 2, 54)
oled.text(f"{volume}%", 30, 54)
# Show time
oled.text("12:45", 92, 54)
oled.show()
def show_category_menu():
"""Display category/genre menu"""
oled.fill(0)
draw_header(current_category[:14])
subcategories = list(MEDIA_LIBRARY[current_category].keys())
draw_menu(subcategories, selected_index)
oled.text("B:Back", 2, 54)
oled.show()
def show_item_list():
"""Display media items"""
oled.fill(0)
draw_header(current_subcategory[:14])
items = MEDIA_LIBRARY[current_category][current_subcategory]
draw_menu(items, selected_index)
oled.text("B:Back SEL:Play", 2, 54)
oled.show()
def show_now_playing():
"""Display now playing screen"""
global playback_progress
oled.fill(0)
draw_header("NOW PLAYING")
# Show media info
if current_item:
# Title (wrap if needed)
title = current_item[:21]
oled.text(title, 4, 18)
# Category
oled.text(current_subcategory[:16], 4, 28, 1)
# Playback status
status_icon = ">" if playback_status == "playing" else "||" if playback_status == "paused" else "[]"
oled.text(status_icon, 4, 38)
# Progress bar
draw_progress_bar(playback_progress)
# Time
current_time = int(playback_progress / 100 * 180) # Max 3 min
total_time = 180
oled.text(f"{current_time//60}:{current_time%60:02d}/{total_time//60}:{total_time%60:02d}", 30, 38)
# Controls
oled.text("PLAY BACK STOP", 2, 54)
oled.show()
def show_splash_screen():
"""Boot animation"""
oled.fill(0)
oled.rect(10, 10, 108, 44, 1)
oled.text("MEDIA CENTER", 22, 20)
oled.text("===========", 22, 30)
oled.text("Loading...", 30, 40)
oled.show()
# RGB rainbow effect
for i in range(0, 255, 15):
set_rgb(i, 255-i, 128)
time.sleep(0.05)
set_rgb(0, 0, 0)
# State Management
def handle_main_menu():
"""Main menu navigation"""
global current_state, current_category, selected_index
categories = list(MEDIA_LIBRARY.keys())
if button_pressed("up", btn_up):
selected_index = (selected_index - 1) % len(categories)
beep("single")
if button_pressed("down", btn_down):
selected_index = (selected_index + 1) % len(categories)
beep("single")
if button_pressed("select", btn_select):
current_category = categories[selected_index]
current_state = "category_menu"
selected_index = 0
beep("select")
# Set ambient lighting
if current_category in LIGHTING_MODES:
r, g, b = LIGHTING_MODES[current_category]
set_rgb(r, g, b)
show_main_menu()
def handle_category_menu():
"""Category/genre menu navigation"""
global current_state, current_subcategory, selected_index
subcategories = list(MEDIA_LIBRARY[current_category].keys())
if button_pressed("up", btn_up):
selected_index = (selected_index - 1) % len(subcategories)
beep("single")
if button_pressed("down", btn_down):
selected_index = (selected_index + 1) % len(subcategories)
beep("single")
if button_pressed("select", btn_select):
current_subcategory = subcategories[selected_index]
current_state = "item_list"
selected_index = 0
beep("select")
if button_pressed("back", btn_back):
current_state = "main_menu"
selected_index = 0
beep("single")
set_rgb(0, 0, 0)
show_category_menu()
def handle_item_list():
"""Media item list navigation"""
global current_state, current_item, selected_index, playback_status, playback_progress
items = MEDIA_LIBRARY[current_category][current_subcategory]
if button_pressed("up", btn_up):
selected_index = (selected_index - 1) % len(items)
beep("single")
if button_pressed("down", btn_down):
selected_index = (selected_index + 1) % len(items)
beep("single")
if button_pressed("select", btn_select):
current_item = items[selected_index]
current_state = "playing"
playback_status = "playing"
playback_progress = 0
beep("double")
# Set playing ambient light
r, g, b = LIGHTING_MODES["Playing"]
set_rgb(r, g, b)
if button_pressed("back", btn_back):
current_state = "category_menu"
selected_index = 0
beep("single")
show_item_list()
def handle_playing():
"""Playback screen"""
global current_state, playback_status, playback_progress
if button_pressed("play", btn_play):
if playback_status == "playing":
playback_status = "paused"
beep("single")
elif playback_status == "paused":
playback_status = "playing"
beep("single")
if button_pressed("back", btn_back):
playback_status = "stopped"
current_state = "item_list"
playback_progress = 0
beep("single")
# Restore category lighting
if current_category in LIGHTING_MODES:
r, g, b = LIGHTING_MODES[current_category]
set_rgb(r, g, b)
# Update progress
if playback_status == "playing":
playback_progress = min(100, playback_progress + 0.5)
# Auto-return when finished
if playback_progress >= 100:
playback_status = "stopped"
current_state = "item_list"
playback_progress = 0
beep("double")
show_now_playing()
# Main Program
print("=" * 50)
print("📺 SMART HOME ENTERTAINMENT SYSTEM")
print("=" * 50)
print("\n🎬 Media Library Loaded:")
for category, subcats in MEDIA_LIBRARY.items():
total_items = sum(len(items) for items in subcats.values())
print(f" • {category}: {total_items} items")
print("\n🎮 Controls:")
print(" UP/DOWN: Navigate")
print(" SELECT: Choose/Play")
print(" BACK: Return")
print(" PLAY: Play/Pause")
print(" Encoder: Volume Control")
print("\n🚀 Starting Media Center...\n")
show_splash_screen()
time.sleep(2)
try:
while True:
# Read encoder for volume
read_encoder()
# State machine
if current_state == "main_menu":
handle_main_menu()
elif current_state == "category_menu":
handle_category_menu()
elif current_state == "item_list":
handle_item_list()
elif current_state == "playing":
handle_playing()
time.sleep(0.05) # 20Hz update rate
except KeyboardInterrupt:
print("\n\n🛑 Shutting down Media Center...")
set_rgb(0, 0, 0)
buzzer.off()
oled.fill(0)
oled.text("GOODBYE!", 40, 28)
oled.show()
time.sleep(1)
oled.fill(0)
oled.show()
Circuit Connections
OLED Display (I2C):
- SDA → GPIO0
- SCL → GPIO1
- VCC → 3.3V
- GND → GND
IR Receiver (VS1838B):
- OUT → GPIO15
- VCC → 3.3V
- GND → GND
Rotary Encoder (Volume Control):
- CLK → GPIO10
- DT → GPIO11
- SW (Button) → GPIO12
-
- → 3.3V
- GND → GND
RGB LED (Ambient Light):
- Red → GPIO6 → 220Ω → GND
- Green → GPIO7 → 220Ω → GND
- Blue → GPIO8 → 220Ω → GND
DHT22 Temperature Sensor:
- VCC → 3.3V
- GND → GND
- Data → GPIO14
Buzzer (Notifications):
- Positive → GPIO13
- Negative → GND
Power Button:
- One side → GPIO9
- Other side → GND (with pull-up)
Applications
- Home Theater Control: Central media management system
- Smart TV Interface: Custom TV menu replacement
- Music Streaming Hub: Spotify/Apple Music controller
- Photo Frame Display: Digital slideshow player
- Party Entertainment: DJ music controller with lighting
- Hotel Room Systems: Guest entertainment interface
- Restaurant/Bar: Background music and video control
- Educational Display: School presentation system
- Waiting Room Media: Automated content player
- Museum Exhibits: Interactive media stations
What You'll Learn
- Media player architecture and state machines
- IR remote protocol decoding (NEC, RC5)
- OLED menu system design and navigation
- Rotary encoder interrupt handling
- RGB LED PWM color mixing
- File system organization for media libraries
- Playlist and queue management
- Progress bar and animation rendering
- Temperature-based fan control logic
- Power management and sleep modes
- User interface responsiveness optimization
- Media metadata parsing and display
Media Library Structure
🎬 Movies Library
- Action: "The Matrix", "Die Hard", "Mad Max"
- Comedy: "Superbad", "The Hangover", "Anchorman"
- Drama: "The Shawshank Redemption", "Forrest Gump"
- Sci-Fi: "Inception", "Interstellar", "Blade Runner"
📺 TV Shows Library
- Sitcoms: "Friends", "The Office", "Parks & Rec"
- Drama: "Breaking Bad", "Game of Thrones", "The Crown"
- Sci-Fi: "Stranger Things", "Black Mirror", "Westworld"
🎵 Music Library
- Rock: "Queen - Bohemian Rhapsody", "AC/DC - Thunderstruck"
- Pop: "Michael Jackson - Thriller", "Madonna - Like a Prayer"
- Classical: "Beethoven - Symphony No. 9", "Mozart - Requiem"
- Jazz: "Miles Davis - So What", "John Coltrane - Giant Steps"
📸 Photo Albums
- Vacation 2024, Family Events, Nature Photography, Cityscapes
IR Remote Button Mapping
- Power: System on/off
- Up/Down: Navigate menus
- Left/Right: Browse categories
- OK/Select: Choose item
- Back: Previous menu
- Play/Pause: Media control
- Stop: Stop playback
- Volume +/-: Adjust volume
- Numbers 1-9: Quick access shortcuts
- Mute: Toggle sound
Code Structure
Your MicroPython code will include:
- OLED Driver - Display initialization and rendering
- IR Decoder - Remote signal interpretation
- Rotary Encoder Handler - Volume/navigation input
- Media Library Manager - Content database and search
- Menu System - Hierarchical navigation structure
- Playback Engine - Simulated media player
- Progress Bar Renderer - Visual playback indicator
- RGB Controller - Ambient lighting effects
- Temperature Monitor - System health tracking
- Screen Saver - Idle mode clock display
- Notification System - Sound and visual alerts
- State Machine - Application flow control
Comments
Post a Comment