Долгое время занимался пониманием паттернов, которые появляются в природе, через код — попыткой comuniровать с присущими вселенной закономерностями.
Филлотаксис — один из таких паттернов: расширяющаяся двойная спираль, видимая в центре подсолнуха, суккулентов и других растений. Кодируется удивительно просто:
// Inner and outer are throw-away,
// just for spacing the cells that are used
float[][] points = new float[numCells + numOuter + numInner][2];
float outerRadius = width / 2;
final int total = numCells + numInner + numOuter;
for (int i = 0; i < total; i++)
{
float f = i / float(total);
float a = i * 1.6180339887;
float distance = f * outerRadius;
float x = -cos(a * TWO_PI) * distance;
float y = sin(a * TWO_PI) * distance;
points[i][0] = x;
points[i][1] = y;
}
Нужно взять ряд точек на радиальной линии круга и повернуть каждую на возрастающий множитель золотого сечения.
Получается красивое облако точек, уже похожее на подсолнух. Диаграмма Вороного этого облака выглядит как-то интересно и похоже на стручки семян:
После экспериментов с этой идеей появилась мысль: что если сделать это физическим объектом и положить RGB-светодиод в каждую ячейку? Что-то вроде матрицы из LED. В школе уже был опыт со сборкой прямоугольной матрицы neopixel, но подходящей формы не находилось.
От цифр к физике
Экспортировал данные краёв ячеек из Processing-скетча и импортировал их в Python-скетч с использованием библиотеки CadQuery.
Написал код для создания физической геометрии из этих данных:
# Create the negative space of a single cell
# shrunk with a negative offset-2d
cell_shape = (cq.Workplane("XY")
.polyline(vertices)
.close()
.offset2D(-wall_thickness/2, kind='intersection')
.extrude(total_height)
.translate((0, 0, base_height)))
# Cut it out of the total to create a shell with walls
total_shape = total_shape.cut(cell_shape)
# Cut the shape of the hole to fit the LED in the center of the cell
total_shape = total_shape.cut(
led_hole.rotate((0, 0, 0), (0, 0, 1), math.degrees(angle))
.translate((centerX, centerY, 0)))
Этот фрагмент вычитает объём каждой ячейки из общего объёма и вырезает под каждый светодиод отверстие нужного размера. Результат — жёлтая фигура ниже, разбитая на четыре части, которые помещаются на 3D-принтер.
Экспортировал STEP-файлы и обработал их в FreeCAD: добавил отверстия под винты, создал тонкую переднюю панель-фаседу с бумагой. Бумагу разрезал, приклеил к тонкой версии и прикрепил винтами к стенкам ячеек.
Напечатал фигуры на 3D-принтере и нашел полупрозрачную бумагу, которая хорошо рассеивает свет и выглядит органично при подсветке. Эта шелковистая бумага с красивыми натуральными волокнами смотрелась идеально.
Пайка 89 светодиодов — довольно утомительное занятие. Но в какой-то момент вошёл в состояние потока, и это стало расслабляющим. Подключил к запасной плате STM32 Blackpill и написал драйвер neopixel через SPI. Даже базовые тестовые паттерны выглядели впечатляюще.
Создал простой framework для скетчей, позволяющий писать обработчики в стиле Processing — shader-подобный код для LED. Используя таблицу поиска с позициями светодиодов на единичном круге, получилось писать такой код:
void radial_spirals(LEDBuffer leds) {
for (int i = 0; i < NUM_LEDS; i++) { // iterate every cell
// Get LED position from LUT
float x = led_positions[i][0];
float y = led_positions[i][1];
// Polar coordinates
float r = sqrtf(x*x + y*y);
float theta = atan2f(y, x); // Angle in radians [-π, π]
// Spiral pattern: combine angle and radius
float spiral = sinf(theta * 2.0f + r * 5.0f - seconds * 2.0f);
float brightness = (spiral + 1.0f) * 0.5f;
uint8_t level = (uint8_t)(brightness * 255.0f);
leds[i] = rgb(level, 0, 0);
}
}
Неплохо получилось.
Быстро обнаружил радиальный паттерн, который особенно нравился — пульсирующая синусоида, модулирующая яркость от центра. Позже это стало основой для звукореактивного скетча.
Добавляем звук
А что если сделать так, чтобы устройство реагировало на звуки в комнате, а не просто воспроизводило заранее заданные паттерны? Хотелось сделать его живым и динамичным.
STM32 поддерживает вычисления с плавающей точкой и ARM DSP-инструкции, поэтому можно запустить преобразование Фурье и анализировать звук. Добавил цифровой I2S-микрофон INMP441 на макетную плату.
Цифровой сигнал легко подключить без риска аналогового шума. Библиотека ARM CMSIS помогла обработать аудиосигнал и провести анализ. С автоматической регулировкой усиления и разбиением на полосы для анализа энергии на разных частотах получилась хорошая основа для написания кода, реагирующего на звук.
После множества настроек и хаотичного скетча с глобальными таймерами получился звукореактивный обработчик, который до сих пор работает на плате. Устройство ожило, когда проиграл трек Jon Hopkins — Neon Pattern Drum.
Полировка деталей
Нужна была прочная основа для монтажа. Нашел бамбуковую разделочную доску и обработал её в круг — натуральная текстура бамбука идеально дополняла органичную бумагу.
Контроллер и микрофон собрал на макетной плате и поместил в 3D-печатный корпус с педальным переключателем. Электроника была ненадёжной и шумела, заставляя LED мигать при движении корпуса. Решил научиться проектировать печатные платы и создал настоящую PCB с Blackpill, микрофоном, кнопкой и местом для логического преобразователя уровня — для получения чистого 5V-сигнала. Получилось хорошо, и создание PCB оказалось проще, чем ожидалось.
Вторая версия
Показал устройство на новогодней вечеринке в начале 2026 года, и оно понравилось — друзья даже попросили себе по такому. Вдохновлённый этим, решил собрать вторую версию, которая была бы проще в сборке и более прочной. После овладения навыками проектирования PCB решил перейти с 3D-печатной пластины для крепления LED на использование PCB как подложки. Немного изменил форму и добавил пятикратную симметрию — PCB заказываются партиями по 5 штук, поэтому вместо четырёх уникальных напечатанных деталей можно заказать пять одинаковых PCB и собрать их в кольцо.
Оказалось, что ручная пайка neopixel светодиодов паяльником — ещё больший кошмар, так как контакты расположены под светодиодом, а не на его поверхности. Проект на время встал.
Привез части проекта на программистский ретрит в Recurse Center летом 2026 года. Друг, которого встретил там, был гораздо более опытен с горячим воздушным паяльником. Вместе управились со всеми пайками, и создал новые стенки ячеек, которые крепились к PCB и соединяли их вместе.
После завершения новой версии во время батча Recurse решил оставить её там как инсталляцию. Перешел на ESP32, чтобы подключиться к wifi-сети центра, и переписал прошивку на Rust. Контроллер запускает локальный сайт, где участники Recurse могут загружать скетчи и игры, используя тонкий WebAssembly-backend и минимальный API.
Что дальше?
Проект всё ещё не полностью удовлетворяет.
Достаточно людей выразили интерес, чтобы хотелось сделать больше экземпляров — хотя бы подарить друзьям. Но процесс не идеален: пайка была утомительной по разным причинам в обоих случаях. Приклеивание бумаги сложно, и сама бумага хрупкая и трудно поддаётся ремонту при разрывах. Есть идеи для улучшений: другие типы светодиодов, легче паяющихся вручную, использование PCB-сборки, тестирование слоя прозрачного акрила для защиты бумаги, крепление передней панели с помощью магнитов. Много экспериментов впереди.
Пока проект заслуживает отдыха, но когда время придёт, будет третья версия!