Гирлянда из первого урока раздела уже висела над столом, эффекты шли своим чередом, и оставалось последнее чудо — чтобы лента дышала вместе с музыкой. Я воткнул микрофонный модуль MAX4466 в A0, написал десять строк с analogRead и получил идеально ровную подсветку, которая не реагировала ни на голос, ни на колонку на полной громкости. Монитор порта показывал почти неизменное число около 512. Полчаса я подозревал брак модуля, потом провод, потом себя, пока не вспомнил физику: выход MAX4466 всегда сидит на середине питания — при 5 В это примерно 2,5 В, что после АЦП и есть 512, — а звук лишь качает это напряжение вверх и вниз. Мой код смотрел на среднюю точку и делал вывод «тишина», потому что размах между качаниями он не считал вообще. В этом уроке разбираем модуль, огибающую громкости, VU-метр на ленте и честно говорим про спектр: что из звука Uno вытаскивает, а что — нет.
MAX4466: что стоит на выходе модуля
MAX4466 — это электретный микрофон и усилитель на одноимённой микросхеме, собранные на готовой платке с тремя пинами: питание, земля, выход. Питание — от 2,4 до 5,5 В, сам модуль ест смешные 24 мкА, искажения — 0,05 %, то есть для Arduino он идеальный сосед. Главный орган — синий подстроечник на плате: им задаётся усиление, до 125 раз. Вывернете в минимум — лента не увидит даже крик; вывернете в максимум — сигнал упрётся в потолок 0 и 1023 при каждом слове, и размах потеряется так же, как при слабом сигнале. Настраиваю я его так: открываю Serial Plotter, говорю обычным голосом вполоборота и кручу отвёрткой, пока волны не займут треть-две трети экрана высоты. Пять минут — и модуль видит разговор с другого конца комнаты, но не спотыкается от хлопка ладонью в полуметре.
Второе, что нужно знать про выход: постоянная составляющая. В тишине на пине лежит половина питания, звук — это переменные качания вокруг неё. Значит, работать надо не с самим значением analogRead, а с его отклонением от 512. Отсюда и выросла моя первая цветомузыка: размах сигнала за короткое окно — это громкость, а громкость уже превращается в пиксели.
| Подход | Что меряем | Цена на Uno | Что увидим на ленте |
|---|---|---|---|
| Огибающая (max − min за окно 25–50 мс) | Громкость в моменте | Почти бесплатно: сотни выборок analogRead в окно | VU-столбик, пульс в такт музыке |
| FFT, 64 точки (arduinoFFT) | Басы, середина, верх по отдельности | Единицы миллисекунд на кадр плюс пара буферов по 128 байт | Три-пять цветных зон: низ красный, верх синий |
| Трюк «бас сам вылезает» | Та же огибающая | Бесплатно | Ритм пляшет в такт бочке: в реальной музыке максимум почти всегда в басах |
Как считается огибающая. За окно времени — 25–50 мс — набираем минимум и максимум всех выборок analogRead, размах vmax − vmin и есть уровень. Одно чтение АЦП на Uno занимает около 112 мкс (делитель 128 при 16 МГц), значит за окно 50 мс успевает около 440 выборок — с запасом на любую музыку. Чтобы столбик не дрожал как заячий хвост, добавляю пик-холд: если новый уровень больше старого — беру его сразу, если меньше — старый сам спадает на несколько процентов за кадр, и падение получается живым, как у стрелочного индикатора. Честная оговорка про частоту: при 9 килогерцах выборок Найквист — 4,5 кГц, поэтому басы и середина слышны отлично, а тарелки и шипящие живут выше — их Uno с голым analogRead не достанет, нужен FFT.
Гашение хвоста столбика я не пишу циклом — за меня это делает fadeToBlackBy, про которую мы подробно говорили в уроке про затухание. И вообще почти вся каркасная работа уже сделана в прошлых уроках: кадры рисуются по таймеру millis() из каркаса без delay, цвет выбирается по колесу оттенков. Нового здесь ровно одна идея — превратить переменное напряжение в число.
VU-метр: от уровня к пикселям
#include <FastLED.h> #define LED_PIN 6#define NUM_LEDS 60#define MIC_PIN A0 CRGB leds[NUM_LEDS];unsigned long lastFrame = 0;unsigned long winStart = 0;int vmin = 1023;int vmax = 0;int level = 0; void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(160);} void loop() { // окно 40 мс: копим минимум и максимум сигнала if (millis() - winStart >= 40) { winStart = millis(); int span = vmax - vmin; if (span > level) { level = span; } // новый пик — берём сразу else { level = level - level / 12; } // старый пик плавно спадает vmin = 1023; vmax = 0; } int raw = analogRead(MIC_PIN); // в тишине — около 512 if (raw < vmin) { vmin = raw; } if (raw > vmax) { vmax = raw; } // кадр 30 мс: уровень превращается в пиксели if (millis() - lastFrame >= 30) { lastFrame = millis(); int lit = map(level, 10, 400, 0, NUM_LEDS); lit = constrain(lit, 0, NUM_LEDS); for (int i = 0; i < NUM_LEDS; i++) { if (i < lit) { leds[i] = CHSV(map(level, 10, 400, 120, 0), 255, 255); } else { leds[i].fadeToBlackBy(40); } } FastLED.show(); }}Две карты в этом скетче — его характер. Первая растягивает уровень в число горящих пикселей: диапазон 10–400 подобран под размах голоса при среднем усилении, тише 10 — молчим, громче 400 — горит вся лента. Вторая красит горящие пиксели от зелёного (оттенок 120) к красному (0) по мере роста уровня — глаз ловит цвет быстрее, чем длину столбика, и по одному взгляду видно, что стало громче. Диапазоны уровней у каждого свои: поменяйте усиление подстроечником — перепроверьте и 400, иначе столбик либо не доползает до конца, либо живёт в красной зоне.
Про спектр. Библиотека arduinoFFT ставится из Менеджера библиотек и превращает 64 выборки в 32 частотных бина; на Uno такое преобразование занимает единицы миллисекунд — при кадре 20–30 мс это заметная, но посильная доля. Дальше бины группируются в три полосы — низ, середина, верх — и каждая красит свой участок ленты: получается классическая «светомузыка из нулевых». В движок симулятора этого курса FFT не входит, поэтому в симуляторе ниже живёт огибающая — но логика уровней, пик-холда и раскраски там ровно та же, что в коде выше.
И про шум. Аналоговый вход чувствителен к тому, что творится на макетке: линия данных ленты гонит 800-килогерцовый поток бит и фонит, когда провода идут в одном жгуте с микрофоном. Я развёл их по разным сторонам макетки — наводки ушли. Земля у модуля должна быть общей с землёй ленты и платы, а конденсатор 1000 мкФ у питания ленты из урока про питание WS2812 полезен вдвойне: он не только спасает первый пиксель, но и сглаживает пульсации, которые иначе лезут в микрофонный вход. Если после этого в тишине столбик всё же подёргивается на единицы — это нормальный шум АЦП, порог 10 в map его полностью отрезает.
- 1. Что вернёт analogRead с модуля MAX4466 в тишине при питании 5 В?
- 2. Что такое огибающая громкости в этом уроке?
- 3. Сколько выборок analogRead Uno сделает за окно 50 мс?
- 4. Зачем оттенок пикселя привязан к уровню (зелёный в тишине, красный на пике)?
- 5. Что добавляет FFT по сравнению с одной огибающей?
- Выход MAX4466 в тишине сидит на середине питания (около 512): звук качает значение вверх-вниз, работать надо с отклонением
- Огибающая — размах max − min за окно 25–50 мс; за 50 мс Uno набирает около 440 выборок
- Пик-холд делает столбик живым: новый уровень берётся сразу, старый спадает на несколько процентов за кадр
- FFT (arduinoFFT, 64 точки) стоит единицы миллисекунд за кадр и даёт настоящие полосы бас/середина/верх
- Усиление подстроечника настраивают по Serial Plotter: волны занимают треть-две трети экрана, упор в 0 и 1023 — перебор
Частые вопросы по теме урока
- Что вернёт analogRead с модуля MAX4466 в тишине при питании 5 В?
- Выход модуля имеет постоянную составляющую на середине питания: при 5 В это 2,5 В, что после АЦП и даёт 512. Звук лишь качает значение вокруг этой точки, поэтому работать надо с отклонением от 512, а не с самим числом. Около 512 — половина питания
- Что такое огибающая громкости в этом уроке?
- Огибающая — это размах vmax − vmin за короткое окно. За 50 мс Uno успевает набрать около 440 выборок analogRead, поэтому даже быстрый удар барабана попадает в окно целиком. Размах между максимумом и минимумом за окно 25–50 мс
- Сколько выборок analogRead Uno сделает за окно 50 мс?
- АЦП с делителем 128 при 16 МГц тратит около 112 мкс на чтение: 50 000 / 112 даёт примерно 440 выборок. Частота Найквиста при этом около 4,5 кГц — басы и середина видны, тарелки нет. Примерно 440 — одно чтение занимает около 112 мкс
- Зачем оттенок пикселя привязан к уровню (зелёный в тишине, красный на пике)?
- По одному взгляду человек оценивает цвет за доли секунды, а длину столбика приходится прочитывать. Совмещение обоих признаков — длина плюс цвет — даёт индикатор, понятный даже в темноте с другого конца комнаты. Цвет считывается глазом быстрее, чем длина столбика
- Что добавляет FFT по сравнению с одной огибающей?
- FFT раскладывает 64 выборки на 32 частотных бина, которые группируются в полосы — низ, середина, верх — и каждая красит свой участок ленты. Цена на Uno — единицы миллисекунд на кадр, что при 20–30 мс на кадр посильно. Разделяет сигнал на частотные полосы: бас видно отдельно от верхов