Raspberry Pi Zero 2 W + 3.5″ SPI TFT (480×320) → Digital Clock & Date

Complete From-Scratch Guide (Tested & Working – Nov 2025)

Goal: Boot directly to fullscreen digital clock + date on 3.5″ LCD No desktop, no icons, no X, no splash, no cursor For fresh Raspberry Pi OS Lite install Total time: ~20 mins


1. HARDWARE REQUIRED

ItemNotes
Raspberry Pi Zero 2 W
3.5″ SPI TFT LCD (480×320)Generic ILI9486 + XPT2046 (Waveshare, Elecrow, etc.)
MicroSD card (8GB+)
5V/2A Micro-USB power
Internet (WiFi)For initial setup

2. FLASH OS (ON YOUR PC)

  1. Download: Raspberry Pi OS Lite (64-bit)
  2. Use Raspberry Pi Imager → Choose OS → Select Raspberry Pi OS Lite (64-bit)
  3. Click gear icon →
    • Hostname: clockpi
    • Enable SSH
    • Username: admin
    • Password: raspberry
    • WiFi: Enter your SSID/password
    • Locale: Set your timezone
  4. Flash to SD card → Eject

3. BOOT & INITIAL SETUP (HEADLESS VIA SSH)

bash

ssh admin@clockpi.local
# Password: raspberry

bash

# Change password
passwd

# Update system
sudo apt update && sudo apt upgrade -y
sudo reboot

4. INSTALL 3.5″ LCD DRIVER (ILI9486)

bash

cd ~
sudo rm -rf LCD-show
git clone https://github.com/goodtft/LCD-show.git
cd LCD-show
chmod +x LCD35-show
sudo ./LCD35-show

Reboots automatically After reboot: You should see green console text on 3.5″ LCD


5. VERIFY FRAMEBUFFER

bash

ls -l /dev/fb*

Expected output:

text

crw-rw---- 1 root video 29, 0 ... /dev/fb0
crw-rw---- 1 root video 29, 1 ... /dev/fb1

/dev/fb1 = your 3.5″ LCD


6. GIVE USER ACCESS TO /dev/fb1

bash

sudo usermod -a -G video admin

# Permanent udev rule
sudo tee /etc/udev/rules.d/99-fb1.rules > /dev/null << 'EOF'
KERNEL=="fb1", GROUP="video", MODE="0660"
EOF

sudo udevadm control --reload-rules
sudo udevadm trigger

Log out & back in (or reboot later)


7. INSTALL MINIMAL DEPENDENCIES

bash

sudo apt install -y python3-pip
pip3 install --user pygame==2.6.1

8. CREATE FINAL clock.py (PURE FRAMEBUFFER)

bash

cat > ~/clock.py << 'EOF'
#!/usr/bin/env python3
import os
import pygame
import datetime
import struct

os.environ['SDL_VIDEODRIVER'] = 'dummy'
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'

pygame.init()
screen = pygame.Surface((480, 320))
font_time = pygame.font.SysFont('freesansbold', 100)
font_date = pygame.font.SysFont('freesansbold', 45)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

fb = open('/dev/fb1', 'wb')

def rgb_to_rgb565(r, g, b):
    return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)

clock = pygame.time.Clock()

while True:
    now = datetime.datetime.now()
    time_str = now.strftime("%H:%M:%S")
    date_str = now.strftime("%Y-%m-%d %A")

    screen.fill(WHITE)
    t = font_time.render(time_str, True, BLACK)
    d = font_date.render(date_str, True, BLACK)
    screen.blit(t, t.get_rect(center=(240, 140)))
    screen.blit(d, d.get_rect(center=(240, 220)))

    pixels = pygame.image.tostring(screen, 'RGB')
    buffer = bytearray(480 * 320 * 2)
    idx = 0
    for i in range(0, len(pixels), 3):
        r, g, b = pixels[i:i+3]
        color = rgb_to_rgb565(r, g, b)
        buffer[idx:idx+2] = struct.pack('<H', color)
        idx += 2

    fb.seek(0)
    fb.write(buffer)
    fb.flush()
    clock.tick(1)
EOF

bash

chmod +x ~/clock.py

9. TEST CLOCK (MUST WORK)

bash

python3 ~/clock.py

Clock appears on 3.5″ screen Press Ctrl+C to exit


10. CREATE SYSTEMD SERVICE (AUTO-START)

bash

sudo tee /etc/systemd/system/clock.service > /dev/null << 'EOF'
[Unit]
Description=Digital Clock on 3.5" LCD
After=local-fs.target

[Service]
Type=simple
User=admin
Group=video
Environment=SDL_VIDEODRIVER=dummy
ExecStart=/usr/bin/python3 /home/admin/clock.py
Restart=always
RestartSec=2
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

bash

sudo systemctl daemon-reload
sudo systemctl enable --now clock.service

11. DISABLE SPLASH & BOOT TEXT

bash

sudo raspi-config
# → 1 System Options → S1 Splash Screen → No
# → Finish

Or manually:

bash

sudo sed -i 's/ quiet splash//g' /boot/cmdline.txt
echo "disable_splash=1" | sudo tee -a /boot/config.txt

12. FINAL REBOOT

bash

sudo reboot

FINAL RESULT

StageDisplay
Power onBlack screen
3–5 secLarge digital clock + date
NO desktop, NO icons, NO cursor, NO splash

TROUBLESHOOTING

IssueFix
Blank screenRun sudo ./LCD35-show again
Only /dev/fb0Check /boot/config.txt has dtoverlay=ili9486
Permission denied /dev/fb1Reboot after usermod -a -G video admin
Clock not startingsudo journalctl -u clock.service -b

OPTIONAL: 12/24 HOUR TOGGLE (Touch Top-Left Corner)

Replace clock.py with this version:

bash

cat > ~/clock.py << 'EOF'
#!/usr/bin/env python3
import os, pygame, datetime, struct, time

os.environ['SDL_VIDEODRIVER'] = 'dummy'
pygame.init()
screen = pygame.Surface((480, 320))
font_time = pygame.font.SysFont('freesansbold', 100)
font_date = pygame.font.SysFont('freesansbold', 45)
WHITE, BLACK = (255,255,255), (0,0,0)

fb = open('/dev/fb1', 'wb')
def rgb_to_rgb565(r,g,b): return ((r&0xF8)<<8)|((g&0xFC)<<3)|(b>>3)

# Load format from file
fmt_file = '/home/admin/time_format.txt'
if not os.path.exists(fmt_file):
    with open(fmt_file, 'w') as f: f.write('24')

def get_format():
    with open(fmt_file) as f: return f.read().strip()

clock = pygame.time.Clock()
last_touch = 0

while True:
    # Touch detect (top-left 50x50)
    try:
        with open('/dev/input/touchscreen', 'rb') as t:
            t.read()
            if time.time() - last_touch > 2:
                fmt = '12' if get_format() == '24' else '24'
                with open(fmt_file, 'w') as f: f.write(fmt)
                last_touch = time.time()
    except: pass

    fmt = get_format()
    now = datetime.datetime.now()
    time_str = now.strftime("%I:%M:%S %p" if fmt == '12' else "%H:%M:%S")
    date_str = now.strftime("%Y-%m-%d %A")

    screen.fill(WHITE)
    t = font_time.render(time_str, True, BLACK)
    d = font_date.render(date_str, True, BLACK)
    screen.blit(t, t.get_rect(center=(240, 140)))
    screen.blit(d, d.get_rect(center=(240, 220)))

    pixels = pygame.image.tostring(screen, 'RGB')
    buffer = bytearray(480*320*2)
    idx = 0
    for i in range(0, len(pixels), 3):
        r,g,b = pixels[i:i+3]
        buffer[idx:idx+2] = struct.pack('<H', rgb_to_rgb565(r,g,b))
        idx += 2
    fb.seek(0); fb.write(buffer); fb.flush()
    clock.tick(1)
EOF

Touch top-left corner → toggles 12/24h


SAVE THIS GUIDE

Leave a Reply

0
Would love your thoughts, please comment.x
()
x