A physical chess board with Hall effect sensors, dual displays, LED lighting, and full Lichess integration — built on Raspberry Pi / Orange Pi Zero 2W, written in Go.
Chess Pro is an open-source smart chess board that detects piece positions using Hall effect sensors embedded under each square. It connects to Lichess for online play, runs Stockfish locally for engine analysis, and provides real-time visual feedback via WS2812B LED strips and two displays.

⚠️ Migrated to DietPi! See the DietPi section for details on the optimized system image with power-loss protection.
- 64 Hall effect sensors (A3144) — one per square, detect piece presence via neodymium magnets embedded in piece bases
- Dual display system:
- SSD1681 e-paper display (200×200 px) — shows current board position with pixel-art pieces, fast partial refresh on each move
- SH1106 OLED display (128×64 px) — chess clock, status, engine info, game events
- WS2812B RGB LED strip — highlights valid moves, last move, check, premoves
- Stockfish engine — local analysis, engine-vs-player mode
- Lichess Board API — play online games; opponent moves appear on the physical board
- Premove support — queue a move during the opponent's turn
- Puzzle mode — solve Lichess puzzles on the physical board
- WiFi provisioning via BLE (Android companion app)
- Game history — PGN storage with Stockfish analysis web UI
- Power-loss protection — read-only root overlay + journaled
/datapartition - Graceful shutdown / restart via hardware button
| Component | Model | Qty |
|---|---|---|
| Single-board computer | Raspberry Pi Zero 2W | 1 |
| Hall effect sensor | A3144 (digital, TO-92, 5 V) | 64 |
| Neodymium magnet | 10×3 mm | 32 (one per piece) |
| I2C GPIO expander | PCF8575 16-bit | 4 |
| LED strip | WS2812B 60 LED/m, 5 V | ~1.5 m |
| Pull-up resistor | 10 kΩ, 0.25 W | 64 |
| I2C pull-up resistor | 4.7 kΩ | 2 |
| Bulk decoupling capacitor | 1000 µF 10 V electrolytic | 4 |
| Hall bypass capacitor | 100 nF ceramic | 64 |
| Power MOSFET | AOD4184 (N-channel, 40 V / 50 A) | 1 |
| Current sensor | INA219 breakout | 1 |
| Microcontroller | Arduino Nano | 1 |
| Gate resistor | 430 Ω, 0.25 W | 1 |
| Component | Model | Interface |
|---|---|---|
| E-paper display | SSD1681 1.54″ 200×200 B/W | SPI |
| OLED display | SH1106 1.3″ 128×64 | I2C |
| Component | Notes |
|---|---|
| Tactile button 6×6×5 mm | 3× — menu, confirm, back |
| Passive buzzer 5 V | Move confirmation, alerts |
- 5 V / 2.5 A micro-USB power supply, or 10 000 mAh powerbank
- MikroTik hAP ac2 USB port (5 V / 1 A) is sufficient for light load
WS2812B LEDs draw up to 60 mA each at full white; 64 LEDs fully lit would exceed 3.8 A and trip a powerbank's protection. A hardware safety circuit prevents this:
5 V supply
│
├──► INA219 (current sensor, I²C to Arduino Nano)
│ │
│ Arduino Nano
│ │ PWM soft-start + overcurrent cutoff
│ ▼
│ AOD4184 MOSFET gate (via 430 Ω)
│ │
└── MOSFET drain/source ──► LED 5 V rail
Arduino Nano behaviour:
- On power-up: ramps the MOSFET gate PWM from 0 % to 100 % over ~200 ms (soft-start), avoiding the inrush current spike that triggers powerbank protection
- Continuous monitoring: if INA219 reads > 2 A, MOSFET is switched off immediately
- Pi and sensors are powered directly from the 5 V rail, not through the MOSFET — they remain on even if LEDs are cut
Why not software-only? A firmware bug or misconfiguration could light all 64 LEDs simultaneously. The Arduino + INA219 cutoff is independent of the Pi and cannot be bypassed by bad application code.
- Birch plywood 8 mm — base, sides
- Birch plywood 4 mm — top panel, cell dividers
- Chess pieces: king height 85–95 mm; neodymium magnet pressed into a 10 mm × 5 mm hole drilled in each piece base
- WS2812B strip mounted in a routed channel around the board perimeter
Pin 1 (Vcc) → 5 V
Pin 2 (GND) → GND
Pin 3 (OUT) → 10 kΩ pull-up to 3.3 V → PCF8575 input
Copper shielding tape is not needed — Hall sensors respond to magnetic fields, not conductivity.
Place a 100 nF ceramic capacitor between Vcc and GND as close to each sensor as possible. Without it, switching transients from the WS2812B data line can cause false trigger readings on sensors sharing the same 5 V rail.
SDA → RPi GPIO 2 (I2C1 SDA)
SCL → RPi GPIO 3 (I2C1 SCL)
VCC → 3.3 V
GND → GND
Address pins A0/A1/A2 set per board via solder pads.
Place a 1000 µF / 10 V electrolytic capacitor on the 5 V rail near each PCF8575 pair. Four capacitors total stabilise the supply against voltage dips when multiple LED channels switch simultaneously.
BUSY → GPIO 24
RST → GPIO 25
DC → GPIO 8
CS → GPIO 7 (SPI CE1)
CLK → GPIO 11 (SPI CLK)
DIN → GPIO 10 (SPI MOSI)
VCC → 3.3 V
GND → GND
SDA → GPIO 2 (I2C1, shared bus with PCF8575)
SCL → GPIO 3
VCC → 3.3 V
GND → GND
DIN → GPIO 18 (PWM0)
5 V → 5 V rail (separate supply; ~3 A for 60 LEDs at full brightness)
GND → Common GND
chess-pro/
├── cmd/
│ └── chess-pro/ # main entry point
├── internal/
│ ├── clock/ # chess clock
│ ├── config/ # config loader
│ ├── display/ # OLED (SH1106) controller
│ ├── engines/ # Stockfish, Lichess API, puzzle engine
│ ├── epaper/ # E-paper (SSD1681) + pixel-art piece renderer
│ ├── game/ # main game loop, premove logic, capture detection
│ ├── hardware/ # WS2812B LED controller
│ ├── logger/ # structured logger
│ ├── sensors/ # Hall sensor matrix (PCF8575 × 4)
│ ├── state/ # game state machine
│ └── storage/ # PGN save, game history, safe writes
├── ble-server/ # WiFi provisioning BLE GATT server (Go)
├── android-app/ # Companion Android app (Kotlin)
├── web/ # Game analysis web UI (Stockfish + PGN viewer)
├── image/
│ ├── chess-pro-image.mk # Makefile — builds protected DietPi image
│ └── configure.sh # Interactive image configurator
├── config.yaml # Board runtime configuration
├── Makefile # make build / make deploy
└── README.md
- Go 1.22+
- Raspberry Pi Zero 2W or Orange Pi Zero 2W running DietPi (recommended) or Raspberry Pi OS Lite
- Stockfish:
sudo apt install stockfish - I2C enabled in
/boot/config.txtor viasudo raspi-config
# Cross-compile for ARM
make build
# Deploy to board over SSH
make deploy
# Or build directly on the board
go build -o chess-pro ./cmd/chess-pro/lichess:
token: "your_lichess_api_token"
stockfish:
path: "/usr/games/stockfish"
depth: 15
display:
epaper: true # SSD1681
oled: true # SH1106
sensors:
i2c_bus: 1
addresses: [0x20, 0x21, 0x22, 0x23]
leds:
gpio_pin: 18
count: 64
brightness: 80┌──────────────────────────────────────────────────────────────┐
│ Game Loop │
│ │
│ Hall Sensors (64×) ──► Piece State Matrix │
│ │ │ │
│ ▼ ▼ │
│ Diff Detection ──────► Move Validation (notnil/chess) │
│ │ │ │
│ ▼ ▼ │
│ LED Feedback Premove Queue │
│ (valid / invalid) │ │
│ ▼ │
│ Lichess API / Stockfish │
│ │ │
│ ┌────────────┴────────────┐ │
│ ▼ ▼ │
│ E-paper update OLED update │
│ (SSD1681 partial) (SH1106 status) │
└──────────────────────────────────────────────────────────────┘
Piece detection: Each square has one Hall sensor beneath it. Pieces have a neodymium magnet (10×3 mm) in their base. The sensor output goes low when a piece is present, high when lifted.
Move detection: Each scan computes a diff between the current and previous board state. A legal move manifests as one square going low (destination) and one going high (source). Captures show two going high (captured piece + source) and one going low.
Premove: If a player lifts and places a piece during the opponent's turn, the move is queued. When the opponent's move arrives from Lichess or Stockfish, the premove is auto-confirmed if legal.
- Full board render on game start and position reset
- Partial refresh on each move (~0.6 s) — only the two changed squares are redrawn to minimise refresh wear
- Pixel-art chess pieces rendered entirely in software — generated mathematically in Go, no bitmap files required
- Last-move highlight
- Chess clock (white / black time)
- Current turn indicator
- Engine name and search depth
- Game result, check / checkmate alerts
- WiFi and Lichess connection status
The project ships a ready-to-flash DietPi image builder that provides power-loss protection — safe to unplug at any moment without corrupting the SD card.
| Metric | DietPi | Raspberry Pi OS Lite |
|---|---|---|
| Image size | ~400 MB | ~2 GB |
| RAM after boot | ~60 MB | ~150 MB |
| Extra RAM for Stockfish | +100 MB | — |
| I2C / SPI / GPIO | configured out of the box | manual setup |
| Logs written to RAM | ✅ | ❌ |
┌─────────────────────────────────────────┐
│ SD Card (6 GB) │
├─────────────────────────────────────────┤
│ /boot 128 MB FAT32 │
│ DietPi configs, config.txt │
│ I2C / SPI / GPIO enabled │
├─────────────────────────────────────────┤
│ / (root) 2 GB ext4 + overlay │
│ System READ-ONLY (~400 MB) │
│ Changes stored in RAM (tmpfs) │
│ Clean state restored on reboot │
│ RAM after boot: ~60 MB │
├─────────────────────────────────────────┤
│ /data 3.5 GB ext4 + journaling │
│ data=journal — writes go to log first │
│ sync — no write cache │
│ Atomic operations via SafeWrite() │
│ │
│ /data/games/ PGN game files │
│ /data/stats/ Player statistics │
│ /data/config/ Board settings │
│ /data/logs/ Application logs │
└─────────────────────────────────────────┘
Peripherals configured out of the box:
✅ I2C 400 kHz — SH1106 OLED, PCF8575
✅ SPI — SSD1681 e-paper
✅ GPIO — buzzer (GPIO 27), Hall sensors
✅ WS2812B LED strip support
# Fedora
sudo dnf install qemu-img parted e2fsprogs dosfstools wget p7zip p7zip-plugins
# Ubuntu / Debian
sudo apt install qemu-utils parted e2fsprogs dosfstools wget p7zip-full
# Arch
sudo pacman -S qemu-img parted e2fsprogs dosfstools wget p7zip# Interactive wizard (recommended)
./image/configure.sh
# Or edit manually
cp image/chess-pro.config.example image/chess-pro.configMinimum required parameters:
WIFI_SSID="YourNetworkName"
WIFI_PASSWORD="YourPassword"
WIFI_COUNTRY="UA"
SSH_PASSWORD="chess2026"Full parameter reference:
# WiFi
WIFI_SSID="MyNetwork"
WIFI_PASSWORD="password123"
WIFI_COUNTRY="UA" # UA / US / GB / DE ...
WIFI_SSID_2="" # Optional backup network
WIFI_PASSWORD_2=""
DISABLE_WIFI_POWERSAVE="yes"
# Network
HOSTNAME="chess-pro"
STATIC_IP="" # Empty = DHCP, or "192.168.1.100/24"
GATEWAY="" # Required only for static IP
DNS_SERVERS="8.8.8.8 8.8.4.4"
# SSH
ENABLE_SSH="yes"
SSH_PASSWORD="chess2026"
SSH_PUBLIC_KEY="" # Optional — paste public key for passwordless login
# Locale
TIMEZONE="Europe/Kiev"
LOCALE="en_US.UTF-8"
KEYBOARD_LAYOUT="us"
# Chess Pro
CHESS_BOARD_NAME="Chess Pro Board #1"
CHESS_LED_BRIGHTNESS="80"
CHESS_SOUND_ENABLED="true"
CHESS_LICHESS_TOKEN=""
# System
DISABLE_BLUETOOTH="no"
ENABLE_WATCHDOG="yes"
NTP_SERVERS="0.ua.pool.ntp.org"# Full build in one step
make -f image/chess-pro-image.mk all
# Step by step
make -f image/chess-pro-image.mk download # Download base DietPi image
make -f image/chess-pro-image.mk prepare-image # Partition the image
make -f image/chess-pro-image.mk setup-overlay # Configure root overlay
make -f image/chess-pro-image.mk configure-system # WiFi, SSH, locale
make -f image/chess-pro-image.mk create-data # Prepare /data partition
make -f image/chess-pro-image.mk install-app # Install firmware binary
# Preview configuration before building
make -f image/chess-pro-image.mk info
# Utility targets
make -f image/chess-pro-image.mk test-mount # Mount image for inspection
make -f image/chess-pro-image.mk test-umount # Unmount
make -f image/chess-pro-image.mk clean # Remove temp files
make -f image/chess-pro-image.mk deep-clean # Remove output imagelsblk # Identify SD card device
make -f image/chess-pro-image.mk flash DEVICE=/dev/sdX
# Or manually
sudo dd if=chess-pro-protected.img of=/dev/sdX bs=4M status=progress conv=fsync && sync- Insert the SD card and power on the board
- First boot takes 3–4 minutes — DietPi auto-installs packages
- Green LED blinking fast → installation in progress
- Green LED steady → system ready
- The board connects to WiFi automatically
- SSH in:
ssh dietpi@chess-pro.local(default password:chess2026)
Finding the IP address:
ping chess-pro.local
nmap -sn 192.168.88.0/24 | grep chess-pro
# Or check the DHCP lease list in your router's web interfaceVerify peripherals after boot:
ssh dietpi@chess-pro.local
i2cdetect -y 1 # Expect: 0x3c (SH1106), 0x20–0x23 (PCF8575)
ls /dev/spidev* # Expect: /dev/spidev0.0 /dev/spidev0.1
gpiodetect
lsmod | grep -E "i2c|spi"sudo dietpi-launcher # Main menu
sudo dietpi-config # Network, display, peripherals
sudo dietpi-software # Install / remove packages
sudo dietpi-backup # System backup
# Temporarily disable overlay to make persistent changes
sudo dietpi-ramdisk 0
# ... make changes ...
sudo dietpi-ramdisk 1
sudo reboot # Returns to read-only mode# On dev machine — cross-compile
make build-arm64
# Copy binary to board
scp bin/chess-pro dietpi@chess-pro.local:/tmp/
# On board — temporarily disable overlay, replace binary, re-enable
ssh dietpi@chess-pro.local
sudo dietpi-ramdisk 0
sudo cp /tmp/chess-pro /opt/chess-pro/
sudo systemctl restart chess-pro
sudo dietpi-ramdisk 1
sudo reboot| Layer | Mechanism | Effect |
|---|---|---|
| Root partition | overlayfs (read-only + RAM tmpfs) | System files survive any power cut; state resets cleanly on reboot |
| Data partition | data=journal + sync mount options |
Every write is journaled before committing; no silent data corruption |
| SQLite | WAL mode + PRAGMA synchronous=FULL |
Database always consistent; incomplete transactions auto-roll back |
| File writes | SafeWrite() — write .tmp → fsync → rename |
Atomic replacement; old file intact until new one is fully flushed |
Testing protection:
# Start a game, then pull the power cord mid-game
# After reboot, verify:
sqlite3 /data/games/index.db "PRAGMA integrity_check;"
sqlite3 /data/games/index.db "SELECT COUNT(*) FROM games;"
ls -la /data/games/The database will always be either intact or at the last complete transaction — never a corrupt partial write.
Sync mode reduces raw write throughput to ~5–10 MB/s vs ~50 MB/s without it. For this project that is irrelevant: a full PGN file is 2–5 KB and a single SQLite game record is ~1 KB — a complete game save takes under 100 ms.
Recommendations: batch move records before writing; run SQLite WAL checkpoint every 10 games; avoid logging on every move (log important events only).
// Open game database
db, err := storage.OpenGameDB()
defer db.Close()
// Save a completed game
pgnPath, _ := storage.SavePGN(pgnString)
db.SaveGame(&storage.GameRecord{
Date: time.Now(),
Result: "1-0",
PGNPath: pgnPath,
MovesCount: 42,
})
// Load recent games
games, _ := db.GetRecentGames(10)
// Atomic config write — safe under power loss
storage.SafeWriteJSON("/data/config/board.json", configMap)
// Update player statistics
stats, _ := storage.LoadStats()
stats.GamesPlayed++
stats.Wins++
storage.SaveStats(stats)On startup the board advertises a BLE GATT service. Use the companion Android app to send WiFi credentials — no keyboard or screen needed.
Service UUID: 12345678-90AB-CDEF-1234-567890ABCD01
Write char: 12345678-90AB-CDEF-1234-567890ABCD02 ← {"ssid":"...","password":"..."}
Status char: 12345678-90AB-CDEF-1234-567890ABCD03 ← "connected" / "failed"
- PCB design (replace perfboard wiring)
- iOS companion app (WiFi provisioning)
- Opening book display on e-paper
- Bluetooth game broadcast (spectate on phone)
- Over-the-air firmware update
- 3D-printed enclosure option
Image does not boot
sudo parted chess-pro-protected.img print
sudo losetup -f --show -P chess-pro-protected.img
sudo e2fsck -f /dev/loop0p2
sudo e2fsck -f /dev/loop0p3
sudo losetup -d /dev/loop0Overlay not working
cat /etc/overlayroot.conf
mount | grep overlay
sudo overlayroot-chroot # Enter writable chroot for debuggingSQLite errors on /data
sqlite3 /data/games/index.db "PRAGMA integrity_check;"
sqlite3 /data/games/index.db "REINDEX;"
sqlite3 /data/games/index.db "PRAGMA wal_checkpoint(TRUNCATE);"I2C devices not found
cat /boot/config.txt | grep i2c # Should show: dtparam=i2c_arm=on
sudo modprobe i2c-dev
i2cdetect -y 1Game data backup
# Local backup
rsync -av /data/ /mnt/backup/
# Over the network
rsync -av /data/ user@backup-server:/backups/chess-pro/
# Remove games older than one year
find /data/games -name "*.pgn" -mtime +365 -delete
sqlite3 /data/games/index.db \
"DELETE FROM games WHERE date < strftime('%s','now','-1 year'); VACUUM;"PRs are welcome. Please open an issue first for major changes.
| Part | License |
|---|---|
| Firmware (Go) | GPL-3.0 |
| PCB schematics | CERN OHL v2 |
| Documentation | CC BY 4.0 |
- notnil/chess — Go chess library
- Stockfish — chess engine
- Lichess Board API — online play integration
- DietPi — lightweight OS for single-board computers
- periph.io — Go hardware I/O
- tinygo-org/bluetooth — BLE GATT server