Нужно поставить стенд крутящий на экране какую-то инфу. Особых требований по автономности нет, ибо стационарное. Имеем варианты:
- Типовое железо с виндой в районе 15-20 тыр, в браузере показываем страницу. Админится любым эникеем, запчасти в ближайшем компьютерном лабазе в наличии.
или
- Кастомная железка, узко заточенная под задачу. Разработка и мелкосерийное производство я даже хз во сколько обойдется. Обслуживается квалифицированным инженером, под запчасти нужно иметь свой склад этого того самого мелкосерийного.
Что должно сподвигнуть ко второму варианту, даже если нет хотелки заказчика "нужно завтра"?
Охренеть сколько же оказывается в мире оверкилла по железу, что на обычные табло и экраны мед. оборудования ставят полноценный ПК внутри с виндой. И все они выводят простой набор буков, либо другую квазистатичную картинку или менюшку для пользователя. И дело даже не в винде, было бы всё на дистрибутивах линуха, подобное тоже стало бы возможным, пусть и в меньших масштабах.
А тем временем 90 процентов этих решений можно было бы заменить копеечным решением на микроконтроллере с Ethernet, на который не нужна винда, нужна максимум RTOS, которая не просит обновлений и просто делает что ей скажет программист, потребляет на порядок меньше чем полноценный CPU и стоит копейки по сравнению с тем же CPU.
Но дофига эффективным бизнесменам это неинтересно, им интересно побыстрее склепать очередную красивую снаружи, но ужасную технически хрень и пустить в продакшн, лишь бы бабки потекли. Замечательно работает этот ваш капитализм, что теперь все зависят от факапа одной монополии, на решения которой от технической безграмотности все присели.
А помните эти чудесные возгласы хипстеров о том, что скоро в каждом холодильнике будет компьютер? Ну вот к чему это приводит. Пихайте дальше в каждую мультиварку по компу.
В своей жизни я обожаю как минимум три вещи: это C# (как и .NET в целом), интересное железо и одноплатные компьютеры. В Embedded-системах на Linux обычно принято писать код на C/C++ для решения чувствительных к производительности задач и интерпретируемых Lua/Python для быстрого прототипирования, которые стали популярны в встраиваемых устройствах сравнительно недавно. Однако о нативной разработке под одноплатники на C# практически ничего не слышно и я решил исправить это недоразумение! В сегодняшнем материале: рассмотрим, какие платформы .NET нам доступны на одноплатниках, научимся работать с GPIO и SPI в юзерспейсе, а также напишем практическое приложение, которое реализовывает драйвер дисплея и выводит на экран определенное изображение.
❯ Предисловие
Одноплатники уже давно вошли в повседневную жизнь многих DIY-щиков, сисадминов и людей, которые интересуются мини-компьютерами. Казалось бы, одну и ту же задачу можно решить несколькими методами на самых разных языках: кто-то предпочитает писать нативный код на тех же плюсах, а особо прожженные — на Plain-C и ассемблере, стараясь получить максимальную производительность, а кто-то хочет сразу перейти к реализации своего устройства не заморачиваясь с подробным изучением того, как чип работает «под капотом» и какие шины существуют, ограничиваясь использованием готовых библиотек.
Но я лично очень люблю C# за его максимальную гибкость, позволяющую оптимизировать некоторые обращения к памяти путем получения прямых указателей на данные, умеет в удобные темплейты, а также имеет механизм для маршаллинга (прямой импорт функций из библиотек, возможность создать нативный трамплин на управляемый делегат, возможность быстрого копирования из unmanaged в managed окружение и т. п.). Потому всегда думал: почему бы его не использовать в своих embedded-проектах на базе одноплатников?
Сейчас .NET можно накатить на большинство современных одноплатников, за исключением самых слабых с 64Мб ОЗУ «бутербродом» на чипе (AllWinner F1C100s, AllWinner V3s, некоторые MStar и т. п.). Доступно два рантайма, которые предлагают разные профили и соответственно, разный функционал.
dotnet — официальный рантайм, который реализует профиль .NET Core (ой, простите, так уже не модно, теперь это просто .NET). Предоставляет весь современный базовый функционал дотнета вкупе с современными версиями самого C#, но в нём нет, например, Windows Forms для UI (если вы используете полноценные «иксы» и GTK), и System.Drawing для обработки графики и отрисовки текста. Это эталонная реализация дотнета и его можно без проблем накатить на любой одноплатник, для которого есть достаточно свежий Linux.
Mono — альтернативная реализация .NET Framework для Linux, ранее активно использовалась в Unity. В отличии от .NET Core, может работать и на более старых одноплатниках на прошлых версиях дистрибутивов Linux, в том числе и самой первой Raspberry Pi. Считается более медленной, чем dotnet, зато имеет значительно большую функциональность, почти идентичную фреймворку на Windows.
В сегодняшней статье мы будем писать программу на C# для OrangePi One, которая должна инициализировать дисплей из юзерспейса и выводить на него определенные данные. В качестве профиля используем .NET Framework 4 (да, я порой старомоден), а одноплатником выступит OrangePi One в стоковой конфигурации ядра, без правок devicetree, где по умолчанию у нас доступен spidev без аппаратных чипселектов, доступ к GPIO из /sys/ и i2cdev.
❯ Настраиваем окружение
Для начала нам нужен образ системы для нашего одноплатника. Какой — выбирать вам. Для большинства устройств на чипсетах AllWinner доступны образы с ядром 3.x, которые более стабильны, но не используют devicetree и не входят в мейнлайн и 5.x, так называемый мейнлайн, но там всё ещё есть некоторые нюансы. Я выбрал Ubuntu Xenial с ядром 5.3.5.
Теперь самое время накатить рантайм, что мы и делаем командой:
apt-get install mono-all
Обратите внимание, Mono громоздкий и с учетом всех зависимостей может устанавливаться минут 30, если у вас достаточно медленная флэшка. Всё, теперь устройство готово к запуску программ на дотнете, нашу программу можно запустить следующей командой:
mono assembly.exe
Давайте же перейдём к фактической реализации нашей программы и узнаем как работать с периферией устройства!
❯ GPIO
Начинаем с GPIO или «ногодрыга». В Linux есть удобный интерфейс, позволяющий экспортировать пины общего назначения в юзерспейс и рулить ими прямо из sysfs, в том числе и из терминала! Для реализации софтварного SPI или быстрого опроса цифровых пинов такой способ не подойдет — слишком большой оверхед, но для моргания светодиодами, обработки кнопок или… программного ногодрыга чипселектом — вполне подойдет :)
Как я и говорил выше, GPIO сначала нужно сделать видимым в sysfs — т. е. экспортировать, путём записи номера нужного пина в «файл» /sys/class/gpio/export. Посчитать ID нужного пина можно с помощью простой формулы: (позиция буквы в алфавите — 1) * 32 + номер пина. То есть, для PA10 ID будет 10. При ошибке, системный вызов close выбросит ошибку, а поток в C# — IOException.
После этого, по пути /sys/class/gpio/gpio10/ появится директория с файлами direction, куда нужно записать направление нашего пина («in» — ввод, «out» — вывод) и value, куда мы будем записывать или читать значение пина. Реализовать управление пином можно так:
Да, всё так просто! Мигалка светодиодом в нашем случае будет выглядеть так:
Переходим к чему посложнее, а именно к SPI из всё того-же юзерспейса!
❯ SPI
Для управления SPI нам потребуется вызов ioctl, который позволяет отправлять устройству различные пакеты с описанием команд. Для этого нам пригодится PInvoke:
Для каждой аппаратной шины SPI создаётся одно устройство spidev. В случае OrangePi One, по умолчанию экспортирована только одна шина (поскольку и SPI-контроллер на гребенке лишь один) — spidev0.0. Для начала открываем наше устройство для записи:
Драйвер spidev работает по принципу транзакций — вы посылаете IOCTL с запросом SPI_IOC_MESSAGE (в оригинале это макрос с возможностью послать сразу несколько транзакций в драйвер) и указателем на структуру spi_ioc_transfer с описанием отправляемых или получаемых данных, а драйвер уже сам решает что и когда отправить, при этом вызов ioctl — блокирующий, то есть управление в поток вернется только когда драйвер завершит работу. Но есть нюанс — драйвер SPI у чипсетов AllWinner не может отправлять более 128-байт (на AllWinner A10/A13 — 64-байт) данных за транзакцию, поэтому большой массив данных придётся разбивать на несколько мелких:
Уже в шоке от обилия указателей в коде на шарпе? :) Надеюсь, комментарии помогут вам разобраться.
Тоже самое и для чтения данных с шины, только вместо txBufPointer — rxBufPointer.
Пример работы прост до безобразия:
Имея GPIO и SPI уже можно переходить к реализации чего-то более конкретного!
❯ Дисплей
В качестве дисплея я буду использовать стандартную дешёвую 2.4" матрицу с разрешением 240x320 и контроллером ST7789 с интерфейсом SPI. Для использования дисплея с питанием 3.3В нужно поставить перемычку на позиции J1, как показано на фото ниже.
Для подключения такого дисплея, достаточно всего лишь 4 (5, если нужен чипселект) сигнальные линии на 40-пиновой гребенке RPi One, плюс один для ШИМ (если нужно регулировать подсветку) и два на питание. Обратите внимание, что лучше сдуть гребенку и паяться к одноплатнику напрямую — у меня из-за китайских дюпонтов постоянно помехи на дисплее и мусор на шине.
Схема подключения:
VCC -> 3.3V
GND -> Масса
CS -> PA9
RESET - PA10
D/C - PA20
MOSI - PC0
SCK - PC2
LED -> 3.3V
Начинаем с подготовки необходимых GPIO. Для управления дисплеем всегда нужен аппаратный RESET и D/C (бит команда/данные). Чипселект необязателен (его можно кинуть на массу), если это будет единственное устройство на шине, однако в случае ST7789 почему-то в таком случае нужно использовать SPI MODE 3.
Переходим к реализации коммуникации с дисплеем. Здесь всё просто — ставим CS в низкий уровень, начиная транзакцию, устанавливаем D/C в низкий уровень в случае команды, либо высокий в случае данных и отправляем байт контроллеру, после чего устанавливаем чипселект обратно в высокий уровень.
Теперь дисплей нужно инициализировать. Здесь нужно сконфигурировать регистры контроллера дисплея для установки режима адресации, цветности и порядка байт в пикселях (BGR или RGB).
Если всё сделано правильно — то после этого вы должны увидеть «мусор» на дисплее, поскольку состояние ОЗУ не определено после подачи питания на контроллер (но при сбросе содержимое DRAM останется на месте).
Теперь нам надо установить границы нашего изображения, в пределах которых работает автоинкермент контроллера дисплея. Нужно это для того, чтобы мы могли, например, пнуть уже готовую картинку в DMA-контроллер и уйти заниматься своими делами, а когда картинка отправилась — установить новые границы и нарисовать что-то ещё. В моём случае, всё рисование производится во второй буфер, который затем рисуется на дисплей — поэтому мне нужны размеры всего дисплея сразу:
После этого, достаточно лишь непрерывно слать изображение на контроллер дисплея и всё будет работать!
Поскольку ни один формат изображений не соответствовал моим требованиям (RGB565, без выравнивания), я быстренько накостылил конвертер в самопальный:
Загрузчик такого формата выглядит так:
А фактическое использование — так:
❯ Заключение
Как мы видим, писать программы для одноплатников на C# отнюдь не сложно и можно пользоваться всеми приятными фишками языка. Часть кода из этой статьи выдрана из моего сайд-проекта, о котором хочу рассказать вам в ближайшее время — поэтому местами код совсем не причесан, но надеюсь — всё было понятно :)
Также у меня есть канал в Telegram, куда я выкладываю посты с тематикой DIY, ремонта и моддинга, а также программирования под гаджеты прошлых лет и вовремя ссылки на новые статьи.
Друзья! Знаю, что среди читателей есть люди, которые работали над embedded-устройствами. Может у вас есть девкит Samsung S3C6410, когда либо списанный, на продажу?
В целом, интересно было бы любой девкит покопать. Друзья, именно девкит, а не просто Raspberry Pi или Orange Pi. Впрочем, одноплатник на чем-то необычном типа Samsung Hummingbird или Tegra был бы интересен!
Уже не первый пост мой с подобным вопросом, так что сначала предыстория.
Во времена короны меня сократили и я решил попробовать себя в работе с микроконтроллерами. Я был полный 0 в этой области, а про с/с++ знал только, что это языки программирования и что 1с не из их серии. За пол года немного подружился с esp32, понял главные моменты с масштабированием кода и используя чужие библиотеки мог собрать что угодно, что укладывалось в функционал библиотек. Даже начал ковырять freertos, разобрался с mitt app inventor, что бы делать примитивные приложения под андроид. Ну и нашел заказчика на такие не высокие требования, потому что я умел всего по чуть-чуть, мог и корпус замоделить и плату развести (посмотрев один ролик гайвера по EasyEDA) ну и какой то код составить из экзамплов, который на удивление еще и работал. На покушать хватало. Но будучи самоучкой дальше я столкнулся со стеной нехватки доступной инфы, так как это уже не популярно в массах, а интересует только профессионалов. И тут мне предложили помонтажить РЭА. Монтаж не сложный, но много и деньги платили не плохие... и все... на 2 года я забыл про программирование, по сути опять просрал 2 года. И вот после этого нового года у меня начало резко ухудшаться здоровье, сначала спина, что уже не мог сидеть паять, потом еще несколько неприятных недугов подтянулись, в общем пока работать вообще не могу и на восстановление наверное уйдет несколько месяцев, благо я отложил денюжку и мне хватит на пол года жизни или на год, если урезаться по максимум.
И вот тут у меня встал вопрос что делать дальше. Это место с монтажом я уже 100% потерял, потому что сам сказал, что выбыл на долго и пусть ищут замену. Я прошерстил вакансии на hh и даже на самые низкооплачиваемы в этой области я пришел к выводу что я должен овладеть следующими навыками:
- Знание языка С/С++ на высоком уровне (я же, если брать классический учебник, только первые главы освоил С++, С вообще не касался)
- Работа с STM32 - и тут самое веселое, даже если я разберусь с инструментарием GСС в VS Code + Cube MX, еще надо разобрать особенности типовых видов архитектуры этого семейства МК, при том не просто блок-схемы разобрать, а глубоко понимать, что бы я мог все это настраивать правильно и понимать как оно взаимодействует, потом разобрать работу всех видов периферии МК, а тут не как в arduino ide, не выйдет, просто, например для i2c написать Wire.begin(); и дальше даже не думать что оно делает, тут надо на низком уровне взаимодействовать, а я попытался разобраться в том же i2c, тайминги в нем это просто жесть, а это еще все и кодить надо, а я до битовых операций даже не добирался. Есть вроде либа hal от самих ST для упрощения работы, но как я понял там тоже не все так прекрасно.
- Знание основ схемотехники и теории цепей. Я только закон Ома на практики применял. Начал их читать, 3 закона Кирхгофа самое легкое, дальше начинается жуть с морем высшей математики, а я даже не помню как интегрировать, не то что как сигнал разложить в ряд Фурье для фильтрации гармонических составляющих. Это все фактически надо учить по новой. Плюс физика. Я на практике убедился как важно понимание взаимодействия электромагнитных полей при проектирование печатной платы, что бы у тебя сигнал не превращался в кашу только от того, что ты землю не правильно развел.
- Работа в Altium Designer - саму программу освоить не проблема, но вот я начал читать про проектирование помехоустойчивых систем и понял, что просто так на плату накидать по схеме элементов, что бы просто не пересекались дорожки не выйдет. Во-первых надо хорошо знать элементную базу и понимать как работает каждый из этих элементов, про ОТЦ и физику писал выше. Я нашел серию ГОСТ 61188, надо хорошо их знать и понимать.
-Английский язык... что у меня с ним всегда так не клеится, сейчас можно перевести все гугл переводчиком, но в профессиональной деятельности это явно не пойдет, так как может быть утерян какой то ключевой момент при переводе документации.
Это только самые часто встречаемые требования для людей с опытом от года, при этом почти всегда требуют, что бы это была твоя не первая работа в этой области. А я описал, только то, что я понимаю, что нужно, при этом я на stm32 смог пока только помигать светодиодом а в познании С++ мне еще предстоит долгий путь.
И вот я думаю, у меня есть несколько месяцев свободного времени, а успею ли освоить хоть какой то минимум, что бы меня взяли джуном? Или не имея только что законченного универа в этой области со свежими знаниями или уже не поработав в этой области, не имеет смысл вообще в нее суваться и попробовать себя в чем-то попроще? Я бы через пару месяцев, когда самочувствие будет получше, хотел бы найти какую то удаленную работу (но только не на обзвонах), тут тоже бы принял пару советов, в городе у меня больше не осталось вариантов, потом работу скорее всего придется искать в Ростове\Москве, но с учетом съёма жилья это все так грустно становится.
Я единственное что за последние 2 года не бесполезно потратил, это я с каждой получки что-то покупал. У меня есть макетки Nucleo-64 STM32F446, Discovery STM32F407, несколько F103, куча esp32, недавно купил несколько esp32-S3, ардуинки и малинки, осциллограф Hantec DSO02D15 (и еще USBишный), мультиметр uni-t UT61E+, лог анализатор DSLogic Plus, JTAG отладчик, ЛБП, 3D принтер 5й медведь, все для пайки, куча всевозможных датчиков, экранов, двигателей и драйверов для них. В общем для учебы у меня есть не плохая элементная база.
Почему в среде Proteus таймер/счётчик2 м/к Atmega16A в асинхронном режиме работает без кварцевого резонатора (32768Hz)? В дополнение могу сказать, что бит AS2 в регистре ASSR установлен в единицу. Наличие/отсутствие кварца между TOSC1 и TOSC2 роли не играет. С выхода счётчика OC2 снимается выход частотой 64 Гц = (2^15 (32768) / 256 (8-bit) / 2). Я понимаю, что протеус это симуляция, но как сделать так, чтобы он работал так, как описано в даташите? Сбивает с толку.
Пожалуй, немалая часть моих читателей так или иначе интересуется 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 тысяч рублей. За каждую копейку я готов отчитаться (по факту покупки машины я сделаю пост с фотографиями авто, ДКП, а также оглашу фронт будущих работ и сразу начну заниматься проектом).
Доброго времени суток. Есть у меня тонкий клиент TONK TN1000 с Linux Embedded на борту. Хочу сделать из него микросервер на Debian, но штатными методами систему на нём поменять нельзя. Может быть, можно ему как-то её незаметно подсунуть… Характеристики у него, как у Линукс-машины, вполне себе неплохие. В идеале - справиться без пайки. Предвещая будущие вопросы: доступа к консоли нет, по сети на него тоже не попасть.Гуглить пробовал - безрезультатно.