Introduction: World Time Zone Clock
Six-Zone World Time Wall Using Raspberry Pi Pico and 16×2 I²C LCDs or A modern rebuild of the classic newsroom world clock — analog faces with digital OLED placards, one power source, zero batteries.
Overview
I've procrastinated long enough. It's time to finally get this project rolling. Of course, I waited until almost the last minute to do so, but save the time machine build for another day, this will suffice. Once completed I'll either technically bust or beat the deadline (depending on which clock you look at).
This project builds a six-clock world time display.
Pending future mods to power from a single 5V USB source (wall brick or power bank). Each clock runs on a regulated 1.5V rail while six OLED screens display city names and offsets, all driven by one microcontroller.
**I'll update the Instructable when the rest of the parts arrive.**
For a cohesive look, I chose multiple clocks that were the same at a good enough price to justify the project. However, for those that might be interested in the exact clocks I used, I bought them circa 2019 at Bed Bath and Beyond and believe they went the way of the dinosaur with in recent years.
Project Scope
Build a hardware world clock wall that shows six time zones at once using:
A Raspberry Pi Pico
Six 16×2 I²C LCDs (LCD1602 with backpacks)
A single power source
Static labels (city + timezone) for:
Seattle
Denver
Omaha
Boston
London
Tokyo
This version:
Does not show live time (just city/timezone labels)
Is designed to pair with six analog clocks mounted above/below each LCD
Uses one I²C address (0x27) for all six LCDs, solved by:
One hardware I²C bus, plus
Five additional SoftI2C buses on separate pin pairs
With all that out of the way... let's build.
Supplies
Clocks of your choice
Core electronics
1 × Raspberry Pi Pico or Pico H (headers pre-soldered is ideal)
6 × 16×2 LCD1602 displays with I²C backpacks (PCF8574-based, typical address 0x27)
1 × Solderless breadboard
1 × USB-A to Micro-USB data cable (not charge-only)
~30–40 × jumper wires (male–male and female–female)
Power
USB power source (5 V via Pico USB)
(Optional) External 5 V supply if you want to power Pico + clocks from one brick
Optional mechanical / mounting
Cardboard or plastic sheet for temporary standoffs
Hot glue gun / double-sided tape / zip ties
Backing board (plywood, MDF, acrylic, etc.) to mount the displays
Tools
Computer with Thonny installed (or similar MicroPython IDE)
Small screwdriver (for LCD contrast trimpot)
Optional multimeter (to sanity-check voltage)
Buck converter (future mod to step down the voltage powering the clocks)
Step 1: Getting Clocks Centered
This was the hardest part of the build. Getting everything aligned and spaced precisely. I knew I wanted equal gaps between each clock. If I'd used 5 clocks instead of 6 I could have just used the 3rd clock to determine the middle.
I measured each clock as approximately 8 3/4" then did the math on the desired gaps, came out to 74". I used common pine and painted it.
In the photos I'm attempting to show that I was using known references such as a paper plate and an 8x11.5" sheet of paper to build my templates. For the spacer, I used Inkscape to create an SVG PDF that i printed and cut out.
This took a lot longer than expected getting the spacing right. It worked out, the components hadn't arrived yet. If only the postal office was as efficient as the weather service.
Eventually some of the parts showed up.
Step 2: Programming the Raspberry Pi Pico
Unlike other Raspberry pis, the Pico doesn't require an SD car. Rather, you flash it with MicroPython. The Raspberry Pi webpage has instructions on this here :
https://www.raspberrypi.com/documentation/microcontrollers/micropython.html
Flash MicroPython onto the Pico
This step turns the Pico into a MicroPython-ready device.
Download MicroPython UF2 for Pico
Use the firmware for: “Pico” (not Pico W, not Pico 2).
Put the Pico in BOOTSEL mode
Unplug the Pico.
Hold down the BOOTSEL button on the board.
While holding it, plug in the USB cable to your computer.
Release BOOTSEL.
A new drive named RPI-RP2 should appear.
Copy firmware
Drag and drop the downloaded .uf2 file onto RPI-RP2.
The Pico will reboot and the drive will disappear. That’s expected.
At this point, MicroPython is running on your Pico.
Step 3: Thonny
Set Up Thonny and Verify the Pico
- Open Thonny.
- Go to:
- Run → Select interpreter
- Choose MicroPython (Raspberry Pi Pico).
- Click Stop/Restart in Thonny.
Step 4: Wire a Single LCD to Known-Good I²C Pins
We start with just one LCD, on the canonical I²C pins (GP0/GP1), with 3.3 V power for safety/debugging.
Connections (direct)
- LCD GND → Pico GND
- LCD VCC → Pico 3V3(OUT)
- LCD SDA → Pico GP0
- LCD SCL → Pico GP1
On the LCD backpack, verify the header is labeled:
GND VCC SDA SCL
and wire accordingly.
Step 5: Scan the I²C Bus and Confirm the LCD Address
In Thonny, run:
You should see:
- 39 (decimal) = 0x27 (hex), which is the common LCD I²C address.
- If you see [63], then your address is 0x3F instead.
Remember that value; we’ll use 0x27 in this write-up.
Stop the script (Ctrl+C or Stop button).
Step 6: Add the LCD Driver (i2c_lcd.py)
We’ll use a simple MicroPython driver for HD44780 LCDs over PCF8574 I²C backpacks.
- In Thonny, create a new file:
- File → New
- Paste this:
- Save it to the Pico as:
- i2c_lcd.py
Step 7: Test the First LCD (Seattle) on GP0/GP1
Create a new script:
Run it.
If you see nothing but the backlight, tweak the little blue contrast pot on the backpack until characters appear.
Once this works, you’ve proved:
- Firmware is good
- Driver works
- Wiring is correct
Seattle is now your reference display.
Step 7 – Move to 5 V and Introduce the Breadboard Rails
Now move from direct wiring to a breadboard power bus so you can support all six LCDs.
Power distribution:
- Pico VSYS → breadboard +5 V rail
- Pico GND → breadboard GND rail
- All LCD VCC → +5 V rail
- All LCD GND → GND rail
Keep Seattle’s LCD data pins on:
- SDA → GP0
- SCL → GP1
Re-run the Seattle test script to confirm it still works off 5 V rails.
Step 8 – Add Denver on Its Own SoftI2C Bus (GP2/GP3)
Wire the second LCD (Denver):
- VCC → +5 V rail
- GND → GND rail
- SDA → GP2
- SCL → GP3
Denver test script:
Run it. Now you should see Seattle on LCD #1 and Denver on LCD #2.
Step 8: Full Code and Wiring
Use more SoftI2C buses, each with its own SDA/SCL pin pair:
CityLCD #SDASCL
Seattle
1
GP0
GP1 (hardware I²C0)
Denver
2
GP2
GP3
Omaha
3
GP4
GP5
Boston
4
GP6
GP7
London
5
GP8
GP9
Tokyo
6
GP10
GP11
Wire each LCD’s SDA/SCL pair exactly like that (all VCC/GND already on rails).
Put this on the Pico as main.py:
from machine import Pin, I2C, SoftI2C
import time
from i2c_lcd import I2cLcd
LCD_ADDR = 0x27 # From your scan
def center_16(text: str) -> str:
"""Return text centered in a 16-character field."""
text = text[:16]
spaces = (16 - len(text)) // 2
return " " * spaces + text
def setup_seattle():
"""LCD #1 – Seattle on hardware I2C0 (GP0/GP1)."""
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=100000)
lcd = I2cLcd(i2c, LCD_ADDR, num_lines=2, num_columns=16)
city = center_16("SEATTLE")
zone = center_16("UTC-8 (PST)")
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(city)
lcd.move_to(0, 1)
lcd.putstr(zone)
return lcd
def setup_denver():
"""LCD #2 – Denver on SoftI2C (GP2/GP3)."""
i2c = SoftI2C(sda=Pin(2), scl=Pin(3), freq=100000)
lcd = I2cLcd(i2c, LCD_ADDR, num_lines=2, num_columns=16)
city = center_16("DENVER")
zone = center_16("UTC-7 (MT)")
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(city)
lcd.move_to(0, 1)
lcd.putstr(zone)
return lcd
def setup_omaha():
"""LCD #3 – Omaha on SoftI2C (GP4/GP5)."""
i2c = SoftI2C(sda=Pin(4), scl=Pin(5), freq=100000)
lcd = I2cLcd(i2c, LCD_ADDR, num_lines=2, num_columns=16)
city = center_16("OMAHA")
zone = center_16("UTC-6 (CT)")
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(city)
lcd.move_to(0, 1)
lcd.putstr(zone)
return lcd
def setup_boston():
"""LCD #4 – Boston on SoftI2C (GP6/GP7)."""
i2c = SoftI2C(sda=Pin(6), scl=Pin(7), freq=100000)
lcd = I2cLcd(i2c, LCD_ADDR, num_lines=2, num_columns=16)
city = center_16("BOSTON")
zone = center_16("UTC-5 (ET)")
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(city)
lcd.move_to(0, 1)
lcd.putstr(zone)
return lcd
def setup_london():
"""LCD #5 – London on SoftI2C (GP8/GP9)."""
i2c = SoftI2C(sda=Pin(8), scl=Pin(9), freq=100000)
lcd = I2cLcd(i2c, LCD_ADDR, num_lines=2, num_columns=16)
city = center_16("LONDON")
zone = center_16("UTC+0 (GMT)")
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(city)
lcd.move_to(0, 1)
lcd.putstr(zone)
return lcd
def setup_tokyo():
"""LCD #6 – Tokyo on SoftI2C (GP10/GP11)."""
i2c = SoftI2C(sda=Pin(10), scl=Pin(11), freq=100000)
lcd = I2cLcd(i2c, LCD_ADDR, num_lines=2, num_columns=16)
city = center_16("TOKYO")
zone = center_16("UTC+9 (JST)")
lcd.clear()
lcd.move_to(0, 0)
lcd.putstr(city)
lcd.move_to(0, 1)
lcd.putstr(zone)
return lcd
def main():
setup_seattle()
time.sleep_ms(100)
setup_denver()
time.sleep_ms(100)
setup_omaha()
time.sleep_ms(100)
setup_boston()
time.sleep_ms(100)
setup_london()
time.sleep_ms(100)
setup_tokyo()
time.sleep_ms(100)
# Keep the MCU alive; displays stay static
while True:
time.sleep(1)
main()
Now:
Save as main.py on the Pico.
Unplug and replug USB.
All six LCDs should come up with city/timezone labels on boot.


