Стробоскоп для детской дискотеки я собрал за полчаса: потенциометр, светодиод, delay. Работало, но ручка частоты «догоняла» с опозданием: крутишь — а ритм меняется только после очередной вспышки, и на медленных настройках ждать приходилось до секунды. Второй сюрприз был красивым: в темноте, при некоторых положениях ручки, вращающийся пропеллер настольного вентилятора вдруг замирал — так я впервые увидел стробоскопический эффект не в учебнике, а в своей комнате. Племянник потребовал стробоскоп на следующий день, и вторая сборка уже была осознанной: с полупериодом, таймером и честным диапазоном частот.
В этом уроке собираем стробоскоп правильно: analogRead(A0) читает ручку, map() переводит 0–1023 в диапазон 1–15 Гц, а мигает светодиод не delay(), а millis-таймером — поэтому частота подстраивается мгновенно, в любой момент цикла. Разберём математику периода и полупериода, поймаем оба моих тупика — лаг delay-версии и перепутанный полупериод — и честно поговорим о технике безопасности: мигающий свет не игрушка, и у этого утверждения есть конкретные цифры.
Период, полупериод и куда девается частота
Частота и период — обратные друг другу величины: частота f герц означает f вспышек в секунду, а период одного цикла равен 1000 / f миллисекунд. При скважности 50 процентов — вспышка занимает половину цикла — светодиод горит полупериод 500 / f мс и столько же темен. Цифры для нашей вилки: 1 Гц — это 500 мс горит и 500 мс темнота, ленивый маяк; 10 Гц — по 50 мс, уже ритмичная тряска; 15 Гц — по 33 мс, вспышки ещё различимы по отдельности, но глазу уже тесно. Мой первый тупик случился ровно здесь: я ставил delay(период) вместо delay(полупериода), и стробоскоп мигал вдвое медленнее расчётного — период это вспышка плюс пауза, а не пауза сама по себе.
Ручка потенциометра отдаёт analogRead(A0) от 0 до 1023, а стробоскопу нужен диапазон 1–15 Гц — переводит map(raw, 0, 1023, 1, 15). Это та же связка, что в уроке про analogRead и уроке про dimmer с map, только масштабируемся мы не в яркость, а в герцы. Нижняя граница именно единица, не ноль: при нуле герц деление 500 / f провалится в деление на ноль, и скетч умрёт арифметикой, а не светодиодом. Заодно заметим: analogRead никаких pinMode не требует — аналоговые входы всегда настроены на чтение, поэтому ручка подключается в три действия: плюс и минус питания, средний контакт в A0. map округляет вниз целочисленно, и на границах диапазона значение дёргается между соседними герцами — к этому вернёмся при печати в Serial.
Про верх диапазона — честно и с цифрами. Отдельные вспышки глаз различает примерно до 40–50 Гц, выше они сливаются в ровный свет, и стробоскоп теряет смысл: на дискотеках классика — 4–12 Гц, маяки живут на 1–2 Гц. Мои 15 герц — верх с запасом до порога слияния: круче уже не стробоскоп, а просто включенный свет с невидимой пульсацией, как у сети с её 50–100 мерцаниями в секунду, которые мы не замечаем.
const int SVET = 12; void setup() { pinMode(SVET, OUTPUT); Serial.begin(9600);} void loop() { int raw = analogRead(A0); int gz = map(raw, 0, 1023, 1, 15); int poluperiod = 500 / gz; // 1 Гц: 500 мс, 15 Гц: 33 мс digitalWrite(SVET, HIGH); delay(poluperiod); digitalWrite(SVET, LOW); delay(poluperiod); // loop занят паузой: ручка ждёт}Delay-версия корректна математически и полностью годится для разового эффекта: частота действительно меняется от ручки. Но посмотрите, где в ней стоит analogRead: он выполняется один раз за цикл, между вспышками. При 15 Гц это незаметно — цикл 66 мс; при 1 Гц между опросами ручки проходит секунда, и настройка доехала бы с лагом до полупериода. Мой дискотечный симптом «крутишь — ритм догоняет» был именно этим: loop был занят delay и не мог читать ручку чаще, чем раз за цикл вспышки.
Второй тупик delay-версии мягче: печать в Serial. Если вставить её в такой loop, на 15 Гц она забьет порт тремя сотнями строк в секунду — я печатаю частоту только при её изменении, сравнивая с прошлым значением. А в полноценной версии из симулятора ниже печать и чтение ручки живут вне таймера вспышек: millis-гейт отделяет «когда мигнуть» от «что происходит вокруг». Вся философия без-delay-скетчей с картинкой и объяснениями — в уроке про millis вместо delay; здесь один таймер, но приём тот же.
Millis-версия, ШИМ и техника безопасности
Полная версия хранит lastSmena — момент последней смены состояния диода — и на каждом обороте loop спрашивает: прошло ли полупериода с тех пор. Прошло — переключили, запомнили новое время. Всё остальное — чтение ручки, пересчёт частоты, печать — выполняется на каждом обороте, сколько бы вспышек ни было. Ручка подхватывается мгновенно: между двумя вспышками loop успевает провернуть десятки полных опросов. Тот же принцип, на котором держатся мультизадачные скетчи из урока про millis и автомат светофора, — просто таймер тут один. Таймеров легко становится больше: встряска кубика из первого урока этой тройки, маяк из меню, вспышки стробоскопа — один и тот же каркас «прошло ли N мс с прошлой смены» обслуживает их всех, а loop остаётся свободным для кнопок и ручек.
Светодиод в этой макетке сидит на D12 без ШИМ, и стробоскопу это не мешает: яркость вспышки никто не режет, вспышка всегда на полную — в этом жанр. Захотите «мягкий» дежурный режим с плавной яркостью — переносите диод на ШИМ-пин и берите analogWrite, как в уроке про ШИМ; самодельный ночник на этом же пине собирался в первом уроке про мигание. На реальной плате для мощного диода (1–3 Вт) светодиод запитывают через транзистор: пин Arduino выдаёт максимум около 20–40 мА, а дискотечному стробоскопу нужны амперы.
И серьёзная часть. Вспышки в диапазоне примерно 3–30 в секунду у части людей провоцируют приступы фотосенситивной эпилепсии — это не страшилка, а ограничение, с которым считаются производители всего мигающего. Практические правила моего дома: стробоскоп светит в стену или потолок, никогда — в лицо и в глаза; гости предупреждены до включения; первый запуск делаем на минимальной частоте. Направление вспышки тоже имеет значение: заливающий свет в упор опаснее отражённого от стены при той же мощности. Одна вспышка в секунду видна всем как эффект, и дискотека от этого не проигрывает.
| Частота | Эффект | Где живёт |
|---|---|---|
| 1–2 Гц | Ленивые вспышки, глаз успевает отдохнуть | Маяки, ночные индикаторы, первый запуск стробоскопа |
| 4–12 Гц | Классический диско-строб | Танцполы, сцена, моя детская |
| 15 Гц | Вспышки ещё различимы — верх диапазона урока | Наш потенциометр, 33 мс на полупериод |
| 50+ Гц | Вспышки сливаются в ровный свет | За порогом слияния стробоскоп бессмыслен |
| Совпала с оборотами | Вращающийся предмет «замирает» | Строботахометр: проверка вентиляторов и моторов |
В симуляторе макетка — потенциометр на A0 и светодиод на D12. У диода в этой макетке нет ШИМ, и стробоскопу он не нужен: мигаем digitalWrite-ом на полную яркость, как и положено вспышке. Ползунок от края до края — это map от 0–1023 к 1–15 Гц; Serial печатает частоту при каждом её изменении строкой вида «Частота: 7 Гц», так что ручку можно калибровать по логу, а не на глаз.
Проверьте лаг на обеих версиях: в delay-версии из кода выше ручка доезжает с опозданием до полупериода, в millis-версии ниже — мгновенно. Заодно покрутите диапазон на самых низких герцах: на 1 Гц полупериод 500 мс, и вспышка превращается в маяк — хорошая проверка, что вы понимаете, откуда берутся эти числа. Полупериод в скетче считается в одну строку — 500 / gz, — и на быстрых настройках loop проворачивается десятки раз между вспышками: это и есть мгновенная подстройка.
- Измените скважность: пусть вспышка занимает 20 процентов цикла — горит 100 / gz мс, темнеет 400 / gz. Частота останется той же, а характер света станет резким, вспышечным.
- Найдите порог слияния: расширьте map до 1–40 Гц и ищите глазами, где вспышки перестанут различаться (в симе диод виртуальный, на плате порог виден чётко). Сверьте с таблицей: 50 Гц и выше — ровный свет.
- Добавьте «паузу» кнопкой на D2: пока зажата, вспышки не идут, а при отпускании таймер не должен стрелять очередью — обнулите lastSmena при выходе из паузы.
- Напечатайте рядом с герцами и raw: Serial.print("raw="). Покрутите ручку и найдите зону, где map уже сменил герц, а глаз ещё не видит разницы — это честная карта чувствительности вашей ручки.
- Частота f Гц — период 1000 / f мс; при скважности 50 процентов светодиод горит полупериод 500 / f: 10 Гц — это 50 мс горит, 50 мс темнота.
- map(analogRead(A0), 0, 1023, 1, 15) превращает ручку в 1–15 Гц; нижняя граница единица, иначе 500 / gz упрётся в деление на ноль.
- Delay-версия работает, но loop занят паузой и ручка подхватывается с лагом до полупериода; millis-таймер делает подстройку мгновенной.
- Выше примерно 40–50 Гц вспышки сливаются в ровный свет; диско-диапазон 4–12 Гц, маяки 1–2 Гц.
- Вспышки 3–30 Гц — зона риска фотосенситивной эпилепсии: светим в стену, не в лицо, предупреждаем гостей; мощный диод — только через транзистор.
- 1. Светодиод мигает с частотой 10 Гц при скважности 50 процентов. Сколько миллисекунд он горит?
- 2. Почему в delay-версии ручка частоты работает с запаздыванием?
- 3. analogRead(A0) вернул 0. Какую частоту даст map(raw, 0, 1023, 1, 15)?
- 4. Что видит глаз при вспышках чаще примерно 50 Гц?
- 5. Почему стробоскоп спокойно живёт на пине без ШИМ?
Частые вопросы по теме урока
- Светодиод мигает с частотой 10 Гц при скважности 50 процентов. Сколько миллисекунд он горит?
- Период цикла 1000 / 10 = 100 мс, при скважности 50 процентов вспышка занимает половину: 500 / 10 = 50 мс горит и 50 мс темнота. 50 мс
- Почему в delay-версии ручка частоты работает с запаздыванием?
- delay держит loop занятым полупериода: при 1 Гц ручка опрашивается раз в секунду, и новое значение частоты доезжает с лагом до полупериода. Millis-таймер снимает ограничение. loop занят delay и читает ручку только между вспышками
- analogRead(A0) вернул 0. Какую частоту даст map(raw, 0, 1023, 1, 15)?
- Нижняя граница выхода map — единица, не ноль: ноль герц сломал бы расчёт полупериода делением 500 / gz на ноль. Поэтому диапазон именно 1–15. 1 Гц
- Что видит глаз при вспышках чаще примерно 50 Гц?
- За порогом слияния — примерно 40–50 Гц — отдельные вспышки перестают различаться и свет кажется постоянным. Стробоскопический эффект исчезает. Ровный немигающий свет
- Почему стробоскоп спокойно живёт на пине без ШИМ?
- Стробоскоп управляет не яркостью, а частотой: вспышка всегда полной яркости, и digitalWrite хватает. ШИМ понадобился бы только для плавной яркости дежурного режима. Яркость вспышки не регулируют — вспышка всегда на полную