Возможно, вы уже сталкивались с одноразовыми электронными сигаретами, у которых есть яркий цветной ЖК-экран. Нет смысла говорить о том, насколько вредно для экологии, когда на свалки и просто на обочину дорог выкидываются устройства с литий-ионными батареями (причём нормальными!). Я сам не курю, но интересные вейпы собираю. И вот недавно смог заняться реверс-инжинирингом одной модели с ЖК-дисплеем. Об этом и расскажу.
Вейп, который я буду разбирать, называется Kraze HD7K. Но его также знают под другим названием: Raz TN9000. Внутри они одинаковы, отличие только в прошивке и логотипе.
Хочу предупредить, что разбирать одноразовые вейпы опасно. Работайте в перчатках, чтобы содержащая никотин жижа не навредила вам. Да и одеться лучше во что-то не очень ценное — отстирывать одежду от содержимого электронной сигареты — то ещё удовольствие. Литий-ионная батарея также может стать причиной ожогов или материального ущерба в случае короткого замыкания. Она обычно не оснащена защитой. Поэтому будьте внимательны и осторожны.
Разборка
У одноразовых вейпов нет отверстий для винтов, так что мне нужно было найти другой способ (неразрушающий) открыть вейп. Я заметил шов на нижней торцевой крышке и обнаружил, что, просунув в этот шов шлицевую отвёртку и аккуратно поддев её, можно отсоединить внутренние защёлки. Как только крышка была освобождена, выпал и подвижный «переключатель» подачи воздуха. Извлечение начинки вейпа было несложным, но потребовались плоскогубцы, чтобы вытащить её из-за используемого производителем соединения с натягом.
Вытащив начинку, я увидел, что вейпе используется литий‑ионный элемент 13450 на 650 mAh и большая ёмкость для жижи с губкой для поглощения жидкости и предотвращения её утечки. Я также заметил небольшой 9-контактный прямоугольный разъем, к которому подключалась чёрная логическая плата с микроконтроллером и ЖК‑экраном, а также зелёная плата питания с датчиком вдоха, разъёмом нагревательной катушки, разъёмом USB‑C и площадками для пайки аккумулятора.
Реверс-инжиниринг
Три основных компонента логической платы вейпа — это микроконтроллер, микросхема SPI‑NOR Flash и ЖК‑экран.
Микроконтроллер — это Nations Tech N32G031K8Q7–1, содержащий процессор Arm Cortex‑M0 с частотой 48 МГц, 64 КБ встроенной флэш‑памяти и 8 КБ SRAM; флэш‑память SPI — Giantech GT25Q80A‑UZLI объёмом 1 МБ.
ЖК‑дисплей неизвестного производителя, но имеет номер детали FXD096QQ08B‑F, и разрешение 80×160 пикселей.
Учитывая, что именно ЖК-дисплей делает этот вейп особенным, я решил разобраться, какой контроллер и распиновку он использует. У меня было предчувствие, что он будет использовать общий интерфейс и контроллер (чтобы снизить затраты), и я искал 0,96-дюймовый ЖК-дисплей с 13-контактным разъёмом FPC. Это привело меня к Smart Prototyping #102106, который использовал 13-контактный разъем и, похоже, имел совместимую распиновку. Вышеупомянутый ЖК-дисплей использует обычный контроллер ST7735S и управляется через 4-проводной интерфейс SPI.
Я посмотрел на других сайтах ЖК-дисплеи с такими же размерами и разъёмом, и все они использовали эту распиновку:
PIN | NAME | FUNCTION |
---|---|---|
1 | TP0/NC | Неиспользуемый (может использоваться для какого-либо сенсорного датчика?) |
2 | TP1/NC | Неиспользуемый (может быть, используется для какого-либо сенсорного датчика?) |
3 | SDIN | Передача данных SPI на ЖК-дисплей |
4 | SCLK | SPI clock |
5 | RS | Logic low = command, high = data |
6 | /RST | Сброс |
7 | /CS | Выбор микросхемы |
8 | GND | Источник питания/заземление |
9 | NC | Не подключен |
10 | VDD | Источник питания (3.3V) |
11 | LEDK | Катод светодиодной подсветки |
12 | LEDA | Анод светодиодной подсветки |
13 | GND | Источник питания/заземление |
Чтобы убедиться, что найденная в интернете распиновка действительно соответствует тому, что было в вейпе, я подключил данные, тактовый сигнал и другие управляющие линии к своему логическому анализатору DSLogic Plus и отслеживал трафик, пока вейп инициализировал дисплей. Быстрый взгляд на данные логического анализатора подтвердил правильность распиновки.
Чтобы окончательно убедиться в том, что у меня дисплей на базе ST7735S, я перепаял ЖК-дисплей на разъёмную плату TSSOP-to-DIP (что было неприятно, так как шаг выводов немного отличался, а выравнивание требовало большой точности) и использовал графическую библиотеку Adafruit и драйвер ST7735S на микроконтроллере Teensy 3.0. Это сработало! Ну, почти. Черно-белый текст работал идеально, а красный и синий каналы, похоже, менялись местами при использовании процедуры tft.initR(INITR_MINI160x80), и, похоже, я был не единственным, кто столкнулся с этой проблемой.
Я решил, что с идеей переиспользования одного дисплея покончено, и начал изучать, как работает сам вейп и как он выводит изображения. Первым шагом было изучение содержимого памяти SPI Flash объёмом 1 МБ, так как это довольно большой объем памяти для такого простого устройства. Я отпаял чип, установил его в переходник SSOP-to-DIP и сбросил его содержимое в файл с помощью универсального программатора MiniPro TL866CS.
Мои подозрения подтвердились, когда я проанализировал данные изображения, отправляемые на ЖК‑дисплей, на соответствие тому, что было в начале SPI Flash. Данные выглядели так, как будто они были в необработанном формате «RGB565», который представляет собой метод упаковки 16-битного пикселя в два байта данных. Термин «565» означает 5, 6 и 5 битовых значений, относящихся к красному, синему и зелёному каналам соответственно.
Поскольку изображение на экране использовало синий цвет в левом верхнем углу, это подтвердило мои выводы, поскольку в данных аналогичным образом были установлены только биты, относящиеся к синему каналу. А ещё были дополнительно описаны данные как «big-endian», как бы предполагая, что красные биты считываются раньше зелёных и синих.
Данные, по-видимому, передавались блоками по 4096 байт, которые, вероятно, выполнялись с использованием DMA (прямого доступа к памяти). Передача данных казалась слишком быстрой и регулярной, чтобы осуществляться только с помощью программного обеспечения. Позже это подтвердилось, когда я смог проанализировать объём оперативной памяти работающего вейпа.
Изучение Flash
После того как чип SPI Flash объёмом 1 МБ был распакован, пришло время изучить и его! Вооружившись электронной таблицей, шестнадцатеричным редактором, ImageMagick и онлайн‑утилитой RGB565 от Rinky‑Dink Electronics, пришло время начать изнурительный процесс распаковки всего содержимого SPI Flash.
Первым делом я попытался извлечь первый кадр изображения, размер которого, по моим расчётам, составлял 25 600 байт (80 x 160 x 2 байта на пиксель). Следующим шагом было использование ImageMagick для преобразования необработанных данных RGB565 в PNG для просмотра на компьютере:
magick convert -size 80x160 rgb565:<file>.bin <file>.png
Результат был похож на изображение, но все цвета были неправильными! Затем я попробовал утилиту RGB565 от Rinky‑Dink, в которой такой проблемы не было. Большинство моих попыток было выполнено с помощью ImageMagick, поскольку мне было легко вырезать и тестировать изображения разных размеров. (Позже я решил добавить тестовое изображение RGB, и, похоже, ImageMagick интерпретирует то, что должно быть RGB, как BRG).
Пытаясь преобразовать остальные изображения, я словно заглянул в «Матрицу», пытаясь разобраться в перемешанных данных, похожих на стереограммы (те, где нужно скосить глаза, чтобы увидеть изображение). Поскольку данные на Flash были сырыми и недокументированными, я понятия не имел, где начинается и заканчивается каждое изображение и каково их разрешение. Потребовалось много времени, чтобы разобраться во всём.
Одно за другим я каталогизировал каждое изображение и добавлял его в свою электронную таблицу, задокументировав адрес, длину, разрешение, к какой категории оно относится и было ли оно в виде последовательности изображений (по сути, анимации).
Так продолжалось до тех пор, пока я не заполнил более 95 % всего адресного пространства в 1 МБ. Я даже нашёл неиспользуемые анимации (по крайней мере, для версии, настроенной для моего региона). Там было немного пустого пространства, которое я некоторое время игнорировал... но, как оказалось, зря. Там хранились ещё какие-то скрытые данные (это был счётчик, который vape использовал для определения уровня жидкости в вейпе, которые нужно отобразить). Я не буду показывать всю таблицу, так как она занимает более 100 строк, но вот выдержка из неё.
INDEX (#) | OFFSET (HEX) | LENGTH (HEX) | FRAME H (PX) | FRAME V (PX) | CATEGORY | SEQ (#) |
0 | 0 | 6400 | 80 | 160 | Background | 0 |
1 | 6400 | 2880 | 72 | 72 | Battery Icon | 0 |
2 | 8C80 | 2880 | 72 | 72 | Battery Icon | 1 |
… | … | … | … | … | … | … |
19 | 33D00 | 6400 | 80 | 160 | Vaping Animation | 0 |
20 | 3A100 | 6400 | 80 | 160 | Vaping Animation | 1 |
… | … | … | … | … | … | … |
72 | D53E2 | 6400 | 80 | 160 | Plugin Background 3 | 0 |
73 | DB7E2 | E9A | 21 | 89 | Charger Logo Wipe | 0 |
74 | DC67C | E9A | 21 | 89 | Charger Logo Wipe | 1 |
… | … | … | … | … | … | … |
104 | F8000 | 4 | N/A | N/A | Total Vape Time x0.01s (LSB->MSB) | N/A |
105 | F8004 | 1 | N/A | N/A | Vape In Use Flag (0xBB) | N/A |
Создание инструментов
Когда весь SPI Flash был расписан, я (не без помощи ChatGPT) смог сделать пару пользовательских инструментов: сплиттер и переупаковщик Flash‑изображений. Я также использовал инструменты преобразования библиотеки Rinky‑Dink UTFT для замены данных изображения (т. е. для поддержки пользовательских тем). Представьте себе: поддержка пользовательских тем/скинов на вейпе — это реальность! Без помощи производителя!
Для чего я этим занялся
Возможно, вы спросите: зачем кому‑то понадобилось это делать? Не знаю. Я просто был очарован необычным одноразовым вейпом, и мне хотелось разобрать его и узнать, как он работает. Возможно, это послужит поводом для людей попытаться пополнить (и, следовательно, повторно использовать) свои одноразовые вейпы. А это значит, что их меньше будет на свалке или обочине дороги, и, честно говоря, для меня это достаточно веская причина.
Когда все инструменты были готовы, пришло время доказать, что эти вейпы можно переделать, и я придумал отличную ретро-эстетику, которая полностью отличается от оригинальной темы, но которую легко сделать с помощью простых инструментов редактирования изображений: Windows 95.
Реально Windows 95?
Ну... не совсем. Мне нужна была интересная и совершенно другая тема для пользовательского интерфейса, а я не настолько артистичен, чтобы создавать тему Doom (я пытался, но не смог придумать ничего, что бы работало в рамках ограничений оригинальной прошивки).
Используя лишь копию Windows NT 4.0 (по сути, корпоративную/профессиональную версию Windows 95), несколько виртуальных машин, инструменты для создания скриншотов и записи, а также Microsoft Paint, я смог создать пользовательский интерфейс, который точно воссоздаёт ностальгический интерфейс Windows 95 на крошечном пространстве 80×160.
Совет: если вам нужно часто перепрограммировать устройство, использующее внешнюю (последовательную) микросхему памяти, подумайте о том, чтобы добавить или сделать для неё разъём. Кроме того, вейпы, использующие 2-контактный микрофонный элемент для определения вдыхаемого воздуха, можно активировать замыканием контактов на землю с помощью кнопки или даже ёмкостной нагрузки пальцем. Если вейп требует наличия нагрузки, подойдёт небольшая галогенная лампочка.
Главный экран
Главный экран имеет классический бирюзовый фон с двумя «окнами», в которых отображаются уровни заряда батареи и вейп-жижи. В соответствии с механикой пользовательского интерфейса Windows, уровень жидкости отображается так, как будто это неактивное окно.
Уровень заряда батареи
Значок батареи, хотя и выглядит тривиально, требует много ручной работы. Начиная с Windows 95 и заканчивая XP, стандартные значки батареи в пользовательском интерфейсе Windows были очень ограниченными: полный, наполовину, почти пустой и пустой. Мне пришлось копировать ряды пикселей и выравнивать их по нужным уровням, а затем вручную дорисовывать пиксели там, где это требовалось. Я также использовал немного более детализированный набор иконок из Windows XP, который добавил немного больше яркости в левую часть значка, а не однотонного синего цвета. Хотя это несколько нарушает «чистоту» набора иконок, улучшение внешнего вида стоило того, чтобы пойти на компромисс.
Уровень жидкости в вейпе
Представление уровня жидкости было намного сложнее в плане реализации. Я подумывал о том, чтобы использовать значок корзины, но решил, что если расположить значки между пустым и полным, это будет выглядеть непонятно. Я также подумал о круговой диаграмме пурпурного/синего цветов, используемой для представления использования диска, но этот вариант был исключён из‑за аналогичных проблем с неоднозначностью (я знаю, что пурпурный цвет — это свободное пространство, а синий — используемое пространство, но это может быть недостаточно интуитивно понятным без какой‑либо легенды).
В конце концов я решил, что у меня достаточно места, чтобы сделать самое крошечное в мире окно Проводника с шестью маленькими значками размером 16×16. Я выбрал значки, которые можно увидеть в корневом каталоге C:: папка, пакетный файл, системный файл, приложение, файл настроек.ini и неизвестный файл (тот, на котором изображён маленький логотип Windows). По мере того как жидкость для вейпа заканчивается, иконка удаляется из окна. Как только микроконтроллер посчитает, что жидкость полностью исчерпана, мигающая иконка папки становится почти аналогом аналогичного явления на компьютерах Mac, если он не может найти операционную систему для загрузки.
Анимация зарядки
Это было ещё одной проблемой, в основном из-за ограниченного размера анимации: тридцать кадров размером 21× 89, расположенных не по центру на статичном фоне. Кроме того, прошивка задерживает воспроизведение последней анимации примерно на секунду, поэтому любой цикл анимации должен быть полностью остановлен на последнем кадре.
Отсутствие анимируемого пространства на экране означало, что диалог копирования файлов не подходит, а загрузочный экран Windows 95 также был не реализуем. В итоге я остановился на небольшом диалоговом окне Charging с анимированным курсором в виде песочных часов посередине.
Пришлось пойти на уступки: удвоить каждый кадр анимации курсора, а также потерять один угол и край песочных часов во время начальной анимации, поскольку первые несколько кадров песочных часов имеют ширину 22 пикселя. К счастью, это происходит достаточно быстро, чтобы быть не особенно заметным.
Я подумывал о том, чтобы наложить это диалоговое окно поверх «окон» основного пользовательского интерфейса, но обнаружил, что оно выглядит слишком перегруженным, поэтому выбрал единственное диалоговое окно на бирюзовом фоне.
Вейп-анимация
Этот этап, пожалуй, был самым забавным, но в то же время самым сложным в реализации. Я знал, что хочу использовать заставку для анимации парения, но какую именно? Мне нужна была заставка, которая была бы одновременно культовой и в то же время зацикленной. «Flying Windows» были конкурентом, но плохо масштабировались и, из‑за своей случайной природы, плохо зацикливались. По той же причине была пропущена трёхмерная «Flower Box» (и в любом случае, зацикливание раз в секунду выглядело бы не очень хорошо). 3D‑лабиринт, 3D‑текст и простые графические заставки тоже были недоступны… так что же осталось? В итоге осталась только одна заставка: Трубопровод.
3D Pipes — одна из самых известных заставок эпохи Windows 95, и хотя она генерировалась случайным образом, в ней были относительно чёткие переходы, что упрощало анимацию зацикливания. Она также хорошо масштабировалась как с точки зрения разрешения, так и по времени, но её было очень сложно записывать.
В конце концов я решил запустить версию Windows 95 на виртуальной машине Windows XP с настраиваемым размером экрана, максимально приближенным к соотношению сторон 1:2. После записи экрана мне нужно было выбрать последовательность, которая выглядела лучше всего, извлечь из видео 16 кадров, уменьшить их масштаб до 80×160, и только после этого я получил анимацию в виде зацикленной заставки, но результат стоил затраченных усилий.
Я продолжил ковыряться в вейпе и нашёл ещё кое-что интересное.
Секретный порт
Во время первоначальной разборки я заметил, что линии программирования SWD (Serial Wire Debug) микроконтроллера выведены на порт USB‑C, но необычным способом. Линии CC1/CC2 использовались не только в качестве обычных разъёмов 5.1k, позволяющих зарядным устройствам USB‑C распознавать вейп, но и в качестве разъёма для программирования. Мне пришлось изготовить специальный кабель для взаимодействия с ним, но я смог использовать свой Segger J‑Link для связи с микроконтроллером на месте. И вот он, бонус: прошивка полностью читаема, без какого‑либо шифрования или защиты от считывания!
От устройства, рассчитанного на несколько тысяч «затяжек», можно было бы ожидать какого-то контроля за уровнем жидкости или вейп-манометра. Но нет.
Время парения
В отличие от многих других одноразовых вейпов, данные счетчика жидкости хранятся в энергонезависимой памяти. Ни сброс, ни отключение питания не вернут счётчик в исходное состояние. Поначалу я не знал, где микроконтроллер хранит эту переменную, и сканирование внутренней флеш‑памяти и нескольких «дополнительных байт», доступных поверх обычной области прошивки, не выявило никаких изменений.
Только после того, как я решил проверить разницу во внешней SPI Flash между «затяжками», я заметил, что байты меняются в районе расположения 0xF8000, в море пустых/0xFF байтов. Стирание байтов из мест 0xF8000-0xF8004 привело к тому, что счётчик жидкости для вейпа сбросился и стал показывать, что устройство полное!
Поскольку датчики вдыхания вейпа обычно включают 10-секундный тайм‑аут, я решил запустить 10-секундную «затяжку» и заметил, что ячейка памяти обновилась до 0×3E8, что соответствует 1000 в десятичной системе счисления. Тогда было легко определить, что микроконтроллер подсчитывает время испарения с точностью до 0,01 секунды.
Работа с буфером
Поскольку у меня был доступ к памяти микроконтроллера почти в режиме реального времени, я решил экспериментально определить, где данные из SPI Flash копируются в оперативную память. Поскольку я уже знал, что микроконтроллер использует DMA для потоковой передачи данных изображения с флэш‑памяти SPI, я решил, что смогу заполнить все его содержимое понятным для человека текстом (в моем случае я выбрал последовательные копии сценария «Bee Movie») и наблюдать за изменением содержимого ОЗУ в Segger J‑Мем. Этот метод сработал, и стало очевидно, где в оперативной памяти находится буфер DMA!
Скрытые данные
От скуки и желания повторно протестировать анимацию зарядки во время разработки я быстро подключал и отключал питание к порту зарядки. После нескольких последовательных быстрых подключений/отключений я увидел, что на чёрном экране появился красный текст. На нем было написано «GV‑K23 0904V1», и попытка перехватить его была трудной, пока мне не пришла в голову идея использовать J‑Link Commander для остановки процессора сразу после старта.
Учитывая, что на экране был текст, но в дампе прошивки текстовых строк не наблюдалось, я решил, что это должно быть растровое изображение. И оказался прав. Оно хранилось в самом конце прошивки. Если определить, где оно находится в дампе, было просто, то попытаться восстановить его из дампа — нет. Из‑за лишней подложки в 0×00 байт (по сути, чёрной, если интерпретировать её как RGB565), которая не совпадала с 60-пиксельными/120-байтными строками, пришлось долго экспериментировать с размерами и смещениями изображения, пока я не получил разборчивую картинку. Даже сейчас я всё ещё не уверен, что захватил весь битмап.
Что было дальше
За время, прошедшее между запуском этого проекта по реверс‑инжинирингу/моддингу и публикацией этого поста в блоге, я опубликовал свою документацию и элементарные инструменты в своём профиле на GitHub в надежде заручиться большей поддержкой сообщества. Через несколько дней другой пользователь GitHub по имени «xbenkozx» смог применить и расширить мои исследования в области отображения и отладки внешней флэш‑памяти.
Он создал свои собственные инструменты для сброса флэш‑памяти и моддинга и даже создал инструмент для перепрошивки, требующий только доступ к SWD-ключу и специальный кабель. Пайка для поверхностного монтажа не требовалась. Он также провел более подробный анализ различных модификаций платы и некоторых важных несовместимостей, которые могли привести к повреждению из‑за перегрева нагревательной спирали вейпа. Зацените сами!
Надеюсь, что этот проект будет расширяться и дополняться. И мы найдём новые способы использования начинки одноразовых вейпов с экранами. А ещё очень жду какого-нибудь умельца, который сможет запустить на таком же вейпе Doom!
Спасибо за внимание!
Ваш Cloud4Y.