A TM1637 module is four 7-segment digits with a colon — the classic clock display. A Pico W can drive it and fetch the correct time from the internet, so the clock never needs setting.
Wire CLK to GP16 and DIO to GP17, power it from 3V3, and add your Wi-Fi name. The display shows the time in IST with a blinking colon.
Wiring
| Raspberry Pi Pico W | TM1637 4-digit 7-segment display | Wire | Notes |
|---|---|---|---|
| GP16 | CLK | yellow | GP16 is physical pin 21. |
| GP17 | DIO | green | GP17 is physical pin 22. |
| 3V3 | VCC | red | 3V3 is pin 36. The module’s pull-ups then hold CLK/DIO at 3.3 V. |
| GND | GND | black |
3.3–5 V part, 3.3 V board. Works from 3.3 V or 5 V — power it from your board’s logic voltage so CLK/DIO stay in range. At 3.3 V the digits are a little dimmer.
Board quirks
- The TM1637 is not I2C even though it has CLK and DIO — any two GPIOs work. The 45-line driver is inside
main.py. - DIO is set up as
Pin.OPEN_DRAIN, so the TM1637 can pull it low for its acknowledge bit without fighting the Pico. - With Wi-Fi set, the Pico W fetches time from
in.pool.ntp.org, adds 5:30 for IST and re-syncs every 6 hours. Without Wi-Fi it shows a minutes:seconds stopwatch. - The Pico loses the time at a power cut and re-syncs on boot. For a clock that must work without Wi-Fi, add a DS3231 (see that guide).
Code
# TM1637 4-digit display as an internet-synced IST clock on a Raspberry Pi Pico W (MicroPython).
# Wiring: VCC -> 3V3 (pin 36), GND -> GND, CLK -> GP16 (pin 21), DIO -> GP17 (pin 22).
# Without Wi-Fi it shows minutes:seconds since power-up instead.
# The small TM1637 driver below is written for FlashReady (MIT) from the TM1637 datasheet.
from machine import Pin
import network
import time
import json
WIFI_SSID = "your-wifi" # optional
WIFI_PASS = "your-password"
UTC_OFFSET_S = 5 * 3600 + 30 * 60 # IST
# The FlashReady flasher saves your settings to /settings.json; they win over the lines above.
try:
with open("/settings.json") as f:
_s = json.load(f)
except (OSError, ValueError):
_s = {}
WIFI_SSID = _s.get("wifi_ssid") or WIFI_SSID
WIFI_PASS = _s.get("wifi_pass") or WIFI_PASS
DIGITS = (0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F)
DASH = 0x40
class TM1637:
def __init__(self, clk, dio, brightness=4):
self.clk = Pin(clk, Pin.OUT, value=1)
# Open-drain: writing 1 releases DIO (the module's pull-up lifts it), so the TM1637 can
# pull it low for its ACK without fighting a pin that is driving HIGH.
self.dio = Pin(dio, Pin.OPEN_DRAIN, value=1)
self.brightness = brightness & 7
def _start(self):
self.dio(0)
time.sleep_us(5)
self.clk(0)
def _stop(self):
self.clk(0)
self.dio(0)
time.sleep_us(5)
self.clk(1)
time.sleep_us(5)
self.dio(1)
def _byte(self, b):
for i in range(8): # LSB first
self.clk(0)
self.dio((b >> i) & 1)
time.sleep_us(5)
self.clk(1)
time.sleep_us(5)
self.clk(0) # ninth clock for the ACK (not checked)
self.dio(1)
time.sleep_us(5)
self.clk(1)
time.sleep_us(5)
self.clk(0)
def show(self, segs):
self._start()
self._byte(0x40) # data command: write, auto-increment address
self._stop()
self._start()
self._byte(0xC0) # start at digit 0
for s in segs:
self._byte(s)
self._stop()
self._start()
self._byte(0x88 | self.brightness) # display on + brightness 0-7
self._stop()
def numbers(self, left, right, colon=True):
segs = [DIGITS[left // 10], DIGITS[left % 10], DIGITS[right // 10], DIGITS[right % 10]]
if colon:
segs[1] |= 0x80 # the colon is wired to bit 7 of the second digit
self.show(segs)
disp = TM1637(16, 17)
disp.show([DASH] * 4)
def ntp_sync():
if WIFI_SSID == "your-wifi":
print("No Wi-Fi name set - showing a stopwatch instead of the time.")
return False
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASS)
for _ in range(30):
if wlan.isconnected():
break
time.sleep(0.5)
if not wlan.isconnected():
print("Wi-Fi failed - showing a stopwatch instead.")
return False
try:
import ntptime
ntptime.host = "in.pool.ntp.org"
ntptime.settime() # Pico clock = UTC
print("Time set from NTP")
return True
except Exception as e:
print("NTP failed:", e)
return False
synced = ntp_sync()
start = time.time()
last_sync = time.time()
while True:
if synced:
_, _, _, h, m, s, _, _ = time.localtime(time.time() + UTC_OFFSET_S)
disp.numbers(h, m, colon=s % 2 == 0) # blinking colon
if s == 0:
print("{:02d}:{:02d} IST".format(h, m))
if time.time() - last_sync > 6 * 3600: # re-sync every 6 hours
last_sync = time.time()
ntp_sync()
else:
up = time.time() - start
disp.numbers(up // 60 % 100, up % 60)
time.sleep_ms(500)# TM1637 4-digit display as an internet-synced IST clock on a Raspberry Pi Pico W (MicroPython).
# Wiring: VCC -> 3V3 (pin 36), GND -> GND, CLK -> GP16 (pin 21), DIO -> GP17 (pin 22).
# Without Wi-Fi it shows minutes:seconds since power-up instead.
# The small TM1637 driver below is written for FlashReady (MIT) from the TM1637 datasheet.
from machine import Pin
import network
import time
import json
WIFI_SSID = "your-wifi" # optional
WIFI_PASS = "your-password"
UTC_OFFSET_S = 5 * 3600 + 30 * 60 # IST
# The FlashReady flasher saves your settings to /settings.json; they win over the lines above.
try:
with open("/settings.json") as f:
_s = json.load(f)
except (OSError, ValueError):
_s = {}
WIFI_SSID = _s.get("wifi_ssid") or WIFI_SSID
WIFI_PASS = _s.get("wifi_pass") or WIFI_PASS
DIGITS = (0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F)
DASH = 0x40
class TM1637:
def __init__(self, clk, dio, brightness=4):
self.clk = Pin(clk, Pin.OUT, value=1)
# Open-drain: writing 1 releases DIO (the module's pull-up lifts it), so the TM1637 can
# pull it low for its ACK without fighting a pin that is driving HIGH.
self.dio = Pin(dio, Pin.OPEN_DRAIN, value=1)
self.brightness = brightness & 7
def _start(self):
self.dio(0)
time.sleep_us(5)
self.clk(0)
def _stop(self):
self.clk(0)
self.dio(0)
time.sleep_us(5)
self.clk(1)
time.sleep_us(5)
self.dio(1)
def _byte(self, b):
for i in range(8): # LSB first
self.clk(0)
self.dio((b >> i) & 1)
time.sleep_us(5)
self.clk(1)
time.sleep_us(5)
self.clk(0) # ninth clock for the ACK (not checked)
self.dio(1)
time.sleep_us(5)
self.clk(1)
time.sleep_us(5)
self.clk(0)
def show(self, segs):
self._start()
self._byte(0x40) # data command: write, auto-increment address
self._stop()
self._start()
self._byte(0xC0) # start at digit 0
for s in segs:
self._byte(s)
self._stop()
self._start()
self._byte(0x88 | self.brightness) # display on + brightness 0-7
self._stop()
def numbers(self, left, right, colon=True):
segs = [DIGITS[left // 10], DIGITS[left % 10], DIGITS[right // 10], DIGITS[right % 10]]
if colon:
segs[1] |= 0x80 # the colon is wired to bit 7 of the second digit
self.show(segs)
disp = TM1637(16, 17)
disp.show([DASH] * 4)
def ntp_sync():
if WIFI_SSID == "your-wifi":
print("No Wi-Fi name set - showing a stopwatch instead of the time.")
return False
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASS)
for _ in range(30):
if wlan.isconnected():
break
time.sleep(0.5)
if not wlan.isconnected():
print("Wi-Fi failed - showing a stopwatch instead.")
return False
try:
import ntptime
ntptime.host = "in.pool.ntp.org"
ntptime.settime() # Pico clock = UTC
print("Time set from NTP")
return True
except Exception as e:
print("NTP failed:", e)
return False
synced = ntp_sync()
start = time.time()
last_sync = time.time()
while True:
if synced:
_, _, _, h, m, s, _, _ = time.localtime(time.time() + UTC_OFFSET_S)
disp.numbers(h, m, colon=s % 2 == 0) # blinking colon
if s == 0:
print("{:02d}:{:02d} IST".format(h, m))
if time.time() - last_sync > 6 * 3600: # re-sync every 6 hours
last_sync = time.time()
ntp_sync()
else:
up = time.time() - start
disp.numbers(up // 60 % 100, up % 60)
time.sleep_ms(500)Expected serial output
Time set from NTP
18:43 IST
18:44 ISTTime set from NTP 18:43 IST 18:44 IST
Common errors
Display stays dark
CLK and DIO swapped, or brightness 0. Check GP16 → CLK and GP17 → DIO.
Shows - - - - forever
The display works but the code stopped before the loop — look at the Thonny shell for a Wi-Fi or NTP error.
Time is 5:30 off
You changed UTC_OFFSET_S. NTP gives UTC; IST is 19800 seconds ahead.