Звук на Arduino
В этой статье я рассмотрю примеры работы со звуков на контроллере Arduino
Данный пример я планирую использовать в системе звукового оповещения домашней метеостанции, чтобы своевременно реагировать на критические значения измеряемых параметров.
Подключение пьезоизлучателя к Arduino
На самом деле подключение очень простое:
- 1 вывод пьезоизлучателя подключаем к 9 дискретному пину Arduino
- 2 вывод пьезоизлучателя подключаем к GND Arduino
Генерация звуков на Arduino
Для генерации звуков на Arduino существует функция tone()
Функция tone()
Генерирует сигнал прямоугольной формы с заданной частотой. Длительность может быть задана параметром. Без указания длительности сигнал генерируется пока не будет вызвана функция noTone(). К порту Arduino может быть подключен к пьезо или другой высокоомный динамик для воспроизведения сигнала. Одновременно может воспроизводиться только один сигнал.
Синтаксис функции tone()
- tone(pin, частота)
- tone(pin, частота, длительность)
Пример использования функции tone()
const int SoundPin = 9; // Пин подключения пьезоизлучателя – 9 дискретный
int DelaySound = 1000; // Пауза 1 секунда
void loop()
<
// Пример использования tone()
//tone(pin, частота)
tone(SoundPin, 1915); // Воспроизводим сигнал с частотой 1915 Гц
delay(DelaySound); // Пауза 1 секунда (1000 миллисекунд – значение переменной DelaySound ) – длительность воспроизведения сигнала
tone(SoundPin, 1700);
delay(DelaySound);
tone(SoundPin, 1519);
delay(DelaySound);
tone(SoundPin, 1432);
delay(DelaySound);
tone(SoundPin, 1275);
delay(DelaySound);
tone(SoundPin, 1136);
delay(DelaySound);
tone(SoundPin, 1014);
delay(DelaySound);
Play a Melody using the tone() function
This example shows how to use the tone() command to generate notes. It plays a little melody you may have heard before.
Hardware Required
piezo buzzer or a speaker
Circuit
Schematic
The code below uses an extra file, pitches.h. This file contains all the pitch values for typical notes. For example, NOTE_C4 is middle C. NOTE_FS4 is F sharp, and so forth. This note table was originally written by Brett Hagman, on whose work the tone() command was based. You may find it useful whenever you want to make musical notes.
The main sketch is as follows:
To make the pitches.h file, either click on the button just below the serial monitor icon and choose «New Tab», or use Ctrl+Shift+N.
Then paste in the following code:
and save it as pitches.h
See Also
BlinkWithoutDelay — Blink an LED without using the delay() function.
Button — Use a pushbutton to control an LED.
Debounce — Read a pushbutton, filtering noise.
DigitalInputPullup — Demonstrates the use of INPUT_PULLUP with pinMode().
StateChangeDetection — Count the number of button pushes.
toneKeyboard — A three-key musical keyboard using force sensors and a piezo speaker.
toneMultiple — Play tones on multiple speakers sequentially using the tone() command.
tonePitchFollower — Play a pitch on a piezo speaker depending on an analog input.
Подключение пьезопищалки и генерация звука. Функции tone() и noTone()
Сегодня мы поговорим о том, как воспроизводить мелодии и отдельные звуки с помощью платы Arduino, применяя функции tone() и noTone().
Элемент, который используется для примера в данной статье, называется пьезопищалка, он же зуммер, он же пьезодинамик.
Ранее мы научились подключать к плате светодиод и познакомились с функциями digitalWrite() и delay(). С их помощью можно подавать напряжение на пины контроллера и приостанавливать выполнение программы. Эти функции здесь тоже пригодятся.
Для чего нам звуки
Любой робот или другое электронное устройство становятся проще в использовании, если имеют возможность сигнализировать о своем состоянии. Для индикации могут использоваться световые или акустические элементы.
К примеру, известный робот R2-D2 использовал оба типа обратной связи — он мигал и издавал странные звуки.
Для начала разберемся, как работает пьезопищалка. Очевидно, что она должна издавать звук. Но что это и как возникает?
Что такое звук
Звук — это колебания воздуха, которые волнами расходятся от своего источника. Волны эти не видны, однако воздействуют на барабанные перепонки, вызывая в них механические колебания, которые мозг человека умеет преобразовывать в звуки.
Получается, чтобы появились колебания и возник аудиосигнал, пьезопищалка должна двигаться и воздействовать на воздух.
Так оно и есть. Пьезодинамик может совершать маленькие, незаметные для глаз движения под воздействием электричества. Мы их не увидим, однако воздух колебать они могут, а значит будет издаваться звук.
Имея в своем распоряжении Arduino, мы можем использовать электричество и подавать напряжение на пищалку по определенной программе.
Как извлечь звук из пьезопищалки
Если подключить пьезопищалку напрямую к батарейному отсеку и подать постоянный ток на пластину, то ничего не произойдёт. Попробуем подключить ее к какому-нибудь D-пину и GND нашего Arduino.
Но сначала нужно загрузить в плату программу, которая будет управлять напряжением на этом пине:
Итак, код залит в плату. Присоединяем новый элемент.
Рис. 2. Подключение пьезопищалки к плате Arduino
Настройка звучания сигнала
Если все сделано, как описано выше, то должны быть слышны щелчки.
Мы установили чередование подачи и отключения напряжения с длительностью в 500 тысячных долей секунды через параметр функции delay(). Поэтому звук длится полсекунды, после чего на такой же отрезок времени затихает.
Чтобы колебания происходили быстрее, то есть чаще, нужно уменьшить время задержки.
Поэкспериментируйте с разными значениями для функции delay(). Попробуйте выставить в программе вместо 500 числа 10, 5, 1.
Чем чаще происходят колебания, тем выше звук, который мы слышим. Получается, если увеличивать частоту, звук будет превращаться в тонкий писк. А если уменьшать — в гул или гудение.
Кстати, именно поэтому мы слышим писк, когда рядом летает комар. Он часто-часто машет своими крылышками. В противовес ему — полет шмеля, который гораздо тяжелее и медленнее, поэтому он как бы “гудит”.
Как рождается мелодия
Для вопроизведения последовательности звуков, то есть мелодии, поможет функция tone().
Ее параметры дают возможность управлять направлением сигнала (какой из пинов на контроллере должен быть задействован) и частотой колебаний звука.
Запись функции выглядит так:
Номер пина здесь — это тот пин Arduino, куда подключена пьезопищалка, из которой хотим извлечь звук. А частота — требуемое значение частоты в герцах (положительное целое число).
Рис. 3. Схема подключения пьезопищалки к контроллеру
Пример программы с функцией tone():
При загрузке и запуске данного кода можно получить звук, если к указанному пину подключена пьезопищалка или другое акустическое устройство.
Серия из команд с различными частотами для одного пина позволит нашему Arduino сыграть мелодию, последовательно сменяя один тон на другой.
Одним из применений может стать установка музыкального приветствия на запуск робота:
Горшочек, не вари!
Функция tone() по умолчанию воспроизводит звук нужной частоты без конечного срока, то есть бесконечно. Чтобы прервать его, требуется использовать противоположную по значению операцию — noTone().
У этой функции лишь один параметр — указание нужного пина.
Если на выбранном пине воспроизводится звук, он будет приостановлен. Если же генерации сигнала там не было, то функция ничего не сделает, и программа пойдет дальше.
С небольшими изменениями предыдущий пример превращается в код для мелодии приветствия нашего робота:
Обратите внимание, что при использовании нескольких пьезопищалок нужно сначала подавать noTone() на пин, где уже были активированы колебания, и только потом вызывать tone() на другой пин.
Функция tone, noTone в Ардуино
Arduino tone используется для генерации на выходе микроконтроллера ШИМ-сигнала с 50% заполнением и с заданной частотой. Для воспроизведения аудио сигнала к пину нужно подключить пьезопищалку или динамик через транзистор. Продолжительность сигнала может быть задана параметром в команде tone или генерация остановится при перезагрузке платы или при вызове функции noTone Arduino Uno.
Команда tone в Ардуино описание
Для этого занятия потребуется:
- Arduino Uno / Arduino Nano / Arduino Mega
Воспроизводиться на Ардуино может одновременно только один сигнал. Если аудио сигнал воспроизводится уже на каком то порту, то новый вызов функции tone() с номером другого порта ни к чему не приведет. Если функция tone() Arduino будет вызвана с номером того же порта, то будет воспроизводится сигнал с новой частотой. Т.е. воспроизводится может только одна частота сигнала и на одном пине.
Синтаксис
tone(pin, frequency)
tone(pin, frequency, duration)
Параметры
pin — номер порта вход/выхода, на котором будет генерироваться сигнал
frequency — частота сигнала в Герцах
duration — длительность сигнала в миллисекундах
Функция tone, noTone использует аппаратный таймер, поэтому использовать в скетче ШИМ (PWM) сигнал для пьезодинамика невозможно на портах выхода 3 и 11 (кроме платы Arduino Mega). Генерация сигнала является полуаппаратной, то есть пин Ардуино включается/выключается по прерыванию таймера (Timer 2), поэтому на высокой частоте звука, микроконтроллер может немного притормаживать.
Arduino tone пример программы
Arduino tone максимальная частота
Генерируемая частота звука зависит от размера таймера в плате Arduino. Для микроконтроллера Arduino Uno или Nano и минимальная частота воспроизводимого звука составляет 31 Гц, а максимальная частота tone – 65535 Гц. Диапазон воспринимаемых частот ухом обычного человека значительно меньше.
Генерирование и чтение сигналов
Начнём с самого простого: генерация импульса заданной длины, такое часто бывает нужно. Проще всего сделать это на delay() и delayMicroseconds() :
Нужно помнить, что digitalWrite() сам по себе выполняется в районе 3.6 мкс (58 тактов процессора). Для ускорения можно использовать например библиотеку directIO или прямую работу с регистрами портов.
Генерирование квадратного сигнала
Программное
Квадратный сигнал может быть использован для тактирования и управления, а также для генерации звука через усилитель. Самый базовый пример, Blink, по сути тоже является генератором квадратного сигнала:
Если заменить 1000 например на 10 , то получится квадратный сигнал с частотой 50 Гц. Этот способ называется программной генерацией сигнала, то есть микроконтроллер своими силами считает время и сам вручную дёргает ногой. Это как мешает работе остального кода, так и остальной код может сбивать частоту. Такую генерацию можно сделать более мене асинхронной на миллисе:
На практике такой способ используется редко, потому что на высокой частоте остальной код программы будет мешать генерации и частота будет плавать.
Функция tone()
В ядре Arduino есть встроенная функция для полуаппаратной генерации квадратного сигнала – tone(pin, frequency, duration) :
- pin – цифровой пин, с которого будет генерироваться сигнал.
- frequency – частота в Герцах. Диапазон 31.. 65’535 Гц
- duration – продолжительность сигнала в миллисекундах. Опциональный параметр, если не указывать – сигнал будет генерироваться без остановки.
Для ручной остановки генерации сигнала можно вызвать noTone() . Также у генерации при помощи tone() есть особенности:
- Генерация является полуаппаратной: пин дёргается МК “вручную” по прерыванию таймера, что на высокой частоте может чуть тормозить код.
- Генерация использует Timer 2, перенастройка или использование его для других целей (включая ШИМ на пинах D3 и D11 у Nano) отключит активную генерацию или изменит её частоту.
- При вызове tone() таймер перенастраивается на генерацию, то есть можно использовать таймер в своих целях между вызовами tone() .
- Генерация работает только на одном пине в один момент времени, причём для включения генерации на другом пине нужно сначала отключить текущую генерацию, то есть вызвать noTone() .
ШИМ сигнал
Аппаратный таймер позволяет генерировать квадратный сигнал аппаратно и полностью асинхронно работе остального кода, не тратя ни такта процессорного времени: время считается самим таймером, и сам же таймер управляет состоянием ноги МК. Для генерации ШИМ сигнала в среде Arduino есть функция analogWrite(pin, duty) , подробнее мы говорили в ней в уроке про ШИМ. Чтобы сделать ШИМ квадратным, нужно запустить его с duty , равной 128 . Что касается частоты полученного сигнала, то Ардуино настраивает таймеры так, что частота в зависимости от таймера может быть 490 или 980 Гц. Частоту можно изменить с довольно большим шагом, об этом мы говорили в уроке про увеличение частоты ШИМ.
Аппаратный таймер
Можно вручную настроить аппаратный таймер на генерацию квадратного сигнала. Тонкости настройки регистров таймера мы в рамках этих уроков не разбираем, но это можно сделать и при помощи библиотеки, например GyverTimers. Работу библиотеки мы разбирали в уроке о прерываниях таймера. Данная библиотека позволяет настроить генерацию квадратного сигнала с максимально возможной точностью и частотой, а также поднять на одном таймере генерацию двух или трёх (Arduino MEGA) меандров со смещением по фазе. Пример:
ШИМ сигнал
Аппаратный
Для генерации ШИМ сигнала с заданным заполнением есть стандартная функция analogWrite(pin, duty) , подробнее обсуждали в уроке про ШИМ сигнал, а частоту можно изменить перенастройкой таймера, как в уроке об увеличении частоты ШИМ. На самом деле таймеры позволяют настроить ШИМ сигнал с более точной или более высокой частотой и другими диапазонами заполнения (до 10 бит), но в ядре Arduino это не предусмотрено. Если такое будет нужно, можно воспользоваться библиотекой GyverPWM. Пример:
Программный ШИМ
Программная генерация ШИМ сигнала может пригодиться, если не хватает лишнего таймера или частота ШИМ низкая и не повлияет на остальной код, а он на неё. ШИМ сигнал на “миллисе” можно организовать вот таким образом, переключая выход по двум периодам:
Функцию PWMgen(заполнение) в данной реализации нужно вызывать как можно чаще в основном цикле программы:
Здесь мы на каждом вызове считаем новый период переключения, тратя на это какое-то время. Можно считать период в отдельной функции, а сам ШИМ генерировать отдельно. Реализацию можно посмотреть в библиотеке PWMrelay.
Полуаппаратный ШИМ
Можно снизить нагрузку на процессор, отдав счёт времени аппаратному таймеру. Примеры на базе GyverTimers (для ATmega328, 2560):
Как известно, digitalWrite() является очень тяжёлой и долгой функцией, и для генерации софт ШИМ рекомендуется заменить её чем-то более быстрым, например прямым обращением к регистру или вот такой конструкцией (для ATmega328p):
Если не хватает количества стандартных ШИМ-выходов, можно поднять полуаппаратный ШИМ на таймере на несколько пинов сразу:
Этот алгоритм является не самым оптимальным, более интересный можно посмотреть в GyverHacks.
Примечание: во всех трёх алгоритмах используется проверка совпадения со счётчиком counter == pwm_duty . Это сильно снижает использование процессорного времени в прерывании, но при резком уменьшении заполнения может приводить к одиночным “вспышкам” заполнения до максимума, так как условие не выполнится. Для более плавной работы можно сделать counter >= pwm_duty , тогда условие будет каждый раз “подстраиваться” под новое значение заполнения, но установка пина будет осуществляться на каждом тике!
Можно ввести буферизацию заполнения ШИМ и брать новое значение только при нулевом значении счётчика, это решит проблему:
Можно применить буферизацию и к остальным алгоритмам.
Библиотека Servo
Как известно, RC сервоприводы управляются при помощи ШИМ сигнала с частотой
50 Гц и длительностью импульса от
2500 микросекунд. В стандартной библиотеке Servo.h реализована генерация полуаппаратного ШИМ сигнала, причём количество пинов можно менять во время работы. Библиотеку можно использовать как генерацию ШИМ, если его параметры подходят для использования.
Чтение сигналов
Чтение цифрового сигнала сводится к измерению времени между его импульсами, то есть изменениями состояния HIGH-LOW: так можно измерить период и частоту квадратного сигнала, заполнение и частоту ШИМ и вообще любой другой сигнал.
Функция pulseIn()
В ядре Ардуино есть готовые функции для измерения импульсов:
- pulseIn(pin, value, timeout) – для импульсов от 10 мкс до
3 минут, работает на счёте тактов процессора, лучше работает при отключенных прерываниях, более точно измеряет короткие импульсы.
pulseInLong(pin, value, timeout) – для импульсов от 10 мкс до
3 минут, основано на micros() (т.е. на Таймере 0), не работает при отключенных прерываниях, более точно измеряет длинные импульсы.
Измеренная мной точность на коротких импульсах: 0.5 мкс
Обе функции возвращают длину импульса в микросекундах. Возвращают 0, если импульса не было и был достигнут тайм-аут. Обе функции блокирующие, то есть останавливают выполнение кода, пока не поймают импульс или не завершатся по тайм-ауту. Аргументы:
- pin – цифровой пин (GPIO), на котором ожидается импульс.
- value – направление импульса, HIGH или LOW .
- timeout – тайм-аут ожидания импульса в микросекундах. Необязательный параметр, по умолчанию равен 1’000’000 мкс (1 секунда).
Как это работает: пусть мы настроили импульс на HIGH , функция будет ожидать изменение значения с LOW на HIGH . Если скачок с LOW на HIGH не произошёл за время, установленное тайм-аутом, функция завершит выполнение и вернёт 0.
Для превращения длины импульса (мкс) в частоту (Гц) достаточно поделить на него секунду (точнее, 1’000’000 мкс).
Измеряем сигналы вручную
Квадратный сигнал можно “измерить” вот таким образом:
Таким образом можно сделать измеритель частоты или тахометр, но лучше не выводить в сериал каждый фронт (нагружает процессор и тормозит), а считать импульсы и иногда измерять частоту (см. следующий пример).
Считаем импульсы и иногда делаем расчёт:
Также рассмотрим измерение параметров ШИМ сигнала, например на прерываниях и micros() :
Библиотека тахометра
Также предлагаю использовать класс тахометра, оформленный в виде библиотеки. Скачать можно с гитхаб. Также прикладываю здесь:
“Запоминаем” сигнал
Также можно очень просто запомнить цифровой сигнал в Arduino для дальнейшего воспроизведения и исследования. Вот пример, который будет работать на любом пине (используется digitalRead() и micros()):
Измерение начнётся по изменению сигнала и продлится до тех пор, пока не переполнится буфер или работа не завершится по таймауту (в примере выше 1 секунда с последнего импульса). После этого в монитор порта будет выведен начальный уровень сигнала fval и тайминги каждого следующего фронта/спада (изменения). Для примера я подключил ИК приёмник и нажал кнопку на пульте:
Точность измерения должна быть около 5 мкс на AVR, так как используется тяжёлое чтение пина и микрос. Лучше переписать на прерывания по CHANGE и завести отдельный таймер, тогда точность можно повысить в сотни раз. Код не привожу, так как для разных платформ он будет разный.
Полученные данные можно использовать для анализа интерфейсов и протоколов, а также можно “воспроизвести” запись. В примере ниже я делаю это отдельным скетчем, вставив тайминги и начальное значение сигнала из лога предыдущего примера.
Для визуализации подключил дешёвый китайский логический анализатор (ссылка на али, ещё одна) на указанный пин:
Отлично! Прекрасно виден NEC протокол, его 4-х байтный пакет и код повтора.
Можно реализовать чтение пакета и его вывод в одной программе, запись дампа в EEPROM/SD для воспроизведения по кнопке и прочих сценариев работы, получив дубликатор цифрового сигнала.