#202Новичок

Двоичные часы на Raspberry Pi Pico (6 светодиодов + кнопка)

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

0.0(0)
Двоичные часы на Raspberry Pi Pico (6 светодиодов + кнопка)
Часы, которые показывают время огоньками вместо цифр: шесть светодиодов показывают секунды, минуты или часы в двоичном виде. Слева направо светодиоды стоят 32, 16, 8, 4, 2 и 1, и сумма горящих даёт текущее значение. Одна кнопка переключает секунды, минуты и часы, долгое нажатие позволяет установить время, а в начале каждого часа по светодиодам пробегает световая волна.

Видео

Схема

Двоичные часы на Raspberry Pi Pico (6 светодиодов + кнопка) - Circuit

Исходный код

MicroPython
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 Ω. Если старшим разрядам дать другой цвет, часы читать проще.