Двоичные часы на Raspberry Pi Pico (6 светодиодов + кнопка)
У этих часов нет цифр! Собираем на Raspberry Pi Pico часы, которые показывают время в двоичном виде. 6 светодиодов — это разряды 32-16-8-4-2-1; сложи горящие, и получишь секунды, минуты или часы. Одна кнопка переключает поля и устанавливает время, а каждый полный час по светодиодам пробегает световая волна.

Видео
Схема

Исходный код
1# ─────────────────────────────────────────
2# BlueGrays · bluegrays.com
3# Двоичные часы на Raspberry Pi Pico (6 светодиодов + кнопка)
4# ─────────────────────────────────────────
5# Часы, показывающие время двоичными числами. 6 светодиодов — разряды
6# 32-16-8-4-2-1; горящие светодиоды в сумме дают значение. Короткое
7# нажатие переключает секунды, минуты и часы; долгое — настройка поля.
8# Каждый полный час пробегает световая волна, а встроенный светодиод
9# отсчитывает секунды.
10#
11# Пины:
12# GP2 -> Светодиод 1 (бит 0), через 220 ohm
13# GP3 -> Светодиод 2 (бит 1), через 220 ohm
14# GP4 -> Светодиод 4 (бит 2), через 220 ohm
15# GP5 -> Светодиод 8 (бит 3), через 220 ohm
16# GP6 -> Светодиод 16 (бит 4), через 220 ohm
17# GP7 -> Светодиод 32 (бит 5), через 220 ohm
18# GP15 -> Кнопка (другая ножка к GND)
19# LED -> Встроенный светодиод (тик секунд)
20# GND -> Катоды светодиодов, кнопка
21
22from machine import Pin
23import time
24
25# ============ НАСТРОЙКИ ============
26START_TIME = (12, 59, 50) # Начальное время (ч, м, с)
27SEC, MIN, HOUR = range(3)
28START_MODE = SEC # Поле, показываемое при старте
29LONG_MS = 800 # Время долгого нажатия
30DEBOUNCE_MS = 20 # Время антидребезга кнопок
31MODE_MS = 500 # Сколько показывается метка режима
32BLINK_MS = 300 # Период мигания в режиме настройки
33SET_TIMEOUT_MS = 10000 # Режим настройки сохраняет после такой паузы
34SWEEP_STEP_MS = 60 # Шаг световой волны полного часа
35SWEEP_ROUNDS = 2 # Число проходов волны
36TICK_MS = 100 # Длительность вспышки встроенного светодиода
37LOOP_MS = 5 # Шаг цикла
38
39# По порядку бит 0 (1) ... бит 5 (32)
40LED_PINS = (2, 3, 4, 5, 6, 7)
41MODE_NAMES = ("SANIYE / SECONDS", "DAKIKA / MINUTES", "SAAT / HOURS")
42# Метка режима: 1, 2 или 3 светодиода слева (секунды, минуты, часы)
43MODE_MARKS = (0b100000, 0b110000, 0b111000)
44
45
46# ============ ЖЕЛЕЗО ============
47leds = [Pin(gp, Pin.OUT, value=0) for gp in LED_PINS]
48# Имя "LED" находит встроенный светодиод и на Pico, и на Pico W (на W это не пин GP)
49tick_led = Pin("LED", Pin.OUT, value=0) # Встроенный светодиод Pico
50
51
52class Button:
53 # Кнопка с подтяжкой: при нажатии читается 0. update() 1 = нажата, -1 = отпущена
54 def __init__(self, gp):
55 self.pin = Pin(gp, Pin.IN, Pin.PULL_UP)
56 self.down = False
57 self.changed_at = time.ticks_ms()
58
59 def update(self, now):
60 pressed = self.pin.value() == 0
61 if pressed != self.down and time.ticks_diff(now, self.changed_at) >= DEBOUNCE_MS:
62 self.down = pressed
63 self.changed_at = now
64 return 1 if pressed else -1
65 return 0
66
67 def held_ms(self, now):
68 return time.ticks_diff(now, self.changed_at)
69
70
71button = Button(15)
72shown = -1 # Последний узор на светодиодах
73
74
75def show_bits(pattern):
76 # Если узор изменился, записать 6 светодиодов: бит i -> светодиод i
77 global shown
78 if pattern == shown:
79 return
80 shown = pattern
81 for i, led in enumerate(leds):
82 led.value((pattern >> i) & 1)
83
84
85def binary(v):
86 # Двоичный текст из 6 цифр, например 13 -> 001101
87 return "".join("1" if v >> i & 1 else "0" for i in range(5, -1, -1))
88
89
90# ============ ЧАСЫ ============
91clock_s = (START_TIME[0] * 3600 + START_TIME[1] * 60 + START_TIME[2]) % 86400
92start = time.ticks_ms()
93next_second = time.ticks_add(start, 1000)
94tick_off_at = start
95
96
97def hms():
98 return clock_s // 3600, clock_s // 60 % 60, clock_s % 60
99
100
101def field_value(m):
102 h, mi, s = hms()
103 return (s, mi, h)[m]
104
105
106def time_text():
107 return "%02d:%02d:%02d" % hms()
108
109
110# ============ СОСТОЯНИЕ ============
111mode = START_MODE
112setting = False # Режим настройки
113long_done = False # Долгое нажатие для этого нажатия обработано
114marker_until = start # Когда заканчивается метка режима
115sweep_at = None # Начало световой волны
116blink_ref = start # Начало цикла мигания
117last_press = start # Последнее нажатие в режиме настройки
118
119
120def report(prefix):
121 v = field_value(mode)
122 print("%s %s: %d = %s (%s)" % (prefix, MODE_NAMES[mode], v, binary(v), time_text()))
123
124
125def save_setting(now):
126 global setting
127 setting = False
128 print("Saat ayarlandi / Time set: %s" % time_text())
129
130
131def on_short(now):
132 global mode, marker_until, clock_s, blink_ref, last_press
133 if setting:
134 # В режиме настройки каждое короткое нажатие добавляет 1 к выбранному полю
135 h, mi, s = hms()
136 if mode == HOUR:
137 h = (h + 1) % 24
138 else:
139 mi = (mi + 1) % 60
140 clock_s = h * 3600 + mi * 60 + s
141 blink_ref = now
142 last_press = now
143 report("+1")
144 else:
145 mode = (mode + 1) % 3
146 marker_until = time.ticks_add(now, MODE_MS)
147 report("Mod / Mode")
148
149
150def on_long(now):
151 global setting, clock_s, next_second, blink_ref, last_press
152 if setting:
153 if mode == MIN:
154 # После настройки минут секунды начинаются с нуля
155 clock_s -= clock_s % 60
156 next_second = time.ticks_add(now, 1000)
157 save_setting(now)
158 elif mode == SEC:
159 # В режиме секунд долгое нажатие обнуляет секунды (синхронизация по сигналу точного времени)
160 clock_s -= clock_s % 60
161 next_second = time.ticks_add(now, 1000)
162 print("Saniye sifirlandi / Seconds zeroed: %s" % time_text())
163 else:
164 setting = True
165 blink_ref = now
166 last_press = now
167 report("Ayar / Set")
168
169
170def frame(now):
171 # Узор светодиодов прямо сейчас: волна > метка режима > настройка (мигание) > значение
172 if sweep_at is not None:
173 step = time.ticks_diff(now, sweep_at) // SWEEP_STEP_MS
174 pos = step % 10
175 return 1 << (pos if pos < 6 else 10 - pos) # туда и обратно
176 if time.ticks_diff(marker_until, now) > 0:
177 return MODE_MARKS[mode]
178 if setting and time.ticks_diff(now, blink_ref) // BLINK_MS % 2:
179 return 0
180 return field_value(mode)
181
182
183# ============ ГЛАВНЫЙ ЦИКЛ ============
184print("== BlueGrays · bluegrays.com ==")
185print("Pico Ikili Saat / Binary Clock")
186print("Kisa basis: alan degistir, uzun basis: ayarla / Short press: next field, long: set")
187print("LED'ler / LEDs: 32 16 8 4 2 1")
188report("Mod / Mode")
189
190next_loop = time.ticks_ms()
191while True:
192 now = time.ticks_ms()
193
194 # Счётчик секунд: каждая следующая секунда прибавляется к последней, поэтому ход никогда не
195 # уплывает
196 if time.ticks_diff(now, next_second) >= 0:
197 next_second = time.ticks_add(next_second, 1000)
198 clock_s = (clock_s + 1) % 86400
199 tick_led.value(1)
200 tick_off_at = time.ticks_add(now, TICK_MS)
201 if clock_s % 60 == 0:
202 print("Saat / Time %s" % time_text()[:5])
203 if clock_s % 3600 == 0 and not setting:
204 sweep_at = now # Полный час: световая волна
205 if not setting and time.ticks_diff(now, tick_off_at) >= 0:
206 tick_led.value(0)
207 if sweep_at is not None and time.ticks_diff(now, sweep_at) >= SWEEP_STEP_MS * 10 * SWEEP_ROUNDS:
208 sweep_at = None
209
210 # Кнопка: короткое нажатие срабатывает при отпускании, долгое — как только прошло LONG_MS
211 b = button.update(now)
212 if b == 1:
213 long_done = False
214 sweep_at = None # Нажатие останавливает волну
215 elif button.down and not long_done and button.held_ms(now) >= LONG_MS:
216 long_done = True
217 on_long(now)
218 elif b == -1 and not long_done:
219 on_short(now)
220
221 if setting:
222 tick_led.value(1) # Во время настройки встроенный светодиод горит
223 if time.ticks_diff(now, last_press) >= SET_TIMEOUT_MS:
224 save_setting(now)
225
226 show_bits(frame(now))
227
228 # Фиксированный шаг: ждать до следующих 5 мс
229 next_loop = time.ticks_add(next_loop, LOOP_MS)
230 wait = time.ticks_diff(next_loop, time.ticks_ms())
231 if wait > 0:
232 time.sleep_ms(wait)
233 else:
234 next_loop = time.ticks_ms()
235Частые вопросы
Как читать двоичные часы?
Слева направо светодиоды стоят 32, 16, 8, 4, 2 и 1. Сложи значения горящих светодиодов: в узоре 001101 горят 8, 4 и 1, значит, значение 13. Часы никогда не превышают 23, поэтому в поле часов крайний левый светодиод остаётся тёмным. Монитор порта выводит каждое значение вместе с его двоичной формой.
Почему хватает 6 светодиодов?
6 бит считают от 0 до 63 (2 в степени 6 = 64 значения). Секунды и минуты доходят до 59, а часы до 23, поэтому всё помещается в 6 бит. Чтобы видеть часы, минуты и секунды одновременно, понадобился бы отдельный ряд светодиодов для каждого поля (всего 17 светодиодов).
Сбрасываются ли часы после отключения питания?
Да. У Pico нет часов с батарейкой, поэтому код при каждой загрузке начинает с `START_TIME`. Время сохранял бы модуль RTC с батарейкой, например DS1307. В этом проекте часы и минуты можно установить кнопкой за несколько секунд после включения.
Можно ли использовать светодиоды разных цветов?
Да. Красные, зелёные, жёлтые и оранжевые светодиоды хорошо светятся при 3,3 V с 220 Ω. Синим и белым нужно около 3 V, и при 3,3 V они могут выглядеть тусклыми; уменьши их резистор до 100 Ω. Если старшим разрядам дать другой цвет, часы читать проще.
