#171Новичок

Кнопочное пианино на Raspberry Pi Pico (5 кнопок + зуммер + запись мелодии)

Мини-пианино на пять клавиш, которое запоминает твою игру! Raspberry Pi Pico играет ДО, РЕ, МИ, ФА и СОЛЬ на пассивном зуммере по пяти кнопкам, и нота звучит, пока удерживается её клавиша. Удержи ДО секунду, сыграй мелодию и снова удержи ДО: Pico проиграет её, сокращая паузы между нотами длиннее 1,5 секунды до 1,5 секунды. Индикатор записи — встроенный светодиод платы, лишние детали не нужны. Ноты через PWM, подавление дребезга и неблокирующие задержки в одном проекте.

0.0(0)
Кнопочное пианино на Raspberry Pi Pico (5 кнопок + зуммер + запись мелодии)
Крошечное пианино с пятью кнопками: каждая играет на зуммере до, ре, ми, фа или соль, и нота звучит, пока кнопку держат. Удержи клавишу до секунду, и начнётся запись, а светодиод на плате будет мигать; сыграй мелодию, снова удержи до секунду, и Raspberry Pi Pico проиграет её тебе в том же ритме. Самый простой способ записать свою мелодию и услышать её.

Видео

Схема

Кнопочное пианино на Raspberry Pi Pico (5 кнопок + зуммер + запись мелодии) - Circuit

Исходный код

MicroPython
1# ─────────────────────────────────────────
2#   BlueGrays · bluegrays.com
3#   Кнопочное пианино на Raspberry Pi Pico (5 кнопок + зуммер + запись мелодии)
4# ─────────────────────────────────────────
5# Мини-пианино на пять кнопок: DO, RE, MI, FA, SOL. Нота звучит, пока её
6# клавиша нажата. Удерживай DO 1 с: начинается запись, и ноты сохраняются
7# со своим временем. Снова удерживай DO 1 с: запись останавливается, и
8# мелодия проигрывается в том же ритме.
9#
10# Пины:
11#   GP7  -> Кнопка DO (другая ножка к GND)
12#   GP8  -> Кнопка RE (другая ножка к GND)
13#   GP9  -> Кнопка MI (другая ножка к GND)
14#   GP10 -> Кнопка FA (другая ножка к GND)
15#   GP11 -> Кнопка SOL (другая ножка к GND)
16#   GP17 -> Зуммер (+), другая ножка к GND
17#   LED  -> Встроенный светодиод: запись / воспроизведение
18
19from machine import Pin, PWM
20import time
21
22# ============ НАСТРОЙКИ ============
23NOTE_PINS = (7, 8, 9, 10, 11)
24NOTE_NAMES = ("DO", "RE", "MI", "FA", "SOL")
25NOTE_HZ = (523, 587, 659, 698, 784)   # C5 D5 E5 F5 G5 (Hz)
26VOLUME = 32768         # Заполнение PWM, 32768 = 50%
27
28REC_KEY = 0            # Клавиша записи: DO
29LONG_PRESS_MS = 1000   # Время удержания для записи
30DEBOUNCE_MS = 20       # Антидребезг кнопок
31MAX_NOTES = 64         # Макс. нот в записи
32MAX_GAP_MS = 1500      # Самая длинная тишина при воспроизведении
33PLAY_DELAY_MS = 500    # Пауза перед воспроизведением
34REC_BLINK_MS = 500     # Мигание светодиода во время записи
35LOOP_MS = 2            # Шаг главного цикла
36
37FREE, REC, PLAY = 0, 1, 2   # Режимы
38
39# ============ ЖЕЛЕЗО ============
40buzzer = PWM(Pin(17))
41buzzer.duty_u16(0)
42try:
43    led = Pin("LED", Pin.OUT)      # встроенный светодиод
44except (TypeError, ValueError):
45    led = Pin(25, Pin.OUT)         # Старая прошивка
46led.value(0)
47
48
49class Key:
50    # Кнопка с антидребезгом; update() возвращает +1 при нажатии, -1 при отпускании
51    def __init__(self, gpio):
52        self.pin = Pin(gpio, Pin.IN, Pin.PULL_UP)
53        self.raw = 1
54        self.stable = 1
55        self.changed = time.ticks_ms()
56        self.down_at = 0
57        self.used = False      # Использовано командой
58
59    def update(self, now):
60        raw = self.pin.value()
61        if raw != self.raw:
62            self.raw = raw
63            self.changed = now
64        if raw != self.stable and time.ticks_diff(now, self.changed) >= DEBOUNCE_MS:
65            self.stable = raw
66            if raw == 0:
67                self.down_at = now
68                self.used = False
69                return 1
70            return -1
71        return 0
72
73    def held_ms(self, now):
74        return time.ticks_diff(now, self.down_at) if self.stable == 0 else 0
75
76
77keys = [Key(p) for p in NOTE_PINS]
78
79
80# ============ ЗВУК ============
81def tone(hz):
82    buzzer.freq(hz)
83    buzzer.duty_u16(VOLUME)
84
85
86def silence():
87    buzzer.duty_u16(0)
88
89
90def chirp(*steps):
91    # Короткий сигнал: пары (Hz, мс); 0 Hz = тишина
92    for hz, ms in steps:
93        if hz:
94            tone(hz)
95        else:
96            silence()
97        time.sleep_ms(ms)
98    silence()
99
100
101# ============ СОСТОЯНИЕ ============
102mode = FREE
103held = []          # Удерживаемые ноты, последняя в конце
104rec_notes = []     # Запись: [клавиша, начало в мс, длительность в мс]
105rec_t0 = 0
106play_list = []     # Воспроизведение: (время в мс, клавиша, длительность в мс)
107play_index = 0
108play_start = 0
109play_end = 0
110play_sounding = False
111
112
113def update_sound():
114    # Зуммер играет одну ноту: побеждает последняя нажатая клавиша
115    if held:
116        tone(NOTE_HZ[held[-1]])
117    else:
118        silence()
119
120
121def start_recording(now):
122    global mode, rec_notes, rec_t0
123    mode = REC
124    rec_notes = []
125    rec_t0 = now
126    chirp((880, 70), (0, 40), (1319, 110))       # Восходящий сигнал
127    print("Kayit basladi / Recording started (en fazla / max %d nota / notes)" % MAX_NOTES)
128
129
130def build_play_list():
131    # Отсортировать ноты по началу, сдвинуть первую на 0, укоротить длинные паузы
132    notes = sorted(rec_notes, key=lambda n: n[1])
133    result = []
134    t = 0
135    for i, (k, start, length) in enumerate(notes):
136        if i > 0:
137            prev_k, prev_start, prev_len = notes[i - 1]
138            t += min(start - prev_start, prev_len + MAX_GAP_MS)
139        result.append((t, k, length))
140    return result
141
142
143def stop_recording(now):
144    global mode, play_list, play_index, play_start, play_end, play_sounding
145    led.value(0)
146    chirp((1319, 70), (0, 40), (880, 110))       # Нисходящий сигнал
147    if not rec_notes:
148        mode = FREE
149        print("Kayit bos / Recording is empty")
150        chirp((220, 300))
151        return
152    play_list = build_play_list()
153    names = " ".join(NOTE_NAMES[n[1]] for n in play_list)
154    print("Kayit bitti / Recording stopped: %d nota / notes" % len(play_list))
155    print("Melodi / Melody:", names)
156    mode = PLAY
157    play_index = 0
158    play_end = 0
159    play_sounding = False
160    play_start = time.ticks_add(time.ticks_ms(), PLAY_DELAY_MS)
161    print("Geri calma / Playback...")
162
163
164def finish_playback(message):
165    global mode
166    mode = FREE
167    silence()
168    led.value(0)
169    print(message)
170
171
172def update_playback(now):
173    # Неблокирующее воспроизведение: запускать ноты вовремя, глушить их по окончании
174    global play_index, play_end, play_sounding
175    elapsed = time.ticks_diff(now, play_start)
176    if elapsed < 0:
177        return
178    while play_index < len(play_list) and elapsed >= play_list[play_index][0]:
179        t, k, length = play_list[play_index]
180        tone(NOTE_HZ[k])
181        led.value(1)                  # Светодиод горит вместе с нотой
182        play_end = t + length
183        play_sounding = True
184        play_index += 1
185    if play_sounding and elapsed >= play_end:
186        silence()
187        led.value(0)
188        play_sounding = False
189    if not play_sounding and play_index >= len(play_list):
190        finish_playback("Bitti / Done. DO 1 sn = yeni kayit / DO 1 s = new recording")
191
192
193def on_press(k, now):
194    if mode == PLAY:
195        # Любая клавиша останавливает воспроизведение
196        keys[k].used = True
197        finish_playback("Durduruldu / Stopped")
198        return
199    held.append(k)
200    update_sound()
201    tag = "[REC] " if mode == REC else ""
202    print("%sNota / Note: %s (%d Hz)" % (tag, NOTE_NAMES[k], NOTE_HZ[k]))
203
204
205def on_release(k, now):
206    if k in held:
207        held.remove(k)
208        update_sound()
209    if keys[k].used or mode != REC:
210        return
211    start = time.ticks_diff(keys[k].down_at, rec_t0)
212    length = time.ticks_diff(now, keys[k].down_at)
213    rec_notes.append([k, start, length])
214    if len(rec_notes) >= MAX_NOTES:
215        print("Kayit dolu / Recording full")
216        stop_recording(now)
217
218
219def on_long_press(k, now):
220    # Долгое нажатие на DO: начать или остановить запись. Удерживаемая нота DO не записывается.
221    keys[k].used = True
222    if k in held:
223        held.remove(k)
224    update_sound()
225    if mode == FREE:
226        start_recording(now)
227    elif mode == REC:
228        stop_recording(now)
229
230
231# ============ ГЛАВНЫЙ ЦИКЛ ============
232print("== BlueGrays · bluegrays.com ==")
233print("Pico Buton Piyano / Button Piano")
234print("Tuslar / Keys: DO RE MI FA SOL")
235print("DO 1 sn basili = kayit baslat / bitir + geri cal")
236print("Hold DO 1 s = start / stop recording + play back")
237chirp((NOTE_HZ[0], 90), (NOTE_HZ[2], 90), (NOTE_HZ[4], 90), (1047, 180))   # Запуск
238
239while True:
240    now = time.ticks_ms()
241    for i, key in enumerate(keys):
242        event = key.update(now)
243        if event == 1:
244            on_press(i, now)
245        elif event == -1:
246            on_release(i, now)
247
248    rec_key = keys[REC_KEY]
249    if (mode != PLAY and not rec_key.used
250            and rec_key.held_ms(now) >= LONG_PRESS_MS):
251        on_long_press(REC_KEY, now)
252
253    if mode == PLAY:
254        update_playback(now)
255    elif mode == REC:
256        # Во время записи светодиод мигает
257        led.value(1 if (time.ticks_diff(now, rec_t0) // REC_BLINK_MS) % 2 == 0 else 0)
258
259    time.sleep_ms(LOOP_MS)
260

Частые вопросы

Как записать мелодию и проиграть её?

Удержи ДО одну секунду: два восходящих сигнала означают, что запись началась. Сыграй мелодию, затем снова удержи ДО одну секунду. После двух нисходящих сигналов мелодия проиграется; паузы между нотами длиннее 1,5 секунды сокращаются до 1,5 секунды. Нажатие любой клавиши во время воспроизведения останавливает его.

Нужно ли добавлять отдельный светодиод для индикатора записи?

Нет. Код управляет светодиодом на плате через `Pin("LED")`; на Pico это GP25, и так же работает на Pico W. Во время записи он мигает каждые полсекунды, а при воспроизведении загорается с каждой нотой.

Как изменить ноты или октаву?

Частоты хранятся в `NOTE_HZ`: 523, 587, 659, 698 и 784 Hz. Раздели их все пополам, чтобы опуститься на октаву, или удвой, чтобы подняться. Чтобы добавить шестую клавишу, допиши новый пин, название и частоту (880 для ЛЯ) в `NOTE_PINS`, `NOTE_NAMES` и `NOTE_HZ`.

Как играть на пианино в Wokwi?

Запусти симуляцию и один раз щёлкни по схеме. Клавиши с 1 по 5 на клавиатуре играют от ДО до СОЛЬ, а удержание клавиши делает ноту длиннее. Для записи удерживай 1 около секунды; сыгранные ноты и записанная мелодия появляются в мониторе порта.