А объединяет их моё безудержное желание идти своим путем там, куда нормальный разработчик и под дулом пистолета не сунется – это если кратко. А тем, кого данная аннотация не испугала, готов представить под катом развернутое описание того, как был проведен уникальный генетический эксперимент по скрещиванию перечисленных в заголовке сущностей, и что из этого вышло.

Я – самоучка-многостаночник, т.е. и электронщик, и программист, и швец, и жнец (с дудой не вышло – слухом обделен). И, естественно, в моём анамнезе есть радиокружок и бегущие огни… Но к сути: автомат световых эффектов (бегущие огни) – это нечто, доводящее до истерики своим однообразием. Даже китайские гирлянды с десятком эффектов надоедают почти сразу. И, казалось бы, в то время, как космические автомобили бороздят просторы галактики, не сделать автомат световых эффектов, который был бы не так надоедлив – что может быть проще?! А вот попробуйте найти готовые решения – сумеете? Поскольку (см. первую строку статьи) изобретение велосипедов практически мой конёк, я даже искать не стал, а сразу приступил к.

Итак, для гибкости применим микроконтроллер (кто б сомневался). Для красочности – «умные» светодиоды WS2812B (или подобные). Но что делать с неповторимостью эффектов? И тут все просто – надо сделать не жестко запрограммированные эффекты, а генерируемые динамически по определенным алгоритмам с элементами случайности.

Вы подумали, что сейчас пойдет речь про микропитон (пардон, майкропайтон) и аппаратную Nucleo или другую ардуиноподобную штуку? Это не наш путь! Свой собственный интерпретируемый скриптовый язык – как вам?! А чтобы совсем было не скучно, то добавить поддержку microSD в качестве носителя этих самых скриптов. И микроконтроллер для пущего веселья взять «дохленький» (на «нормальном» и дурак сумеет). Только AVR, только хардкор!

Итак, основа – atmega328P, схема, практически, из этого контроллера и состоит:

Принципиальная схема
Принципиальная схема

Собственно, главное в моём велосипеде – это скриптовый алгоритмический язык. Скудные ресурсы выбранного контроллера не позволяют выполнять интерпретацию серьёзных языков типа того же микропитона, поэтому я придумал специализированный ассемблер. Собственно, от ассемблера у меня только трех- и двухбуквенные мнемоники команд и наличие в командах не более двух операндов, но функционально каждая мнемоника соответствует достаточно сложной функции, например, CLR очищает все внутренние переменные, а PNT реализует обновление до 254 светодиодов в гирлянде.

Концептуально мой ассемблер – 8-битный, т.е. все переменные занимают физически 1 байт, отсюда ограничение на число светодиодов в гирлянде – не более 254 (один пал жертвой необходимости контролировать переполнение вычислений). Это достаточно много, если идет речь о гирлянде на ёлку, хотя, конечно, по сравнению с пиксельными дисплеями почти ничто…

В распоряжении того, кто вздумает создать скрипт светового эффекта на моём ассемблере, есть 36 однобайтных переменных, любое количество которых может быть «назначено» массивом с доступом по индексу. Массив значений RGB-цвета каждого светодиода в гирлянде так же доступен, причем в особо экзотических случаях это пространство так же можно использовать для хранения промежуточных результатов вычислений (хотя это я погорячился – вряд ли такой самурай найдется среди живущих).

Как я уже говорил, синтаксис для простоты интерпретации, очень ограниченный: все переменные и функции именуются двумя символами – ни больше, ни меньше, - это сделано для совместимости с записью чисел только в шестнадцатеричном формате. Т.е. интерпретатор, взяв два символа, не совпадающие ни с одной из мнемоник команд, может быть точно уверен, что это будет число (результат функции или значение переменной тоже число). В итоге любая переменная именуется V*, где вместо звездочки – одна из цифр или букв английского алфавита, а, например, функция генерации псевдослучайного числа – RD.

Данный подход позволил, например, при описании RGB-цвета использовать такие варианты:

  • FF003E— прямо заданный RGB цвет;

  • RD0000— красный компонент случайный, а остальные обнулены;

  • V1V2V3— RGB‑составляющие находятся, соответственно, в переменных V1, V2 и V3.

Для косвенного обращения к массивам введено обозначение X* (звездочка имеет тот же смысл), что означает «взять значение из V* и использовать его в качестве номера ячейки в массиве для извлечения значения». Например, массив состоит из V0, V1 и V2, а переменная VJ используется в качестве индексной, тогда запись единицы в третий элемент массива будет реализован такими командами: VJ=02 XJ=01 (нумерация с нуля, поэтому для обращения к третьему элементу используем число 2).

Математические действия поддерживаются в объеме «школьной арифметики» - сложение-вычитание, умножение-деление и взятие остатка от деления – вот и весь ассортимент, да и то с «нюансиком»: только вычитание может привести к переполнению результата, т.е. 0-1=255, в то время как все прочие арифметические действия никогда не переполняются (250+250=255). И формат мнемоники немного экзотический, например, эквивалент традиционной для Си записи V1 += V2 в моём языке выглядит проще: V1+V2, т.е. первый операнд всегда приемник результата.

Так же есть ряд встроенных функций: генерация псевдослучайного числа, задержка, ввод аналогового сигнала и еще несколько других.

Есть возможность организовать циклы с глубиной вложенности до 5 уровней, а также условное выполнение команд с той же глубиной вложенности.

Команды могут записываться как по одной в строке, так и по несколько (ограничение на длину строки тоже 255 символов), между командами может быть любое количество символов-разделителей (пробелы, табуляции), но в тело самих команд не может содержать разделителей, т.е. запись V1=01 корректна, а V1 = 01 – недопустима.

Вот чего нет, так это подпрограмм…

Для обновления гирлянды светодиодов имеется две команды: PNT (обновляет светодиоды и тут же возвращает управление) и WT=SS (обновляет светодиоды и затем ожидает SS «тиков» по 10 мс, тем самым задавая темп эффекта). Во время ожидания каждые 10 мс происходит автоматическое изменение яркости светодиодов, для чего предусмотрен особый параметр «затухание».

Предусмотрены и комментарии – любые символы, не распознанные, как корректная мнемоника языка, трактуются комментарием, т.е. игнорируются. Однако, следует признать, что комментарии, равно как и «любое количество разделителей», приводит к увеличению времени интерпретации команд, что для достаточно маломощного микроконтроллера может быть критично, т.е. все эти излишества не приветствуются.

А теперь несколько примеров (с комментариями).

Одинокий бегущий огонек красного цвета по всей длине гирлянды:

CLR инициализация всех параметров
PC=FF0000 цвет «рисования» красный
GB=FF глобальная (т.е. сразу всех светодиодов) яркость максимальная
PM=00 заливка первого светодиода цветом рисования
RPT начало главного цикла 
   RLC сдвиг цвета светодиодов по всей гирлянде влево
   WT=10 обновление гирлянды и задержка в 160 мс
INF конец главного цикла – команда бесконечного повторения

В чем же фишка подобного описания эффекта? Да в гибкости! Если гирлянда состоит из 10 светодиодов, или из 50, или из 150 – скрипт будет один и тот же, и в каждом случае, добежав до конца гирлянды, красный огонек начнет сначала свой бег. Чисто аппаратными средствами такой эффект реализовать, по-моему, в принципе нереально.

А теперь разнообразим эффект непредсказуемостью цвета. В сущности, вся модификация скрипта сведется к первой его строке: PC=RDRDRD, и все! Однако, случайно генерируемые таким способом цвета чаще всего будут достаточно бледными. Чтобы повысить сочность, будем формировать цвет огонька по правилу: RGB-составляющие могут принимать случайным образом значение только FF или 00, при том, что в цвете хотя бы одна составляющая должна быть равна FF. Сделать это можно таким циклом:

RPT повторять
   VR=00 принимаем красный равным 0
   IRD>80 если случайное число больше 128
      VR=FF красный на полную
   EI конец «если»
   VG=00 принимаем зеленый равным 0
   IRD>80 если случайное число больше 128
      VG=FF зеленый на полную
   EI конец «если»
   VB=00 принимаем синий равным 0
   IRD>80 если случайное число больше 128
      VB=FF синий на полную
   EI конец «если»
   VC=VR VC+VG VC+VB вычисляем сумму составляющих цвета
LVC=00 повторяем цикл, если сумма нулевая
PC=VRVGVB теперь цвет рисования будет ярким и случайным (из 6 вариантов).

Далее команды скрипта те же, что и ранее.

Конечно, алгоритм получения случайного сочного цвета так себе, можно и получше придумать, но в формате этой статьи подобные экзерсисы неуместны.

Упомянутая ранее возможность автоматически менять яркость светодиодов во время «ожидания» позволяет буквально парой команд реализовать весьма занятные эффекты, например:

RPT 
   GI=RD задать глобально цвет по случайному индексу
   GB=00 яркость на минимум
   GF=FE управление «затуханием»
   WT=FF задержка на 2,5 секунды
INF

Сможете угадать, что делает вышеприведенный скрипт? Он реализует такой эффект: вся гирлянда плавно разгорается до максимума и затем так же плавно гаснет, причем каждый раз новым случайным цветом. Как видите, для плавного изменения яркости от минимума до максимума и наоборот, потребовалось всего одна команда WT (с нюансом – ранее был задан параметр «затухания»). Признаюсь, яркость меняется нелинейно…

В общем, я надеюсь, идея языка и его применимости понятна. Есть в нем и несколько занятных фишек, например, предусмотрено 2 аналоговых входа, сигналы на которых можно использовать, как переменные в скриптах, а так же «матричный режим» для создания несложных 2D-эффектов (если гирлянду развесить определенным способом)… Но об этом, если кому интересно, лучше прочесть в хелпе к IDE, без которой создавать скрипты на этом языке оказалось затруднительно (хотя изначально задумка была – обойтись блокнотом Windows), ведь ни одна «нормальная» IDE, ни один отладчик не поддерживает это чудо… Вот и пришлось в придачу сделать собственную IDE, в которой можно и скрипт написать, и отладить его в «почти реальном времени» без реальной светодиодной гирлянды. Ссылка на IDE также в конце статьи.

IDE под названием DSM
IDE под названием DSM

Ну и несколько деталей о внутреннем устройстве процесса интерпретации скриптов:

  • Сразу после установки SD-карты считывается конфигурационный файл, в котором указан, сколько всего светодиодов в гирлянде, как именно эта гирлянда используется (т.е. в виде полоски 1D или матрицы 2D), сколько всего скриптов на карте и ряд других параметров.

  • Затем выбирается один из имеющихся файлов для загрузки. Имя каждого файла – его номер (расширение фиксированное), т.е. 0.sc, 1.sc и т.д. Выбор очередного скрипта может быть либо случайным из имеющихся на карте, либо последовательным по их номерам.

  • Поскольку для работы с SD-картой используется SPI-интерфейс, а внутреннее устройство Petit FatFS таково, что даже для чтения одного байта считывается целый сектор, чтение файлов – очень ресурсоёмкая операция, поэтому реализовано внутреннее кэширование считываемых данных. В частности, из-за этого длинные циклы могут быть очень медленными из-за необходимости постоянно подгружать команды с карты. Это, кстати, одна из причин отсутствия подрограмм.

  • Считанные в кэш данные анализируются символ за символом, найденные команды тут же исполняются.

  • Постоянно контролируется время работы каждого скрипта и, если в конфигурации задан лимит времени работы одного скрипта, скрипт принудительно останавливается, после чего происходит выбор следующего. Если лимит времени не задан, скрипт с бесконечным циклом будет исполняться непрерывно вплоть до сброса устройства.

В общем, такой вот светодиодный велоассемблер… Я не очень надеюсь снискать за это почет и уважение, но даже если смог кого только повеселить – и то хорошо! Хотя лично меня и моих близких данное поделие радует уже третий новый год. И если вы подумали, что никаких красивых эффектов на таком примитиве сделать невозможно, я категорически не соглашусь! Вот, например, небольшая симуляция-анимация эффекта «вращающейся радуги»:

А скрипт этого эффекта вот такой:

CLR
GB=FF IRD<80 REV EI
VN=05
VD=E0
RPT
  VD+01
  IVD>F0 VD=E0 EI
  V0=VC
  VP=TP
  RPT
    PI=V0
    IV0<VD VB=VD VB-V0 V0=FF V0-VB V0+01 VS=01 EI
    IVS=00 V0-VD EI
    VS=00
    PM=VP
  LVP
  V0=VC
  IV0<VD VB=VD VB-V0 V0=FF V0-VB V0+01 VS=01 EI
  IVS=00 V0-VD EI
  VS=00
  VC=V0
  WT=03
INF 

Обещанные исходники и IDE:

Комментарии (10)


  1. aborouhin
    00.00.0000 00:00
    +6

    Количество свободного времени у Вас внушает зависть, а готовность его потратить на сколь фундаментальный, столь же и практически бессмысленный проект - уважение, конечно :)


    Один маленький вопросик - всё-таки в 2023 году исходники ожидаешь увидеть на Github, а не в архиве на mail.ru :) Тем более ссылка на IDE ведёт не туда (продублировалась предыдущая).


    1. A-R-V Автор
      00.00.0000 00:00
      +1

      Я сейчас в пути, как буду дома, поправлю ссылки. А вот с гитхабом я ну никак не дружу...


      1. aborouhin
        00.00.0000 00:00

        Спасибо, я уже по названию Вашей IDE нагуглил на другом сайте. Любопытно было глянуть, на чём это написано. Глянул, понял, что вот с этим уже я совершенно точно не дружу :) (кому ещё любопытно - это вообще Free Pascal / CodeTyphon)


    1. A-R-V Автор
      00.00.0000 00:00
      +1

      Поправил ссылку на IDE


  1. sim2q
    00.00.0000 00:00
    +1

    Хабр - торт!, автору - респект:)
    ps не забываем во все весёлые (визуализация,аудиоэффекты итд) штуки где есть рандом реализовывать его "честным", например как смещённый в обратную сторону p-n переход. Так при определённой сноровке (но это не точно) можно получить занимательную обратную связь.


    1. A-R-V Автор
      00.00.0000 00:00

      Случайность у меня реализуется стандартным rand-ом, но для начальной его инициализации я использую CRC8 всей области ОЗУ сразу после включения питания, когда там еще есть мусор. Для автомата световых эффектов этого более, чем достаточно


  1. mark_ablov
    00.00.0000 00:00

    ИМХО, логичнее было бы компилировать в байткод, тем более toolstack самостоятельно создаётся.


    1. A-R-V Автор
      00.00.0000 00:00

      Идея изначально была обойтись только "блокнотом", а значит, процесс правки скрипта должен быть предельно прост. Поэтому от компиляции в байт-код отказался.


      1. mark_ablov
        00.00.0000 00:00

        Ну как бы JIT или просто компиляция на самом МК. Давно не трогал atmega, но пара сотен байт в RAM должна же найтись?


        1. A-R-V Автор
          00.00.0000 00:00

          Вряд ли пары сотен байт ОЗУ будет достаточно, особенно с учетом того, что нет ограничений на размер "исходника" на SD-карте.