🌐 日本語
Display Spotify's currently playing album art on a circular display with ESP32.
📝 Write-up: 円形ディスプレイに Spotify カバーアートを表示するデバイスを作った話 (Japanese)
JC3636W518 - ESP32-S3 module with 1.8" 360x360 circular display (ST77916, QSPI) and touch (CST816S)
- Rotating vinyl record animation with album art
- Smooth start/stop rotation (accelerates when playing, decelerates when paused)
- Anti-aliased circular mask edges
- Touch controls:
- Center tap: Play/pause toggle
- Left side tap: Previous track
- Right side tap: Next track
- Swipe left: Next track
- Swipe right: Previous track
- Cover art transitions (circular reveal animation)
- Dynamic Spotify polling (faster detection at track end)
- Rate limit handling with automatic backoff
- SD card album art cache (~60x faster loading)
- Device filtering: Only display playback from specific devices (see docs/DEVICE_FILTERING.md)
SPNFY/
├── SPNFY/ # Main ESP32 Arduino sketch
│ ├── SPNFY.ino # Main entry point
│ ├── config.example.h # Copy to config.h with your credentials
│ ├── display.h # Display types and declarations
│ ├── display.cpp # Rotation rendering, compositing
│ ├── display_init.cpp # Display & backlight initialization
│ ├── display_send.cpp # Double buffering, DMA transfer
│ ├── image_processing.h/cpp # Album art buffers, mask bounds, AA
│ ├── spotify.cpp/h # Spotify API (auth, now playing, playback)
│ ├── touch.cpp/h # Touch input handling (tap, swipe)
│ ├── wifi_manager.cpp/h # WiFi connection
│ ├── sd_cache.cpp/h # SD card album art cache
│ ├── font.cpp/h # 5x7 bitmap font for status text
│ ├── log.h # Debug logging macros
│ ├── pins.h # Hardware pin definitions
│ ├── circle_aa_lut.h # Auto-generated AA lookup table
│ └── vinyl_texture.h # Vinyl record texture
├── token-retriever/ # Local server for Spotify token retrieval
├── esp32_display_test/ # Display test sketch
├── tools/ # Development tools
│ ├── arduwrap # ESP32 serial monitor/compile wrapper
│ ├── generate_circle_aa_lut.py # Generate AA boundary LUT
│ └── convert_image.py # Image conversion utility
├── docs/ # Documentation
└── README.md
- Go to Spotify Developer Dashboard
- Click "Create app" → Enter app name and description
- Set Redirect URI:
http://127.0.0.1:8888/callback - Copy the Client ID
cd token-retriever
mise trust && mise install
mise exec -- bun run devOpen http://127.0.0.1:8888 in your browser and authenticate.
Required scopes: user-read-currently-playing user-read-playback-state user-modify-playback-state
Copy SPNFY/config.example.h to SPNFY/config.h and fill in:
- WiFi SSID and password
- Spotify Client ID
- Spotify Refresh Token (from step 2)
Required Arduino Libraries:
- ESP32_Display_Panel 0.1.4 + ESP32_IO_Expander 0.0.2 - Get from manufacturer's download page in
Demo_Arduino_V1.0/library/ - JPEGDEC - From Arduino Library Manager
- ArduinoJson - From Arduino Library Manager
⚠️ Important: You MUST use exact versions above. Latest versions from Arduino Library Manager DO NOT WORK.
Arduino ESP32 Core: 3.0.1
arduwrap monitors the serial port and automatically releases/reopens during compile/upload. Port is auto-detected from the serve process.
# Setup (first time)
cd tools && uv sync
# Start monitor server (separate terminal)
./tools/arduwrap serve --port /dev/cu.usbmodem1101 --baud 115200
# Compile and upload (port auto-detected from serve)
./tools/arduwrap compile \
--fqbn esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,FlashMode=qio,FlashSize=16M,PartitionScheme=huge_app,PSRAM=opi \
SPNFY/SPNFY.ino
# Get logs
./tools/arduwrap log # Full log
./tools/arduwrap log -f "error" -i # Filter (case-insensitive)
./tools/arduwrap log -n 50 # Last 50 linesarduino-cli compile \
--fqbn esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,FlashMode=qio,FlashSize=16M,PartitionScheme=huge_app,PSRAM=opi \
SPNFY/SPNFY.ino
arduino-cli upload -p /dev/cu.usbmodem1101 \
--fqbn esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,FlashMode=qio,FlashSize=16M,PartitionScheme=huge_app,PSRAM=opi \
SPNFY/SPNFY.ino- Core 0: Spotify API polling, HTTP requests, image download
- Core 1: Rendering loop, touch handling, display updates
This separation ensures smooth 30 FPS animation even during network operations.
- Vinyl texture (360×360): Nearest neighbor sampling for outer ring
- Album art (300×300): Bilinear interpolation, scaled to 286px diameter
- Small composite (292×292): Pre-baked cover art + AA boundary
- Double buffering: Render and send in parallel for 30+ FPS
- Display Troubleshooting - Critical setup lessons for JC3636W518
- Dual-Core Architecture - FreeRTOS task separation for smooth animation
- Rotation Rendering - Optimized rendering pipeline and AA algorithm
- Spotify Polling Strategy - Dynamic polling for fast track detection
- SD Card Cache - Album art caching with stale-while-revalidate
- ESP32 Logging - Debug logging on ESP32-S3 USB CDC
MIT
