Продолжение статьи о написании своей прошивки для фотополимерного LCD 3D-принтера.
В этой части продолжу описывать этапы своего проекта:
2. Работа с USB-флэшкой и файлами на ней
3. Управление шаговым двигателем для движения платформы.


Часть 1: 1. Пользовательский интерфейс.
Часть 2: 2. Работа с файловой системой на USB-флэшке. 3. Управление шаговым двигателем для движения платформы.
Часть 3: 4. Вывод изображений слоев на дисплей засветки. 5. Всякая мелочь типа управления засветкой и вентиляторами, загрузки и сохранения настроек и т.п. 6. Дополнительные возможности для комфорта и удобства.

2. Работа с USB-флэшкой и файлами на ней


До этого я никогда не работал с USB-хостом на микроконтроллерах. Как USB-device — делал прошивки и с классом CDC (эмуляция COM-порта) и с классом HID, но вот с хостом не работал. Поэтому для ускорения процесса я создал всю инициализацию этой периферии в STM32CUBE. На выходе я получил работающий в режиме USB FS хост, поддерживающий устройства хранения данных (Mass storage). В том же кубе я сразу подключил и библиотеку FatFS для работы с файловой системой и файлами. Дальше оставалось просто скопировать полученные исходники в свой проект и разобраться как с ними работать. Это оказалось несложно и описывать тут особо нечего. В файле usb_host.c из Куба имеется глобальная переменная Appli_state с типом ApplicationTypeDef:
typedef enum {
  APPLICATION_IDLE = 0,
  APPLICATION_START,
  APPLICATION_READY,
  APPLICATION_DISCONNECT
}ApplicationTypeDef;

Эта переменная при разных событиях периферии USB-хоста (в прерываниях) может принимать одно из перечисленных состояний, указывающее на текущее состояние хоста. В основном цикле программы остается просто отслеживать изменения этой переменной и реагировать соответственно. Например, если ее значение изменилось на APPLICATION_READY, значит флэшка или кардридер подключены и успешно инициализированы, можно читать с них файлы.

С FatFS тоже никаких сложностей — Куб ее уже полностью настраивает и «соединяет» с USB-хостом, так что сразу после подключения флэшки можно обращаться к функциям этой библиотеки для работы с файлами. Правда, свежеобновленный куб подключает библиотеку старой версии. После обновления ее файлов на свежую версию пришлось поисправлять кое-где в кубовских исходниках имена дефайнов из конфигурации FatFS, т.к. в новой версии они изменились. Но особых проблем обновление не доставило, все прошло быстро и легко.

А вот для работы FatFS с кириллицей в именах файлов и каталогов пришлось немного повозиться. Для того, чтобы FatFS корректно читала кириллические имена, нужно в ее конфигурации включить работу с Unicode, и после этого все строки, связанные с FatFS, должны быть только в этой кодировке — имена дисков, имена файлов и т.д. При этом текстовый редактор в IDE и FatFS поддерживают Юникод с разным расположением старшего байта — один с Little Endian, другая с Big Endian, так что просто писать исходники с текстами в Юникоде не получится. Да и не хочется, если честно. Вот тогда и пришлось писать конвертеры из ANSI и UTF-8 в Unicode и обратно, плюс несколько функций по работе со строками разных кодировок в разных сочетаниях. Например, скопировать UTF-8-строку в Unicode-строку, или добавить к Unicode-строке ANSI-строку. Впрочем, ANSI-строк, кажется, нигде уже и не осталось, все исходники полностью перешли в кодировку UTF-8.
Так, открытие файла с заданным именем выглядит сейчас примерно вот так:
tstrcpy(u_tfname, UsbPath);	// задаем полному пути (Unicode) имя диска в (Unicode)
tstrcat_utf(u_tfname, SDIR_IMAGES);	// добавляем к пути (Unicode) имя каталога (UTF-8)
tstrcat_utf(u_tfname, (char*)"\\");	// добавляем к пути (Unicode) слэш (UTF-8)
tstrcat(u_tfname, fname);	// добавляем к пути (Unicode) имя файла (Unicode)

Когда все это быстренько заработало, захотелось проверить скорость чтения файлов с флэшки. Чтение 10-мегабайтного файла блоками по 4 КБ показало скорость около 9 Мбит/сек, что, в общем-то, довольно неплохо и меня устроило.

Сунулся было изучить вопрос по переводу этого дела на DMA, но оказалось, что периферия USB-хоста просто не имеет доступа к DMA. Ну или я не нашел его :) Поэтому показалось логичным все буферы чтения/записи для файлов USB организовать в CCM (Core Coupled Memory) — области оперативной памяти размером 64 КБ, которая так же не имеет выход на DMA. В этой же области памяти имеет смысл размещать и другие переменные/массивы, которые не работают с DMA, просто чтобы больше памяти оставить в обычной оперативке. Кстати, мне показалось, что само ядро работает с этой памятью чуть быстрее, чем с обычной.

2.1 Пользовательский файловый интерфейс


Принтер Anycubic Photon S, который у меня имеется, выводит список файлов в виде значков предпросмотра, 4 штуки на экран. И в принципе, это достаточно удобно — видно имя файла, в картинке предпросмотра видно примерно что за модель. Поэтому и я пошел по тому же пути — файлы выводятся по 4 штуки на страницу в виде картинок предпросмотра с именем файла.

На значках каталогов рисуется знакомая всем желтая папка, на файлах настроек — шестеренка. Выводятся только те файлы, у которых расширение попадает под одно из известных прошивке. На данный момент это файлы .pws (фалы, подготовленные слайсером для печати) и файлы .acfg (текстовые файлы с параметрами настроек принтера).

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


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


Кстати, по поводу картинок предпросмотра, которые рисуются для файлов в режиме иконок — тут никакой интриги нет. Прошивка не анализирует весь файл, чтобы построить изображение по 3D-модели, как некоторые думают :) Эта картинка сохраняется в файле печати самим слайсером, в формате, схожем с BMP — массив 16-битных значений цвета пикселей. Размеры картинки предпросмотра хранятся в специальных полях внутри файла. Так что все очень просто.

Единственное, в чем прошивке приходится поднапрячься — это в масштабировании картинки из файла под размер иконки на дисплее. Масштабирование прошивка осуществляет очень простым способом: вычисляется коэффициент масштабирование k (дробное число) — ширина исходной картинки делится на ширину области вывода на дисплее (так же вычисляется коэффициент по высоте и в работу берется наибольшее из двух значений) и затем из исходной картинки берутся для вывода на дисплей пиксели и строки с шагом k.

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

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

2.2 Просмотр информации о файле перед началом печати


Как правильно заметили в комментариях к предыдущей части, у того же Anycubic-а нет никакой информации о файле при его выборе. Просто появляются кнопки начала его печати и удаления. И это очень неудобно — чтобы узнать предполагаемое время печати, или количество слоев, или другие параметры этого файла, нужно запустить его печать. Я решил не повторять этот недочет и при нажатии на отслайсенный файл открывается экран с максимально полной информацией о нем:


Имя файла, его размер, время последней модификации и практически все параметры печати. Тут, правда, мне на руку сыграл еще тот факт, что дисплей у MKS DLP имеет разрешение 480х320, тогда как у Эникубиков он поменьше — 320х240, на таком особо не размахнешься с кучей текста.

2.2.1 По поводу расчета времени печати напишу отдельно.

Этот показатель не хранится в файле, в отличии от всех остальных параметров. Его принтер должен рассчитать самостоятельно, исходя из известной ему информации. Тот же Anycubic Photon S имеет обыкновение промахиваться с этим расчетом, причем в меньшую сторону — например, обещает 5 часов печати, тогда как в реальности печатает 6 часов. А Longer Orange 30 вообще во время печати меняет это время туда-сюда чуть ли не в два раза. Я решил подойти к этому моменту максимально тщательно. Из чего складывается это время?
  1. Время, за которое платформа опустится с заданной скоростью на высоту очередного слоя.
  2. Время паузы перед началом засветки.
  3. Время засветки слоя.
  4. Время, за которое платформа поднимется на заданную высоту с заданной скоростью после засветки слоя.


Вот эти 4 параметра суммируются, умножаются на количество слоев и получается общее время печати. Если с временами паузы и засветки все элементарно — они выдерживаются с миллисекундной точностью, то вот с движением платформы уже все немного сложнее.

Платформа не набирает заданную скорость мгновенно, у нее есть некоторое ускорение, которое задается в настройках. Причем при печати это довольно маленькое ускорение, так как платформа должна начинать подъем очень плавно, чтобы последний отвержденный слой безболезненно оторвался от пленки на дне ванны (да, полимер липнет и к пленке тоже, к сожалению).

Получается, что движение платформы складывается из трех составляющих — ускорение до достижения заданной скорости, равномерное движение с заданной скоростью, замедление до полной остановки. И вот тут начинаются варианты — например, заданные ускорение и высота подъема не позволяют платформе достичь заданной скорости, она все еще ускоряется в тот момент, когда ей уже нужно начать замедляться, чтобы остановиться на заданной высоте. Или ускорение и высота достаточны, чтобы платформа разогналась до заданной скорости и какую-то часть пути прошла в равномерном движении перед тем как начала замедляться. Нужно все это проверить, высчитать времена и расстояния для каждой составляющей.

Если честно, у меня голова кругом шла когда я писал функцию расчета времени печати :) И в результате все равно получил небольшую погрешность. Например, реальное время печати — 07:43:30 вместо расчетных 07:34:32.


Или 05:48:43 вместо расчетных 05:43:23.


Но в принципе, меня такая погрешность устроила. Я пытался найти ошибку в расчетах, но там вроде бы все верно. Скорее всего, реальное ускорение слегка не соответствует задаваемому из-за особенностей управления шаговым двигателем. Вот так плавно мы подошли к следующему этапу :)

3. Управление шаговым двигателем для движения платформы.


Сначала у меня была мысль написать свое управление шаговым двигателем. Это ведь совсем не сложно, имея на плате нормальный драйвер — выставил на одном выводе направление вращения и погнал на другой вывод импульсы шагов. Надо быстро вращать — поднимаешь частоту импульсов, надо медленно — уменьшаешь.

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

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

Сначала я взял из исходников Марлина файл stepper.cpp — непосредственно управление шаговым двигателем. Однако оказалось, что его работа очень сильно зависима от планировщика движений из файла planner.cpp, пришлось взять и его. Ну и до кучи я взял оттуда еще и файл endstops.cpp — обработка концевиков осей, так как мне все равно нужно было обрабатывать события от них, а тут и планировщик и управление двигателем уже были связаны с этим файлом для концевиков.

Я очень долго провозился с тем, чтобы убрать из этих файлов все ненужное и отвязать их от остальной экосистемы Марлина. Дело в том, что Марлин заточен под управление 6 или 7 шаговиками одновременно, при этом их работа может зависеть от температуры нескольких нагревателей, от параметров пластика и т.д. Система там на самом деле сложная. Мне пришлось очень многое переделать, в основном удаляя лишние оси и ненужные экструдеры и избавляясь от целой толпы макросов, полезных в оригинальной версии, но очень мешающих в моей. Просто для понимания — размер взятых мною из Марлина исходников сократился с 346 до 121 КБ. И каждую строку приходилось удалять с оглядкой.

Естественно, в процессе этой жесткой обрезки я слегка вник в работу всей системы, как она работает. Для движения оси в планировщик через одну из его функций передается целевое положение оси (текущее положение планировщик хранит у себя). Планировщик рассчитывает количество шагов и их параметры на ускорение, прямолинейное движение и замедление и формирует из этих данных специальный пакет данных для функции непосредственного управления двигателем (stepper). Этих пакетов может быть несколько, планировщик при каждом новом задании рассчитывает и создает новый, очередной пакет.

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

Что любопытно реализовано в stepper-е — при небольших скоростях он выдает по одному импульсу шага в каждом прерывании, подстраивая таймер так, чтобы следующее прерывание наступило через нужный период времени. Когда требуемая скорость шагов переваливает за некоторое значение, stepper начинает выдавать по несколько шагов в каждом прерывании. При этом, все тайминги подобраны так хорошо, что равномерность следования шагов получается очень хорошей, я ради любопытства смотрел на осциллографе.

Планировщик еще умеет «стыковать» соседние пакеты. Что это означает: если у планировщика уже имеется подготовленный для stepper-а пакет и тут ему приходит новое задание, то он формирует следующий пакет и изменяет уже имеющийся предыдущий так, чтобы в результате последовательной отработки этих двух пакетов stepper-ом получилось одно плавное движение.

Поясню на примере. Планировщик свободен, ему приходит задание на движение оси вперед на 20 мм со скоростью 30 мм/сек. Планировщик формирует первый пакет, в котором описывает ускорение с нуля до 30 мм/сек, прямолинейное движение с этой скоростью и замедление от этой скорости до нуля. Если до того как stepper заберет у планировщика этот пакет планировщику будет выдано новое задание на движение этой оси еще на 50 мм вперед, но уже со скоростью 40 мм/сек, то планировщик не просто создаст новый пакет с ускорением от нуля, а изменит первый пакет, удалив замедление и продлив на его расстояние прямолинейное движение, а в созданном втором пакете ускорение уже будет начинаться не с нуля, а от скорости предыдущего пакета.

В результате получится одно движение, в котором ось ускорится до 30 мм/сек, проедет 20 мм, затем еще раз ускорится уже до 40 мм/сек и проедет еще 50 мм, замедлившись в конце до нуля. Но это только если stepper еще не успел забрать в работу предыдущий пакет, иначе эти два задания будут отработаны как два отдельных движения с нулевой начальной и конечной скоростью в каждом из них. Поэтому, кстати, в принтерах при ручном управлении платформой если несколько раз подряд нажать подъем с шагом 10 мм, то платформа после первых 10 мм подъема остановится и потом уже продолжит движение без остановок на всю высоту, нащелканную кнопкой.

В новой версии Марлина уже появилось средство против такого «дерганного» движения — теперь планировщик не отдает stepper-у пакет в течении определенного времени после его формирования если этот пакет является единственным готовым. Это время отводится на выжидание — не поступит ли следующее задание, чтобы можно было состыковать его с имеющимся.

3.1 Интерфейс управления движением платформы




Тут, в общем-то, все стандартно и привычно для фотополимерных принтеров. Вверху выбор шага движения оси, справа кнопки движения оси вверх или вниз с выбранным шагом.
Кнопка «Домой» служит для обнуления платформы (парковки, хомления), при ее нажатии платформа начинает двигаться в сторону «домашнего» концевика. Достигнув его, платформа останавливается, немного отъезжает назад и еще раз медленно (для пущей точности) наезжает на концевик. После этого прошивка совершенно определенно знает точную текущую высоту подъема платформы.

Кнопка «Уст. Z=0» предназначена для калибровки высоты платформы над дисплеем. Такая система калибровки используется, например, в принтерах Anycubic, когда нулевая точка платформы (оптимальная ее высота над дисплеем) находится на 1-2 мм ниже срабатывания «домашнего» концевика. И эта система калибровки видится мне более правильной, чем становящиеся в последнее время популярными системы, когда высота срабатывание концевика является одновременно и нулевой высотой платформы.

Ну и последняя кнопка «Стоп!» — это безусловная и немедленная остановка движения платформы. Кстати, пока платформа находится в движении, из этого экрана нельзя выйти, кнопка «Назад» не сработает. Это сделано как раз для того, чтобы пока платформа двигается, кнопка «Стоп» была в моментальной доступности.

3.2 Другие моменты по движению платформы


Вот в Anycubic Photon меня жутко раздражают несколько вещей.

Первая — это почему ручное движение платформы происходит с тем же черепашьим ускорением, что и в режиме печати? При печати такое маленькое ускорение полезно, но когда при ручном управлении осью она разгоняется 2 секунды — это просто кошмар. Да и скорость движения так себе.

Второй момент — почему при постановке печати на паузу платформа поднимается на высоту паузы с той скоростью, которая задана в параметрах печати? Черт возьми, ждать 15 секунд пока платформа поднимется на два (всего лишь) сантиметра — это за гранью добра. Но спасибо, что хоть поднимается. У Orange 30 пауза вовсе не подразумевает подъем платформы хоть на миллиметр, так что даже непонятно для чего она там вообще есть.

И третий момент, который просто бесит — после окончания печати платформа поднимается на самый верх. С той же скоростью, которая была задана в параметрах печати — 1 мм/сек. Это 100 секунд на подъем наверх с высоты 5 см!

Поэтому в своей прошивке я сделал настраиваемыми скорости и ускорения отдельно для режима печати и отдельно для ручного управления платформой. Но с двумя ограничениями:
  1. Пока ось не будет обнулена кнопкой «Домой» скорость движения будет снижена втрое. Это сделано ��отому что пока принтер не знает точную текущую высоту платформы, существует опасность раздавить дисплей, не успев остановиться с высокой скорости (инерция, чтоб ей) или повредить верхний упор оси. После обнуления оси принтер уже точно знает положение платформы и в силу вступают программные ограничения высоты, которые так же задаются в настройках.
  2. На высоте меньше 30 мм скорость так же снижается втрое, независимо от того обнулена ось или нет. Это сделано для предотвращения разбрызгивания фотополимера из ванны при слишком быстром опускании в него платформы. Или при слишком быстром подъеме из него.


Конечно же, в настройках присутствуют и другие стандартные параметры оси — количество шагов на 1 мм, направление движения, работа концевиков и т.п. Если кому-то интересно, то под спойлером приведен текстовый конфигурационный файл со всеми поддерживаемыми параметрами. Такой файл с расширением .acfg кушается прошивкой прямо из списка файлов, загружая параметры, сохраняя их в EPROM и применяя немедленно, без перезагрузки:
Содержимое конфигурационного файла
# Stepper motor Z axis settings
[ZMotor]

# Изменяет направление движения платформы.
# Допустимые значения: 0 или 1. По умолчанию: 1.
# Измените этот параметр если платформа двигается в неверном направлении.
invert_dir = 1

# Направление движения платформы при поиске домашней позиции.
# Допустимые значения: -1 или 1. По умолчанию: -1.
# Если этот параметр равен -1, то при поиске домашней позиции
# платформа будет двигаться вниз, к нижнему концевику. При значении 1
# платформа будет двигаться к верхнему концевику.
home_direction = -1

# Значение оси Z после поиска домашней позиции. Как правило, для нижнего
# домашнего концевика это 0, для верхнего - максимальная высота оси.
home_pos = 0.0

# Ограничение на минимальную допустимую нижнюю позицию платформы в миллиметрах.
# Допустимые значения: число в диапазоне от -32000.0 до 32000.0.
# По умолчанию: -3.0
# Это ограничение действует только после нахождения домашней позиции. Если
# поиск домашней позиции не производился, то движение ограничивается концевиками.
min_pos = -3.0

# Ограничение на максимальную допустимую верхнюю позицию платформы в миллиметрах.
# Допустимые значения: число в диапазоне от -32000.0 до 32000.0.
# По умолчанию: 180.0
# Это ограничение действует только после нахождения домашней позиции. Если
# поиск домашней позиции не производился, то движение ограничивается концевиками.
max_pos = 180.0

# Работа нижнего концевика.
# Допустимые значения: 0 или 1. По умолчанию: 1.
# Если при срабатывании концевика напряжение на его выходе пропадает, то поставьте
# значение 1, если наоборот - поставьте 0.
min_endstop_inverting = 1

# Работа верхнего концевика.
# Допустимые значения: 0 или 1. По умолчанию: 1.
# Если при срабатывании концевика напряжение на его выходе пропадает, то поставьте
# значение 1, если наоборот - поставьте 0.
max_endstop_inverting = 1

# Количество шагов двигателя на 1 мм движения платформы.
steps_per_mm = 1600

# Скорость первого, быстрого движения к концевику при поиске домашней
# позиции, мм/сек. По умолчанию: 6.0.
homing_feedrate_fast = 6.0

# Скорость второго, медленного движения к концевику при поиске домашней
# позиции, мм/сек. По умолчанию: 1.0.
homing_feedrate_slow = 1.0

# Ускорение платформы в режиме печати, мм/сек2.
acceleration = 0.7

# Скорость движения платформы в режиме печати, мм/сек.
feedrate = 5.0

# Ускорение платформы в режиме свободного движения (движение кнопками из интерфейса,
# подъем по окончании печати и т.п.), мм/сек2.
travel_acceleration = 25.0

# Ускорение платформы в режиме свободного движения (движение кнопками из интерфейса,
# подъем по окончании печати и т.п.), мм/сек. На высоте менее 30 мм платформа
# двигается в три раза медленнее заданной в этом параметре скорости, но не менее
# 5 мм/сек.
travel_feedrate = 25.0

# Ток двигателя для интегрированного в плату драйвера, мА.
current_vref = 800.0

# Ток двигателя для интегрированного в плату драйвера в режиме удержания, мА.
current_hold_vref = 300.0

# Время с момента последнего движения двигателя, после которого включается режим
# удержания с пониженным током. Задается в секундах. Значение 0 отключает режим
# удержания с пониженным током.
hold_time = 30.0

# Время с момента последнего движения двигателя, после которого мотор полностью
# отключается. Задается в секундах. Значение этого параметра должно быть не меньше
# значения параметра hold_time. Значение 0 отключает этот режим.
# Следует учесть, что при отключении мотора теряется домашняя позиция.
off_time = 10.0

# General settings
[General]

# Длительность звука зуммера в миллисекундах (0.001 сек) при окончании печати
# или при выводе сообщений об ошибках.
# Допустимые значения: от 0 до 15000. По умолчанию: 700 (0.7 сек).
buzzer_msg_duration = 700

# Длительность звука зуммера в миллисекундах (0.001 сек) при нажатии
# на активную зону сенсорного дисплея, например на кнопку.
# Допустимые значения: от 0 до 15000. По умолчанию: 70 (0.07 сек).
buzzer_touch_duration = 70

# Переворачивает изображение на интерфейсном дисплее на 180 градусов.
# Служит для возможности переворота дисплея в принтере для более удобного его размещения.
# Допустимые значения: 0 или 1. По умолчанию: 0.
rotate_display = 0

# Время перехода дисплея в режим скринсейвера с отображением времени и даты, задается в минутах.
# Скринсейвер эмулирует настольные LCD-часы. Переход обратно в рабочий режим - нажатие в любом
# месте дисплея.
# Допустимые значения: от 0 до 15000. По умолчанию: 10. Значение 0 отключает режим скринсейвера.
screensaver_time = 10



И на этом я закончу эту часть, и так уже получилось слишком много текста :)
Как и раньше — с удовольствием отвечу на вопросы и приму замечания.

Часть 1: 1. Пользовательский интерфейс.
Часть 2: 2. Работа с файловой системой на USB-флэшке. 3. Управление шаговым двигателем для движения платформы.
Часть 3: 4. Вывод изображений слоев на дисплей засветки. 5. Всякая мелочь типа управления засветкой и вентиляторами, загрузки и сохранения настроек и т.п. 6. Дополнительные возможности для комфорта и удобства.

Ссылки


Комплект MKS DLP на Алиэкспресс
Исходники оригинальной прошивки от производителя на Гитхабе
Схемы от производителя двух версий платы на Гитхабе
Мои исходники на Гитхабе