image

Сейчас появилось достаточно много различных дешевых одноплатников с очень достойными характеристиками, которые вполне можно назвать экономичными и портативными. Однако очень часто встает вопрос вывода изображения на дисплей: к сожалению, в подобные устройства обычно ставят урезанные версии чипсетов без видеовыхода на обычные матрицы. Конечно в них практически всегда есть HDMI, но это совершенно не выход для портативного устройства: прожорливый чип скалера будет очень негативно влиять на время работы от АКБ. Да и сами подобные дисплеи очень дорогие: почти 2.000 рублей за матрицу со скалером — это действительно бьет по карману. Сегодня я расскажу Вам о существующих протоколах для дисплеев, подскажу, как применить экранчики от старых навигаторов/мобильников и мы подключим с вами SPI-дисплей к одноплатнику без видеовыхода. Причем мы реализуем как просто библиотеку, которая позволяет выводить произвольную графику из ваших программ, так
и службу, которая будет напрямую копировать данные из фреймбуфера и преобразовывать в формат для нашего дисплея. Интересно? Тогда жду вас в статье!

Предисловие


На самом деле, существует достаточно много различных физических протоколов для общения с дисплеями. На программном уровне, общение с ними относительно стандартизированно, однако на аппаратном уровне различий довольно много. Самые распространенные из них:

  • MIPI DSI — дифференциальный многоканальный LVDS протокол. Если говорить совсем условно — то это эдакий быстрый низковольтный SPI, который для передачи одного байта использует минимум 4 линии — D+, D-, CLK+, CLK-, где фактических линии две, но для подавления помех используются доп. линии инвертированной полярности, из которых затем вычитаются положительные. Этот протокол позволяет подключать дисплеи очень высокого разрешения и используется практически во всех современных смартфонах. Насколько мне известно, такие дисплеи имеют собственную видеопамять размером с буфер кадра (т.е для 1920х1080х3 дисплея — ~5мб).
  • TTL/RGB — относительно простой для реализации протокол, очень похож на VGA, но по сути является цифровым: для передачи пикселей используются отдельные линии — например, 5 битов красного, 6 битов синего и 5 битов зеленого (RGB565). Не требует инициализации и обычно не имеет системы команд — пиксели синхронизируются с помощью тактовых сигналов HSYNC/VSYNC. Эти крайне дешевые дисплеи можно встретить на старых китайских игровых консолях, планшетах (до 720p) и автомобильных навигаторах (о них ниже), а также КПК (но на них даташиты найти сложнее). На МК и одноплатниках их использовать можно, но для этого нужно большое кол-во пинов (~18). У таких дисплеев нет собственной памяти, поэтому обновлять картинку нужно всегда, иначе будет белый дисплей. Есть еще аналоговая разновидность, практически 1 в 1 похожая на VGA, используется в ранних автомобильных телевизорах — но ей управлять сложнее из-за кучи различных тактовых сигналов.
  • 8080 — 8 или 16-битная параллельная шина, именно этот протокол использовали большинство телефонов в середине-конце нулевых, а его 16-битная разновидность использовалась в ультрадешевых китайских смартфонах начала 2010-х (Fly Jazz, Explay N1, Fly Era Nano 1, Fly Wizard — дисплеи всех этих копеечных на вторичке телефонов можно использовать и в своих проектах!). Занимает минимум 11 пинов — 8 на данные, 2 на сигналы RD/WR (он определяет, хотим ли мы сейчас что-то прочитать или записать) и 1 DC (определяет, куда мы пишем данные — в регистры, или в видеопамять). Такие дисплеи имеют собственную ОЗУ, поэтому необязательно гонять в них данные постоянно.
  • SPI — популярный протокол, который используется и в DIY-проектах и возможно в китайских старых MP3-плеерах (информация пока не точная). Отличается тем, что требует всего 3 пина для подключения — MOSI (данные), CLK (тактовая частота) и DC (имеет ту же роль, что и в 8080 дисплеях). Он гораздо предпочтительнее для использования в домашних проектах, поскольку хардварный SPI есть во многих микроконтроллерах/одноплатниках, а частенько к нему в комплект идёт DMA, позволяя разгрузить процессор. Кроме того, эти дисплеи использовали в телефонах начала нулевых — Nokia и Siemens точно использовала именно их. Причём у Siemens сами пины не на шлейфе, а «прижимаются» — бери да подпаивайся, только бустер подсветки до 12в придётся сделать.
  • I2C — редкий протокол для дисплеев из-за медлительности. Сейчас используется в недорогих OLED-модулях низкого разрешения, использовался в мобильниках самого начала нулевых (Ericsson) и Motorola C350.

Я не стал упоминать «большие» протоколы типа HDMI или eDP — они так или иначе, в физическом плане близки к MIPI DSI. Как видите — протоколов много и самых разных, соответственно и дисплеи нужно искать в разных местах. Дешевые DIY-дисплеи можно найти за довольно разумные деньги на алике — 1.8" матрицы на момент написания статьи стоили ~200 рублей, 2.4 — ~400 рублей, 3.5 и выше — от 700 рублей и выше. Пичем Вы вольны выбирать интерфейс — кому-то удобнее SPI, кому-то удобнее 8080. Я лично выбрал SPI — поскольку он есть в «хардварном» виде на большинстве одноплатников и доступен для программирования как из обычного пользовательского режима (т.е можно пользоваться шиной из обычной программы), так и из драйверов.

Где найти дисплеи?


Однако есть способ найти дисплеи «бесплатно» — из старых и нерабочих устройств. Например, из автомобильных навигаторов. Недавно читатель с DTF предложил заслать с 10-ок подобных девайсов, я конечно же согласился! Что самое приятное в них — так это то, что дисплеи там обычно стандартизированы — как по размерам, так и по шлейфу. Суть вот в чем: китайские компании довольно долго производили 4" дисплеи с разрешением 480x232 и резистивным тачскрином.

image

Поэтому Вы практически на 100% можете быть уверены, что один дисплей подойдет к другому навигатору и покажет картинку (а если нет — то открываем даташит на дисплей и корректируем тайминги). Эти дисплеи используют TTL/RGB протокол, поэтому для того, чтобы с ними работать, вам понадобится либо много свободных пинов, либо превратить микроконтроллер в видеоконтроллер (Raspberry Pi Pico/ESP32 должен с этим справиться без проблем). Большинство из этих дисплеев работает в 16-битном режиме, т.е до 65536 цветов. Ниже прилагаю распиновку к ним:

image

Для более удобно подключения, можно использовать такие breakout-платы для 40-пин шлейфов. Я себе заказал несколько, в том числе и для паябельных шлейфов от старых мобилок. Стоят на алике копейки — в среднем, 100 рублей за 5 плат (берите 40 пин/0.5мм).

image

На некоторых одноплатниках уже есть готовый 40-пин коннектор для подключения ваших дисплеев. Большинство из них базируется на базе чипсетов AllWinner F1C100s/F1C200s/V3s и экран работает там «из коробки», за исключением тачскрина (с ним надо повозиться), известные мне — Lctech Pi, MangoPi (извиняюсь за плохое качество фото, это с моего сайд-проекта):

image

Если Вам нужен маленький дисплей, то можно взять оный от старого нерабочего кнопочного телефона. Из самых простых — Siemens C65, S65, M65, A55, A65. Эти дисплеи работают по протоколу SPI и к ним легко подпаяться. Как еще один из вариантов — дисплей от «народного» Motorola C350, который работает через интерфейс SPI, но требует 12-битного формата на цвет:

image

Обратите внимание, что для этих дисплеев нужно самому мастерить бустер подсветки: от 3.7в они не заведутся. Сименсовским дисплеям нужно 12в — связано это с тем, что светодиоды в подсветке подключены последовательно, дабы уменьшить потребление. Если есть желание — можно разобрать модуль и перепаять светодиоды параллельно, но «кушать» такая сборка будет ощутимо, проще взять step-up преобразователь до 12В с алика за пару соток.

MIPI дисплеи можно достать из копеечных старых смартфонов ZTE/Lenovo/МТС/Билайн и.т.п. Предпочтительнее здесь именно именитые бренды, поскольку и ZTE и Lenovo делятся исходниками прошивки — так что можно будет найти команды инициализации и самому запустить дисплей. Кроме инициализации дисплея, там же можно будет найти и драйвер тачскрина — обычно они общаются по протоколу I2C и при очень большом желании, можно будет заставит работать и его.

image

Для работы с ними, я также рекомендую Breakout-платы, а схему на коннектор дисплея можно найти в сервисмануале или схеме устройства (если таковой имеется для вашего смартфона). Для Lenovo подобные ищутся без проблем, но для топовых Samsung S2/S3/S4 с крутыми OLED-дисплеями за MIPI-дисплеи придётся забыть, т.к схем в открытом доступе нет.

image

8080 дисплеи можно достать из старых китайских «кнопочников». Ищите те модели, на которые есть сервис-мануал (Fly DS124 и другие модели, некоторые Explay), тогда Вы сможете прочесть ID дисплея из регистра 0x0 (вида 0x9325/0x7739 и.т.п), найти даташит на интересующий вас контроллер и использовать его в своем проекте. В этих дисплеях самое приятное — паябельный шлейф и подсветка 5в, которая будет работать и на 3.7в, но немного тусклее.

image

Если же Вам хотелось бы экранчик побольше, с разрешением 480x320, то смотрите в сторону очень дешевых мобильников из начала 2010х — Explay N1, Fly Jazz, Fly Wizard. Вполне может быть так, что у Вас лежит подобный девайс будучи разбитым или утопленным, а дисплей остался. Кстати, если вдруг у вас лежит один из подобных ультрадешевых китайчиков, но вам они не нужны — пишите в ЛС, есть идеи для проектов с ними.

image

Обратите внимание, что эти дисплеи используют 18-битный физический интерфейс, но для программного доступа должно хватать 16-бит. Кроме того, на этом шлейфе есть пин IM0 — он отвечает за установку режима работы контроллера дисплея. Если бы у нас был еще IM1 и IM2, то мы могли бы хоть режим SPI установить, но в данном случае, мы можем установить либо 8-битный режим, либо 16-битный. Можете отследить пин IM0 на шлейфе и если он идет к обвязке, где предположительно разрывается/соединяется IM1/IM2, то можете попробовать разорвать/кинуть на них высокий уровень. Насчет подсветки на таких дисплеях пока что не знаю. Если распиновки на телефон нет, то поищите диагностические пятачки под коннектором, с осциллографом или даже просто тестером можно попытаться найти распиновку.

image

От слов к делу — userspace часть


На этом предлагаю перейти к практической реализации нашего драйвера дисплея. Как я уже говорил, реализовать его можно двумя способами: в виде user-space библиотеки для вывода картинки из обычных программ, так и kernel-mode драйвер, который будет реализовать framebuffer, что позволит выводить туда и X Window System, и SDL — что душе угодно.

У каждого подхода есть плюсы и минусы. Перечисляю их:

  • Универсальность: Библиотека сможет выводить только ту картинку, которая формирует для нее программа. Однако, она может это делать максимально эффективным для этого образом, да и никто не мешает написать сервис, который будет копировать из /dev/fb0 картинку на наш дисплей (однако это лишняя нагрузка на процессор), китайцы так и делают.
  • Производительность: Kernel-mode драйвер может быть теоретически быстрее, хотя по факту вся SPI-подсистема Linux выделен в удобный spidev.
  • Стабильность: По понятным причинам, User-space библиотека будет куда стабильнее драйвера и не крашнет систему в случае ошибки.

Работать мы будем с простеньким 1.8" дисплеем, который имеет разрешение 128x160, работает на контроллере ST7739.

В качестве одноплатника я взял Orange Pi One. Брал я его на вторичке за 1.000 рублей, однако продавец меня порадовал и положил не один, а два девайса — в благодарность за статьи о Orange Pi 3G IoT :) Сейчас старые модели RPi и Orange Pi (но не их Mini и Zero версии) стоят копейки.

image

Накатываем систему на флэшку (я выбрал Debian с ядром 3.4 — то которое еще не имело поддержки DeviceTree) и идем изучать гребенку:

image

Видим SPI? Он нам и нужен! Подключаем питание дисплея (3.3В на VCC, 5В на LED и не забываем землю), подключаем сигнальные линии (SCK — CLK, SDA — MOSI, A0 и RESET — цепляем на произвольный GPIO, на котором «ничего нет», я выбрал PA10 и PA20 пины). Если SPI Вам нужен только для дисплея, то можно просто поставить перемычку между CS и землей. Оставлять его «в воздухе» нельзя — иначе дисплей не будет работать.

image

Если подключили все верно, то при включении одноплатника, Вы увидите подсветку.
Теперь для того, чтобы им управлять, нам нужно получить доступ к шине SPI и проинициализировать контроллер. Для этого убеждаемся в том, что у нас есть spidev в каталоге /dev/, где spidev0.0 — первый контроллер SPI с первой линией CS, spidev0.1 — первый контроллер SPI с второй линией CS. У OrangePi One в стоке он только один — а для CS предлагается использовать sysfs. Кроме этого, нам нужно «экспортировать» из задать направлением пинам, которые мы будем использовать для сигналов RESET и DC. Для этого пишем номера пинов на гребенке прямо в устройство /sys/class/gpio/export, например так:

echo 10 > /sys/class/gpio/export
echo 20 > /sys/class/gpio/export

echo out > /sys/class/gpio/gpio20/direction
echo out > /sys/class/gpio/gpio10/direction

Обратите внимание, что в свежих версиях ядра появилось нормальное API для доступа к GPIO из userspace, управлять пинами через sysfs — в какой-то степени считается плохим тоном.

Открываем устройство как обычный файл:

    fd = open("/dev/spidev0.0", O_RDWR | O_NONBLOCK);
    dcFd = open("/sys/class/gpio/gpio10/value", O_RDWR);
    resetFd = open("/sys/class/gpio/gpio20/value", O_RDWR);

И отправляем контроллер дисплея в RESET:

gpHelperSetState(resetFd, 0);
usleep(250000); // 250ms
gpHelperSetState(resetFd, 1);

После этого, реализовываем методы для передачи данных через SPI. В Linux, общение через эту шину идёт посредством транзакции, причем размер одной транзакции ограничен конкретным SPI-контроллером. В случае AllWinner, тут от 64, до 128 байт. Для каждой транзакции можно установить тактовую частоту — AllWinner поддерживает до ~100мгц.

void CLCM::Command(unsigned char cmd)
{
    spi_ioc_transfer tf;
    memset(&tf, 0, sizeof(tf));
    tf.bits_per_word = 8;
    tf.len = 1;
    tf.speed_hz = 64000000;
    tf.tx_buf = (unsigned long)&cmd;

    gpHelperSetState(dcFd, 0);

    if(ioctl(fd, SPI_IOC_MESSAGE(1), &tf) < 0)
        LOG("SPI transfer failed\n");
}

void CLCM::Data(unsigned char data)
{
    spi_ioc_transfer tf;
    memset(&tf, 0, sizeof(tf));
    tf.bits_per_word = 8;
    tf.len = 1;
    tf.speed_hz = 64000000;
    tf.tx_buf = (unsigned long)&data;

    gpHelperSetState(dcFd, 1);

    if(ioctl(fd, SPI_IOC_MESSAGE(1), &tf) < 0)
        LOG("SPI transfer failed\n");
}

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

void CLCM::SoftwareReset()
{
	Command(0x11);//Sleep out
	usleep(120000);
	//ST7735R Frame Rate
	Command(0xB1);
	Data(0x01);
	Data(0x2C);
	Data(0x2D);
	Command(0xB2);
	Data(0x01);
	Data(0x2C);
	Data(0x2D);
	Command(0xB3);
	Data(0x01);
	Data(0x2C);
	Data(0x2D);
	Data(0x01);
	Data(0x2C);
	Data(0x2D);
	//------------------------------------End ST7735R Frame Rate-----------------------------------------//
	Command(0xB4);//Column inversion
	Data(0x07);
	//------------------------------------ST7735R Power Sequence-----------------------------------------//
	Command(0xC0);
	Data(0xA2);
	Data(0x02);
	Data(0x84);
	Command(0xC1);
	Data(0xC5);
	Command(0xC2);
	Data(0x0A);
	Data(0x00);
	Command(0xC3);
	Data(0x8A);
	Data(0x2A);
	Command(0xC4);
	Data(0x8A);
	Data(0xEE);
	//---------------------------------End ST7735R Power Sequence-------------------------------------//
	Command(0xC5);//VCOM
	Data(0x0E);
	Command(0x36);//MX, MY, RGB mode
	Data(0xC8);
	//------------------------------------ST7735R Gamma Sequence-----------------------------------------//
	Command(0xe0);
	Data(0x02);
	Data(0x1c);
	Data(0x07);
	Data(0x12);
	Data(0x37);
	Data(0x32);
	Data(0x29);
	Data(0x2d);
	Data(0x29);
	Data(0x25);
	Data(0x2b);
	Data(0x39);
	Data(0x00);
	Data(0x01);
	Data(0x03);
	Data(0x10);
	Command(0xe1);
	Data(0x03);
	Data(0x1d);
	Data(0x07);
	Data(0x06);
	Data(0x2e);
	Data(0x2c);
	Data(0x29);
	Data(0x2d);
	Data(0x2e);
	Data(0x2e);
	Data(0x37);
	Data(0x3f);
	Data(0x00);
	Data(0x00);
	Data(0x02);
	Data(0x10);
		Command(0x2A);
	Data(0x00);
	Data(0x02);
	Data(0x00);
	Data(0x81);

	Command(0x2B);
	Data(0x00);
	Data(0x01);
	Data(0x00);
	Data(0xA0);
	//------------------------------------End ST7735R Gamma Sequence-----------------------------------------//

	//Command(0x3A);
	//Data(0x05);
	Command(0x3A);//65k mode
	Data(0x05);
	Command(0x2C);//Display on

	Command(0x29);//Display on

	// Set viewport
	int x1 = 0;
	int x2 = 128;
	int y1 = 0;
	int y2 = 160;

	Command(0x2A);
	Data(x1>>8);
	Data(x1);
	Data(x2>>8);
	Data(x2);

	Command(0x2B);
	Data(y1>>8);
	Data(y1);
	Data(y2);
	Data(y2);

	Command(0x2C); // Начинает запись фреймбуфера в память
}

Для передачи фреймбуфера, мы реализовываем отдельный метод, который разобьёт его на транзакции. В нашем случае, фреймбуфер занимает 128 * 160 * 2 = 40960 байт, делим на 64, получаем 640 транзакций на передачу одного кадра.

void CLCM::Bitmap(void* data, int len)
{
    gpHelperSetState(dcFd, 1);

    for(int i = 0; i < len / 64; i++)
    {
        spi_ioc_transfer tf;
        memset(&tf, 0, sizeof(tf));
        tf.bits_per_word = 8;
        tf.len = 64;
        tf.speed_hz = 32000000;
        tf.tx_buf = (unsigned long)data;

        data += 64;

        if(ioctl(fd, SPI_IOC_MESSAGE(1), &tf) < 0)
            LOG("SPI transfer failed\n");
    }
}

Компилируем нашу программу, запускаем и видим: на дисплее появился мусор, а это значит, что он успешно проинициализирован. Если у Вас всё равно белый дисплей — смотрите подключение и убедитесь, что подключили сигнальные линии RESET/DC куда надо. После инициализации, на DC должен быть логический 0 (0В), на RESET — логический 1 (3.3В).

Пишем простенький загрузчик TGA и выводим картинку на экран:

CImage* img = CImage::FromFile("test.tga");

if(img)
  Bitmap(img->RGB, img->Width * img->Height * 2);

Всё работает и у нас есть картинка на дисплее! Производительность системы, скажем так, оптимальная, но учтите: чем выше разрешение, тем выше нагрузка на ядро!

Выводим фреймбуфер на экран


Это всё конечно замечательно, однако зачастую есть необходимость отображать картинку, которые рисуют другие программы — X Window System, или, например, порт эмулятора денди на SDL1.2. Для этого, нам нужен способ выводить на наш дисплейчик то, что рисуется в главный фреймбуфер — /dev/fb0. И для этого, у нас есть целых два способа:

  • Реализация kernel-mode драйвера фреймбуфера: Это правильный вариант, однако при условии отсутствия dts, придется «подвигать» родной драйвер на другой фреймбуфер, либо перенастраивать уже имеющееся окружение на /dev/fb1.
  • Служба-прослойка, которая копирует фреймбуфер и вручную рисует на наш дисплей Этот способ я подсмотрел у китайцев: именно он реализован в драйвере дешевых дисплеев для Raspberry Pi. В целом, если так подумать, то это действительно довольно простой, портативный (не зависящий от версии ядра) и шустрый метод.

Именно второй способ мы и выберем в силу его некоторой диковинности. Фреймбуфер Linux имеет одну очень приятную особенность: он способен сам выполнять преобразования формата пикселей и динамически менять размер рабочего пространства. Мы можем просто попросить драйвер установить комфортный для нашего дисплея режим (128x160), цветность (RGB565) и читать уже готовые битмапы, по необходимости пересылая их на дисплей.

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

struct CLCM
{
    char* name;
    int width, height;
    void(*init)();
    void(*presentBuffer)(void* buf);
};

CLCM lcm7735
{
    .name = "ST7735",
    .width = 128,
    .height = 160,
    .init = &st7735Init,
    .presentBuffer = &st7735Bitmap
};

CLCM* lcmList[] = {
  &lcm7735  
};

Теперь у нашей службы есть некоторая гибкость. Захотели — поставили дисплей на базе ILI9341, захотели — на базе ILI9325, достаточно лишь портировать код инициализации.

Открываем всем необходимые устройства и назначаем нашему фреймбуферу желаемое разрешение. Обратите внимание, что мы можем весь буфер кадра отобразить в наш процесс с помощью mmap: это гораздо быстрее и экономичнее к памяти, чем выделять отдельный буфер под read/write.

bool setupFrameBuffer()
{
    LOG("Open framebuffer device");
    fbDevice = open("/dev/fb0", O_RDWR);
    
    if(!fbDevice)
    {
        LOG("Failed to open primary framebuffer");
        return false;
    }
    ioctl(fbDevice, FBIOGET_VSCREENINFO, &fbVar);

    fbVar.xres = lcm->width;
    fbVar.yres = lcm->height;

    if(ioctl(fbDevice, FBIOPUT_VSCREENINFO, &fbVar) < 0)
    {
        LOG("Unable to set framebuffer size :c");
        return false;
    }

    ioctl(fbDevice, FBIOGET_VSCREENINFO, &fbVar); // Get yet another time for test
    LOGF("Parent FB: %ix%i %i-bits", fbVar.xres, fbVar.yres, fbVar.bits_per_pixel);
    ioctl(fbDevice, FBIOGET_FSCREENINFO, &fbFix);

    fbMem = (char*)mmap(0, fbFix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbDevice, 0);
    buf = (unsigned short*)malloc(lcm->width * lcm->height * 2);
    if(!fbMem)
    {
        LOG("mmap failed");
        
        return false;
    }

    return true;
}

К сожалению, в случае с OrangePi, мне не удалось запросить драйвер обрабатывать картинку в формате RGB565, поэтому для вывода пришлось выделять внешний буфер, где мы на лету конвертируем картинку из 32х-битного RGB в 16-битный.

__inline unsigned short lcmTo565(unsigned int r, unsigned int g, unsigned int b)
{
    short ret = ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3);
    return bswap_16(ret);
}


Ну и переходим, собственно, к копированию фреймбуфера на наш дисплей:

void lcmCopyFramebuffer()
{
    int bpp = fbVar.bits_per_pixel / 8;

    for(int i = 0; i < lcm->width; i++)
    {
        for(int j = 0; j < lcm->height; j++)
        {
            unsigned char* rgbData = (unsigned char*)&fbMem[(j * fbFix.line_length) + (i * bpp)];

            buf[j * lcm->width + i] = lcmTo565(rgbData[0], rgbData[1], rgbData[2]);
        }
    }

    lcm->presentBuffer(buf);
}

Да, это вся программа. Тестируем наш результат:

image

Работает! Теперь если мы захотим запустить, например, эмуляторы, или вывести иксы на внешний экранчик — то мы смоежм сделать это без каких либо проблем.

Заключение


Как видите, даже из казалось бы, из неактуальных и нерабочих гаджетов можно вытащить дисплеи для собственных проектов. Документация по протоколам доступна в свободном доступе в сети, да и со схемами уже не так сложно, как в нулевых.

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



Возможно, захочется почитать и это:


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


  1. bodyawm Автор
    14.08.2023 08:07
    +35

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

    Недавно мне стало скучно, и я ради прикола решил применить описанную в моей прошлой статье практику написания нативных приложений под Android-смартфоны без загрузки самого "ведра". По итогу у меня получилось написать что-то типа альтернативной прошивки, с которой можно даже звонить! Кроме того, под капотом было реализовано довольно много различных узконаправленных подсистем:

    1. Графическая подсистема с отрисовкой прозрачных/непрозрачных картинок, умножением цвета, автоматическим управлением формата пикселя (на смартфоне 16-битный фреймбуфер, т.е 565, на 32х-битном фреймбуфере все должно работать также), отрисовкой примитивов и растровых моноширинных шрифтов. Изначально хотел её делать на GLES, но нативный рендерер мне завести пока не удалось (буду копать исходники ведра, чтобы понять шаги инициализации), на софтрендере работает не очень плавно, но вполне шустро.

    2. Простой механизм для анимаций. Я изначально хотел слизать UX для прошивки с первых версий iOS, поэтому без анимаций никуда. Пока что есть примитивный интерполятор нескольких значений (X, Y, поворот и.т.п), который вполне работает.

    3. Обработка ввода с тачскрина с распознаванием базовых жестов (свайпы). Ну, тут все очевидно, правда пока распознается только одно нажатие - мультитача ещё нет.

    4. Простенький драйвер модема. Звонить и отправлять СМС-ки умеет.

    5. GUI. Причём я решил реализовать интерфейс самым нетривиальным по мобильным меркам способ - концепцией Immediate GUI (т.е вызвали GUI::Button - получили кнопку на экране, GUI::TextField - текстовое поле и всё это с минимальным количеством стейтов). Для сторонних приложений есть механизм «экранов».

    6. Менеджер питания. Замер уровня заряда АКБ, статус ЗУ, управление подсветкой - это все сюда.

    Интересно ли вам почитать об этом и узнать, как я на деле выкинул Android и запилил собственную прошивку? :)


    1. bodyawm Автор
      14.08.2023 08:07
      +2

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


    1. alstutor
      14.08.2023 08:07
      +3

      Однозначно интересно!


    1. axe_chita
      14.08.2023 08:07
      +1

      Конечно будет интересно увидеть, как можно кастомную прошивку залить и запустить.



  1. MajorMotokoKusanagi
    14.08.2023 08:07

    Опознать бы дисплей с планшета. NeuTab N7 Pro, на дисплее примерно такой набор символов d070BE018030, на шлейфе fpc70063 p3832. Сам по себе планшет морально очень старый, Андроид 4.4, динамика нет, камера не функционирует, аккумулятор всё, остаётся из полезного дисплей в 7 дюймов.


    1. bodyawm Автор
      14.08.2023 08:07

      Сфотографируйте шлейф дисплея и коннектор на материнской плате, скажу конкретно.


    1. bodyawm Автор
      14.08.2023 08:07
      +2

      Кажись у вас 60-пин матрица. Это тоже TTL-дисплей, можно использовать в своих проектах, но его потянут либо отдельные скалеры, либо очень мощные одноплатники (либо ПЛИС) из-за высокого разрешения. Часть схемы с похожего планшета, но с 7" матрицей (шлейф дожен быть совместимым). Распиновка на схеме. Обратите внимание, что пины гамма-коррекции обязательны, иначе тёмные пиксели будут немного мерцать (не критично, но всё же).


      1. MajorMotokoKusanagi
        14.08.2023 08:07
        +1

        60-пиновая, скорее, и есть. Крайний правый вывод с обозначением 60. Увы, без фото — камера на смартфоне унылая, других возможностей нет.
        Разрешение по спецификациям — 1024x600.
        З.Ы. + за меня добавьте.


  1. Zara6502
    14.08.2023 08:07
    +1

    Конечно в них практически всегда есть HDMI, но это совершенно не выход для портативного устройства: прожорливый чип скалера будет очень негативно влиять на время работы от АКБ

    А что за устройства вы хотите подключать через АКБ? Всё более менее интересное само чаще всего требует 5В/2А, а мелкие и простые платы часто в экране с HDMI вообще не нуждаются, так как есть OLED дисплеи и e-ink.


    1. bodyawm Автор
      14.08.2023 08:07
      +1

      Всё более менее интересное само чаще всего требует 5В/2А

      Не требует. На входе платы всегда стоит линейный регулятор, который преобразует 5В в 3.3В, при этом вне зависимости от падения напряжения (т.е если подать 4.5, или 3.7 - все равно будет 3.3в), и те же 5В уходят в КП, который затем уже формирует напряжения для ядра (~1.2в) и остальной периферии (~1.8в). Кроме того, 2А платы потребляют только в пике, как и смартфоны при поиске сети, в обычной ситуации потребление не слишком большое даже с подключенным дисплеем. Сами литий-ионные АКБ выдерживают до 2А нагрузки (если убитые - то меньше).

      Я OrangePi питал от USB-порта ПК (0.5А), работает норм, если не подключать USB-устройства и не юзать сеть. Малинки без проблем от юсб работают.


      1. Zara6502
        14.08.2023 08:07
        +1

        Я OrangePi питал от USB-порта ПК (0.5А), работает норм, если не подключать USB-устройства и не юзать сеть. Малинки без проблем от юсб работают

        вы намного прокаченнее меня в этой теме, поэтому спорить не особо имеет смысл, только скажу, что я работаю с OrangePi/RaspberryPi и больное место этих устройств - это качественный БП и минимум 2А. То что оно там во что-то преобразуется не важно. От USB ПК тоже подключал, но с даунвольтом и пониженной частотой, что на мой взгляд уже хак, а не стандартный режим работы. С такой логикой можно Core i9 запустить от пальчиковой батарейки. Вы же оборудование используете для работы, а не только в качестве спортивного интереса.

        Ну и если занудствовать, то вы написали про "любой одноплатник", а "одноплатник питающийся от АКБ" и уж тем более "мобильный телефон" это устройства с уклоном в мобильность или автономную работу, что является отдельной категорий одноплатных устройств, так что вы либо про все говорите, либо только про автономные. Простой пример, у меня дома два одноплатных устройства Eltex NV-501 и Eltex NV701, первое завелось только от третьего БП, два других не позволяли ему работать стабильно, все БП 5В/2А. Модель 701 уже 5В/3А. В штатном режиме с более слабым питанием оно не работает.


        1. bodyawm Автор
          14.08.2023 08:07

          OrangePi на AllWinner H тоже вполне себе портативные устройства) H - A без вывод на дисплей и с эзернетом :)


        1. MaFrance351
          14.08.2023 08:07
          +1

          От USB ПК тоже подключал, но с даунвольтом и пониженной частотой, что на мой взгляд уже хак, а не стандартный режим работы.

          Проблема всех этих способов подключения в том, что можно словить такие глюки, о каких и не догадывался. Разбираться, почему вдруг плата без видимых причин виснет на полном ходу, весьма такое себе занятие.
          Давным-давно, играясь с RPi (самой первой ещё), долго пытался понять, почему после примерно пятнадцати минут работы плата виснет просто намертво, даже на клавиатуру реакции ноль. После замены БП на более мощный проблема полностью ушла.


          1. Zara6502
            14.08.2023 08:07
            +1

            я об этом автору и говорю, а он утверждает что всё будет работать от пальчиковой батарейки годами. У любого SoC свои причуды и свой сегмент рынка. Формально Intel NUC тоже одноплатники, но от АКБ их не особо есть смысл эксплуатировать.


            1. bodyawm Автор
              14.08.2023 08:07

              Что?) Где я такое утверждаю?)

              Вы же понимаете, что под капотом логика работает не на 5в, а мало-мальски живой АКБ от старого смартфона способен обеспечить стабильную работу без просадок?) Это изначально МОБИЛЬНЫЕ чипсеты.


              1. Zara6502
                14.08.2023 08:07

                Что?) Где я такое утверждаю?)

                "Я OrangePi питал от USB-порта ПК (0.5А), работает норм"

                Вы же понимаете, что под капотом логика работает не на 5в

                Почему вы решили что я это не понимаю? Вольтаж вообще не имеет значения так как это абстрактная единица, важно - какая мощность требуется.

                а мало-мальски живой АКБ от старого смартфона способен обеспечить стабильную работу без просадок?

                Вы у меня спрашиваете?

                Это изначально МОБИЛЬНЫЕ чипсеты

                Никто не сомневается что их можно питать от АКБ, тут вопрос целесообразности. Если ваше устройство будет питаться от АКБ и не будет регулярно переходить в режимы низкого энергопотребления, то никакой мобильности у вас не будет, потому что АКБ будет пустеть на глазах и ваша мобильность превратится в тыкву. У меня есть читалка Digma E500, её АКБ разряжается в спящем режиме год, а в режиме чтения - месяц. Вот это мобильность. А TFT/LCD/HDMI это не про мобильность и если я держу в руках OrangePi на H3 и у него есть Ethernet, 4USB, HDMI и я вынужден клеить на него радиатор чтобы он вообще работал, то это не про мобильность. Отсюда и моя претензия к вашим выводам в статье. Поэтому вы либо четко и однозначно в статье обозначаете применимость ваших рассуждений к мобильным вариантам исполнения (и HDMI там даже не будет распаян), либо ваши слова про внешнее питание и HDMI не имеют смысла и можно не городить городушку и подключать любой HDMI экран, так как вы все равно "сидите у розетки".

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


            1. bodyawm Автор
              14.08.2023 08:07

              Если и есть какие-то действитеьльно жрущие платформы типа интеловских или Jetson - что мешает объединить два АКБ последовательно!?


              1. MaFrance351
                14.08.2023 08:07

                А разве такие платформы не ориентированы таки на стационарное использование?


                1. bodyawm Автор
                  14.08.2023 08:07

                  Для чипсета нет понятия "стационарный", "не стационарный" - ему нужен источник питания. Напряжения всей плате раздают контроллеры питания, которые изначально проектируются с уклоном на то, чтобы работать и от АКБ, и от 5в.

                  Хоть он будет от АКБ, хоть он будет от 5В БП - лишь бы выдерживал необходимое потреблени


    1. bodyawm Автор
      14.08.2023 08:07

      Лично я не очень люблю дисплеи со скалерами из-за их дороговизны и лишнего потребления (хотя судя по всему, не очень большому - ~100мА кушает скалер). Но для быстрых проектов это норм решение.


  1. rsk
    14.08.2023 08:07

    @bodyawmа по поводу камер можете что-нибудь сказать? легко ли использовать камеру из старого мобильника?


    1. bodyawm Автор
      14.08.2023 08:07
      +1

      Не прям легко, но возможно. Зависит от протокола этой самой камеры, но на модули OV вроде есть даташиты легкодоступные. Плюс кто-то камеры от сименсов старых юзал.э


      1. Kurochkin
        14.08.2023 08:07

        Соотношения цена/качество или цена/время_разработки не оставят камерам из старых телефонов никаких шансов. Модули OVxxxx действительно есть, с документацией и самой разной оптикой, и даже по нормальной цене. Их массово подключают к "Малинкам", и не имеют потом головной боли.


        1. bodyawm Автор
          14.08.2023 08:07

          Сименсы фоткают очень даже ничего))