Ambilight — подсветка экрана на ESP32 + SK6812
Захват цвета с краёв экрана в реальном времени → передача по UART → отображение
на адресной RGBW-ленте SK6812 через ESP32. В отличие от датчиков остального
курса, здесь поток данных идёт в обратную сторону — компьютер становится
источником сигнала, а ESP32 с лентой — актуатором. Исходный код,
platformio.ini и pyproject.toml — в
firmware/Projects/Ambilight_project.
Ambilight на ESP32 + SK6812
Захват цвета с краёв экрана (Linux) → передача по UART → отображение на адресной
RGBW-ленте SK6812 через ESP32 DevKit V1. Подробная теория (физика/математика/протоколы) —
в THEORY.md, схема подключения — в WIRING.md.
Основные возможности
- Универсальный захват периметра: одна функция
sample_edge()обслуживает все четыре стороны экрана — расширение с двух сторон (left/top) до полного периметра не требует нового кода, только раскомментирования готовых блоков. - Протокол с защитой целостности: кадр
'A','d','a'+ N×4 байта (G,R,B,W) + XOR-чексумма; битый кадр отбрасывается целиком, а не отображается как “мусор” на ленте. - Watchdog обрыва связи: если Python-скрипт не прислал валидный кадр дольше 2 секунд (упал, отключили USB), контроллер сам гасит ленту — не оставляет “замёрзшую” картинку.
- Программный лимит яркости/тока: ограничение через
setBrightness(), чтобы не просаживать слабый источник питания при работе всех светодиодов на максимум. - Два слоя кода: продвинутый класс
Sk6812AmbilightStrip(Adafruit_NeoPixel, нативный RGBW) и педагогический фасадsimple_api.h/.cpp— только простые функции для скетча.
Состав проекта
ambilight.py - Python-скрипт захвата экрана и отправки по UART
include/Sk6812AmbilightStrip.h - продвинутый слой (класс, Adafruit_NeoPixel)
include/simple_api.h - педагогический слой-фасад (простые функции)
src/Sk6812AmbilightStrip.cpp
src/simple_api.cpp
src/main.cpp - главный скетч ESP32 (использует только фасад)
platformio.ini - конфигурация сборки (PlatformIO)
pyproject.toml / uv.lock - зависимости Python-скрипта (управляются `uv`)Аппаратные требования
- ESP32 DevKit V1 (или совместимый — см. окружения
esp32s3/esp32c6вplatformio.ini) - Лента SK6812, 60 светодиодов (сейчас физически: 16 левая сторона + 34 верхняя)
- Конденсатор ~440 мкФ на входе V+/GND ленты
- Рекомендуется добавить: level-shifter 3.3V→5V (74AHCT125/74HCT245) на линии данных
и керамический 100nF конденсатор у первого светодиода (см.
THEORY.md, раздел 5, и Troubleshooting ниже) - Источник 5V с запасом по току (≥1.5A на 60 RGBW-светодиодов в максимуме)
Полная схема подключения — в WIRING.md.
Установка ПО
Прошивка ESP32 (PlatformIO)
cd firmware/Projects/Ambilight_project
pio run -e esp32dev -t uploadБиблиотека Adafruit NeoPixel подтягивается автоматически через lib_deps в
platformio.ini — устанавливать вручную не нужно. Перед прошивкой проверьте
DATA_PIN/NUM_LEDS в src/main.cpp.
Python-скрипт (Linux, venv через uv)
cd firmware/Projects/Ambilight_project
uv sync
uv run ambilight.pyЕсли порт не /dev/ttyUSB0 — посмотреть реальный через ls /dev/ttyUSB* /dev/ttyACM*
и поправить константу PORT в начале ambilight.py.
Настройка под другой экран / другую раскладку ленты
Все параметры — константы в начале ambilight.py:
MONITOR_INDEX— какой монитор захватывать (для мультимониторных систем).LEDS_LEFT / LEDS_TOP / LEDS_RIGHT / LEDS_BOTTOM— сколько светодиодов на какой стороне.RIGHT/BOTTOMсейчас закомментированы в основном цикле — когда физически расширите ленту на полный периметр, достаточно раскомментировать соответствующий вызовappend_edge_to_packet(...), никакой новый код писать не нужно (используется одна универсальная функцияsample_edge).ZONE_DEPTH_PERCENT— глубина зоны сэмплирования в % от экрана (а не в пикселях) — поэтому смена разрешения монитора не требует ручной подстройки этого параметра.GAMMA— включить гамма-коррекцию, если в тёмных сценах лента выглядит “пустой”.
Troubleshooting
Случайные яркие вспышки на однотонном свете: Раз кадр данных по UART защищён чексуммой, проблема почти наверняка на участке ESP32 → лента (этот участок чексуммой не защищён, протокол однонаправленный). По убыванию вероятности:
- Нет согласования логических уровней 3.3V (ESP32) → 5V (SK6812) — поставить level-shifter.
- Нет общего провода GND между питанием ESP32 и питанием ленты (особенно если лента от пауэрбанка, а ESP32 от USB ПК) — соединить GND отдельным проводом. Самая частая и самая быстро проверяемая причина — начинайте отладку с неё.
- Источник питания не держит резкие скачки тока / шумит по питанию — добавить керамику 100nF у первого светодиода в дополнение к электролиту 440 мкФ; на тест попробовать обычный 5V/2A блок питания вместо пауэрбанка.
Подробное физическое объяснение каждого пункта — в THEORY.md, раздел 5.
Лента “замерла” на последней картинке: проверить isAmbilightConnectionLost() —
если Python-скрипт не присылал валидный кадр >2 сек, контроллер сам гасит ленту.
Если лента всё равно горит старым цветом — проверить, что прошивка с этой логикой
действительно залита (не старая версия без watchdog).
Roadmap / идеи на будущее
Готовое приложение для Linux. Технически реализуемо несколькими путями:
- Системный трей-иконка (
pystray) с переключателем вкл/выкл и слайдером яркости — лучший вариант для демонстрации, не требует знаний терминала от зрителя. systemd-юзер-сервис — для “фонового” запуска без какого-либо UI.- Локальная веб-панель (Flask/FastAPI + простая HTML-страница) — даёт управление с телефона/планшета в той же сети и попутно учит концепции клиент-сервер/REST.
pyinstaller— сборка в один исполняемый файл, чтобы не требовать от пользователя настройки Python/venv вручную.
“Эмоциональный” режим от микрофона. Архитектурно — да, без изменений на стороне
ESP32 (см. THEORY.md, раздел 1 и 6): меняется только источник данных в Python.
Честная реализация — “audio-reactive” режим (громкость→яркость, тон через FFT→оттенок),
а не заявка на настоящее распознавание эмоций, у которого даже в лаборатории точность
далека от 100% (см. THEORY.md, раздел 6 — хороший повод для разговора с учениками
о реалистичных границах ИИ).
Теория проекта Ambilight: от фотона на экране до фотона из светодиода
Документ для преподавателя/наставника: что именно происходит в каждой строке кода на уровне физики, математики и архитектуры, и какую пользу из этого можно вытащить для занятия — не только “сделали мигающую ленту”, а реально перевариваемые концепции.
1. Общая архитектура: конвейер обработки сигнала
Любая “умная” система ввода-вывода — это четырёхзвенный конвейер:
ИСТОЧНИК СИГНАЛА → ОБРАБОТКА/МАТЕМАТИКА → ПРОТОКОЛ ПЕРЕДАЧИ → АКТУАТОР
(экран/звук) (фильтры, гамма) (UART, чексумма) (LED-лента)Ключевая методическая мысль: звенья конвейера независимы. Если правое звено
(ESP32 + лента) не знает и не должно знать, откуда взялся цвет — из захвата экрана,
из микрофона или из случайного генератора — значит, архитектура построена правильно.
Именно поэтому в разделе 6 заменить “источник” на микрофон можно вообще без изменения
кода ESP32: меняется только Python-скрипт до того места, где формируется led_packet.
Это и есть на практике принцип разделения ответственности (separation of concerns) —
одна из главных идей в инженерии ПО, и его проще всего объяснить именно на таком
наглядном проекте, а не на абстрактном примере.
2. Математика дискретизации экрана
Экран — это сетка пикселей с RGB-значениями. Лента — это 60 точек. Значит, нам нужно “сжать” миллионы пикселей в 60 чисел. Здесь происходит две математические операции:
2.1. Усреднение как фильтр низких частот
zone.mean(axis=(0,1)) — это не просто “взять цвет”, это пространственное усреднение,
которое физически работает как сглаживающий (low-pass) фильтр: одиночный яркий пиксель
(например, курсор мыши) “размазывается” по всей зоне и почти не влияет на итоговый цвет.
Без усреднения (если бы мы брали один конкретный пиксель) лента бы дрожала от каждого
шевеления курсора — это и есть разница между “взять образец” и “отфильтровать сигнал”.
2.2. Прореживание (даунсэмплинг) — [::4]
screenshot[y_start:y_end:4, x_start:x_end:4] — мы не усредняем ВСЕ пиксели зоны, а берём
каждый 4-й по обеим осям. Это снижает количество обрабатываемых данных в 16 раз (4×4),
почти не теряя в качестве среднего цвета — потому что соседние пиксели на экране сильно
коррелируют между собой (если один синий, соседний с вероятностью почти 100% тоже похож).
Практическая ценность: это ровно тот же принцип, на котором работает сжатие видео
и понижение частоты дискретизации звука — “достаточно редкая выборка не теряет смысл,
если сигнал гладкий”.
3. Гамма-коррекция: когда линейная математика обманывает глаз
PWM-скважность светодиода физически линейна по мощности: 50% скважности = 50% от
максимальной световой энергии. Но человеческий глаз воспринимает яркость по степенному
закону (приблизительно perceived = physical^(1/γ), γ≈2.2) — то есть физически линейный
сигнал выглядит для нас нелинейным. Поэтому в коде есть LUT (lookup table):
_gamma_lut[i] = round(((i/255) ** (1/GAMMA)) * 255)Это классический пример того, как один и тот же физический параметр (яркость) нужно линеаризовать под восприятие, а не под физику прибора — то же самое происходит в фотографии (sRGB-кодирование), в калибровке мониторов и в звуке (децибелы — это тоже логарифмическая, “перцептивная” шкала, а не линейные ватты).
4. Протокол передачи: UART как канал с шумом
4.1. Кадрирование (framing). Заголовок 'A','d','a' — это маркер начала кадра.
Без него приёмник (ESP32) не может понять, где начинается новый набор данных в потоке
байт — UART физически передаёт просто последовательность байт без понятия “начало/конец
сообщения”. Это азбука протоколов связи: любой протокол прежде всего должен решить
проблему синхронизации, прежде чем говорить о содержимом данных.
4.2. Контроль целостности — XOR-чексумма. checksum ^= byte для каждого байта —
простейший код обнаружения ошибок. Математически XOR — операция “чётности”: чексумма
гарантированно покажет несовпадение, если изменилось нечётное число бит в кадре,
но может не заметить ошибку, если изменилось чётное число бит сразу в нескольких местах
(редкий, но реальный случай). Точка для углублённого занятия: это ровно то место,
где можно показать продвинутым ученикам CRC (Cyclic Redundancy Check) — тот же принцип
обнаружения ошибок, но математически надёжнее (используется в Ethernet, ZIP, USB).
4.3. Бюджет канала. UART на N бод передаёт примерно N/10 байт/сек (8 бит данных + старт/стоп-биты). Кадр в 244 байта при 60 кадрах/сек требует 14 640 байт/сек — это прямое применение формулы “пропускная способность = объём данных × частота”, той же, что лежит в основе любых расчётов сетевой инфраструктуры или сжатия видео.
5. Протокол адресных светодиодов и электрическая теория
SK6812 получает данные по одному проводу через тайминг импульсов, а не через уровень напряжения как таковой: “0” и “1” различаются длительностью высокого сигнала в импульсе примерно 1.25 мкс. Это делает протокол чувствительным к:
- Уровню логики — порог распознавания “1” у 5V-чипа выше, чем может надёжно выдать 3.3V-контроллер без согласования уровней (см. раздел про вспышки выше).
- Электропитанию — конденсатор (440 мкФ у тебя на ленте) гасит низкочастотные скачки тока при смене яркости/цвета: каждый светодиод при включении на максимум резко “запрашивает” ток у источника, и без буферной ёмкости это просаживает напряжение по всей цепочке.
- Общей земле (GND) — сигнал “0V” для приёмника имеет смысл только относительно ОБЩЕЙ точки отсчёта. Если у источника данных и у питания ленты разные, не соединённые между собой “нули” — сигнал плывёт. Это та же идея, что и заземление в любой электронике, просто на бытовом проекте она становится наглядной и осязаемой.
6. Расширение на звук: честный разговор про “распознавание эмоций”
Architecturally — да, заменить источник на микрофон легко (раздел 1). Но важно различать два уровня сложности, и это отличная тема для критического мышления на занятии:
Уровень А — “audio-reactive” (просто и честно). Громкость (RMS-амплитуда сигнала) → яркость; высота тона (через автокорреляцию или БПФ — поиск основной частоты F0) → оттенок цвета. Это легитимная, понятная, действительно работающая обработка сигнала — и хороший повод объяснить, что такое FFT (преобразование Фурье: “любой сложный звук раскладывается в сумму простых синусоид разной частоты”).
Уровень Б — “распознавание эмоций” (на самом деле сложно и спорно). Настоящее Speech Emotion Recognition — это модели машинного обучения, обученные на размеченных датасетах (RAVDESS, IEMOCAP и т.п.), точность которых даже в лабораторных условиях обычно 60-75%, а “в дикой природе” (другой голос, другой микрофон, другой язык) — заметно ниже. Методическая ценность: это прекрасный пример для разговора с учениками о том, что “ИИ определяет эмоции” — это упрощение, которое легко продать как магию, но за которым стоят вполне измеримые (и не идеальные) числа точности. Хороший проект для старших учеников — НЕ “верить” готовому ярлыку эмоции, а самим построить простой классификатор и честно измерить его ошибку.
Это разница между “лента мигает в такт голосу” (честно, работает, объяснимо) и “лента знает, что ты злишься” (преувеличение, которое стоит разобрать критически).
7. Практическая ценность — зачем это всё в реальной жизни
| Концепция из проекта | Где встречается в индустрии |
|---|---|
| Усреднение/прореживание сигнала | Сжатие видео/звука, обработка сенсоров IoT |
| Гамма-коррекция / линеаризация | Калибровка дисплеев, фотография, аудио (дБ) |
| Кадрирование протокола + чексумма | Любой сетевой протокол, прошивки, телеметрия |
| Бюджет пропускной способности | Проектирование сетей, выбор интерфейсов (UART/SPI/I2C/Ethernet) |
| Согласование логических уровней | Любое сопряжение разных by-питанию чипов (3.3V/5V/12V) |
| Архитектура “источник ⇄ актуатор” | Микросервисы, драйверы устройств, MVC |
| FFT / анализ звука | Распознавание речи, музыкальные анализаторы, медицинские сенсоры |
Это не “развлекательный проект ради проекта” — каждая строка кода тут является учебной моделью настоящей инженерной задачи в уменьшенном, наглядном масштабе.
8. История отладки: как выглядят реальные инженерные баги
Текущая реализация (checksum, Adafruit_NeoPixel, watchdog обрыва связи) — не
первая версия проекта. Ранняя версия работала на других решениях, и разбор того,
как и почему они были заменены, — хороший материал для урока о разнице между
“работает на столе” и “работает надёжно”.
8.1. Пропускная способность UART как невидимый баг. Ранняя версия использовала
115200 бод. Кадр в 244 байта (3 заголовка + 60×4 GRBW + 1 checksum) при цели 60
кадров/сек требует ≈14.6 КБ/с — а канал на 115200 бод физически прокачивает
только ≈11.5 КБ/с, то есть на 27% меньше нужного. Скрипт “думал”, что шлёт 60
FPS, а реально упирался в потолок ≈47 FPS — буфер ОС постепенно копился, и лента
со временем начинала “подвисать” относительно картинки на экране. Баг был
невидим в коде: ни одна строка не была логически неверна, проблема была в
физическом бюджете канала, который никто явно не посчитал. Решение — поднять
BAUDRATE до 460800 (см. README.md) синхронно в Python и в Serial.begin().
8.2. RGBW через 3-канальную библиотеку — арифметическое совпадение, а не
архитектура. До перехода на Adafruit_NeoPixel лента управлялась через
FastLED, которая рассчитана на 3-байтные структуры CRGB (WS2812B), а SK6812 —
честный 4-канальный чип (G,R,B,W). Обходной приём выставлял NUM_LEDS = (60*4+2)/3 = 80, и это “работало” только потому, что 80*3 = 240 случайно
совпадает с 60*4 = 240. При другом числе светодиодов, например 50, 200/3 —
не целое число, и байты начали бы “расползаться” между соседними пикселями
(зелёный одного светодиода становится красным у соседнего). Урок: код может
пройти визуальную проверку (“лента же светится!”) и всё равно быть построен на
случайности, которая ломается при первом же изменении параметра.
8.3. Баг-репорт: случайные вспышки на статичной картинке. Показательный пример дедукции по имеющимся данным. Наблюдение: картинка на экране статична, значит Python шлёт почти один и тот же кадр раз за разом. Кадр защищён чексуммой — значит, повреждённый байт на участке Python→ESP32 был бы просто отброшен целиком (лента держала бы старый цвет, а не вспыхивала). Раз вспышки всё равно происходят, это сильный признак, что проблема физически на участке ESP32→лента, где протокол SK6812 однонаправленный и никакой чексуммой не защищён (см. раздел 5). В реальном случае причиной оказался отвалившийся контакт общего провода GND между ESP32 и питанием ленты — то есть пункт 2 из списка Troubleshooting в README, а не пункт 1 (согласование уровней), хотя оба дают визуально одинаковый симптом. Методическая ценность: при отладке электроники симптом редко указывает на причину напрямую — нужно исключать гипотезы по цепочке, начиная с самой быстро проверяемой (общий провод проверяется за 10 секунд).
🔌 Схема подключения Ambilight (ESP32 + SK6812)
1. Базовая схема (без level-shifter)
┌─────────────────┐
│ ESP32 │
│ │
│ GPIO5 ├───[330-470Ω]───► DIN ленты
│ │
│ GND ├───────┬────────► GND ленты
└─────────────────┘ │
│
┌────────┴────────┐
│ 5V источник │
│ (≥1.5A запас) │
└────────┬────────┘
│
└────────► V+ ленты2. Рекомендуемая схема (с level-shifter)
┌─────────────────┐ ┌─────────────────┐
│ ESP32 │ │ 74AHCT125/245 │
│ │ │ (3.3V → 5V) │
│ GPIO5 ├───────►│ A Y ├───[330-470Ω]───► DIN ленты
│ │ └─────────────────┘
│ GND ├───────────────┬──────────────────────────────► GND ленты
└─────────────────┘ │
│
┌────────┴────────┐
│ 5V источник │
│ (≥1.5A запас) │
└────────┬────────┘
│
└────────► V+ ленты3. Таблица соединений
| Сигнал | ESP32 | Лента SK6812 | Описание |
|---|---|---|---|
| DATA | GPIO5 | DIN | Через резистор 330-470Ω; рекомендуется level-shifter 3.3V→5V |
| GND (логика) | GND | GND | Обязательно, даже если питание ленты отдельное (см. ниже) |
| GND (питание) | — | GND | Общий с GND источника 5V ленты |
| V+ | — | V+ | От отдельного 5V источника, не от USB-порта ESP32 |
4. Рекомендации
- Level-shifter обязателен при нестабильной работе. SK6812 рассчитан на 5V-логику с порогом распознавания “1” около 0.7×5V = 3.5V. ESP32 выдаёт 3.3V — формально ниже порога, особенно на длинной линии данных или при большом числе светодиодов (60+). Дешёвый 74AHCT125/74HCT245 между GPIO и DIN устраняет большинство случайных вспышек.
- Общий GND — обязателен, даже если ESP32 и лента питаются от разных источников
(например, ESP32 от USB ПК, лента от отдельного блока питания или пауэрбанка). Без
общей точки отсчёта логический “0” для чипов ленты плывёт — появляются случайные
вспышки цвета. Физика явления — в
THEORY.md, раздел 5. - Конденсаторы: электролитический ~440 мкФ на V+/GND у входа ленты гасит низкочастотные броски тока при резкой смене цвета/яркости; керамический 100nF у первого светодиода дополнительно гасит высокочастотный шум источника питания.
- Источник питания с запасом по току. SK6812 5050 при максимальной яркости белого на всех 4 каналах потребляет ориентировочно 15-20 мА на светодиод — 60 штук дают до ~1.1-1.2A. Слабый источник (в т.ч. USB-хаб) приводит к просадкам и миганию — используйте отдельный 5V блок питания на ≥1.5A, не запитывайте ленту от USB ESP32.
- Не питайте ленту от того же USB, что и ESP32, если лента больше 10-15 светодиодов — порт компьютера/хаба не рассчитан на такой ток.
Методическое пособие: Ambilight на ESP32 + SK6812
Стенд: ESP32 + адресная RGBW-лента SK6812 + Python-скрипт захвата экрана (ambilight.py).
В отличие от датчиков остального курса, здесь поток данных идёт в обратную сторону —
не “из железа в компьютер”, а “из компьютера в железо”: экран становится источником
сигнала, а лента — актуатором. Пособие связывает это с информатикой, физикой и
вычислительной математикой школьной программы.
Кто чем пользуется
- Инженер отвечает за стенд: подключение по схеме из
WIRING.md, синхронныйBAUDRATEв Python и прошивке, стабильное питание ленты. - Учитель проводит эксперимент по сценарию ниже и обсуждает результат с классом.
- Методист привязывает конкретные параметры протокола и кода к темам программы.
Модуль 1. Информатика: дискретизация и сжатие сигнала
Урок 1. От миллионов пикселей к 60 числам
- Тема: дискретизация непрерывного сигнала, передискретизация, потеря информации.
- Параметры:
zone.mean(axis=(0,1)), срез[::4]вsample_edge().
Ход работы. Запустите ambilight.py с GAMMA = 1.0 на статичном изображении с
резким цветовым переходом (например, половина экрана красная, половина синяя).
Наведите курсор мыши на границу перехода и подвигайте им в пределах зоны сэмплирования
одной стороны — заметьте, что лента почти не реагирует на курсор.
На графике:
Яркость курсора на кадре Реакция ленты (тот же участок)
██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
██ ← курсор, 1 пиксель ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ← почти не меняется
──────────────────► x ──────────────► xРазбор. zone.mean(axis=(0,1)) — усреднение по пространству, работает как
low-pass фильтр: один яркий/тёмный пиксель “размазывается” по всей зоне сэмплирования
и почти не влияет на средний цвет. Без усреднения (если бы код брал ровно один
пиксель) лента дрожала бы от каждого движения курсора. Отдельно — срез [::4] берёт
каждый 4-й пиксель по обеим осям, снижая объём обрабатываемых данных в 16 раз (4×4).
Это работает, потому что соседние пиксели экрана сильно коррелируют между собой —
тот же принцип, на котором строится сжатие видео и понижение частоты дискретизации
звука: “достаточно редкая выборка не теряет смысла, если исходный сигнал гладкий”.
Практическое задание. Замените [::4] на [::16] в коде (учебная копия, не
рабочий скрипт) и опишите на глаз, при каком шаге прореживания качество определения
цвета “на глаз” перестаёт быть приемлемым. Найдите: с какого шага размер минимальной
детали, которую алгоритм ещё “видит”, становится больше самой зоны сэмплирования.
Урок 2. Гамма-коррекция: когда линейная математика обманывает глаз
- Тема: нелинейные шкалы восприятия, степенные и логарифмические функции.
- Параметры:
GAMMA,_gamma_lut.
Ход работы. Установите GAMMA = 1.0 (выключено), откройте на экране плавный
чёрно-белый градиент (например, тестовое изображение или xrandr-заливку). Отметьте
на глаз, на какой доле градиента (по яркости) лента визуально “выглядит уже белой”.
Затем поставьте GAMMA = 2.2 и повторите наблюдение на том же градиенте.
На графике:
Физическая яркость (линейно) Восприятие глазом (то же значение)
100%│ ╱ 100%│ ╱
│ ╱ │ ╱
│ ╱ │ ╱
│ ╱ │╱
0%└──╱─────────────► физ.яркость 0%└──────────────► физ.яркость
50% физ. = "полусветлый" 50% физ. = "почти белый" (без коррекции)Разбор. PWM-скважность светодиода физически линейна по электрической мощности:
50% скважности = ровно 50% световой энергии. Но человеческий глаз воспринимает
яркость по степенному закону: perceived ≈ physical^(1/γ), γ≈2.2 — то есть
физически линейный сигнал выглядит для нас нелинейным (тёмная половина шкалы кажется
“светлее”, чем должна бы). LUT (_gamma_lut[i] = round(((i/255)**(1/GAMMA))*255))
линеаризует сигнал под восприятие, а не под физику прибора — тот же приём лежит в
основе sRGB-кодирования в фотографии, калибровки мониторов и децибельной (логарифмической)
шкалы громкости звука.
Практическое задание. Постройте на бумаге график функции y = x^(1/2.2) для
x от 0 до 1 (можно по точкам: x=0, 0.25, 0.5, 0.75, 1). Сравните форму кривой с
тем, что наблюдали на ленте, и объясните, почему кривая выпуклая вверх, а не прямая.
Модуль 2. Физика: электричество и протоколы передачи сигнала
Урок 3. Бюджет канала связи: пропускная способность UART
- Тема: скорость передачи данных, единицы измерения (бод, байт/с), расчёт по формуле.
- Параметры:
BAUDRATE, размер кадра (244 байта), целевой FPS.
Ход работы. С осциллографом или логическим анализатором на линии TX ESP32
(или просто по секундомеру и счётчику кадров в консоли Python) сравните заявленный
FPS (60, задан в коде через time.sleep) с реальной частотой обновления ленты на
глаз при BAUDRATE = 115200 и при BAUDRATE = 460800.
На графике:
FPS канала (115200 бод) FPS канала (460800 бод)
60│ 60│ ████████████████
│ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │
47│▔▔▔▔▔▔▔▔ ← потолок │
│ │
0└──────────────────► t 0└──────────────────► t
Скрипт "думает" 60 FPS, Заявленное = реальное
реально упирается в каналРазбор. UART на N бод передаёт приблизительно N/10 байт/сек (8 бит данных +
старт/стоп-биты, формат 8N1). Кадр протокола: 3 байта заголовка ('A','d','a') +
60 светодиодов × 4 байта (G,R,B,W) + 1 байт чексуммы = 244 байта. При целевых 60
кадрах в секунду нужно 244 × 60 ≈ 14 640 байт/сек. На 115200 бод канал даёт
только 115200/10 = 11 520 байт/сек — на 27% меньше требуемого. Разница уходит в
накапливающуюся задержку: лента со временем начинает “подвисать” относительно
картинки на экране, хотя ни одна строчка кода не содержит логической ошибки.
Формула “пропускная способность = объём данных × частота” — та же, что используется
при выборе сетевого интерфейса или расчёте битрейта видео.
Практическое задание. Посчитайте, при каком количестве светодиодов на ленте N
(протокол не меняется, только длина кадра) канал на 460800 бод перестанет
успевать за 60 FPS. Формула: (3 + N×4 + 1) × 60 ≤ BAUDRATE/10.
Урок 4. Кадрирование и контроль целостности данных
- Тема: обнаружение ошибок, побитовые операции (XOR), надёжность канала связи.
- Параметры: заголовок
'A','d','a', XOR-чексумма вreceiveFrame().
Ход работы. На макетной плате физически отсоедините и на секунду снова подключите провод DATA между ESP32 и лентой во время работы Python-скрипта (картинка на экране статична). Наблюдайте поведение ленты в момент разрыва и восстановления связи.
На графике:
Валидные кадры (XOR совпал) Кадр с разрывом связи
████ ████ ████ ████ ████ ▓▓▓▓ ---- ████
корректный цвет держится связь разорвана, кадр отброшен,
лента держит ПОСЛЕДНИЙ валидный цветРазбор. UART физически передаёт последовательность байт без понятия
“начало/конец сообщения” — заголовок 'A','d','a' решает проблему синхронизации:
приёмник ищет именно эту последовательность, чтобы понять, где начинается новый
кадр. Это первая задача любого протокола связи. Вторая задача — обнаружение
повреждений: checksum ^= byte для каждого байта — простейший код обнаружения
ошибок на основе операции XOR (“чётности”). Чексумма гарантированно покажет
несовпадение, если изменилось нечётное число бит в кадре, но может не заметить
ошибку при чётном числе одновременных повреждений в разных местах — редкий, но
реальный случай. Если чексумма не совпала, receiveFrame() возвращает false
и весь кадр отбрасывается — лента держит последний валидный цвет, а не показывает
“мусор” на один кадр.
Точка для углублённого занятия. XOR-чексумма — не единственный и не самый надёжный метод. В Ethernet, ZIP и USB используется CRC (Cyclic Redundancy Check) — тот же принцип обнаружения ошибок, но математически надёжнее (ловит и часть случаев с чётным числом повреждённых бит).
Практическое задание (вопрос на понимание). В receiveFrame() контрольная
сумма считается после полного приёма _payloadBuf, а setPixelRGBW() для всех
светодиодов вызывается только если calc == checksum. Что увидит пользователь на
ленте, если один байт в середине кадра придёт повреждённым — мигнёт ли лента
“битым” кадром хотя бы на 1/60 секунды, и почему?
Урок 5. Согласование логических уровней и общая точка отсчёта
- Тема: логические уровни напряжения, электрический потенциал, общий провод (GND).
- Параметры: 3.3V (ESP32) vs 5V (SK6812), порог распознавания “1”.
Ход работы. Соберите стенд по WIRING.md без level-shifter, запитайте ленту
от отдельного источника (например, powerbank), НЕ соединяя его GND с GND ESP32.
На статичной картинке наблюдайте случайные вспышки цвета на ленте. Затем соедините
GND источников отдельным проводом и повторите наблюдение.
На графике:
Без общего GND С общим GND
████░▓████░░████▓███ ████████████████████
случайные вспышки/помехи стабильный ровный цветРазбор. SK6812 получает данные через тайминг импульсов (“0” и “1” различаются длительностью высокого сигнала около 1.25 мкс), а не через абсолютный уровень напряжения — но сам уровень “высокий/низкий” имеет смысл только относительно общей точки отсчёта 0V. Если GND источника данных (ESP32) и GND источника питания ленты физически не соединены, “ноль” для приёмника начинает плыть — сигнал искажается непредсказуемо. Отдельно логический порог: SK6812 рассчитан на 5V-логику с порогом распознавания “1” около 0.7×5V = 3.5V, а ESP32 выдаёт 3.3V — формально ниже надёжного порога, особенно на длинной линии или большом числе светодиодов.
Разбор случая (баг-репорт). В реальной отладке этого стенда картинка на экране была статична — значит Python слал почти одинаковый кадр раз за разом, и кадр был защищён чексуммой (см. Урок 4). Раз повреждённый байт Python→ESP32 был бы просто отброшен целиком, а вспышки всё равно происходили — это указывало на участок ESP32→лента, где протокол однонаправленный и чексуммой не защищён. Причиной оказался отвалившийся контакт именно общего провода GND. Методический вывод: симптом (“случайные вспышки”) редко указывает на причину напрямую — нужно исключать гипотезы по цепочке, начиная с самой быстро проверяемой.
Практическое задание. Начертите схему стенда с двумя отдельными источниками питания (ESP32 от USB, лента от powerbank) и отметьте на ней стрелкой, где именно физически должен пройти дополнительный провод GND, чтобы схема заработала надёжно.
Модуль 3. Вычислительная математика: архитектура и звук
Урок 6. Разделение ответственности: почему смена источника сигнала не требует новой прошивки
- Тема: модульность систем, независимость компонентов.
- Параметры: архитектура “источник → обработка → протокол → актуатор”.
Ход работы. Без физических изменений на ESP32 обсудите с классом: что нужно
поменять в системе, чтобы лента реагировала на громкость музыки вместо цвета экрана?
Дайте ученикам самостоятельно найти ответ по коду ambilight.py, глядя только на
функцию append_edge_to_packet() и то, что она вызывается из while True.
Разбор. Прошивка ESP32 не знает и не должна знать, откуда взялся цвет каждого
байта в кадре — из захвата экрана, из микрофона или из генератора случайных чисел.
Единственный контракт между Python и ESP32 — формат кадра (заголовок + N×4 байта +
чексумма). Значит, замена “источника” (экран → микрофон) требует изменений только
в Python-скрипте до того места, где формируется led_packet — ни одна строка кода
ESP32 не меняется. Это и есть на практике принцип разделения ответственности
(separation of concerns) — одна из главных идей в инженерии программного обеспечения,
и его проще всего показать именно на таком наглядном стенде, а не на абстрактном
примере.
Практическое задание. Нарисуйте схему-конвейер “источник → обработка → протокол → актуатор” для трёх вариантов источника (экран, микрофон, генератор случайных чисел) и обведите на схеме те блоки, которые остаются неизменными во всех трёх случаях.
Урок 7. Честная граница: “реагирует на сигнал” vs “распознаёт эмоцию”
- Тема: критическое мышление о возможностях и ограничениях ИИ, погрешность измерений.
- Параметры: RMS-амплитуда, основная частота (F0), точность классификации.
Ход работы. Обсуждение без нового кода. Предложите классу два варианта режима “лента реагирует на голос”: (А) громкость → яркость, высота тона → оттенок цвета; (Б) лента “определяет”, злится говорящий или радуется. Попросите учеников проголосовать, какой из вариантов реалистично сделать за один урок, и обосновать.
Разбор. Вариант А (audio-reactive) — честная и полностью реализуемая обработка сигнала: RMS-амплитуда сигнала даёт громкость, поиск основной частоты (автокорреляция или БПФ) даёт высоту тона. Вариант Б — задача распознавания эмоций по голосу (Speech Emotion Recognition) — это модели машинного обучения, обученные на размеченных датасетах, чья точность даже в лабораторных условиях обычно составляет 60-75%, а “в дикой природе” (другой голос, другой микрофон, другой язык) заметно ниже. Разница между “лента мигает в такт голосу” (честно, работает, объяснимо) и “лента знает, что ты злишься” (преувеличение) — хороший пример того, что “ИИ определяет эмоции” — упрощение, которое легко продать как магию, но за которым стоят вполне измеримые (и не идеальные) числа точности.
Практическое задание. Найдите (или предложите гипотетически) датасет для распознавания эмоций по голосу и найдите заявленную точность лучшей модели на нём. Обсудите: что означает “точность 70%” для пользователя устройства в реальной жизни — в скольки случаях из 10 лента покажет “неправильную эмоцию”?
Приложение: таблица переноса концепций в индустрию
| Концепция из проекта | Где встречается за пределами лаборатории |
|---|---|
| Усреднение/прореживание сигнала | Сжатие видео/звука, обработка сенсоров IoT |
| Гамма-коррекция / линеаризация | Калибровка дисплеев, фотография, аудио (дБ) |
| Кадрирование протокола + чексумма | Любой сетевой протокол, прошивки, телеметрия |
| Бюджет пропускной способности | Проектирование сетей, выбор интерфейсов (UART/SPI/I2C/Ethernet) |
| Согласование логических уровней | Сопряжение чипов с разным питанием (3.3V/5V/12V) |
| Архитектура “источник ⇄ актуатор” | Микросервисы, драйверы устройств, MVC |
| FFT / анализ звука | Распознавание речи, музыкальные анализаторы, медицинские сенсоры |
Заключение
Ambilight — редкий в курсе пример, где ученик не читает данные с датчика, а сам становится источником сигнала для устройства. Это даёт естественный повод обсудить конвейер обработки данных целиком — от дискретизации до актуатора — и на реальном, осязаемом результате (лента за монитором) показать, что “просто заставить светиться” и “сделать надёжно работающую систему” — это две разные по сложности задачи, и разница между ними видна как раз в тех местах, где что-то один раз не заработало.
📊 Презентация проекта: Ambilight на ESP32 + SK6812
Слайд 1: Экран становится источником сигнала
- Суть: Захват цвета с краёв экрана и передача его на адресную RGBW-ленту в реальном времени — стенд, где не датчик передаёт данные компьютеру, а наоборот.
- Платформа: ESP32 DevKit V1 + адресная лента SK6812 (60 светодиодов).
- Канал связи: UART, 460800 бод, протокол с чексуммой.
- Цель: Показать полный конвейер обработки сигнала — от пикселя на экране до фотона света на ленте — как учебную модель настоящей инженерной задачи.
Слайд 2: Математика “сжатия” картинки в 60 чисел
- Проблема: Экран — миллионы пикселей, лента — всего 60 точек.
- Усреднение:
zone.mean()работает как low-pass фильтр — гасит случайный шум (например, движение курсора), не даёт ленте “дрожать”. - Прореживание: срез
[::4]снижает объём обрабатываемых данных в 16 раз почти без потери качества — соседние пиксели сильно коррелируют между собой. - Гамма-коррекция: линеаризация яркости под нелинейное восприятие глаза
(
perceived ≈ physical^(1/2.2)) — тот же приём, что в фотографии и калибровке мониторов.
Слайд 3: Протокол связи как канал с шумом
- Кадрирование: заголовок
'A','d','a'решает задачу синхронизации — приёмник находит начало нового кадра в непрерывном потоке байт. - Контроль целостности: XOR-чексумма отбрасывает битые кадры целиком — лента держит последний валидный цвет вместо “мусора” на экране.
- Бюджет канала: UART на 460800 бод даёт запас ×3 над требуемыми 14.6 КБ/с — на 115200 бод канал физически не успевал за 60 кадрами в секунду.
- Watchdog: обрыв связи дольше 2 секунд — лента гасится сама, не “замерзает”.
Слайд 4: Электрическая теория адресных лент
- Тайминг, а не уровень: SK6812 различает “0”/“1” по длительности импульса (~1.25 мкс), но сам уровень имеет смысл только относительно общей точки GND.
- Согласование логики: 3.3V ESP32 формально ниже надёжного порога 5V-логики SK6812 — источник большинства “случайных” вспышек без видимой причины.
- Реальный баг-репорт: разобран в проекте случай, где симптом (вспышки на статичной картинке) указывал не на уровни логики, а на отвалившийся общий провод GND — методический пример дедукции по цепочке гипотез.
Слайд 5: Архитектура и честные границы
- Разделение ответственности: ESP32 не знает, откуда взялся цвет — из экрана, микрофона или генератора случайных чисел. Смена источника не требует новой прошивки.
- Расширяемость: архитектура уже поддерживает добавление аудио-реактивного режима без единой изменённой строки в коде ESP32.
- Честность про ИИ: проект прямо разграничивает “лента реагирует на громкость” (работает, объяснимо) и “лента распознаёт эмоции” (реальная ML-задача с точностью 60-75% даже в лаборатории) — повод для разговора с учениками о границах ИИ-маркетинга.
| |
| |
| |
| |
| |
| |