Хабрахабр

Прошивка для фотополимерного LCD 3D-принтера своими руками. Часть 2

Продолжение статьи о написании своей прошивки для фотополимерного 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 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 на Алиэкспресс
Исходники оригинальной прошивки от производителя на Гитхабе
Схемы от производителя двух версий платы на Гитхабе
Мои исходники на Гитхабе

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»