Поведаю вам о нашей небольшой автореставрационной мастерской. Статья получилась большая, но я думаю коллегам технарям будет интересно. Сразу предупрежу, я "молодой" специалист и только обучаюсь ремеслу, многие нюансы не знаю, поэтому буду делиться опытом старших товарищей. Раньше я никогда не думал, что из цельного листа железа, без заводских штампов можно сделать такие сложные детали, при этом не используя сварку. Будет использоваться старая технология формовки металла по болвану, по ней раньше делали кузовные детали для автомобилей.
Так выглядел лист метала до, а так ближе к итогу.
Далее от лица мастера и учителя расскажу подробнее как удалось этого добиться.
Некоторое время назад в мастерской был принят заказ на изготовление облицовки радиатора для легендарного автомобиля ЗиС 101.
ЗиС 101
Автомобиль находится в другом городе, добраться до него я не мог, поэтому источником технической информации у меня были заводские чертежи. Тяжело работать только по чертежам, когда вокруг заготовки нужно бегать с линейкой и прочим измерительные инструментом. Я не представляю как инженеры и мастера 40-х годов обходились без ПК, вернее представляю конечно, но понимаю на сколько это трудоемко и долго. Я решил не повторять их длинный путь, и обратился к конструктору с заданием сделать по чертежам 3д модель корпуса облицовки.
3д модель.
Затем был найден подрядчик, который отфрезеровал болваны двух половин. Фрезеровали из фанеры, сочли что это наиболее доступный, из подходящих, материал.
Болваны! Не только лишь все, но эти точно.
Итак болваны привезены в мастерскую. Материально-затратную часть я сознательно опускаю, но поверьте, все это отнюдь не дёшево. Далее предстояло определить технологию изготовления самих деталей. Варианта виделась два. Первый — сделать фрагментарно несколько частей конструкции и сварить их воедино. Второй — делать каждую деталь из цельного куска, как это делали на заводе наши деды. Первый вариант мне показался не спортивным и не правильным, так делают многие, к тому же при сварке можно ошибиться в геометрии и все труды насмарку. Предпочтение было отдано второму варианту, как более правильному, к тому же это старая школа производства автомобилей, ближе к аутентичным технологиям, а это важно при реставрации. Начал с заготовки. Всё как обычно, шаблон, выкройка, металл… Очень важно закрепить заготовку жёстко, чтобы она не имела возможности съехать или поплыть не в ту сторону, в которую нужно. Поэтому сделал прижимную пластину, вклеил резьбовые втулки и притянул заготовку к болвану. После этого немного обстучал, подогнул руками там где это было возможно.
Закрепленный и обстуканный лист металла.
Затем в ход пошла "тяжелая артиллерия", как иногда в шутку мы говорим. Сделали несколько зажимов, сбегал в магазин за цепями и талрепами. Зажимы закрепил по периметру, притянул цепи к верстаку, начал обстукивать, придавая листу нужную форму.
Больше талрепов богу металшейпинга!
Переставляя зажимы, подтягивал металл в нужных местах, порой снимал заготовку с болвана, разравнивал складки, ставил обратно, продолжал тянуть и стучать.
Молоток, зубило и натягивание в нужный момент творят чудеса. Видно справа складку излишков металла.
Работа физически очень тяжёлая, тысячи ударов молотком, пот льётся ручьём, но энтузиазм и желание увидеть конечный результат толкают вперёд.
После ручной формовки раскатал на английском колесе неровности в местах, где это было возможно, деталь стала ровнее и глаже.
Идеальные формы на фоне творческого беспорядка.
Разравниваем все складки, прорезаем технологические отверстия и повторяем весь процесс для второй половины облицовки.
Оставалось сделать подвороты, подгибы, элементы креплений и прочие мелочи. Эти операции по факту заняли больше времени чем основная формовка. Но все получилось, порой сам не ожидал.
Корпус наконец то готов, но ещё нужна решетка радиатора, которая состоит из почти сорока молдингов. Для изготовления молдингов было придумано специальное приспособление, заготовки вырезаны лазером, работа не сложная, но отняла три дня жизни.
Из этих деталей впоследствии у заказчика будет собрана решетка облицовки радиатора.
В заключении скажу, что работа была очень интересной, хотя очень тяжёлой и ответственной. Такая работа, без ложной скромности замечу, требует от мастера не только понимания происходящих процессов, но и целеустремлённость и силы характера.
Более подробно об изготовлении этих и других деталей, а так же фото и видео рабочих процессов в нашем телеграмм канале: https://t.me/gary_bumgaz P.S. Кстати, нам в мастерскую на работу требуются опытные коллеги, а так же ученики с большим желанием научится уникальной профессии. Свои станки, оборудование, необходимые инструменты и материалы, бесценный опыт и знания, отличные люди и дружеская атмосфера. Территориально Московская область, г. Подольск. По вопросам работы и обучения пишите в телегу, постараюсь всем ответить: @westoff87
Пожалуй, немалая часть моих читателей так или иначе интересуется DIY-тематикой. И в различных самодельных девайсах порой есть необходимость вывести какую-либо информацию на дисплей, будь это текст, графики или даже какая-то анимация! Для разных задач существуют самые разные дисплеи и в сегодняшнем материале я хотел бы систематизировать и собрать подробнейший гайд об использовании дисплеев с нерабочих мобильных телефонов: какие бывают протоколы и шины данных, как читать схемы устройств и определять контроллеры дисплеев, какие дисплеи стандартизированы, а какие придётся реверсить самому и как быть с подсветкой. В практической части статьи мы подключим дисплей используя протокол MIPI DBI к RP2040 с использованием DMA. Интересно? Тогда добро пожаловать в статью!
❯ Виды дисплеев и их протоколы
Пожалуй, ЖК-дисплеи с самого момента их появления стали основным инструментом для вывода информации и взаимодействия с пользователями. Первые ЖК-панели были монохромными и требовали отдельный драйвер, который занимался выводом изображения на экран и формированием необходимых для его работы напряжений.
Сейчас же всё гораздо проще и каждый любитель DIY-электроники может и сам подключить дисплейчик к своему проекту и использовать в необходимых ему целях. Ведь не зря написаны десятки библиотек по типу AdaFruit LCD, которые упрощают задачу программисту и дают ему возможность оперировать готовыми и простыми операциями по типу «вывести линию» или «отрисовать изображение». Однако, готовые библиотеки — это, конечно, здорово, но они не всегда дают понимание о том, как работают такие дисплеи на программном и аппаратном уровне. И первая часть статьи как раз и будет посвящена этому.
Всего в мире дисплейных матриц существует несколько общепринятых аппаратных протоколов. Некоторые из них можно легко использовать в собственных проектов с микроконтроллерами, с другими придется повозиться:
Параллельная шина 8080 — одна из самых простых и понятных шин данных, как в теории, так и на практике. Суть её очень простая: на каждый бит отводится по одной сигнальной линии, плюс две дополнительные линии для сообщения статуса передачи: RD означает запрос чтения, а WR — запрос на запись. Большинство дисплеев использует девятый, неявный бит D/C, который сообщает контроллеру, задаём ли мы номер команды, или уже пишем аргументы для этой команды. Что самое приятное — шина по сути стандартизирована и во многих дисплеях команды на старт записи в видеопамять, а также получение ID-контроллера идентичны. Шина бывает 8-битной и 16-битной (её состояние задаётся битами IM0..IM2 и используется не только для подключения дисплеев, но и микросхем параллельной флэш-памяти, ОЗУ и т. д. Такие шины используются в дисплеях с разрешением до 480x320.
SPI — шина, которая наверняка знакома большинству моих читателей. Достаточно простая — у нас есть две сигнальные линии с входным (MISO) и выходным (MOSI) битом, плюс сигнал тактирования, который согласовывает передачу данных. Таким образом, шина получается полнодуплексной. Фактически, каждый байт передаётся по одному биту через одну сигнальную линию, что, по сравнению с 8080, заставляет повышать тактовую частоту контроллера SPI, но при этом занимает гораздо меньше пинов самого МК или процессора. В программном плане, большинство дисплеев представленных в различных интернет-магазинах полностью совместимы с дисплеями 8080, ведь SPI — просто один из режимов работы. Единственный нюанс — из SPI дисплея не всегда можно вычитать ID-контроллера и вообще что-либо читать из регистров дисплея.
I2C — относительно редко используемая шина для дисплеев из-за её невысокой производительности, однако, тем не менее, очень подходящая для МК (благодаря использованию только двух сигнальных линий — SDA для данных и SCL для тактирования. Даже чипселект здесь программный благодаря тому, что каждое устройство имеет собственный адрес!), однако её можно найти в дисплеях некоторых телефонов из самого начала 2000-х годов.
TTL/параллельный RGB — тут, в общем-то, меня упрекали пару раз из-за того, что я продолжаю называть её TTL, но так сложилось исторически — даже в даташитах эту шину называют именно так. С логической точки зрения она очень простая: у нас есть 16/24 сигнальные линии, где 5 (или 8) бит используются для красного и синего канала и 6 (или опять же 8) бит используются для зеленого цвета (т. е. в 16-битном цвете у нас RGB565, а в 24-битном — RGB888). К ним идут сигналы HSYNC для горизонтальной синхронизации и VSYNC для вертикальной. Вообще, необязательно использовать все сигнальные линии предоставляемые дисплеем — можно использовать, например, RGB332 и использовать всего 8 сигнальных линий. Однако для отображения картинки, необходимо строго соблюдать тайминги синхронизации, иначе дисплей будет просто показывать белый цвет. Помимо цифрового варианта, бывает также аналоговый, очень похожий на телевизионный RGB или VGA. Такие дисплеи обычно используются для матриц до 1024x768 включительно.
MIPI DSI — протокол, используемый для дисплеев высокого разрешения — от 480x800 и выше, его можно встретить в большинстве современных смартфонов и планшетов. Кроме того, такие дисплеи используют относительно мало пинов — по два на каждый канал LVDS (обычно в смартфоне около двух-четырех каналов) + две сигнальные линии на тактирование. Звучит всё хорошо? Как-бы не так: протокол дифференциальный и на каждый канал (т. е. логический бит) приходится по две сигнальные линии — одна с положительная, а вторая отрицательная. Затем одна вычитается из другой и получается окончательный сигнал, а сделано это для уменьшения помех от передачи данных по нескольким линиям с очень высокой тактовой частотой без увеличения битности шины.
LVDS/eDP — Протоколы, используемые в матрицах ноутбуков, телевизоров и иногда планшетов. На физическом уровне близки к DSI, на программном — если честно, не знаю, но наслышан о некой стандартизации и высоком уровне совместимости. Даже «неродные» ноутбучные матрицы вполне «заводятся», максимум после перепрошивки родной EEPROM, даже если дисплей другого разрешения!
В списке выше, мы рассмотрели несколько популярных аппаратных шин для дисплеев. В данной статье, мы разберемся в программных особенностях таких дисплеев и узнаем, где взять по дисплею одного из следующих типов: SPI, I2C, а также 8080.
❯ Виды дисплеев и их протоколы
Пожалуй, писать статью, где были бы только готовые примеры без объяснения принципов работы «под капотом» было бы плохим тоном. Поэтому предлагаю немного разобраться в системе команд для самых распространенных контроллеров дисплеев в наше время.
У рассматриваемых нами дисплеев есть собственная видеопамять, благодаря чему нет необходимости соблюдать тайминги, а также общий набор команд (или аппаратных регистров), которые мы можем записывать и тем самым менять поведение дисплея. Если мы просто подадим питание на дисплей и попытаемся что-то вывести — у нас ничего не выйдет, поскольку при каждом аппаратном RESET'е, состояние большинства регистров, кроме SleepOn и PowerOn не определено и может содержать в себе любой «мусор». Для корректной работы дисплея, нам необходимо послать определенный набор команд, называемый инициализацией, который установит настройки драйвера дисплея, такие как контраст, параметры цветности, направление развертки изображения из VRAM и т. д. Пожалуй, стоит сразу отметить, что некоторые люди называют регистры дисплея командами — это означает одно и тоже!
Пример инициализации. На самом деле, не все люди делают такую простыню из вывозов функций чтения/записи регистров дисплея, поскольку это кушает драгоценный ROM. На AVR, например, команды инициализации можно хранить в ROM и читать из PROGMEM.
Если дисплей инициализирован неправильно, то мы можем наблюдать некорректную развертку, артефакты на дисплее и полосы: если вы когда-нибудь прошивали смартфоны прошивками других ревизий, то могли замечать подобный эффект сами.
Набор команд для контроллеров дисплеев частично стандартизирован спецификацией MIPI DBI, которая описывает и закрепляет некоторые конкретные адреса регистров, общие для всех контроллеров дисплея. К ним относится, например, установка «окна» для записи (0x2B и 0x2A), sleepout (0x11) и некоторые другие. Проприетарными командами остаются настройки питания, развертки, контраста и самого драйвера дисплея. Ну и всяческие LUT, а также палитровые режимы (если они есть) тоже проприетарные.
Пример одной из таких стандартизированных команд:
Почти во всех дисплеях есть разделение отправляемых байтов на команду (или выборка номера регистра для чтения/записи) и на данные. Как обработать текущий байт определяет отдельный пин (или бит, в зависимости от конфигурации дисплея), называемый D/C (Data/Command), иногда также можно встретить названиеRS. Обычно, при записи команды, D/C должен быть на низком уровне, при записи данных, соответственно, на высоком. Суть простая: записываем номер команды (или регистра) при низком D/C, а затем дописываем необходимые аргументы (или конфигурацию регистра) при высоком уровне D/C. Примерно так:
Касательно сброса, то в дисплеях обычно существуют два вида этого процесса: аппаратный сброс через соответствующий пин и программный с помощью специальной команды. Пин RESET никогда нельзя оставлять в «воздухе» (т. е. не подключенным) в надежде что «да состояние пинов МК после ресета известно, мусора на шине явно не будет». Мусора может и не будет, а вот дисплей упадет в вечный ресет, поскольку ожидает перехода сигнала RESET в высокий уровень. Тоже самое касается и пина CS, отвечающий за выбор устройства на шине. Если вам не нужен CS и у вас висит только одно устройство на шине — просто притяните его к массе. Некоторые контроллеры (например, ILI9325) адекватно реагируют на CS «в воздухе», некоторые — нет. Только после того, как RESET оказался на высоком уровне, дисплей начнёт принимать команды:
Переходим конкретно в выводу данных. Для начала вывода изображения на дисплей, нам необходимо выполнить команду 0x2C, которая переведет контроллер дисплея в режим записи данных в видеопамять. После этого, нам остаётся лишь установить высокий уровень на пине D/C и просто слать непрерывный поток пикселей. Контроллер дисплея сам инкрементирует координаты на дисплее и после того, как координаты выйдут за границы нужной области, дисплей сам их переведет в изначальные. Таким образом, достаточно лишь один раз проинициализировать дисплей и просто гонять в него данные, например, с помощью DMA.
Всё просто и понятно :)
❯ Дисплеи с шиной 8080
Пожалуй, подобные дисплеи найти проще всего, поскольку они использовались в большинстве кнопочных телефонов из нулевых. Такие экранчики можно встретить во многих моделях Nokia, Samsung, LG, Fly, Sony Ericsson и большинстве китайских телефонов. С поиском распиновки и разводкой таких дисплеев всё относительно просто и одновременно сложно: на некоторые модели телефонов (например, почти на все Nokia) можно свободно найти схему в гугле и узнать распиновку коннектора дисплея… однако этот коннектор сначала надо сдуть и развести на breakout-плате, или под микроскопом вывести перемычки. В некоторых случаях (например, Siemens S-серии), дисплей просто прижимался к контактам на плате, а сами контакты имели более чем паябельный шаг.
Из схемы на Nokia N70. Этот дисплей применялся во многих Symbian-смартфонах Nokia тех лет: N-Gage/N-Gage QD, N70, N72, 6600 и некоторых других.
Но особо удобными можно считать дисплеи с паябельными шлейфами с большим шагом пинов — такие можно встретить в некоторых телефонах Samsung и большинстве китайских телефонов. Пытливый читатель спросит «так это ж китаец, где ты на него схему будешь искать?». И вот тут, китайские производители нас приятно порадуют, поскольку за редким исключением, такие дисплеи имеют стандартизированную распиновку: лично мне известны матрицы 37 Pin, 39 Pin и 44 Pin. Как найти для них распиновку? Пишем на «алике» или «таобао» 37 pin lcd tft и смотрим: в описании продавец частенько прилагает распиновку (правда учтите, что 37 pin не имеет пинов IM для настройки ширины шины, а 16-битный интерфейс может быть слишком прожорилвый по числу пинов):
В случае с китайцами, иногда можно найти и схему (нажимайте на зеленую стрелку) на устройство: например, почти на все модели Fly схемы лежат в свободном доступе, где почти всегда можно найти распиновку дисплея. Иногда производитель даже выводит тестпоинты на все сигнальные линии и дисплей с тачскрином можно использовать, не выпаивая его с платы!
Распиновка на Fly IQ239. На нижней части изображения, вы можете увидеть, что такие, безусловно, здоровенные дисплеи можно купить за копейки и сейчас :)
Но задумывались ли вы когда-нибудь, откуда на тачскринах в дисплеях с «али» взялись кнопки «домой», «сообщения», «телефон»? Это ведь те самые дисплеи, которые использовались в «ноклах», просто припаянные к удобной плате! :) Кроме того, на китайские дисплеи без проблем можно найти даташит: обычно они используют контроллеры от ST или ILI, в зависимости от разрешения дисплея.
Концептуально, аппаратная реализация протокола одновременно простая и понятна любому: программа устанавливает состояние каждого бита передаваемого байта на сигнальных линиях D0..D7 (либо D00..D15, если шина у нас 16-битная), а затем просто «дёргает» линию RD (Read или чтение), либо WR (Write или запись) по переходу из низкого уровня в высокий, благодаря чему контроллер дисплея понимает, что байт (или слово в случае 16-битного интерфейса) можно «забирать» с шины. По переходу из высокого уровня в низкий, контроллер снова переходит в режим ожидания следующего байта с шины.
Где взять такие дисплейчики? Да почти везде! Но лучше всего брать дисплеи с китайчиков, которые можно развести на вот таких breakout-платах, которые можно заказать на алике за пару сотен рублей.
Обратите внимание на то, как по свински припаивают подсветку на некоторых дисплеях. И это завод! Лучше сразу прозвоните прежде чем подавать питание. Я, вот, забыл, понадеялся на производителя и по итогу сжёг подсветку :(
Другой вопрос, где искать на них информацию? Помимо схем, можно просто поискать на алике «37 pin lcd tft», «39 pin tft lcd», «24 pin tft lcd» и т. п. Обычно продавцы сами выкладывают распиновку и даже прикладывают ID контроллера дисплея. Поскольку иногда различия в распиновках всё же попадаются, обращайте внимание на то, куда у вас идут дорожки от подсветки и от резистивного тачскрина (если есть), а также вызванивайте все пины с массой — это поможет подобрать правильную распиновку без логического анализатора. Вот, например, дисплейчик из китайской нерабочей реплики Nokia 130 с здоровым 2.4" дисплеем… казалось бы, вообще не понятно что за дисплей, однако воспользовавшись смекалкой, мы находим его распиновку!
❯ SPI-дисплеи
SPI-дисплеи в телефонах встречались относительно редко. В основном, подобные дисплейчики можно было найти в моделях начала 2000х годов: сименсах, моторолах, ранних сонериках T-серии и Nokia на S40. Иногда SPI-дисплеи можно встретить в современных кнопочных телефонах — обычно они имеют шлейф с менее чем 15 пинами, как некоторые модели Fly. Обычно контроллер дисплея поддерживал сразу несколько аппаратных шин, а производитель телефона ещё на этапе установки шлейфа к контроллеру дисплея замыкал необходимые IM-пины выбирая необходимую шину, поэтому программный протокол фактически идентичен дисплеям с шиной 8080.
Несомненным плюсом SPI-дисплеев можно назвать малое число пинов для работы с матрицей: достаточно всего два (плюс сигнал D/C, если дисплей не 9-битный), если повесить RESET на VIO, либо три (четыре), если хотите управлять аппаратным RESET вручную. Но есть и, в некоторой степени, минусы: например, не все микроконтроллеры умеют работать в 9-битном режиме и возможно последний бит придётся досылать «ногодрыгом» (что ломает любую возможность реализации DMA).
Многие дисплеи с этим интерфейсом задокументированы ещё в начале 2000х годов на известных форумах и сайтах, таких как VRTP, Радиокот и easyelectronics, поэтому проблем с их подключением не возникнет даже у новичка. Даже такой крутой и уважаемый дядька, как @DIHALT, когда-то писал полезный материал об использовании FSMC в STM32.
Достать их новыми можно и сейчас: различные магазины запчастей для телефонов бывают продают их по 20-30-40 рублей… Я недавно себе целую коробочку накупил, в том числе и просто для ремонта смартфонов для будущих статей :)
❯ I2C-дисплеи
Дисплеи с такой шиной — настоящая редкость и обычно попадались в телефонах самого начала нулевых годов с низким разрешением дисплея. Из известных мне — Ericsson'ы и ранние Sony Ericsson T-серии, ODM Motorola (головастики например) и… пожалуй всё. Казалось бы, разве I2C может быть полезен для работы с дисплеями, где требуется активный вывод графики? Ведь он совсем медленный! Однако, даже он может пригодится для некоторых проектов, а в большинстве МК частенько попадается аппаратный TWI.
Кроме того, I2C дисплейчики удобно отлаживать: благодаря тому, что периферийное устройство должно отрапортовать ACK (состояние успешности получения байта) мастер-устройству, можно сразу определить обрыв линий до дисплея. Но какой-то конкретной информации по ним я не смогу написать — они все совсем разные :( Правда, полезным линком поделюсь, ребята с форума VRTP собрали хорошую таблицу с различными контроллерами дисплеев, где бывают и i2c!
❯ Подсветка
Отдельного радела стоит тема подсветки дисплеев. По первой может показаться, что тут всё просто: современным дисплеями достаточно 5В, а на старых можно замерить напряжение бустера на живом девайсе и смастерить свой DC-DC повышающий преобразователь, или взять, например, уже готовый драйвер, как известный в определенных кругах LTYN. На самом деле и тут есть свои нюансы.
Итак, каким образом реализована подсветка в том или ином устройстве? Обычно её реализация заключается в последовательном соединении двух и более светодиодов, которые формируют небольшую ленту под рассеивающей плёнкой. На современных китайских дисплейчиках, для работы в полную яркость достаточно всего лишь 5В источника питания + токоограничивающего резистора. Но что самое приятное, подсветка в таких дисплеях способна работать и при 3.3В, пусть менее ярко, но всё равно вполне читабельно.
Если вы делаете портативное маломощное устройство, работающее от одного Li-Ion аккумулятора, то достаточно лишь пустить 3.3В с линейного стабилизатора, который формирует напряжение VSYS для микроконтроллера. Таким образом, у вас будет стабильная подсветка среднего уровня яркости. В качестве альтернативного «бомж» варианта, когда нет возможности собрать нормальный драйвер подсветки, можно попробовать подключить светодиоды напрямую к АКБ, но при разряде дисплей будет потихоньку «тухнуть». Ещё один «бомж» вариант — разобрать дисплейный модуль, порезать дорожки на ленте и соединить пару светодиодов параллельно, выведя их через отверстие, откуда выходит шлейф дисплея, однако в таком случае, потребление подсветки заметно увеличится.
Правильным выходом будет взять с того-же телефона бустер подсветки с индуктивностью и иной необходимой обвязкой, и собрать бустер самому. Особой популярностью когда-то пользовались вышеупомянутые LTYN из телефонов Samsung (это маркировка известного драйвера LT1937). Уровнем подсветки на подобных бустерах телефоны управляют с помощью встроенного ШИМ-контроллера, чем можете воспользоваться и вы :)
❯ Запускаем дисплейчик на практике
В первой части статьи, я постарался ввести вас в курс дела и кратко рассказать о том, как работают такие дисплейчики «под капотом». Как видите — с теоретической точки зрения, ничего сложного нет: пересылаем данные на дисплей, да вовремя дёргаем пин D/C. Но какого же это на практике?
К сожалению, у меня на руках не нашлось подходящего дисплейчика от мобильного телефона (я ведь брал новые по уценке, не все заработали нормально), поэтому в качестве примера работы мы возьмём фактически такой же «китайский» дисплей с алика. Но будьте уверены — с большинством дисплеев, принцип работы будет идентичен (если мы говорим о дисплеях 2005г.в и моложе).
В качестве МК, мы возьмём мой любимый RP2040, который, по моему мнению, незаслуженно обделен вниманием. Время от времени я делаю всякие прикольные девайсы на базе этого МК, поэтому крайне рекомендую его всем моим читателям :)
Давайте же перейдем к практической части статьи! Обычно при создании проекта, я просто клонирую с гита RPi сэмплы с уже готовыми файлами CMake, беру hello world, конфигурирую CMakeLists.txt и пишу свою программу. На малинке пока что нет такого удобного способа создания проекта, как idf.py create-project :) Само собой, для удобства отладки я всегда включаю встроенную в чипсет эмуляцию UART через USB.
if (TARGET tinyusb_device) add_executable(hello_usb main.cpp )
# pull in common dependencies target_link_libraries(hello_usb pico_stdlib hardware_spi)
# create map/bin/hex/uf2 file etc. pico_add_extra_outputs(hello_usb)
# add url via pico_set_program_url example_auto_set_url(hello_usb) elseif(PICO_ON_DEVICE) message(WARNING "not building hello_usb because TinyUSB submodule is not initialized in the SDK") endif()
И инициализирую USB-стек и биндинги stdout к нему:
stdio_init_all(); sleep_ms(1000);
Задержка здесь важна, иначе девайс отказывается определятся в системе. Переходим, собственно, к разводке дисплея. Для работы нам достаточно лишь питания, подсветки, общей массы и четырёх сигнальных линий: MOSI, CLK, DC, RESET. На CS я обычно ставлю перемычку с массой, т. к обычно не вешаю что-то ещё на одну шину с дисплеем.
Переходим к инициализации дисплея. Наш экранчик работает на базе контроллера ST7735R и имеет разрешение 128x160. Сначала, назначаем функции для пинов и дёргаем RESET:
Весьма негусто скажете вы? Ну, с минорными изменениями, здесь заработает дисплейчик любого разрешения, даже 480x320! Переходим к фактической инициализации:
Прошиваем наш МК и смотрим что получилось. Видим шум на экране? Значит дисплей инициализирован верно!
После инициализации дисплея, мы можем выводить на него данные! Дабы дать возможность процессору заниматься другими делами во время передачи картинки на дисплей, мы настроим один из DMA-каналов. DMA-контроллер занимается пересылкой данных из ОЗУ в другой участок ОЗУ (аппаратный memcpy) или периферию. Как раз для второго случая, т. е. пересылки данных в контроллер SPI, мы и будем использовать DMA!
Аллокейтим фреймбуфер, куда мы будем выводить нашу картинку и настраивает DMA-канал:
Переходим к выводу изображения на дисплей. Для того, чтобы просто установить цвет пикселя в любых координатах экрана, достаточно лишь посчитать смещение от начала указателя на фреймбуфер к определенным координатам экрана. Формула очень простая и понятная: ширина дисплея * Y-координата + x координата и результат предыдущих операций помноженный на число байт в одном пикселе.
__inline void pixelAt(short x, short y, short color) { if(x < 0 || y < 0 || x >= LCM_WIDTH || y >= LCM_HEIGHT) return;
В функции есть валидация границ дисплея. Если уверены, что не зайдете за границы дисплея — можете убрать проверку, будет шустрее.
Теперь для вывода картинки, нам достаточно лишь скопировать изначальное изображение в наш фреймбуфер и попросить DMA-канал вывести изображение на дисплей. Для прозрачных картинок без альфа-канала (т. е. с цветовым ключом), функция будет выглядеть так:
Можно сделать чуть комплекснее, добавив альфа-блендинг и аффинные трансформации (возможность поворота и скейла картинок), но пока-что такой задачи не стоит. Ну что, всё очень просто и понятно? :) Пример прошивки можно найти на моём GitHub!
Производительность такого способ на RP2040 можно увидеть вот в этом видосе (на Пикабу не смог залить из-за ограничения на число медиа-элементов). Обратите внимание, что подход предложенный выше больше подходит именно для динамического вывода изображения без dirty-регионов. Он подойдет для игровых консолей, камер, анимаций или устройств с выводом динамической информации по типу осциллографов. Если вам нужно обновлять картинку реже, например, если вы делаете умные часы с плеером, то нет необходимости занимать довольно большой объем ОЗУ фреймбуфером, ведь вы можете писать напрямую в видеопамять. Тут уже решать в зависимости от конкретной ситуации именно вам :)
❯ Заключение
Вот мы с вами и систематизировали информацию о том, как использовать дисплеи с мобильных телефонов в своих проектах. Надеюсь, информация была достаточно полезной для вас! Однако, у меня к вам просьба: пожалуйста, не «дербаньте» рабочие девайсы «на запчасти» :( Это будет не очень гуманно по отношению к нашему «технобалдежу», где мы наоборот стараемся найти применение стареньким девайсам :)
Был ли для вас материал полезен? Пишите в комментариях.
Полезный материал?
Какие дисплейчики подключали?
❯ Важное объявление для читателей касательно будущей рубрики
Друзья! Я, как и многие мои читатели, помимо программирования и железа обожаю тачки! Особенно те тачки, где что-то нужно доделывать самому… и речь, конечно-же, о ТАЗах! Я долго думал, но всё же решился: сейчас я коплю на будущий интересный проект, связанный с ультрабюджетным электронным дооснащением автомобиля, который старше меня в полтора раза — скорее всего, речь пойдет о ВАЗ 2108/2109/21099, причём не исключено что карбюраторной! В планах довольно крутой проект, заключающийся в следующем: мы спроектируем очень дешевый бортовой компьютер (т.е панель) для управления автомобилем на базе дешевого Б/У планшета за пару сотен рублей. Планшет будет связан с управляющим МК через UART (о подобной коммуникации через хардварные протоколы я уже писал целых две статьи: сам себе Linux смартфон, превращаем планшет с нерабочим тачскрином в игровую консоль), и с планшета мы сможем не только управлять основными системами машины (стеклоподъемники, центральный замок и соленоид багажника), но и собирать и пытаться примерно посчитать некоторую информацию о расходе, километраже и стабильности работы двигателя на карбюраторной(!) машине без электронных систем с завода!
Если вдруг двигатель машины будет живенький и заводиться с полтычка, то может и удаленный прогрев постараюсь реализовать :)
В наши задачи будет входить не только проектирование аппаратной части такого оснащения, но и разработка симпатичного интерфейса для самой панели, дабы было не хуже чем в BMW :D Всеми схемами, исходным кодом и инструкциями я буду делится с вами в каждой статье и, как обычно, расскажу обо всех деталях реализации во всех подробностях! У меня уже есть некоторые идеи и наработки. Собственно, почему-б и не попробовать? Будет новая рубрика в блоге: апгрейд автомобилей глазами электронщика и прожженного программера.
Фото не моё, из интернета
Если вам нравятся мои статьи, вас интересует развитие такой рубрики и у вас есть желание и возможность — можете помочь проекту копеечкой с помощью формы доната ниже. Пикабу позволяет остаться анонимным и донатить даже без регистрации. Сейчас у меня есть 40 тысяч рублей личных накоплений, на покупку самой машины планирую выделить 70-80 тысяч рублей (я живу в Краснодарском крае, так что здесь ещё есть шансы найти что-то +- живое за такие деньги), так что остаётся собрать около 30-35 тысяч рублей. За каждую копейку я готов отчитаться (по факту покупки машины я сделаю пост с фотографиями авто, ДКП, а также оглашу фронт будущих работ и сразу начну заниматься проектом).
Зачастую в процессе разработки собственных устройств или моддинга уже существующих, встаёт задача выполнения стороннего кода: будь то ваши собственные программы с SD-флэшек, или программы, написанные другими пользователями с помощью SDK для вашего устройства. Тема компиляторов и кодогенерации достаточно сложная: чтобы просто загрузить ELF или EXE (PE) программу, вам нужно досконально разбираться в особенностях вашей архитектуры: что такое ABI, релокации, GOT, отличие -fPIE от -fPIC, как писать скрипты для ld и т. п. Недавно я копал SDK для первых версий Symbian и основываясь на решениях из этой ОС понял, каким образом можно сделать крайне «дешевую» загрузку любого нативного кода практически на любом микроконтроллере, совершенно не вникая в особенности кодогенерации под неё! Сегодня мы с вами: узнаем, что происходит в процессе загрузки программы ядром Linux, рассмотрим концепцию, предложенную Symbian Foundation и реализуем её на практике для относительно малоизвестной архитектуры — XTensa (хотя она используется в ESP32, детали её реализации «под капотом» для многих остаются загадкой). Интересно? Тогда добро пожаловать под кат!
❯ Как это работает?
Думаю, для многих моих читателей реализация процесса загрузки exe-программ и dll-библиотек в память процесса оставалась эдаким чёрным ящиком, в детали реализации которого вдаваться не нужно. Отчасти это так и есть: современные ОС разруливают процесс загрузки бинарников в память сами, не требуя от программиста вообще ничего, даже понимания того, куда будет загружена его библиотека или программа.
Давайте для общего понимания вкратце разберемся, как происходит загрузка программ в Windows/Linux:
1. Система создаёт процесс и загружает в память программы секции из ELF/PE. Обычные программы для своей работы используют 3 секции: .text (код), .data (не-инициализированный сегмент памяти для глобальных переменных), .bss (сегмент памяти для инициализированных переменных). Каждому процессу выделяется собственное адресное пространство, называемое виртуальной памятью, которое не позволяет программе испортить память ядра, а также позволяет не зависеть от разметки физической памяти на выполняющей машине. Концепцию виртуальной памяти реализует специальной модуль в процессоре, называемый MMU.
2. Если бы наши программы не использовали никаких зависимостей в виде динамических библиотек, то на этом процесс загрузки можно было бы закончить: каждая программа имеет свой адрес загрузки, относительно которого линкер строит связи между обращениями к коду/данным программы. Фактически, для самых простых программ линкеру остаётся лишь прибавить адрес загрузки программы (например, 0x100) к каждому абсолютному обращению к памяти. Однако современные программы используют десятки библиотек и для всех предусмотреть собственный адрес загрузки не получится: кто-то где-то всё равно будет пересекаться и вероятно, портить память. Кроме того, современные стандарты безопасности в Linux рекомендуют использовать позиционно-независимый код, дабы использовать преимущества ASLR (Address Space Layout Randomization, или простыми словами возможность загрузить программу в случайное место в памяти, дабы некоторые уязвимости, завязанные на фиксированном адресе загрузки программы перестали работать).
3. Поэтому для решения этой проблемы придуман т. н. динамический линкер, который уже на этапе загрузки программы или библиотеки патчит программу так, чтобы её можно было загрузить в любой участок памяти. Для этого используются данные, полученные от обычного линкера а этапе компиляции программы: помимо .text, .data и .bss, линкер создаёт секции .rel и .rel-plt, которые называются релокациями. Если объяснять совсем условно, то релокации — это просто запись вида «какой абсолютный адрес в коде программы нужно пропатчить» -> «на какое смещение его пропатчить». Самая простая релокация выглядит вот так:
Где по итогу:
.rel-plt же служит для резолвинга вызовов к dll/so: изначально программа ссылается на заранее определенные в процессе компиляции символы, которые уже в процессе загрузки патчатся на физические адреса функций из загруженной библиотеки.
И казалось бы — всё очень просто, пока в дело не вступают GOT (Global Offset Table — глобальная таблица смещений) и особенности реализации конкретного ABI. И ладно бы x86 или ARM, там всё разжевано и понятно, однако на других архитектурах начинаются проблемы и не всегда очевидно что и где за что отвечает.
А ведь чаще всего нужно просто загрузить небольшую программу, которой не нужны комплексные загрузчики: немного кода, немного данных и всё. И тут у нас есть три выхода:
Писать полноценный загрузчик ELF-бинарников. ELF может оказаться громоздким для некоторых окружений и его реализация может оказаться тривиальной не для всех.
Зарезервировать определенный сегмент в памяти (пусть с 0xFFF по 0xFFFF) и скомпилировать нашу программу с адресом загрузки 0xFFF с параметром -fno-pic. В таком случае, линкер сгенерирует обращения к памяти по абсолютным адресам — если переменная лежит по адресу 0xFFF, то программа будет обращаться сразу к этому адресу памяти, без необходимости что либо динамически линковать. Именно такой подход использовался во времена ZX Spectrum, Commodore 64 и MS-DOS (однако там роль «виртуальной памяти» выполняла такая особенность 8086, как сегменты). У такого подхода есть и минусы: относительная невозможность загрузки сразу нескольких программ одновременно, зарезервированное пространство линейно отъест небольшой кусок памяти у основной прошивки, нет возможности динамической аллокации секций. Зато такой код теоретически будет работать быстрее, чем PIC.
Проблемы реализации такого способа: иногда нужно лезть в систему сборки основной прошивки и патчить скрипт линкера так, чтобы он не трогал определенный регион памяти. В случае esp32, например, это требует патча в сам SDK и возможного «откола» от мейнлайн дистрибутива.
Использовать программу с относительной адресацией, однако без сегментов .bss и .data. Самый простой в реализации способ, который к тому же очень экономичен к памяти, позволяет загружать программу в любое место и пользоваться всеми фишками динамического аллокатора и не требует вмешательств в основную прошивку, кроме примитивного загрузчика программ. Именно его я и предлагаю рассмотреть подробнее.
Недавно мы сидели в чате ELF-сцены (разработка нативных программ под телефоны Siemens, Sony Ericsson, Motorola и LG с помощью хаков) и думали, как же можно реализовать загрузчик сторонних программ на практически неизвестных платформах. Кто-то предлагал взять ELF под основу — однако с его реализацией под некоторые платформы есть трудности, а кто-то предлагал писать «бинлоадер» — самопальный формат бинарников, который получается из, например, тех же эльфов.
В это же время я копал SDK для Symbian и хорошо помнил, что в прикладных приложениях для этой ОС нет поддержки глобальных переменных вообще. Да, сегмент .data и .bss полностью отсутствует — переменные предлагается хранить в структурах. Почему так сделано? Всё дело в том, что каждая программа в Symbian — это dll-библиотека, которую загружает EKA и создаёт экземпляр CApaApplication. И дабы была возможность загрузить dll один раз для всех программ (что справедливо для системных библиотек), ребята полностью выкинули возможность использования любых глобальных переменных. А ведь идея интересная!
Однако в таком подходе есть несколько серьезных ограничений:
Отсутствие глобальных переменных может стать проблемой при портированиии уже существующего софта, хотя вашим программам ничего не мешает передавать в каждую функцию структуру с глобальным стейтом, который можно при необходимости изменять. Кроме того, нет ограничений на использование C++ (за исключением необходимости ручной реализации new/delete и отсутствием исключений).
Отсутствие преинициализированных данных. Вот это уже может стать относительно серьёзной проблемой, у которой, тем не менее, есть свои обходные решения. Например если вы храните команды для инициализации дисплея в таблице, или какие-либо калибровочные данные — вы не сможете их объявить, просто используя инициализаторы в C. Тоже самое касается и строковых литерал. Тут есть два варианта: часть таблиц можно вынести на стек (если эти самые таблицы достаточно маленькие), либо подгружать необходимые данные из бинарника с помощью основной прошивки (например, LoadString и т. п.).
Давайте же на практике посмотрим, имеет ли право на жизнь такой подход!
❯ Практическая реализация
Формат нашего бинарника будет до безобразия прост: небольшой заголовок в начале файла и просто сырой дамп сегмента .text, который можно экспортировать из полученного elf даже без необходимости писать скрипт для линкера. При этом нужно учесть, что ESP32 — это микроконтроллер частично Гарвардской архитектуры, т. е. шина данных и кода у него расположены отдельно. Однако у чипа есть полноценный MMU, который позволяет маппить регионы физической памяти в виртуальную память, чем мы и воспользуемся в итоге!
Заголовок нашего бинарника будет выглядеть вот так:
Программа общается с основной прошивкой посредством псевдо-syscall'ов: функции, которая в качестве первого аргумента ожидает номер нужной службы и один 32х-битный указатель для описания структуры с параметрами. Реализация syscall'ов — одна из самых простых и неприхотливых с точки зрения обратной совместимости с будущими прошивками.
Концептуально всё очень просто: GetGlobalStateSize сообщает нашему загрузчику размер структуры для хранения глобального стейта, в то время как Start уже фактически заменяет main() в нашей программе. Необходимости в crt0 нет, поскольку весь необходимый инит выполняет бутлоадер ESP32. Впрочем, при желании вы можете выделить отдельный стек для вашей программы — это повысит надежность, если выполняемая программа удумает испортить стек.
-fno-pic отключает генерацию кода, зависимого от GOT, -nostdlib и -nostartfiles убирает из билда crt0 и stdlib, благодаря чему мы получаем только необходимый код. --section-start задает смещение для загрузки секции .text на 0x0 (в идеале это делать необходимо из скрипта для ld). objcopy скопирует из полученного ELF только необходимую нам секцию .text.
Как же это работает на практике? Давайте дизассемблируем выходной бинарник и посмотрим, что у нас дает на выхлопе cc:
Обратите внимание, что Start вызывает подфункции с помощью инструкции CALLX8, которая в отличии от обычного Immediate-версии CALL8, выполняет переход относительно текущего адреса в PC, благодаря чему переход полностью независим от адреса загрузки программы в памяти. А благодаря тому, что все данные, в том числе и указатель на глобальный стейт передаются через стек, нет необходимости релокейтить сегменты данных.
По итогу всё, что нужно от загрузчика бинарников — это загрузить программу в память для инструкций, выделить память для структуры с стейтом программы и передать управление Start. Всё! Конкретно в случае ESP32, у нас есть два возможных решения задачи загрузки программы в память:
Загрузить программу в IRAM. Такая возможность теоретически есть, однако на практике загрузчик ESP32 устанавливает права только на чтение и выполнение на данный регион памяти. Попытка что-то скопировать туда закончится исключением SIGSEGV. Кроме того, сегмент IRAM относительно небольшой — всего около 200Кб.
Самопрограммирование. Для этого, в esp32 есть два механизма — Partition API и SPI Flash API. Я выбрал Partition API для простоты реализации.
Для нашей прошивки необходимо будет переразметить флэш-память. Для этого запускаем idf.py menuconfig, идём в Partition Table -> Custom partition table CSV. Создаём в папке проекта partitions.csv, куда пишем:
Как видите, ничего сложного в выполнении сторонних программ при условии соблюдении некоторых ограничений нет. Да, в таком подходе есть как серьезные плюсы, так и минусы, однако он делает своё дело и позволяет реализовать запуск игр на кастомных игровых консолях, или сторонних программ на самодельных компьютерах. Ну и конечно же не стоит забывать про плагины! Авось в вашем решении нужна возможность расширения функционала устройства, однако предоставлять исходный код или даже объектные файлы нет возможности — тогда вам может пригодится и такая методика.
Пожалуй, стоит упомянуть ещё один… очень своеобразный метод, который я иногда встречаю при реализации самодельных компьютеров. Люди пишут… эмуляторы 6502/Z80 :) И если такой подход ещё +- применим к ESP32, то в AVR просадки производительности будут слишком серьезными. Так зачем, если можно использовать все возможности ядра на максимум?
Полезный материал?
Приходилось ли загружать сторонний код в ваших устройствах?
Разбил сайлентблоки на нижних передних рычагах. Заменил пару сайлентблоков, но не все. Были и целые сайлентблоки. Целый год ездил не ремонтируя. Глянул цены на новые рычаги - решил спасать старые))
Починил рычаги техникой перепресовки сайлентблоков (Как перепресовывать - гуглите. И на дроме полно сайлентблоков)
Результат:
Скажу так. Усилия для перепресовки приложить надо, зато можно спасти старые рычаги)
Я, как и многие мои читатели, очень люблю игры. Уже довольно обширное число моих статей было посвящено ремонту и моддингу самых разных игровых консолей — как китайских «нонеймов», так и брендовых PSP и PS Vita! Однако, меня тянет к железу не только желание отремонтировать и поставить в строй «устаревшие» девайсы, но и мания делать и созидать что-то своё! А ещё я очень люблю программировать игры и графику сам. Недавно я загорелся идеей разработать с нуля свой портативный «тетрис»: от схемы и разводки платы, до написания прошивки и игр под нее. Что получается, когда программист, который поставил электронику практически во главе своей жизни, пытается сделать свое устройство? Читайте в статье!
❯ Как я к этому вообще пришел?
Проекты разработки самодельных игровых приставок стали очень популярны к нашему времени. Если раньше embedded-разработка была достаточно дорогой и доступной лишь для избранных, то сейчас на рынке можно найти все что хочешь — и мощные микроконтроллеры с кучей периферии за 300 рублей, и готовые дисплейные модули по 250 рублей, и макетные платы с удобными dupont коннекторами за весьма скромные деньги.
Собрать свой гаджет в пределах одной-двух тысяч рублей стало вполне реальным. Люди собирают себе самые разные устройства, а игровые приставки — одна из самых популярных тем. Однако, для многих людей, которые только начинают знакомится с миром embedded-электроники, собрать консоль в своем корпусе с Raspberry Pi на борту и RetroPie в качестве оболочки — за счастье.
Однако есть определенная категория электронщиков, к которой отношусь и я — нам нужно делать всё с нуля! Свои проекты я стараюсь реализовывать на самопальных фреймворках/движках, точно также я мыслю и в подходе электроники — ну не могу я использовать чужие решения и стараюсь разобраться в вопросе сам. За моей спиной есть весьма интересные демки. Например, это моя игрушка с незамысловатым названием «ралли-кубок ТАЗов», которую я написал за неделю с нуля (рендерер, звук, ввод, редактор уровней — все свое) в 2022 году:
Вот так, с любовью программировать игры, я и пришел к мысли сделать свою консоль, так как вижу её именно я. Только без чужих библиотек и наработок, но не прям уж bare metal. Сел я и начал думать, на чём же мы будем строить наш игровой девайс!
❯ Из чего будем делать?
Как я уже говорил выше, в наше время выбор железа для создания своих девайсов большой — тут и мощные микроконтроллеры/одноплатники, по производительности сравнимые с телефонами 2005-2006 годов, и различная периферия — аж глаза разбегаются. Однако проектировать будущую консоль нужно исходя из некоторых требований.
Характеристики моего девайса следующие:
Процессор: двухядерный ARM микроконтроллер RP2040 на частоте 133мгц, построенный на архитектуре Cortex-M3. Сам процессор распаян на плате Raspberry Pi Pico.
ОЗУ: 260 килобайт SRAM, встроена в процессор. Немного, но если грамотно распоряжаться ресурсами — то хватит.
ПЗУ: 2Мб SPI Flash-памяти, также распаяны на плате.
Дисплей: 1.8" TFT-матрица с разрешением 128x160. Выбор разрешения обусловлен производительностью будущей консоли — процессор банально не сможет заполнять матрицу с относительно высоким разрешением.
Ввод: 6 кнопок, 4 из которых — направление, 2 — действий. В будущем могут добавиться еще несколько.
Звук: динамик. Пока не знаю, с чего рулить будем — возможно, возьмем «железный» ШИМ-контроллер процессора, а возможно прикрутим внешний ЦАП с i2s.
Питание: 3.7в аккумулятор BL-4C. Да, да, тот самый с Nokia и современных кнопочников! Аккумулятора, емкостью в 800мАч должно хватать хотя-бы на 4-5 часов игры. При этом зарядка АКБ обеспечивается модулем TP4056.
Весьма неплохо для самоделки, согласны? Как я уже говорил раннее, эти характеристики примерно соответствуют мобильным телефонам 2004-2006 годов — Nokia 6600, Sony Ericsson K510i, Samsung D800. Отличие лишь в ОЗУ (в телефонах её 2-4 мегабайта) и периферийных модулях типа контроллера дисплея.
На фото E398 — мобилка 2004 года выпуска, но она здесь не просто так. :)
Важную пометку нужно сделать касательно дисплеев: эти 1.8" матрицы бывают приходят с «синевой» — это не железная проблема и не совсем брак. Сам контроллер в дисплея в них сильно греется (хотя токоограничивающий резистор стоит) и негативно влияет на клей, из-за чего матрицы отклеивается от подсветки и слои поляризации начинают «синить» картинку. Лечится проклееванием подложки матрицы суперклеем.
RPi Pico я решил выбрать, поскольку информации про них достаточно мало, характеристики хорошие и пока что никто особо ничего на них не делал, тем более в рунете. А ещё у них очень удобное и простое SDK, практически bare-metal. ESP32, например, работает на FreeRTOS и имеет кучу библиотек, здесь же API простое и понятное.
Закупаем все необходимое и начинаем творить!
❯ Графика
В первую очередь нам нужно подключить дисплей и что-нибудь на него вывести. Заодно и SPI погоняем на незнакомом чипсете, благо работа с ним очень простая — задаем конфигурацию пинам (gpio_set_function), настраиваем SPI-контроллер и можно посылать данные.
SPI у RP2040 работает на частоте вплоть до ~60мгц — это достойная скорость передачи, в том числе и для быстрого вывода графики. На самом деле, SPI даже предпочтительнее чем параллельный 8080-интерфейс для использования в микроконтроллерах: дело не только в количестве занимаемых пинов, но и в возможности использования DMA!
В подобных проектах всегда нужно делать так, чтобы дисплей можно было при необходимости поменять, а желательно вообще научить работать его с несколькими контроллерами: разные дисплеи одной диагонали могут использовать разные контроллеры. В моём случае, этоST7735. Для разрешений 240x320 используются ILI9325, ILI9341, ST7789. Команды инициализации дисплея честно позаимствованы, но именно в этом нет ничего зазорного — сама система команд относительно стандартизирована, отличается лишь первичная настройка питания, гамма-коррекции и т. д — часто init sequence вставляет сам производитель в даташит.
После инициализации дисплея пробуем что-нибудь вывести. Да, все работает без проблем. Пару важных нюансов: ST7735 требует посаженный на землю CS, в воздухе его оставлять нельзя, как некоторые ILI (вы ведь навряд ли будете вешать несколько устройств на одну шину с дисплеем, когда есть вторая?) и логическое состояние 1 на пине RESET (в воздухе и «на земле» он будет висеть в постоянном ресете).
Для полустатичной графики, можно обойтись лишь командами дисплея — например, тут есть удобные функции для заливки прямоугольников (setArea и пишем цвет без остановки) или скроллинга. Сделано это для более слабых микроконтроллеров. Нам они не подойдут — выделяем память под фреймбуфер/бэкбуфер и настраиваем канал DMA для разгрузки процессора в процессе передачи данных:
Саму картинку подготавливает процессор: именно он рисует картинки и он же делает их прозрачными. На него ложится основная работа, однако мы можем ему помочь разгрузиться, если отдадим передачу уже подготовленного кадра на дисплей на DMA (Direct Memory Access) — устройство в микроконтроллере, которое позволяет процессору настроить параметры передачи данных, а DMA их будет сам копировать из памяти или в память. Таким образом, можно реализовать асинхронное копирование нескольких блоков ОЗУ, или, как в моем случае — передачу буфера кадра на дисплей, пока процессор готовит следующий. Чем больше разрешение — тем больше эффекта от DMA!
Кроме того, важно выбрать формат цвета для нашего дисплея: я выбрал 2-х байтный RGB565 (5 бит красный, 6 бит зеленый, 5 бит синий). Это экономичный формат который выглядит красивее палитровой графики и кушает не так уж и много драгоценной памяти. Кроме того, на данный момент мы умеем отрисовывать изображения произвольных размеров с прозрачностью — вместо альфа-канала здесь используется так называемый colorkey — концепция, очень близкая к хромакею, только она берет в качестве трафарета конкретный цвет. В нашем случае это «255 0 255» (ярко розовый).
Общая производительность рендерера порадовала: он легко осилит около сотни-двух различных спрайтов с адекватной производительностью, в зависимости от их размера. Но для такого разрешения экрана и будущих игр — это неплохой результат!
❯ Ввод
Теперь нам нужно как-то управлять нашим девайсом. Для этого пора сделать реализовать геймпад: в рамках этой статьи, я собрал его на макетке.
Кидаем общий минус на все кнопки, а второй вывод размыкателя кидаем на соответствующие пины GPIO. Я выбрал предпоследние т. к. на них ничего важного не висит, текущая конфигурация занимает 6 пинов. На фото выглядит не очень красиво — на то она и макетка.
Переходим к реализации драйвера. Игры могут слушать события кнопок из специальной структуры —CInput, где на каждую кнопку выделено по одному полю. В будущем конфигурация геймпада может поменяться — например, я захочу добавить аналоговый стик.
Есть ещё способ реализации больших клавиатур и геймпадов: когда все кнопки вешаются на пару линий, где на выходе каждой кнопки есть резистор определенного номинала. ЦАП микроконтроллера считывает это значение (допустим — 1024 это вверх, а 2048 — вниз) и таким образом определяет текущую нажатую кнопку. Таким раньше любили промышлять китайцы, из-за чего нельзя было нажать одновременно вверх и вправо, или вниз и влево и т. п.
❯ Пишем игру
Теперь у нас есть минимально-необходимая основа для написания игры. Первой игрой для своей консоли я решил написать классический шутер в космосе — летаем на кораблике и сбиваем врагов, попутно уворачиваясь от их пулек. Заодно проверим консоль на стабильность. Писать я её решил в классическом C-стиле, как и принято в embedded-мире: без std и тем более stl, без ООП и виртуальных методов, аллокаций по минимуму. В общем, примерно как писали игры под GBA! В первую очередь, подготавливаем спрайты нашей игры, прямо в пейнте, а затем конвертируем их в представление обычного массива байтов в виде header-файла. На первых порах это удобнее, чем делать свой ассет-пул:
Архитектуру я организовал в виде нескольких подфункций, каждая из которых занимается своим стейтом (world/menu) и своими объектами (playerUpdate) и их отдельные версии для отрисовки. Сами игровые объекты я описал в виде структур, а центральным объектом сделал CWorld.
Время я решил описывать в тиках, а не миллисекундах, как я обычно это делаю на ПК — у консоли железо одно и там следить за этим нужно меньше.
Единственные аллокации, что я использовал — это для пулов с пулями, и с врагами. Оба пула четко ограничены — до 8 врагов на экране, и до 16 пулек — вполне хватает. Динамические аллокации помогли мне найти серьезную ошибку в коде — в один из моментов игра просто валилась с Out Of Memory. После того, как я немного поменял условия и делал аллокейты тех же самых объектов каждый кадр — игра переставала крашится. Причина оказалась простая — невнимательность (вместо >= было >), по итогу при отрисовке спрайтов за пределами экрана, программа сама начинала портить вунтренние структуры аллокатара и самой игры (проявлялось в глюках и телепортациях). После фикса, все заработало как нужно. :)
Ну и для основной части геймплея с выстрелами и столкновениями, я предусмотрел несколько функций, которые спавнят игровые объекты и сами управляют пулом. Противники обновляются как обычно, для коллизий используется AABB (axis aligned bounding box, ну или его 2D-подмножество в виде rect vs rect).
По итогу, у нас получилось простенькая, но рабочая игрушка, которая без проблем работала почти все время, что я писал этот материал, а значит устройство работает стабильно. И я очень горд, что у меня получилось сделать рабочий прототип своего собственного гаджета!
Ниже выкладываю принципиальную схему устройства, она очень простая, поэтому смысла делить ее на несколько листов нет. Разводить учился, читая сервис-мануалы и схемы :)
❯ Заключение
Полная цена сборки прототипа составила:
Raspberry Pi Pico — 557 рублей (но я брал на Яндекс Маркете, на «алике» дешевле — около 300 рублей).
Дисплей — 380 рублей, заказывал на «алике».
Макетка — 80 рублей, в местном радиомагазине.
Кнопки. По 5 или 10 рублей штучка, пусть будет 60 рублей.
По итогу, прототип мне обошелся в 1077 рублей. Бюджетненько, да, с учетом того, что можно сделать еще дешевле? Я тут так подумал, у меня есть желание развивать и поддерживать консоль в будущем и под консоль уже можно делать что-то своё… может, если вам будет интересно, делать их на заказ? Соберу вам по себестоимости (до 1.000 рублей) + доставка, если хочется попрограммировать под что-то маленькое, но самому паять не хочется. Мне было бы очень приятно. Пишите в личку или комменты, если вас заинтересовало бы такое! :)
Весь процесс разработки этого девайса занял у меня всего несколько дней. Я и до этого понимал концепцию работы 2D-графики на видеокартах прошлого века, поэтому ничего особо нового я для себя не открыл. Однако, я попробовал свои силы в разработке игровых девайсов, которые могут приносить удовольствие — как ментальное от самого процесса сборки и программирования, так и физическое от осознания того, что игра на нем работает. :)
Однако, это далеко не конец проекта! У нас ещё много работы: нужно развести и протравить полноценную плату, реализовать звук и API для сторонних игр, придумать корпус и распечатать его 3D-принтере. Кстати, я ведь обещал что скоро будут и другие интересные проекты с 3D-принтером: как минимум, мы доделаем предыдущий проект игровой консоли из планшета с нерабочим тачскрином и RPi Pico.
Пост подготовлен при поддержке TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud, чтобы не пропускать новые статьи каждую неделю!
Пришлось конечно повозиться, но результат мне понравился.
Светильник из металлических труб и чугунных фитингов. Гофра нержавейка, бочонок от простой газовой горелки. Краны от сварочного оборудования. Старые монометры.
Такую задачу поставил Little.Bit пикабушникам. И на его призыв откликнулись PILOTMISHA, MorGott и Lei Radna. Поэтому теперь вы знаете, как сделать игру, скрафтить косплей, написать историю и посадить самолет. А если еще не знаете, то смотрите и учитесь.
Тоска возникает у меня глядя на то, что сделал около года назад, но так было не всегда.
Пропёрло вдруг сделать что-нибудь оригинальное и интересное. Старый абажур от лампы в виде шара подсказал направление, куда двигаться, так сказать, и немного фантазии.
Хочу добавить : имею тягу к "деланию" чего-то руками, из железяк в большей степени. Иногда могу и бутылку приварить к баллону газовому на спор, но это отступление.😁
Несколько дней приятного времяпрепровождения в гараже и вот уже на полу стоит светильник, похожий на одну башню, высотой 1,6 метра. Он немного согнулся по тяжесть шара, но все норм, он не упадет))
А почему тоска? А вот почему. Первый светильник был сделан в качестве подарка и благополучно подарен. Они были немного разные, абажур был не стеклянный, но идея та же. На фото уже второй. Знакомые подбили сделать второй, со словами: "да с рукам оторвут!" Но вот прошел год, как данный светильник висит на одном известном сайте с объявлениями и не вызывает ни у кого интереса.
Это не жалостливые сопли, просто люди выкладывают сюда то, чем живут. Я тоже хочу.
Я люблю "железы" разные и мне кажется у меня немного получается делать поделки похожие на "вещи" Если пост зайдет, выложу ещё , о том что ниже 👇