Новогоднее окно я завещал ленте, реагирующей на музыку, — и первая версия позорилась: лента дёргалась от пылесоса, слепила на басах и гасла в тихих куплетах. Причина была в одном замере на цикл: analogRead ловит случайную фазу звуковой волны, и «громкость» получалась лотереей. Вторая версия дышала ровно, когда я собрал три вещи: окно максимумов, сглаживание и порог на пик. Механика та же стоит в дверном звонке с подсветкой — работает безотказно третий год.
Столбики-эквалайзер, где лента показывает спектр, — в уроке про эквалайзер; бегущие искры с затуханием — в уроке про random8 и искры. Здесь угол — вся лента целиком: амплитуда управляет яркостью, пик бьёт вспышкой, и всё это без delay, на каркасе millis. Спектр здесь не нужен — достаточно одной величины.
Амплитуда, пик, вспышка: три величины
MAX4466 смещает выход на середину питания: в тишине на A0 около 512, волна звука качает значение туда-сюда. Амплитуда — это отклонение от покоя: analogRead минус 512, по модулю. Один замер бесполезен, поэтому беру окно 30 миллисекунд и выуживаю в нём максимум отклонения — бас за окно успеет показать хвост, шипение — свой пик. Дальше величина сглаживается: amp = (amp*7 + level)/8 — яркость дышит, а не дёргается, каждое новое значение тянет среднее лишь на восьмую часть.
Пик — отдельная ветка. Если уровень окна перевалил порог GATE, flash ставится в 255, и каждый кадр затухает примерно на десять процентов: flash = flash*230/255. Вспышка рисуется поверх дыхания fill_solid белым — лента на долю секунды бьёт в потолок и плавно возвращается к синеве. Порог подбирается один раз: запустите скетч с Serial-печатью уровня в тишине комнаты, запишите шумовой пол, поставьте GATE в полтора-два раза выше — и лента перестанет реагировать на пылесос.
#include <FastLED.h>#define LED_PIN 6#define NUM 24CRGB leds[NUM]; int amp = 0; // сглаженная громкость 0..255int flash = 0; // вспышка 0..255const int GATE = 120; // порог пика, снять по Serial в тишине void setup() { FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM); FastLED.setBrightness(180); Serial.begin(9600); // для подбора GATE} void loop() { // окно 30 мс: максимум отклонения от покоя 512 unsigned long t0 = millis(); int peak = 0; while (millis() - t0 < 30) { int dev = analogRead(A0) - 512; if (dev < 0) dev = -dev; if (dev > peak) peak = dev; } int level = map(peak, 0, 400, 0, 255); level = constrain(level, 0, 255); amp = (amp * 7 + level) / 8; // дышим без дёрганья if (level > GATE) flash = 255; // пик - бьём вспышкой flash = (flash * 230) / 255; // -10% за кадр Serial.println(level); // плоттер для подбора GATE for (int i = 0; i < NUM; i++) { leds[i] = CHSV(160, 255, amp); // синева дышит громкостью } if (flash > 20) fill_solid(leds, NUM, CRGB(flash, flash, flash)); FastLED.show();}Разбор. Окно через while с millis работает как delay, только не останавливает скетч: внутри цикла можно успевать проверять кнопки — здесь я оставил голый минимум. map(peak, 0, 400, ...) растягивает сырое отклонение 0-400 единиц АЦП на весь диапазон яркости: у моего модуля басы бьют до 350-400, у вашего числа будут свои — те же 400 поменяйте по плоттеру. Сглаживание (a*7+l)/8 — фильтр скользящего среднего за одно умножение: резкие скачки прячутся, а вспышка живёт отдельной переменной и сглаживание не портит.
Затухание flash*230/255 съедает десятую часть за кадр: при 30-40 кадрах в секунду вспышка гаснет за полсекунды — поменяйте 230 на 210, если хочется строже, или на 245 для хвоста подлиннее. Порог 20 в if(flash > 20) отсекает долгий хвост тусклого налёта: ниже этого уровня вспышка просто перестаёт рисоваться. И про питание: лента на полной вспышке с амплитудой 255 требует свой 5 В с общей землёй — правило из урока про инжекцию питания здесь не пожелание, а необходимость.
- Окно максимумов: 20-50 мс — меньше ловит случайную фазу, больше запаздывает
- GATE: снять по Serial-плоттеру в тишине, поставить в 1,5-2 раза выше шумового пола
- Сглаживание (a*7+l)/8: делитель крупнее — реакция резче, мельче — дышит плавнее
- Затухание flash*230/255: 210 — короткая вспышка, 245 — длинный хвост
- Потолок setBrightness 160-180: вспышка всё равно бьёт белым, а глазу легче
- 1. Почему амплитуду ищут в окне 20-50 мс, а не одним analogRead?
- 2. Что делает строка flash = (flash * 230) / 255 на каждом кадре?
- MAX4466 смещён на VCC/2: покой около 512, амплитуда = |analogRead - 512|
- Окно максимумов 20-50 мс надёжнее одного замера; сглаживание (a*7+l)/8 убирает дёрганье
- Порог GATE снимается Serial-плоттером в тишине: в 1,5-2 раза выше шумового пола
- Пик: flash = 255, затем -10% за кадр; вспышка поверх дыхания через fill_solid
- Ленте на вспышке — отдельные 5 В с общей землёй, провод микрофона короче 30 см
Частые вопросы по теме урока
- Почему амплитуду ищут в окне 20-50 мс, а не одним analogRead?
- Звук — волна на сотни герц: один замер в момент перехода через покой покажет тишину на самом громком басе. Максимум за окно ловит пик независимо от фазы. Одиночный замер ловит случайную фазу волны: громкий бас может попасть в ноль
- Что делает строка flash = (flash * 230) / 255 на каждом кадре?
- Умножение на 230/255 отнимает десятую часть яркости за кадр — экспоненциальное затухание за полсекунды, без float и без массивов. Гасит вспышку примерно на 10% за кадр — плавное затухание