Maker Advent Calendar День #5: Слышишь мой код!

Maker Advent Calendar День 5 — зуммер

Добро пожаловать на пятый день вашего Advent Calendar «12 проектов Codemas». Сегодня мы будем извлекать звуки с помощью кода, используя зуммер, который вы только что обнаружили в своей коробке!

Зуммер подключается очень просто — всего два контакта, никаких дополнительных компонентов — но он способен добавить вашему проекту СТОЛЬКО возможностей! Звуковая обратная связь при ошибках, нажатиях кнопок и других событиях придаёт проекту совершенно новое измерение.

Давайте пошумим!

Содержимое коробки #5

В этой коробке вы найдёте:

  • 1x Зуммер с предустановленными проводами и разъёмами типа «папа»

Компоненты дня 5

Сегодняшний проект

Сегодня мы используем зуммер несколькими способами: сначала простые сигналы и тоны, затем совместим его с потенциометром для управления громкостью, и наконец сделаем кое-что посложнее — используем разные частоты для воспроизведения праздничных тонов!

Этот зуммер работает с ШИМ-сигналами, что позволяет генерировать различные тоны. Мы снова используем всё, что узнали о ШИМ в предыдущей коробке. Будто мы всё это заранее спланировали…

Сборка схемы

Не забудьте отключить Pico от USB перед изменением схемы!

Потенциометр из предыдущей коробки нам понадобится для управления зуммером через несколько минут, так что оставьте схему как есть. Нам нужно только подключить зуммер — и всё готово.

Это один из наиболее простых в подключении компонентов, поскольку мы включили в набор зуммер с предустановленными проводами с разъёмами типа «папа».

Просто вставьте красный провод в GPIO 13 (физический контакт 17) и чёрный провод в соседний контакт GND (физический контакт 18). Карта контактов Pico всегда под рукой, если вам нужно её проверить.

Подключение зуммера к Pico

Задание 1: Попищим!

Наш зуммер может генерировать различные тоны в зависимости от частоты ШИМ и рабочего цикла, которые мы задаём в коде — это должно звучать знакомо, так как мы разбирали ШИМ вчера.

ШИМ-частота и рабочий цикл с зуммерами

Частота здесь изменяет тон зуммера — мы обнаружили, что наши уши способны слышать изменения тона при значениях от 10 до 10000.

Рабочий цикл задаёт громкость зуммера. Диапазон значений в MicroPython технически составляет от 0 до 65535, однако значения ниже 5000 практически неслышимы. Значение 10000 является оптимальным для нашего зуммера.

Если вы хотите заглушить зуммер в коде, просто установите рабочий цикл равным 0 — легко!

Код

Теперь, когда мы знаем, что делают значения ШИМ, давайте попробуем быстрый пример, в котором зуммер звучит несколько секунд.

Пример ниже включает те же элементы, с которыми вы уже знакомы: импорты, настройка контакта (с ШИМ), рабочий цикл ШИМ и частота — так что ничего нового и пугающего здесь нет, а наши подробные комментарии объясняют каждую строку!

Мы настраиваем контакт для зуммера, задаём частоту (1000) и устанавливаем рабочий цикл ШИМ (громкость) равным 10000 на одну секунду, затем снижаем его до 0 для отключения. Просто и понятно!

Скопируйте пример в Thonny и попробуйте:

# Импорты
from machine import Pin, PWM
import time

# Настройка контакта зуммера как ШИМ
buzzer = PWM(Pin(13)) # Переводим зуммер в режим ШИМ

# Устанавливаем частоту ШИМ равной 1000
buzzer.freq(1000)

# Устанавливаем рабочий цикл ШИМ
buzzer.duty_u16(10000)

time.sleep(1) # Ждём 1 секунду

# Рабочий цикл 0 — выключаем зуммер
buzzer.duty_u16(0)

Теперь попробуйте изменить число частоты на 300 и посмотрите, что произойдёт. Тон стал ниже? Можно попробовать и более высокие значения, но при значениях около 10000 зуммер перестанет работать — ваши уши просто не услышат такую частоту.

Задание 2: Смена тонов

Вот ещё один простой пример, демонстрирующий, как легко менять тон зуммера.

Здесь мы начинаем с частотой 1000 в течение одной секунды, затем снижаем её до 500 ещё на одну секунду.

Попробуйте и услышьте разницу сами:

# Импорты
from machine import Pin, PWM
import time

# Настройка контакта зуммера как ШИМ
buzzer = PWM(Pin(13)) # Переводим зуммер в режим ШИМ

# Устанавливаем рабочий цикл ШИМ
buzzer.duty_u16(10000)

# Устанавливаем частоту ШИМ 1000 на одну секунду
buzzer.freq(1000)
time.sleep(1)

# Устанавливаем частоту ШИМ 500 на одну секунду
buzzer.freq(500)
time.sleep(1)

# Выключаем зуммер
buzzer.duty_u16(0)

Задание 3: Управление громкостью потенциометром

Давайте используем один из предыдущих компонентов для управления громкостью зуммера. Этот пример немного шумный и не идеален (звук может искажаться или пропадать на высоких уровнях), но попробовать его интересно! Если зуммер кажется вам слишком громким или резким, можно заклеить отверстие кусочком скотча или пластилином, чтобы приглушить звук.

Показания потенциометра находятся в диапазоне от 0 до 65535, и рабочий цикл зуммера (громкость) использует тот же диапазон, поэтому мы можем напрямую использовать показания потенциометра для управления рабочим циклом — очень удобно!

В примере ниже в каждом цикле while мы считываем показание потенциометра и сохраняем его в переменную „reading“ (точно так же, как делали в задании 3 предыдущего дня), а затем используем эту переменную в качестве значения рабочего цикла зуммера (вместо того чтобы вводить число вручную).

Небольшое предупреждение — этот пример никогда не выключает зуммер, даже при остановке программы, так как это простой базовый пример для знакомства с управлением зуммером. Вы можете отключить USB-кабель для прекращения шума или просто перейти к следующему заданию.

# Импорты
from machine import Pin, PWM, ADC
import time

# Настройка зуммера и потенциометра
potentiometer = ADC(Pin(27))
buzzer = PWM(Pin(13)) # Переводим зуммер в режим ШИМ

reading = 0 # Создаём переменную 'reading' и устанавливаем её в 0

while True: # Выполняем бесконечно

    time.sleep(0.01) # Задержка

    reading = potentiometer.read_u16() # Считываем значение потенциометра и сохраняем в переменную 'reading'

    buzzer.freq(500) # Частота 500
    buzzer.duty_u16(reading) # Используем переменную reading как рабочий цикл

    print(reading) # Выводим переменную reading

Задание 4: Праздничная мелодия!

Как насчёт небольшой праздничной мелодии с помощью нашего зуммера? Изменяя частоту между паузами, мы можем создать мелодию, напоминающую популярный праздничный джингл!

Удобный способ сделать это — определить частоту каждой музыкальной ноты в виде переменной с именем ноты, что сделает код более простым для написания, чтения и проверки. Переменные нам уже знакомы. Мы также используем переменную для громкости (рабочего цикла), что тоже делает код более понятным для человека. Не беспокойтесь — значения тонов для джингла приведены ниже.

Пример ниже намеренно неэффективен и использует ОЧЕНЬ МНОГО строк, главным образом потому, что каждая секция джингла включает строки для включения и выключения громкости, а также задержки. Сначала попробуйте этот вариант, а затем перейдём к более изящному способу в следующем задании.

Мы добавили комментарии к первому блоку кода джингла, чтобы показать, что происходит.

Совет: Это простой зуммер, поэтому у него нет такого гладкого звучания, как у обычного динамика. Зуммер может звучать немного в стиле «Кошмар перед Рождеством», а не «Эльф»!

# Импорты
from machine import Pin, PWM
import time

# Настройка контакта зуммера как ШИМ
buzzer = PWM(Pin(13)) # Переводим зуммер в режим ШИМ

# Создаём библиотеку переменных тонов для "Jingle Bells"
C = 523
D = 587
E = 659
G = 784

# Создаём переменную громкости (рабочий цикл)
volume = 10000

# Воспроизводим мелодию

# "Jing..."
buzzer.duty_u16(volume) # Громкость включена
buzzer.freq(E) # Устанавливаем частоту на ноту E
time.sleep(0.1) # Задержка
buzzer.duty_u16(0) # Громкость выключена
time.sleep(0.2) # Задержка

# "...le"
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "Bells"
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.5) # более длинная задержка

# "Jing..."
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "...le"
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "Bells"
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.5) # более длинная задержка

# "Jing..."
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "...le"
buzzer.duty_u16(volume)
buzzer.freq(G)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "All"
buzzer.duty_u16(volume)
buzzer.freq(C)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "The"
buzzer.duty_u16(volume)
buzzer.freq(D)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# "Way"
buzzer.duty_u16(volume)
buzzer.freq(E)
time.sleep(0.1)
buzzer.duty_u16(0)
time.sleep(0.2)

# Рабочий цикл 0 — выключаем зуммер
buzzer.duty_u16(0)

Задание 5: Праздничная мелодия с использованием функций

Давайте улучшим очень длинный код выше, введя кое-что новое — функции!

Функции

Функции — это блоки кода, которые вы можете создать, а затем вызывать (использовать) в любой момент программы. Они идеально подходят для повторяющихся блоков кода, так как делают программу короткой и эффективной (что может быть важно, если ваш микроконтроллер имеет ограниченный объём памяти).

Давайте разберём, как работают функции, прежде чем продолжить…

Написание функции

Ниже приведён пример простой функции, которая выводит 3 строки. Функция создаётся с помощью слова def, за которым следует пробел и имя функции (без пробелов в названии).

Нашу функцию мы назвали myfunction. Затем добавляем скобки (круглые скобки), которые могут включать аргументы, если необходимо (в этом примере мы их не используем, но рассмотрим через минуту), после чего ставим двоеточие (:).

Отступы снова важны здесь — всё под строкой def должно быть с отступом, чтобы показать, что это часть функции:

def myfunction():
    print("This")
    print("is a")
    print("function")

Использование функций

Итак, теперь, когда у нас есть функция, как её использовать и почему это лучше, чем просто написать код напрямую?

Мы можем вызвать функцию, просто написав имя функции со скобками, вот так:

myfunction()

Почему это лучше? Давайте рассмотрим два примера, в которых мы хотим три раза вывести набор строк. Оба следующих примера дают одинаковый результат (попробуйте сами), однако код с функциями короче.

Это может показаться несущественным в данном примере, однако в крупных проектах это может сэкономить ОЧЕНЬ МНОГО строк кода. Кроме того, обновлять код становится намного проще, поскольку нужно изменить только функцию, а не каждое место в программе.

Пример без функции

print("This")
print("is a")
print("function")

print("This")
print("is a")
print("function")

print("This")
print("is a")
print("function")

Пример с функцией

def myfunction():
    print("This")
    print("is a")
    print("function")

myfunction()
myfunction()
myfunction()

Аргументы функции

Мы упоминали аргументы ранее, так что давайте поговорим о них кратко, прежде чем перейти к примеру с джинглом.

Аргументы в функциях необязательны. Они позволяют передавать информацию в функцию, если это необходимо. Мы передаём информацию функции, когда вызываем её — это может звучать немного запутанно, так что давайте сразу перейдём к примеру, чтобы увидеть всё в действии.

В примере ниже наша функция снова выводит три строки, но на этот раз мы включаем аргументы, запрашивая имя, отчество и фамилию — поскольку нам может понадобиться изменять имена при каждом вызове функции. Эти аргументы используются в строках вывода, как видно ниже:

def myfunction(first,middle,last):
    print(first)
    print(middle)
    print(last)

Теперь, чтобы вызвать функцию И передать ей имена, добавим имена на те же места внутри скобок (обязательно указав их в кавычках, так как речь идёт о строках):

myfunction("Joe","David","Bloggs")

Попробуйте это сами, затем добавьте четвёртый аргумент и выведите ещё одну строку.

Код

Теперь, когда мы знаем, как работают функции, давайте применим их к нашему примеру с джинглом и посмотрим, сможем ли мы сократить длину кода.

В примере ниже используется функция playtone для управления всеми изменениями громкости, задержками и нотами. Вам остаётся лишь передать аргументы для каждого вызова.

Наши аргументы:

  • note — нота для воспроизведения

  • vol — громкость

  • delay1 — первый период time.sleep

  • delay2 — второй период time.sleep

Код примерно вдвое короче и делает то же самое — намного лучше!

# Импорты
from machine import Pin, PWM
import time

# Настройка контакта зуммера как ШИМ
buzzer = PWM(Pin(13)) # Переводим зуммер в режим ШИМ

# Создаём библиотеку переменных тонов для "Jingle Bells"
C = 523
D = 587
E = 659
G = 784

# Создаём переменную громкости (рабочий цикл)
volume = 32768

# Создаём нашу функцию с аргументами
def playtone(note,vol,delay1,delay2):
    buzzer.duty_u16(vol)
    buzzer.freq(note)
    time.sleep(delay1)
    buzzer.duty_u16(0)
    time.sleep(delay2)

# Воспроизводим мелодию
playtone(E,volume,0.1,0.2)
playtone(E,volume,0.1,0.2)
playtone(E,volume,0.1,0.5) #Более длинная вторая задержка

playtone(E,volume,0.1,0.2)
playtone(E,volume,0.1,0.2)
playtone(E,volume,0.1,0.5) #Более длинная вторая задержка

playtone(E,volume,0.1,0.2)
playtone(G,volume,0.1,0.2)
playtone(C,volume,0.1,0.2)
playtone(D,volume,0.1,0.2)
playtone(E,volume,0.1,0.2)

# Рабочий цикл 0 — выключаем зуммер
buzzer.duty_u16(0)

Бонус — больше нот!

Крис Э. любезно добавил в комментариях полный список нот (включая диезы). Ищите любимые мелодии, используйте эти ноты и проявляйте творческий подход. Спасибо, Крис!

  • A = 440

  • As = 466

  • B = 494

  • C = 523

  • Cs = 554

  • D = 587

  • Ds = 622

  • E = 659

  • F = 698

  • Fs = 740

  • G = 784

  • Gs = 830

День #5 завершён!

Сегодня мы охватили многое, юные мастера, — похлопайте себя по плечу!

По мере того как мы переходим к более сложным темам, таким как функции, вам может потребоваться возвращаться к предыдущим дням, чтобы освежить память — и это абсолютно нормально! Даже самые опытные профессиональные программисты регулярно пользуются поисковыми системами (некоторые даже признают это!).

Итак, что мы охватили в день #5? Сегодня вы:

  • Собрали схему с зуммером — вашим первым звуковым компонентом

  • Научились использовать зуммер с MicroPython и Pico

  • Узнали о частоте ШИМ и рабочем цикле применительно к зуммерам

  • Использовали аналоговые входы для управления громкостью с помощью ШИМ

  • Создали праздничную мелодию с MicroPython

  • Научились использовать функции для упрощения кода и повышения его эффективности

Сохраните схему в безопасном месте до завтра (не разбирайте ничего пока).


Схемы подключения на макетной плате созданы с помощью Fritzing.