Готовое устройство
Готовое устройство

Предистория

Организованность — не сильная сторона. Периодически на столе скапливается куча вещей: заказы с Aliexpress, компоненты для проектов, подарки и прочее, что не нашло своё место. Вот так и родился этот проект.

В один момент на столе лежали:

  • Несколько 4G модемов, совместимых с openstick:
    • MF800 — выбран для проекта
    • UZ801 — неплохой размер, но нет батареи и достаточного количества доступных GPIO
    • USB-модем — отложен из-за других проблем
  • Клавиатура Clicks для iPhone 16 Pro Max (подарок от кузена — жаль, что нет подходящего iPhone 🥲)
  • Дисплей Adafruit SHARP Memory Display

И разум, как в пещерные времена, соединил все три компонента вместе.

Конечно, вдохновение пришло не с пустого места — ранее доводилось узнать о:

Модем

По волшебству розницы и цепочек поставок можно купить 4G модем с WiFi, Bluetooth, дисплеем, собственной батареей и полностью разблокированный — менее чем за $20. Это — краеугольный камень проекта.

Существуют версии с дисплеем и без (вместо экрана — светодиоды), печатная плата одинаковая. Дисплей управляется чипом GC9107, но выглядит ужасно, поэтому отказались от него.

Linux устанавливается тривиально благодаря проекту openstick. Заводская прошивка — Android, но adb доступен сразу. Через adb можно перейти в режим EDL и перепрошить устройство. Важно сохранить все критичные разделы. На печатной плате есть контакты, замыкание которых выводит устройство прямо в EDL.

Дерево устройств, извлечённое из запущенной Android, оказалось кладезем информации.

Полезные ресурсы:

Сложности с openstick начинаются на этапе драйверов и деревьев устройств, об этом — ниже.

Клавиатура

О клавиатуре Clicks сказать особо нечего. Очень приятна в использовании, единственная беда — цена (и придётся её резать 😱).

Протокол стандартный — обычная USB клавиатура с дополнительным proprietary endpoint от Apple, который iPhone может авторизовать перед использованием.

Для текущего проекта это значит, что это просто обычная клавиатура.

У Clicks есть мобильное приложение для конфигурации и обновлений. Сама клавиатура работает на CH32V203 или аналоге. Теоретически можно залить свой код, но причин для этого пока нет. В будущем хотелось бы утилиту для конфигурации.

Дисплей

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

Это просто дисплей. Отправляешь команды. Он отображает.

Адаптерная печатная плата

Быстро стало ясно, что нужна собственная плата. Неясно, что находится внутри MF800 под экраном, никогда не открывали, но уверенно можно сказать — внутри нет 5V повышающего преобразователя.

По причинам размера USB разъём пришлось бы обрезать, нужен способ это обработать.

Финальная плата обрабатывает: питание USB в режиме host, переключение режимов USB host/device, питание дисплея и преобразование уровней сигнала дисплея.

Плата со стороны компонентов
Плата со стороны компонентов

Плату заказали с поверхностной сборкой. Двусторонняя сборка дорогая, поэтому уместили все компоненты с одной стороны. Вторая сторона используется для контактных площадок межплатных соединений.

Все файлы на GitHub. Вкратце, плата содержит:

  • TUSB320 — переключение режимов USB
  • SN74LVC8T245 — преобразование уровней сигналов
  • MCP1640 — 5V преобразователь
  • TPS22917 (два экземпляра) — коммутация VBUS/VBAT
  • USB разъём и FPC разъём для дисплея
  • Тестовые площадки для подключения к MF800

Корпус. Часть 1

MF800 значительно больше других openstick модемов — из-за батареи и из-за свободной компоновки.

MF800 без задней крышки, с ненужным вырезом под контакты bootloader
MF800 без задней крышки, с ненужным вырезом под контакты bootloader

Чтобы втиснуть в корпус Clicks, либо ориентируем вертикально (получится чудовище), либо обрезаем плату для горизонтальной ориентации.

Сверху: синие линии показывают USB data линии от разъёма и площадок к SOC
Сверху: синие линии показывают USB data линии от разъёма и площадок к SOC
Снизу: красные линии — планируемые разрезы, синий круг — via для USB data площадок
Снизу: красные линии — планируемые разрезы, синий круг — via для USB data площадок

Правый разрез перерезает линию подключения батареи — это потребует дальнейшей пайки.

Резание платы

Резание прошло неожиданно гладко. Устройство загрузилось сразу, всё работало. Оказалось, под этими участками платы не проходили критичные линии.

Обрезанная плата в тестовом корпусе с батареей и проводом питания VBAT
Обрезанная плата в тестовом корпусе с батареей и проводом питания VBAT

Не тестировали USB соединение и 4G модем. Модем не вызывал опасений, а USB беспокоил — может быть случайно повредили via.

2D скан платы с учётом разрезов и экструдированной толщины — плотное прилегание к ширине iPhone
2D скан платы с учётом разрезов и экструдированной толщины — плотное прилегание к ширине iPhone

Пайка. Часть 1

Решили переиспользовать контактные площадки от старого дисплея. Рационально в этом не было, может быть, планировалось использовать жёсткий flex PCB с похожей схемой пайки (от идеи отказались сразу при виде цены).

Сейчас это выглядит очень наивно. Нужно было использовать помеченные площадки рядом с неустановленным разъёмом microSD. Никогда не проверяли, расшарены ли они с контактами SIM карты.

Когда устройство загрузилось и получили дерево устройств из Android, извлекли какие пины используются для SPI дисплея. Протестировали их gpioset, чтобы убедиться в правильности. То же самое для питания (отключали в дереве устройств и перезагружались) и земли.

Проверка и картирование пинов мультиметром
Проверка и картирование пинов мультиметром

Первым делом спаяли и проверили питание, потом USB. Это позволило тестировать отдельный функционал. USB вызывал больше опасений из-за схемотехники и того самого подозрительного via.

Пайка USB и питания
Пайка USB и питания

Сначала не работало. После проверки напряжений на площадках заметили в dmesg ноутбука, что USB ПЫТАЛСЯ перечислить устройства — что-то происходит.

Также заметили "high-speed". Удивило — не ожидали high-speed USB. Первая мысль — провода слишком длинные. Но перед тем, как их укоротить, попробовали быстрый хак — плотнее закрутили — и сработало 😲!

USB Gadget устройства перечисляется
USB Gadget устройства перечисляется

Сразу после попытались включить обратное направление. Потребовалось время. Оказалось, не все адаптеры с Aliexpress корректно проводят CC линии. Клавиатура сработала сразу, просто боялись тестировать первым, чтобы не сломать.

События от клавиатуры
События от клавиатуры

Пайка. Часть 2

Перед пайкой SPI линий к дисплею решили проверить, правильно ли переконфигурировали дерево устройств. Площадки проверили, но много может пойти не так.

spi@78b9000 {
    compatible = "qcom,spi-qup-v2.2.1";
    reg = <0x78b9000 0x500>;
    interrupts = <0x00 0x63 0x04>;
    clocks = <0x13 0x41 0x13 0x36>;
    clock-names = "core", "iface";
    dmas = <0x6d 0x0c 0x6d 0x0d>;
    dma-names = "tx", "rx";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <0x84>;
    pinctrl-1 = <0x85>;
    #address-cells = <0x01>;
    #size-cells = <0x00>;
    status = "okay";
    spidev@0 {
        //compatible = "linux,spidev"; 
        compatible = "rohm,dh2228fv";
        reg = <0>;
        spi-max-frequency = <16000000>;
        spi-cs-high;
    };
};

Драйверы Qualcomm — нестабильны, и сокращённый compatible не экспортирует spidev, поэтому используем хак с dh2228fv.

С помощью утилиты spi-pipe в цикле смогли зафиксировать изменение напряжения на MOSI и CLK — достаточно чтобы понять, что что-то происходит. Предпочтительнее было бы использовать осциллограф или логический анализатор, но их не было под рукой.

Воодушевлённые успехами с питанием и USB, беспечно подключили дисплей к плате на включённом устройстве 🤦.

С дисплеем тут же что-то произошло, были уверены, что сломали. К счастью, ничего страшного не случилось. (Это происходит каждый раз при включении. Не уверены, нужно ли беспокоиться 😬.)

Убедившись, что ничего не дымит и не перегревается, начали разбираться с дисплеем.

После часа мучений с Python кодом (помощь AI), достигли нулевого прогресса. Слишком много точек отказа: преобразователь уровней, плохая маршрутизация, контакты и прочее.

Как обычно бывает (непонятно почему), драйвер Qualcomm плохо работает с CS (или не работает как надо). Повторили тот же тестовый скрипт, но на этот раз переключали CS пин вручную через libgpiod — и сработало!

Дисплей показывает тестовый паттерн в клетку
Дисплей показывает тестовый паттерн в клетку

Переделка проводки

Сразу разочаровались во всём. От тонкого провода для питания до хрупкого магнитного провода для сигналов и закрученной земли вокруг MOSI и CLK (что ничего не дало) — решили переделать всё.

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

Намного лучше, но без защиты маски и смолы
Намного лучше, но без защиты маски и смолы

Драйвер дисплея в ядре

Все предыдущие тесты использовали простые Python скрипты, но правильный путь — драйвер ядра. Есть несколько вариантов драйверов, выбрали sharp-drm-driver от ardangelo. Причины выбора DRM драйвера:

  • Позволяет использовать прямой вывод для видео (например, mpv)
    • С framebuffer тоже можно, но это нестабильно в 2026
  • Можно запустить X/Wayland
    • Может быть, даже десктопное окружение для прикола
  • Расширить драйвер для частичных обновлений
  • Остаётся интерфейс framebuffer

Сработало почти сразу. Немного поэкспериментировали с CS и его активным высоким уровнем по умолчанию.

Вероятно впервые в жизни не было проблем с компиляцией модуля ядра. Использовавшееся ядро — 6.12.1-msm8916 с поддержкой модулей. Там был файл .config, который взяли.

Потом скачали mainline Linux 6.12.1 и надеялись, что серьёзных изменений нет. Оказалось достаточно, и после нескольких патчей драйвера всё просто заработало.

Вот как выглядит финальное дерево устройств. Обратите внимание, CS логика обрабатывается самим драйвером дисплея.

spi@78b9000 {
    compatible = "qcom,spi-qup-v2.2.1";
    reg = <0x78b9000 0x500>;
    interrupts = <0x00 0x63 0x04>;
    clocks = <0x13 0x41 0x13 0x36>;
    clock-names = "core", "iface";
    dmas = <0x6d 0x0c 0x6d 0x0d>;
    dma-names = "tx", "rx";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <0x84>;
    pinctrl-1 = <0x85>;
    #address-cells = <0x01>;
    #size-cells = <0x00>;
    status = "okay";
    
    sharp_drm@0 {
        compatible = "sharp-drm";
        reg = <0>;
        spi-max-frequency = <4000000>;
        cs-gpios = <0x49 18 1>;
    };
};
На экране стандартный Linux prompt авторизации
На экране стандартный Linux prompt авторизации

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

Видео выглядит плохо с простым округлением цветов
Видео выглядит плохо с простым округлением цветов

Добавили пользовательское значение sysfs, которое позволяет включить dithering и выбрать алгоритм:

  • Atkinson для видео
  • Floyd-Steinberg для статичных изображений

Все исходные функции остались нетронутыми.

Big buck bunny выглядит отлично (проигрывается в стандартном MPV с DRM выводом)
Big buck bunny выглядит отлично (проигрывается в стандартном MPV с DRM выводом)

Подробнее о DRM драйвере и патчах можно найти на GitHub проекта.

Корпус. Часть 2

Когда всё работало и размеры стали известны, начали моделировать почти финальный корпус, который можно втиснуть в корпус клавиатуры без опасений сломать что-то.

Это было рискованно, потому что Apple не публикует расстояние USB-C разъёма внутри iPhone. Обошлись тем, что измерили стандартные кабели Apple (они плотно прилегают) и интерполировали.

Linux вход, работает из коробки после установки USB в режим host
Linux вход, работает из коробки после установки USB в режим host

Этот прототип оказался не особо полезен, но подтвердил правильность размеров USB разъёма и других элементов.

Две боковые стенки не напечатались, и при моделировании дисплей не был в наличии (кроме припаянного к девкит плате), поэтому крышка неполная.

Крышка подходит, но без слота для дисплея, корпус ещё не обрезан
Крышка подходит, но без слота для дисплея, корпус ещё не обрезан

Финальный корпус (пока)

Почти всё подошло идеально. Сначала быстро сбили прототип, скреплённый каптоновой лентой.

Финальный корпус, в ещё не обрезанном случае, скреплён каптоновой лентой
Финальный корпус, в ещё не обрезанном случае, скреплён каптоновой лентой

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

Волей обстоятельств решили склеить корпус горячим клеем вместо печати финальной версии с защёлками.

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

Корпус, склеенный горячим клеем
Корпус, склеенный горячим клеем

Большой момент — обрезание корпуса клавиатуры без инструментов. Результат предсказуемо некрасив. Старались обрезать меньше, чтобы потом обработать напильником.

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

Благодаря фотографии сверху вниз скрыли большую часть недостатков.

Итоговый результат
Итоговый результат

Забыли фотографировать сборку. Это то же самое, что раньше, плюс гибкая 4G антенна, припаянная к плате и приклеенная под дисплеем в верхней половине корпуса. Также установлена мини SIM.

Конфигурация батареи

Скопированные из Android дерево устройств содержали предустановленные конфигурации батареи и зарядки. Они более продвинутые, чем в mainline Linux.

Но всё равно ожидали, что будет просто. Главные вопросы — информация о батарее и энергопотребление адаптерной платы.

На уровне пользователя драйвер выдавал только напряжение батареи в микровольтах и флаг, заряжается ли. Логика батареи будет в приложении, так как не планируем модифицировать драйвер только ради процента батареи.

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

Что не сделано

Сон — главный нерешённый вопрос. Причина — недостаток тестирования в повседневной эксплуатации, особенно с включённым модемом, и отсутствие удобной кнопки питания.

Планируем решить это в ближайшем будущем, когда начнём использовать устройство для сообщений. Хотелось бы быстрый boot/shutdown.

Дополнительные методы ввода, например сенсорный экран или скролл-колесо, были бы отличным дополнением. Сенсор, в особенности, можно реализовать с минимальным пространством.

За ними идут звук или вибрация. Даже крошечный динамик на 8 кГц. На плате хватает места для усилителя и динамика в корпусе.

Косметические проблемы

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

Батарея держится на куске скотча, иначе выпадает без корпуса. Пока это не приоритет.

Большой нижний зазор, определяющий высоту корпуса, тоже можно укоротить, но потребуется батарея с тем же разъёмом и другими параметрами.

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

Наконец, крошечная часть дисплея закрыта корпусом. Как 1-2 пиксела по краям. Это исправится при следующей печати.

В целом очень доволены устройством, но небольшая доработка улучшила бы внешний вид. На фото выглядит нормально, в реальности хотелось бы получше.

Если интересно повторить или сделать что-то похожее, большинство материалов на GitHub проекта.

Будущее

Намеренно опустили софт в этом посте, потому что он совершенствуется по мере использования. И не люблю выпускать незаконченные проекты.

Быстрый превью софта
Быстрый превью софта

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

Текст полностью написан мною, человеком.
Связаться можно по адресу veggie_privacy_8y at icloud dot com