Sleet - 232b интро для STM32 и осциллографа
Идея была — написать 256 байт интро для голого чипа. Выбран был STM32L100RCT6, поскольку я как раз разбирался с STM32, идя сверху вниз — сначала помигал светодиодом на HAL, потом на CMSIS и, наконец, на чистом ассемблере. Конкретно в упомянутом чипе есть два цифро-аналоговых преобразователя, что позволяло обойтись без внешней обвязки, подключив всё непосредственно к дисплею. В качестве дисплея я рассматривал два возможных варианта — либо обычный ЭЛТ монитор с PAL/NTSC композитным входом, либо осциллограф. Сгенерировать осмысленный сигнал для композита в 256 байт будет, как по мне, сложновато, а вот для осциллографа — в самый раз. К тому же тепло (что зимой немаловажно) и лампово.
Подробно расписывать про STM32 особого смысла нет — на эту тему написано уже очень много и подробно. Поэтому скажу в двух словах: STM32 — это такой недоарм микроконтроллер (в данном случае — ARM Cortex M3). Формально он, конечно, ARM, но из него вырезана стандартная для ARM'ов реализация 32-битных инструкций, их условное выполнение заменено на куцее «it», ну и ещё там всякого по-мелочи.
Впрочем, писать 256 байт интро для процессора, в котором все инструкции занимают по 32 бита было бы сомнительной радостью. Здесь же часть инструкций занимает 16 бит (это называется «thumb»), что даёт некоторый простор для кода.
На видео вы можете видеть плату с двумя чипами — это STM32L100-DISCO. На втором чипе там реализована отладка через ST-Link, так что он не используется и не считается (для отладки я использовал J-Link), плюс тактирование включено от внутреннего генератора чипа (т.е. кварц на плате тоже лишний). В теории можно было бы взять вместо этой платы один голый чип и прицепиться к ножкам. Я даже попытался это сделать, но запустить его отдельно мне не удалось — судя по сигналам от J-Link, чип не реагировал на внешние раздражители. Возможно, я просто где-нибудь ноги закоротил при пайке lqfp48 — короче говоря, разобраться не успел, поэтому для конкурса в итоге снял видео с вышеупомянутой платой.
Определившись с чипом, я пошёл на Авито и нашёл там осциллограф Н-313. Честно скажу, выбирал главным образом по размеру и весу. Забирал на Парнасе. Глаза хозяина были грустны, а передаваемый к осциллографу щуп почему-то напомнил мне собачий поводок.
Осциллограф нормально работал, но проблема была со входом X. Он там был, и даже назывался X но, по факту, был входом внешней синхронизации. Странно однако, что вёл себя этот вход очень похоже на обычный вход X. С той лишь разницей, что при достижении некоего порога его начинало, в буквальном смысле слова, плющить.
На снимке изображена фигура Лиссажу, которая должна была быть ровной окружностью. Увы, окружностью она была только с сигналом не выше определённого уровня. От превышения (неважно, самого сигнала или усиления осциллографом) её начинало расплющивать с левой стороны.
Я нашёл журнал Радио где было написано, куда в этом осциллографе надо припаяться, чтобы получить правильный вход X. Однако, эффекта это не дало (новый припаянный вход вообще не работал).
Тут я вспомнил, что у отца до сих пор хранится осциллограф С1-72, с которым я играл в детстве. Съездил за ним, включил и, несмотря на побитый вид (раны были получены на одном из старых CC, больше негде) работал он отлично. Правда, при работе непрерывно свистел с частотой килогерц 15, из-за чего долго отлаживать на нём интро проблематично — начинает вытекать мозг. Так и хочется сказать — «не свисти, денег не будет!».
Когда я открыл vscode (захватывающее описание установки gnu c, as и настройки cortex-debug пропускаю), то понятия не имел, на что будет похоже интро. Понятно, что в 256 байт векторный вывод текста вместе с самим текстом не запихнуть. Есть и ещё один неочевидный момент. Помните Vectrex? На первый взгляд, это почти тоже самое — фактически ведь тоже есть процессор, память и ЦАП, присоединённые к ЭЛТ. Более того, Vectrex медленнее и с ограничениями — более-менее нормально управлять лучом там можно только по одному из каналов, а здесь у нас два полноценных 12-битных ЦАП — делай, казалось бы, что хочешь.
Однако, если смотреть в корень, то Vectrex оказывается куда как мощнее. Дело в том, что там ЦАП присоединён не прямо к соответствующему каналу, а через интегратор. Т.е., чтобы нарисовать линию, в Vectrex не надо программно менять координаты луча каждую микросекунду (как это требуется в случае с STM32+ЦАП) — там всё будет плавно меняться само, по мере разряда конденсатора. Аналоговая часть экономит очень много кода и времени процессора, не говоря уже о красивых артефактах (нахлынули воспоминания..) Увы, на этот раз передо мной стоял не Vectrex, а лежал жалкий STM32.
Поначалу в голове вертелись фигуры Лиссажу. Ну не сами конечно, а мысли о них. Ведь, достаточно сгенерировать на оба выхода синусоиды и, играясь с частотой и разностью фаз, можно будет легко произвести впечатление на зелёного, неподготовленного к осциллографам, зрителя.
Однако, вычисление синуса (или табличка значений для него) в 256 байт если и влезет, то больше там байт ни на что не останется. Возможно, мне следовало взять STM32F4, где есть инструкции для плавающей точки, но в магазине сходу не нашлось платы на F4 с ЦАП-ом (или меня задушила жаба переплачивать? Не помню).
Навешивать же RC цепочки для изготовления аппаратной синусоиды я посчитал читерством (ведь идея была написать интру именно для чипа, без обвязки). С RC цепочками можно было бы, наверное, и вообще без ЦАП-ов обойтись.
Поразмыслив, я сделал на C два вложенных цикла, с изменением в одном X, в другом Y. И стал экспериментировать, запихивая полученное в оба канала ЦАП (в 12-разрядном поканальном режиме). Получил унылую наклонную линию. Ладно, добавил rand() по X и по Y. Получился шум. Прямо как на старом выключенном телевизоре, только векторный. Забавно, но маловато. Случайность явно должна быть менее случайной. Да и сишный rand() сам по себе занимал под килобайт кода и заметно тормозил. Плюс к тому, даже самый симпатичный аналоговый шум будет сложно узнать после его съёмки цифровой камерой, пропускание через кодеки и организаторов CC.
Короче говоря, я пошёл и нарыл простой, короткий и очень быстрый генератор ПСЧ:
x ^= (x << m );
x ^= (x >> 35);
x ^= (x << 19);
После чего стал его портить, меняя количество разрядов сдвига (назовём их параметрами). Стали иногда получаться пусть не фигуры, но довольно интересные стабильные изображения. Однако, для 256 байт интро это было тоже малопригодно — чтобы показать хотя бы десяток-другой таких изображений, пришлось бы делать длинную табличку с параметрами.
Тогда я остановился на каком-то одном получившемся изображении и стал перед запихиванием чисел в ЦАП умножать их, делить, вычитать и складывать с индексами циклов и всякими числами. Чисто интуитивно пробовал разные функции. В какой-то момент результат напомнил мне снегом с дождём. К слову — не первая моя синоптическая работа. Что в Питере не пиши, всё равно получается или дождь или мокрый снег с дождём, ага.
uint32_t random(void) { r_seed ^= (r_seed << 4 ); r_seed ^= (r_seed >> 1); return r_seed; } [...] while(1) { for (uint32_t i = 0; i < 4095; i++) { DAC->DHR12R1 = random() % c + c*(i/2)+d/6; // DAC channel X DAC->DHR12R2 = random() % c - i; // DAC channel Y c++; c %= 3; d++; d %= 15000; } r_seed++; }
Я решил остановиться на этой формуле и вернулся к параметрами в генераторе ПСЧ, подкрутив их для большей реалистичности. Получилось неожиданно неплохо — даже на видео заметно, что капли падают под разными углами, имеют разный размер и есть передний план, как будто отдельные капли и снежинки пролетают прямо перед объективом.
На живом осциллографе это выглядит существенно лучше, чем на видеозаписи на youtube но, тут уж придётся поверить мне на слово…
На современном осциллографе сам сигнал по одному из каналов выглядит следующим образом:
Итак, визуальным эффектом я был более-менее удовлетворён. Но размер бинарника в полтора килобайта (это со включёнными оптимизациями C по размеру) плохо вписывался в первоначальную идею написать 256 байт интро.
Конечно первое, что я сделал — повыкидывал из инициализации STM32 всё, без чего он и так запускался (настройки частоты шины, использование внешнего кварцевого генератора и что-то ещё). После всех утаптываний, получился бинарник в 868 байт. Тут я испугался, что мне придётся вручную соптимизировать код в два раза лучше, чем это делает GNU C. При том, что я ARM ассемблер впервые увидел неделю назад. Тогда я немного упростил генератор ПСЧ и выкинул один из счётчиков. Потом обнаружил, что — код содержал ещё какие-то инициализации регистров, очистку SRAM и ненужные векторы прерываний, подключаемые из startup_stm32l100xc.s.
В теории, наверное, можно было бы повозиться с .ld и Makefile и заставить эту братию сгенерить бинарник в 256 байт даже не влезая в ассемблер. Но это же не путь воина! Затеять всё это, чтобы разобраться с ассемблером, чтобы в итоге добиться результата настройками конфигов компилятора!? Фи…
Я сгенерил .lst файл (ассемблер+машинные коды с условной привязкой к строкам C исходника) и стал его изучать и переносить в отдельный ассемблерный файл с комментариями. Надо сказать, весьма познавательное чтиво — особенно, учитывая краткость программы.
Чтобы это повторить на ассемблере с чистого листа, понадобилось бы разбираться с ним довольно долго. Впрочем, ещё с десяток лишних байт я всё же нацедил вручную — чисто чтобы не пострадала самооценка.
В итоге получился ассемблерный исходник, который после as и ld дал 232 байта чистого кода, который я выставил в Wild demo compo на Chaos Constructions'2021 (первоначально хотел в tiny intro, но это уже другая история).
2 комментария