Долгое время занимался пониманием паттернов, которые появляются в природе, через код — попыткой 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: добавил отверстия под винты, создал тонкую переднюю панель-фаседу с бумагой. Бумагу разрезал, приклеил к тонкой версии и прикрепил винтами к стенкам ячеек.

CadQuery-модели для каждого квадранта
CadQuery-модели для каждого квадранта
Модель фаседы, преобразованная в FreeCAD из выхода CadQuery
Модель фаседы, преобразованная в FreeCAD из выхода CadQuery

Напечатал фигуры на 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 на макетную плату.

Макетная схема с I2S-микрофоном

Цифровой сигнал легко подключить без риска аналогового шума. Библиотека ARM CMSIS помогла обработать аудиосигнал и провести анализ. С автоматической регулировкой усиления и разбиением на полосы для анализа энергии на разных частотах получилась хорошая основа для написания кода, реагирующего на звук.

После множества настроек и хаотичного скетча с глобальными таймерами получился звукореактивный обработчик, который до сих пор работает на плате. Устройство ожило, когда проиграл трек Jon Hopkins — Neon Pattern Drum.

Полировка деталей

Нужна была прочная основа для монтажа. Нашел бамбуковую разделочную доску и обработал её в круг — натуральная текстура бамбука идеально дополняла органичную бумагу.

Собранная бамбуковая подложка и скульптура, установленная на стену

Контроллер и микрофон собрал на макетной плате и поместил в 3D-печатный корпус с педальным переключателем. Электроника была ненадёжной и шумела, заставляя LED мигать при движении корпуса. Решил научиться проектировать печатные платы и создал настоящую PCB с Blackpill, микрофоном, кнопкой и местом для логического преобразователя уровня — для получения чистого 5V-сигнала. Получилось хорошо, и создание PCB оказалось проще, чем ожидалось.

Исходный корпус с беспорядочной макетной платой внутри
Исходный корпус с беспорядочной макетной платой внутри
Пустая PCB, которую создал, до пайки всех компонентов
Пустая PCB, которую создал, до пайки всех компонентов

Вторая версия

Показал устройство на новогодней вечеринке в начале 2026 года, и оно понравилось — друзья даже попросили себе по такому. Вдохновлённый этим, решил собрать вторую версию, которая была бы проще в сборке и более прочной. После овладения навыками проектирования PCB решил перейти с 3D-печатной пластины для крепления LED на использование PCB как подложки. Немного изменил форму и добавил пятикратную симметрию — PCB заказываются партиями по 5 штук, поэтому вместо четырёх уникальных напечатанных деталей можно заказать пять одинаковых PCB и собрать их в кольцо.

Вторая версия проекта — пять радиально расположенных PCB

Оказалось, что ручная пайка neopixel светодиодов паяльником — ещё больший кошмар, так как контакты расположены под светодиодом, а не на его поверхности. Проект на время встал.

Привез части проекта на программистский ретрит в Recurse Center летом 2026 года. Друг, которого встретил там, был гораздо более опытен с горячим воздушным паяльником. Вместе управились со всеми пайками, и создал новые стенки ячеек, которые крепились к PCB и соединяли их вместе.

Запуск PCB-смонтированных светодиодов после часов пайки

После завершения новой версии во время батча Recurse решил оставить её там как инсталляцию. Перешел на ESP32, чтобы подключиться к wifi-сети центра, и переписал прошивку на Rust. Контроллер запускает локальный сайт, где участники Recurse могут загружать скетчи и игры, используя тонкий WebAssembly-backend и минимальный API.

Что дальше?

Проект всё ещё не полностью удовлетворяет.

Достаточно людей выразили интерес, чтобы хотелось сделать больше экземпляров — хотя бы подарить друзьям. Но процесс не идеален: пайка была утомительной по разным причинам в обоих случаях. Приклеивание бумаги сложно, и сама бумага хрупкая и трудно поддаётся ремонту при разрывах. Есть идеи для улучшений: другие типы светодиодов, легче паяющихся вручную, использование PCB-сборки, тестирование слоя прозрачного акрила для защиты бумаги, крепление передней панели с помощью магнитов. Много экспериментов впереди.

Пока проект заслуживает отдыха, но когда время придёт, будет третья версия!