Сообщество - TECHNO BROTHER

TECHNO BROTHER

2 085 постов 13 667 подписчиков

Популярные теги в сообществе:

46

Коробочка, которая поёт

Дополнительные фото в источнике материала и комментариях.

Прошлая статья с попыткой собрать что-то наподобие MIDI-модуля, судя по всему, вам понравилась, поэтому держите описание ещё одного модуля — на сей раз голосового синтезатора — с несколько более запутанной историей, более сложным чипом и менее вырвиглазной дыркой вокруг экрана :-)

❯ Та девушка, которая поёт


Начнём со сжатого экскурса в историю.

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

Всё изменилось с началом исследовательского проекта в Университете имени Пумпеу Фабра в Испании, возглавленного Хидеки Кенмоти и профинансированного компанией Yamaha. Результаты этого исследования впоследствии вылились в коммерческую технологию под названием Vocaloid.

Первые вокалоиды звучали довольно примитивно. По сравнению со всеми прошлыми технологиями это был прорыв, но оглушительным успехом назвать их было сложно. Ровно до тех пор, пока компания Crypton Future Media в 2007 году, взяв за основу движок Vocaloid 2, не выпустила то, что впоследствии совершило фурор в концепции гострайтинга: Хацуне Мику.

Секретом успеха стал не только и не столько удачный голос, сколько то, что это по сути была «поп-звезда в коробке»: при покупке вы получали не только саму программу для синтеза голоса, но ещё и возможность использовать самого персонажа по Creative Commons CC-BY-NC.

Миловидный персонаж с новым для многих голосом лёг на благодатную почву активно развивавшегося тогда культурного сегмента японского интернета. Эпоху удачнее придумать было нельзя — активный бум User-Generated Media, параллельно с переползанием от Shockwave Flash к видеоконтенту на тогда ещё совсем молодом видеохостинге Nico-Nico Douga. Но главным плюсом было даже не это — ведь в отличие от настоящей, живой поп-звезды, Мику просто физически не могла отказаться спеть то, что вы ей там понаписали.

Это породило множество споров и дебатов, а также западающих в душу песен. Среди прочих отличился, например, deadballP — запаковывая в свои песни с лютейшим джазовым вайбом абсолютно неожиданные слова. Порой настолько неожиданные, что сам Nicovideo композитора неоднократно банил за «нарушение общественного порядка». Для примера, предлагаю читателям ознакомиться с его джазовой импровизацией с лейтмотивом «вот ты выпей молоко — сиськи будут о-го-го!» :-)

Бум user generated content подкрепился выходом игры Project DIVA на PSP, куда взяли самые популярные песни, разбавив 3D-графикой для видеоклипов, до кучи добавив возможность создавать свои клипы и карты из произвольных MP3-файлов.

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

А популярность была на внутреннем рынке просто невообразимая! Первая версия игры побила все топы продаж в свой сезон, и разлеталась как горячие пирожки.

Вот, например, было подразделение SEGA AM2 — то самое, которое подарило нам такую классику, как Shenmue, Out Run, Virtua Fighter или Daytona USA. Его на тот момент возглавлял Макото Осаки — и даже ему не удалось получить копию игры по внутренним каналам, пришлось покупать в обычном магазине.

Впрочем, игра ему нужна была не для того, чтобы отдохнуть в свободное время, а ради того, чтобы внутри своей команды изобразить порт её на движок Virtua Fighter.

И вот когда порт был уже готов, и Осаки было уже пошёл к продюсеру Уцуми Хироши с идеей сделать аркадную версию Project DIVA — другой сотрудник AM2, Ясуси Ямасита, предложил: «А почему бы нам эти наработки не использовать для создания живого концерта?»

Идея менеджменту понравилась, за каких-то два месяца кранчей они подготовили революционный ивент — Miku Fes 2009. (Обо всей хронологии — как-нибудь в другой раз :-) Концерт собрал аншлаг, начало было положено — Мику и по сей день выступает с концертами чуть ли не каждые полгода, собирая огромные залы.

(Один из самых любимых концертных треков последних лет. До сих пор не верится, что мне выпала честь слушать его прямо из первого ряда!)

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

❯ А внутри у неё — неонка^W процессор!


Возможность создавать записи, конечно, весьма хороша, но ведь музыку зачастую принято исполнять живьём. В наше время Ямаха выпускает что-то наподобие клавитары на базе своей платформы от обычных цифровых клавиатур:

(к сожалению, на выставке разобрать и сфоткать процессор и разводку платы не разрешают :-))

Однако среди всего многообразия способов извлечения звуков из Мику и Ко. примерно 11 лет назад проскакивал такой интересный прототип, который и запал мне в душу:

Известно о нём было только то, что рабочие название технологии — eVOCALOID (Embedded Vocaloid). Впоследствии вышел пресс-релиз, в котором анонсировали микросхему с его поддержкой — YMW820-S.

Судя по даташиту и параметрам, мне думается, что это какая-то переработка старых чипов из серии Mobile Audio — из тех, что стояли в корейских телефонах с синтезом голоса для уведомлений.

Самым известным устройством на базе этого чипа был Pocket Miku — стилофон от Gakken из серии журналов «Otona no Kagaku».

[via]

Сколько-то лет назад я покупал такой на Yahoo! Auctions, чтобы переслать другу, и стоил он очень недорого. Тогда я ещё удивился, что оно умеет работать как USB-MIDI синтезатор.

Поэтому, когда в этом году полез во всю эту MIDI-тему, то подумал — MIDI-to-USB-host адаптеры штука обыденная, а стилофон стоит копейки, можно бы что-то и сделать! Но тут меня ждал облом — ценник на эти штуки за последние пару лет вырос примерно на порядок с лишним.

Однако вдумчивое чтение пресс-релиза навело на ещё один вектор — некий шилд для ардуины eVY1 Shield, выпускавшийся компанией AIDES.

Более того, он выпускался и в виде готового устройства eVY1 BOX, но и дизайн и цена (¥27,500 = примерно $250) оставляли желать лучшего.

Зарядник от макбука словил кризис среднего возраста и решил податься в музыку

Поэтому я ухватил один из каким-то чудом оставшихся шести «сырых» модулей на амазоне безо всякой обвязки и решил скрафтить свою коробочку. И вот через пару дней у меня в руках самая дорогая микросхема в моей жизни:

С обратной стороны видим уже знакомый по Pocket Miku процессор GPEL3101A и SPI-флешку — то есть в теории оно должно суметь запустить и голос от Pocket Miku!

Быстренько раскидываем на макетке:

Для минимального включения достаточно лишь пары разъёмов и 10кОм-резистора с VBus на USB_SENSE

И да, оно поёт! Правда тихо и шумно, ведь единственный близкий к линейному выход — «наушниковый», использующий ЦАП внутри процессора GPEL, который и дудок, и жнец, и вообще — трындец, в смысле сочетает в себе полный ящик переферии от флеш-контроллеров и прочих USB до генератора вторичных напряжений питания и ЦАП/АЦП :-)

❯ Но, как говорится, есть нюанс...


Поёт этот модуль голосом VY1, также известным как MIZKI — собственный голос от Ямахи. Что, в принципе, логично, ведь Мику для этого модуля не лицензировали. Но хотел-то я чиптюн именно с Мику!

Копание в различных слоях интернета приводит к домашней страничке некоего Hummtaro, который переделал программу-программатор от eVY1 в утилиту для сохранения и прошивки ПЗУ целиком (а я уже за прищепкой на алик бежать хотел...). Также он заботливо забыл удалить оттуда копию ПЗУ своего Pocket Miku :-)
До кучи у него же на канале есть видео, где он наоборот прошивает голос VY1 в Pocket Miku.

Поэтому качаем тулзы, дампим ПЗУ и смотрим, сходства и отличия.

Кажется, где-то тут и начинаются голосовые сэмплы:

Сразу в глаза бросается большой блок данных на #40000h, который, судя по всему, и есть войсбанк. О, ну значит всё так просто! Удаляем из дампа ПЗУ родной блок данных, вставляем таковой из Pocket Miku — впритык, но поместилось. Прошиваем!

И первой композицией, которой мы насладимся в исполнении аппаратной Мику, будет 4'33" Джона Кейджа!

Пытаясь исполнить эту композицию на пианино, мой брат сломал обе ноги и руку:

Всё потому что на выходе ноль. Зеро. Ни-че-го. Ни откликов на ноты, ни даже на SysEx, который должен показать версию прошивки.

Значит, пришло время доставать драконоголовую змею и разбираться, что же там в прошивке. Но вот беда — есть бинарный блоб, который ни по какому адресу грузить непонятно, ни какая у него структура в целом неясно. Всё, что известно о процессоре — это то, что внутри ARMv7-ядро.

Эксперимента ради грузим прошивку от Pocket Miku. Ожидаемо, не работает, но на одном из пинов модуля, отмеченном как «UNUSED», появляется сигнал, подозрительно напоминающий UART. Цепляемся туда консолькой и, о чудо, там логи!

Фраза ptr=20040000 как бы намекает нам, что наш блоб прошивки в адресном пространстве процессора попадает на адрес #20000000h, но как убедиться, что это не отдельный раздел, и исполняемый код находится в том же блоке адресов?

Просто находим в блобе текст ptr=%x,data=%x,r_data_size=%x… и дописываем к нему .%08x%08x%08x %)

Всё развалилось, но не до конца:

После строки логов видим ещё два 32-битных числа — первое является чем-то непонятным, а вот второе — #20003a84h — явно адрес того кода, который вызвал функцию логирования.

Как это работает? Очень просто: каждый следующий аргумент для формирования строки через printf берётся со стека, поэтому если мы возьмём оттуда больше, чем было заложено разработчиком — например, добавив ещё токенов форматирования — то напечатается то, что было на стеке дальше. В нашем случае там был и адрес возврата, который указывал на инструкцию, следующую за той, которая вызвала печать этой строки.

Грузим файл в гидру по адресу #20000000h, прыгаем на смещение #3a84h, жмём Disassemble — и всё взрывается, как попкорн в микроволновке.

Дальше идёт полторы недели медитации над листингом с постоянными попытками перетащить куски инициализации DSP из прошивки Pocket Miku в прошивку eVY1, на случай если просто-напросто не выделяется достаточно памяти перед заливкой войсбанка в него. Но всё тщетно.

В какой-то момент от безысходности я начинаю просто рандомно обрезать войсбанк Мику и прошивать его — и замечаю, что если обрезать его по размеру родного голоса, но сохранив последние 4 байта как в оригинале, то частично всё начинает работать. Ну, пока не попытаешься воспроизвести фонему, которая в прошивку не попала :-)

Постепенно увеличивая блок данных, бинарным поиском прихожу к тому, что всё ломается на превышении прошивкой размера в #1CFE00h байт. Не, ну если бы хотя бы #1D0000h, я бы подумал, что контроллеру флешки не выделяется окно достаточного размера, а так это выглядит как какой-то глупый баг.

Беру родное ПЗУ, добавляю по подозрительному адресу 16 байт мусора — не работает, хотя ту часть памяти мы вообще читать не должны, ведь родной войсбанк существенно короче!

И почти тут же натыкаюсь на странный кусок кода, которого в прошивке Pocket Miku не было. Судя по всему, он проверяет, есть ли по этому самому подозрительному адресу какие-то данные, и если есть — инициализирует USB и вешает систему.

С учётом, что у нас одно ядро и один поток — где-то тут всё и закончится

Обидно, могли бы хоть рядом пасхалочку оставить :-(

Патчим эту проверку и ещё пару похожих, заложенных по разным адресам в разных функциях — и ура, играет, работает!

❯ Собираем в MIDI-модуль


Во-первых, раз уж у нас теперь есть два голоса, почему бы не иметь возможность их переключать? Благо, это делается очень просто — так как линия Chip Select у флешки инвертированная (активна при лог. «0»), то достаточно лишь двух элементов ИЛИ и одного инвертера, чтобы получить схему, переключающую флешки по необходимости:

Сигнал ALT_ROM выбирает, используется основное или дополнительное ПЗУ, а остальные идут напрямую на шину самого модуля:

После переключения ПЗУ просто дёргаем RESET у модуля и он после перезагрузки начнёт петь другим голосом.

Во-вторых повесим на GPIO модуля светодиоды, раз уж они были на родном шилде. Правда не то в даташите ошибка, не то я криво читал — выходы GPIO там Active Low, поэтому в моём включении они постоянно горят и периодически гаснут, а не наоборот.

Так делать не надо, делать надо не так!

Добавляем операционник для того, чтоб привести звук хоть немного к линейному уровню, обыкновенную схему MIDI-входа как в прошлый раз (заменяя каждый логический буфер на пару инверторов, благо их у нас тут в достатке), и ардуину с экранчиком чтобы ловить SysEx'ы переключения ПЗУ и заодно отображать находящиеся в данный момент в ОЗУ фонемы.

Охапку дров, и плов готов!

Раскидываем на макетке, раунд 2:

Кажется, стоило наконец уже зарегистрироваться на JLC PCB...

Без аудиофильских конденсаторов звучать будет точно так же, но радости никакой не принесёт

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

До кучи добавился USB-хаб, чтобы можно было прошивать оба чипа без разборки устройства

И вуаля, готово!

❯ Программирование музыки


Теперь нужно написать хотя бы один MIDI-файл, чтобы на этом всём слушать. Из всего обширного списка дополнительных команд нас интересуют только несколько:

F0 43 79 09 10 07 00 aa bb cc F7: отключение звука для определённых каналов:

  • Биты в позициях aa, bb, cc отключают воспроизведение части каналов

  • Например, паттерн 7E 7F 7F оставит только первый канал, что нам и нужно для использования модуля чисто для синтеза голоса без остальных MIDI-инструментов.


F0 43 79 09 01 01 00 F7: перезагружает модуль (например, после переключения ПЗУ).
F0 43 79 09 10 04 nn F7: выставляет режим работы GPIO:

  • nn = 00: выключить

  • nn = 01: ритм-визуализатор (вокал, бочка, средний, тарелки)

  • nn = 02: реакция на note-on/note-off в 1-4 каналах

  • nn = 03: визуализатор первого канала по нотам

  • nn = 04: ручная установка в виде битовой маски командой F0 43 79 09 03 00 xx F7


F0 43 79 09 00 50 10 dd dd ... F7: установить список фонем (слова песни):

  • Где dd: байты null-терминированной CSV-строки с фонемами в ASCII

  • Фонемы можно найти в документации по системе команд YMW820 на 34 странице

  • Их можно загружать и через NRPN-сообщения, но пока что обойдёмся без этого


В остальном по эффектам и прочему модуль по большому счёту совместим с системой команд Yamaha XG.

У первого MIDI-канала нельзя сменить инструмент — именно там и находится вокал. После задания фонем через SysEx-команду, каждое Note On событие в первом канале сдвигает указатель в буфере фонем на следующую, а после последней — перематывает его на начало. Проще говоря, одна нота — один слог, и так в цикле, пока не загрузишь новую строку в память :-)

Воспользуемся этим, чтобы отлавливать команды установки фонем ардуиной и отображать скроллер со «словами» на дисплее. До кучи добавим и пару своих команд:

F0 7B 7F F7: «жёсткий» сброс чипа, на случай если тот зависнет :-)
F0 7B 00 0r F7: выбор ПЗУ голоса:

  • r=0 — основной, r=1 — вторичный


F0 7B 01 F7: установить текущий голос по умолчанию.
F0 7B 02 tt dd dd... 00 [xx xx xx ... 00] F7: показать сообщение на экране:

  • tt — время в секундах

  • dd dd… 00 — нуль-терминированная верхняя строка экрана

  • xx xx… 00 — опциональная нуль-терминированная нижняя строка экрана


После пары часов мучений (и двух-трёх месяцев спровоцированной ими прокрастинации) выяснилось, что ноты, написанные в стиле караоке, уж совсем не совпадают с количеством фонем в песне, а выравнивать их, не видя, на каком месте строка ломается, практически невозможно.

Поэтому берём поллитру, вспоминаем MFC и патчим такой замечательный редактор, как Sekaiju, на отрисовку слогов под нотами:

Если разваливается, то хотя бы сразу видно, где

Дорисовываем остаток совы… В смысле, дописываем аранжировку в Sekaiju, и потом доводим эффектами в Yamaha XGWorks. Попутно я докинул ещё пару партий в формате AYYMIDI (из прошлой статьи), написав их в ProTracker.

Помимо прочего, нашлось ещё несколько подводных камней:

Как и обещал производитель, поёт NSX-1 ну очень медленно. Даже в такой медленной песне есть места, где приходилось делать рокировочки таймингов десятки раз, чтобы получить более-менее вменяемый саунд. Какой-нибудь Intense Voice уж точно по битрейту не пролезет, разве что интерливом в несколько чипов через управляемый микшер :-)

По ощущениям, использование NSX-1 для всех инструментов ещё сильнее замедляет воспроизведение голоса, поэтому лучше использовать его чисто для вокала, даром что по качеству звучания его даже Yamaha MU50 уделывает с лихвой. Сделать это можно последовательностью команд:

  • F0 43 79 09 10 07 00 7E 7F 7F F7: NSX Channel Mute, оставляем только 1 канал.

  • GM Volume Ch1 = 127: выставляем громкость вокала на максимум.

  • XG Volume Ch1 = 0: отключаем первый канал на MU50. За счёт того, что NSX игнорирует многие XG команды на первом канале, его громкость останется на 127.


Из-за того, что Vendor ID у NSX и у серии MU совпадает, использовать тот же MU50 как RS232-MIDI интерфейс не получилось — слишком сильно задерживаются SysEx'ы задания фонем и слова начинают съезжать относительно нот.

Воспроизведение отдельных нот напоминает скорее чтение текста, чем пение, поэтому Note Off каждой ноты вокала лучше ставить чуть дальше, чем Note On следующей за ней:

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

  • Однако, эту особенность можно использовать для реализации удвоения (っ、напр. в демо-песне для этой статьи слово わらった [waratta] чаще всего записано путём двух «наложенных» нот [wa] [ra], затем Note Off второй, и только после этого «впритык» Note On для [ta].

  • Ни сам чип, ни генератор команд eVo Phonetic не поддерживают удвоение согласных, поэтому единственный другой способ — продублировать слог целиком, но обрезать ноту так, чтобы чип «не успел» дойти до гласной. Иногда это работает, но часто звучит странно и упирается в проблему скорости из п. 1.


Ударения как такового в японском языке нет, но лучше всё равно добавлять экспрессии и подрезать редуцирующиеся звуки через Velocity.

Для конвертации файлов под аппаратные плееры многие пользуются программной MIDI Formatter — оказалось, она перемешивает местами каналы, поэтому для композиций написанных под NSX-1 её использовать нельзя. Впрочем, конвертировать файлы в SMF0 можно через диалог «Сохранить как» в программе XGWorks.

❯ Демо!


Не буду погружать во все остальные тонкости написания MIDI-аранжировок, ведь статья получилась и так слишком длинной — лучше дам послушать итоговый результат :-)

Первым делом, конечно же, была запрограммирована классика жанра: malo — Hajimete no Oto (The First Sound)

❯ И второе!



Казалось бы, одним треком можно было и ограничиться — голос слышно, вроде как будто даже поёт, всё хорошо. Но хотелось всё-таки выжать из конструкции побольше музыкальности, а не просто мелодию в трёх дорожках… Поэтому статья была отложена больше чем на полгода, пока я допишу аранжировку той самой песни Shabon — как дань уважения всей этой культуре, на удивление тёплоламповому вопреки всем событиям 2020 году, да и в целом, потому что душа просила что-то поинтереснее :-)

Впрочем, чип оказался всё же слишком сложным, поэтому звучания прямо один к одному не вышло.

❯ А компот?


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

Если вдруг кто-то имеет такой чип и захочет повторить конструкцию — могу, конечно, причесать всё и расшарить, но пока что вот так ¯\_(ツ)_/¯

  • Прошивка — тут

  • Схема, почти соответствующая правде — тут

  • Миди-файлы для тестов и самих треков — тут

  • Больше интересных статей в нашем блоге на Хабре. Недорогие сервера для ваших проектов — здесь.

Показать полностью 21 4
5672

Сам себе Linux смартфон: Как я выкинул Android и написал свою прошивку с нуля



К огромному сожалению, старые смартфоны всё чаще и чаще находят своё пристанище в мусорном баке. К прошлым, надежным «друзьям» действует исключительно потребительское отношение — чуть устарел и сразу выкинули, словно это ненужный мусор. И ведь люди даже не хотят попытаться придумать какое-либо применение гаджетам прошлых лет! Отчасти, это вина корпораций — Google намеренно тормозит и добивает довольно шустрые девайсы. Отчасти — вина программистов, которые преследуют исключительно бизнес-задачи и не думают об оптимизации приложений совсем. В один день я почувствовал себя Тайлером Дёрденом от мира IT и решил бросить вызов проприетарщине: написать свою прошивку для уже существующего смартфона с нуля. А дабы задачка была ещё интереснее, я выбрал очень распространенную и дешевую модель из 2012 года — Fly IQ245 (цена на барахолках — 200-300 рублей). Кроме того, у этого телефона есть сразу несколько внешних шин, к которым можно подключить компьютер или микроконтроллер, что даёт возможность использовать его в качестве ультрадешевого одноплатника для DIY-проектов. Получилось ли у меня реализовать свои хотелки? Читайте в статье!

Мотивация


Честно сказать, идея попытаться реализовать свою прошивку мне пришла ещё давно. Однако, дабы не завлекать опытного читателя кликбейтом, я сразу поясню, в чём заключается «прошивка с нуля»:

  1. Мы всё ещё используем Linux: в качестве ядра мы продолжаем использовать образ Linux, предоставленный нам производителем. Написание прошивки полностью с нуля заняло бы очень много времени (особенно без схемы на устройство). Однако, мы вообще не загружаем Android никаким образом.

  2. Мы не используем библиотеки AOSP: наша прошивка без необходимости не использует никаких библиотек уже имеющегося образа Android. Вся работа с железом происходит с помощью низкоуровневого API Linux. Это значит, что отрисовка графики, звук, управление ресурсами и питанием ложится полностью на нас.

  3. Прошивка может запускать только нативные программы: да, это тоже камень в сторону Android. Изначально, наша прошивка умеет запускать только нативные программы, написанные на C. Причём она экспортирует собственное C API — дабы приложения могли использовать всю мощь нашего смартфона в виде простого и понятного набора методов.



Проектов по выкидыванию Android из, собственно, Android-смартфонов как минимум несколько: UBPorts — бывший Ubuntu Touch, FireFox OS и его наследник Kai OS и конечно же, postmarketOS. Отчасти можно сюда отнести и Sailfish OS — но там образы имеются в основном на смартфоны от Sony. Все эти проекты объединяет сложность портирования и невозможность их завести на устройствах без исходного кода ядра. Даже если у вас есть исходный код ядра, но, например, устройство использует ядро 2.6 — навряд-ли вы сможете завести современный дистрибутив на нём.

Другой вопрос в том, что можно использовать полу-baremetal подход, когда от Linux берется практически минимальный функционал. Всё, что мы имеем — busybox, libc и низкоуровневый доступ к железу, благодаря API самого ядра. Как под это всё программировать — я рассказывал впрошлойстатье. Этот же подход мы будем использовать и сейчас — как иллюстрация реального применения подобного способа.

Итак, что наша прошивка должна уметь:

  • Отрисовывать произвольную графику: графическая подсистема нашей прошивки должна работать с фиксированным форматом пикселя, уметь загружать прозрачные и непрозрачные изображения, отрисовывать картинки с альфа-блендингом и т. п.

  • Уметь звонить и работать с модемом: общение с модемом происходит посредством AT-команд — общепринятого в индустрии стандарта. Однако в случае нашего устройства, есть м-а-а-а-ленький нюанс, о котором я расскажу позже.

  • Иметь механизм приложений: мы ведь не будем хардкодить все «экраны» в прошивке в виде кучи стейтов, верно? Для этого у нас должен быть простой и понятный механизм слинкованных с прошивкой приложений.

  • Обрабатывать ввод: обработка тачскрина и жестов — это задача подсистемы ввода.

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


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

Аппаратная часть


В качестве смартфона для нашего проекта, я выбрал популярную бюджетную модель из 2012 года — Fly IQ245 Wizard. Это простенький китайский смартфон, который работал на базе популярного в прошлом 2G-чипсета: MediaTek MT6573, да и стоил около 2х тысяч рублей новым. Однако вот в чём суть: мне удалось заставить работать «медиатековский» модем и даже позвонить с него на свой основной телефон, но… только ввод и вывод данных из звукового тракта модема происходит через звуковую подсистему Android — к которой доступа у нас нет!



Именно поэтому, мы идём на очень хитрый и занимательный костыль: мы распаяем внешний модем сами! В качестве радиомодуля у нас выступит модуль SIM800 от компании SIMCOM. И даже он очень близок к нашему смартфону в аппаратном плане: ведь в основе этого модуля лежит популярнейший чипсет из кнопочников тех лет: MediaTek MT6261D. Преимущество SIM800 в его цене — он стоит пару сотен рублей, так что по карману выбор модема не влияет.


На весу паять крайне неудобно. В финальном варианте перепаяю нормально.

Но как его подключать? SIM800 общается с другими устройствами посредством протокола UART — универсальный асинхронный приемо-передатчик. И вот тут мы включаем смекалочку. Разбираем устройство и видим то, что я пытаюсь долгое время донести до моих читателей — аж два канала UART: один практически посередине, второй справа. Нам нужны пятачки TXD4 и RXD4:



Обычно на этот канал UART летят логи ядра, которые можно без проблем отключить минорной правкой U-Boot в HEX-редакторе. Впрочем, модем никак не реагирует на «мусор» из консоли и просто отвечает ошибками — хватит лишь очистить буфер сообщений для того, чтобы все работало нормально. Подпаиваемся к UART'у с помощью преобразователя — у меня оным выступает ESP32 с выпаянным чипом.


Увидели логи? Замечательно, пора попытаться что-то отправить на ПК и с ПК. UART работают без тактовых сигналов и зависит исключительно от старт/стоп битов и бодрейта, который на устройствах MediaTek равен 921600. TXD4 и RXD4 обнаруживаются в системе на консоли/dev/ttyMT3. Пробуем что-то отправить: всё работает!



Вот теперь-то можно подключить наш внешний модем и попытаться пообщаться с ним, отправив тестовую командуAT. Модем отвечаетOK! На этот раз я работаю с смартфоном из режимаFactory mode— практически тоже самое, что и режим recovery, но позволяющий, например, получить доступ к камере устройства. Простая и понятная схема, поясняющая что и куда подключать:



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

Заставляем смартфон запускать нашу прошивку


На этот раз я решил загружать смартфон из режима рекавери. Однако никто не мешает в будущем просто прошить раздел recovery вместо boot и получить прямую загрузку прямо в нашу прошивку. Время такой загрузки будет заниматься ~3-4 секунды с холодного старта. Очень даже ничего.



Я взял уже готовый образ TWRP для своего смартфона и пропатчил его, дабы сам рекавери не мешал своим интерфейсом. Для этого я распаковал образ recovery.img с помощью MtkImgTools и убрал в init.rc запуск службы /sbin/recovery. После этого, я залил прошивку обратно на устройство и получил подобную свободу действий — консоль через USB и чистый холст в виде смартфона! Старые смартфоны на чипсетах MediaTek шьются через USB только после замыкания тест-поинта — на моем аппарате его местонахождение очевидно. Замыкаем контакты между собой, подключаем смартфон без АКБ к ПК и ждем прошивки:



Теперь можно деплоить программы! Важный нюанс: в отличии от Makefile из прошлой статьи, для Android 2.3 параметр -fPIE нужно убрать — иначе динамический линкер (/sbin/linker) будет вылетать в segmentation fault.

❯ Графическая подсистема


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



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

В случае с этим устройством (и большинством старых устройств), формат пикселя оказался RGB565 — т. е. 5 бит красный, 6 бит синий, 5 бит зеленый. Конвертация форматов пикселей всегда была занозой в заднице для программных рендереров, поскольку занимает дополнительное время, которое обратно зависимо от размера дисплея. Изначально я решил выделить буфер в том же формате, что и фреймбуфер, но затем решил сделать классический и самый портативный формат — RGB888 (24х-битный цвет), а при копировании кадра на экран, на лету делать преобразования цвета:

Очень важный нюанс, который я не упомянул в предыдущей статье: на устройствах прошлых лет для обновления фреймбуфера необходимо послать структуру var_screeninfo, где хотя бы что-то изменено, иначе никаких изменений мы не увидим. Этот же костыль используется в родном recovery для отрисовки, а судя по исходникам драйвера fb, «правильный» способ обновить экран — послать драйверу ioctl (который я пока что не пробовал).

После того, как я смог управлять дисплеем, я решил загрузить и отобразить какую-нибудь картинку. Пусть это будут обои для нашей прошивки:

Загрузчик TGA сильно не поменялся: я таскаю его в неизменном виде из проекта в проект. Он поддерживает любые форматы пикселя, кроме палитровых, но я его искусственн ограничиваю на RGB888 и RGBA8888 — для поддержки обычных картинок и картинок с альфа-каналом. После этого, я написал не очень шустрые, но достаточно универсальные методы для отрисовки картинок. Для больших участков кода, я буду использовать pastebin, поскольку на Пикабу до сих пор не добавили ни подсветки синтаксиса, не нормальный перенос форматирования табов :(

__inline void __ClipPrimitive(CFrameBuffer* fbDesc, int* dw, int* dh){ if - Pastebin.com

PutPixel желательно заинлайнить в будущем. В целом, сама отрисовка работает достаточно быстро, но поскольку рендеринг выполняется на ЦПУ — рано или поздно мы упремся в количество картинок на экране. Есть некоторые оптимизации: например, непрозрачные картинки можно просто коприовать сканлайнами прямо в задний буфер.

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

Формат примитивнейший:

1 байт говорит нам о размере шрифта и далее идут 255 изображений символов. Да, это не очень эффективно т.к попадают пустые символы из ASCII-таблицы, но в будущем это можно поправить.

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

Теперь у нас есть отображение картинок и текста! Что с этим можно сделать?

❯ Обработка ввода

Пока что здесь не хватает обработки «хардварных» кнопок — домой, меню, назад и т. п. Однако в будущем это всё можно реализовать!

❯ Анимация


Не забыл я и про анимации. Ну кому с такими ресурсами нужен неанимированный топорный интерфейс? Пусть лучше будет анимированный, пусть и примитивный!

Аниматор напоминает оный из ранних версий Android: он имеет фиксированный набор свойств, которые умеет интерполировать в промежутках определенного времени. Если простыми словами: то он оперирует линейными отрезками времени a и b, в промежутке которых мы имеем значение «прогресса» — которое даёт нам результат от 0.0f (начало анимации) до 1.0f (конец анимации). Пока время тикает до необходимого интервала (duration), аниматор интерполирует заранее назначенные ему поля до нужных значений.

Именно так и получается плавность! Похожим образом реализованы анимационные системы во многих играх и мобильных ОС, только там они гораздо более комплексны: есть сериализация/десериализация из файлов, поддержка кейфреймов (несколько последовательных состояний на одном промежутке времени), поддержка кастомных свойств и т. п.


❯ Модем


Как я уже говорил раннее, работа с модемом происходит посредством AT-команд. Лучше всего обрабатывать ввод-вывод модема из отдельного потока, поскольку он может отвечать довольно медленно и тормозить UI-поток основной программы, вызывая лаги. В SIM800 уже реализован весь GSM-стек, в том числе декодирование и вывод звука через встроенный усилитель с фильтром — остается только подключить динамики и микрофон от нашего телефона. Пока что я подсобрал аудиотракт на том, что было под рукой — микрофон от нерабочего смартфона и динамик от планшета, но для проверки этого хватает:

Важный нюанс: по умолчанию, tty-устройства в Linux работают по терминальному принципу — т. е. дробят транзакции по символу окончания строки (\n), имеют ограниченный буфер и т. д. Для нормальной работы в условиях модема — когда фактически длина ответа неизвестна, а в сам ответ могут «вклиниваться» Unsolicited-команды (своеобразные флаги о состоянии от модема, которые могут прийти в произвольное время — т. е. при входящем звонке, модем начнёт флудить RING в терминал), необходимо иметь возможность точно прочитать весь буфер до конца и парсить данные «по месту». Для этого используется raw-режим терминала:

tcgetattr(modemFd, &tio);

tio.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

tio.c_oflag &= ~(OPOST);

tio.c_cflag |= (CS8);

tio.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

tcsetattr(modemFd, TCSAFLUSH, &tio);

После чего можно запросить состояние модема:

И продолжить работу дальше. После этого, можно переходить к реализации самой прослойки между модемом и вашей программой:

void CModem::Dial(char* number){ if(strlen(number) > 32) return; cha - Pastebin.com

Пытаемся позвонить с помощью метода Dial и видим, что всё работает! Это очень круто! А теперь, конечно же, самое время переходить к реализации того, чего вы ждали — пользовательского интерфейса!

❯ Главный экран


К выбору концепции для интерфейса, я поступил максимально просто — «слизал» дизайн первых версий iOS. Как по мне, это одни из самых красивых версий iOS вообще — все эти приятные градиенты и переливания. Конечно, я не так крут, как инженеры Apple, да и мощного UI-фреймворка у меня пока что нет, поэтому я приступил к реализации с «минимальным» функционалом.



Начал я с разделения главного экрана на модули и продумывания архитектуры основного «лаунчера». У нас есть статусбар, который рисуется поверх всех приложений, полка с приложениями — AppDrawer и сами экраны приложений, унаследованные от суперкласса CScreen.

class CScreen{protected: CAnimator* windowAnimator;public: CScreen(); - Pastebin.com

На данный момент, отрисовка достаточно примитивная: сначала рисуются фоновые обои, затем, если нет никаких активных экранов — AppDrawer и в самом конце рисуется статусбар и всевозможные оверлеи.

void CLauncher::DrawAppDrawer(){ for(int i = 0; i < sizeof(Apps) / sizeof(CA - Pastebin.com

Практически сразу я решил обкатать анимационную «систему» и добавить первые анимашки — выезжающий статусбар и анимация а-ля айфон:

animator = new CAnimator();

animator->SetTranslation(0, -imFiller->Height, 0, 0);

animator->Run();

Выглядит симпатичненько. Если я смогу поднять хардварный GLES, то это получится сделать в разы плавнее и шустрее — не хуже айфонов тех лет! Реализация самого статусбара примитивненькая, но вполне рабочая:

gLauncher->Graphics->DrawImage(imFiller, animator->X, animator->Y);

gLauncher->Graphics->DrawImage(imBattery[(int)gLauncher->PowerManager->GetBatteryLevel()], imFiller->Width - imBattery[0]->Width - 5, animator->Y + 5);

char timeFmt[64];

time_t _time = time(0);

tm* _localTime = localtime(&_time);

strftime((char*)&timeFmt,

sizeof(timeFmt), "%R", _localTime);

gLauncher->Graphics->DrawString(gLauncher->Font, (char*)&timeFmt, 0, 0);

Кроме этого, я сразу же реализовал предварительный механизм приложений в системе — пока что они слинкованы статически с основным лаунчером. Для этого есть структура CAppDesc, которая содержит минимально-необходимую информацию для показа информации о приложении и фабрику для создания его основного экрана.

Обратите внимание на удобство примененного подхода Immediate GUI. Нам понадобился новый элемент интерфейса, который описывает кнопку номеронабирателя? Мы просто реализовываем ещё один метод, который берет за основу стандартную кнопку и дорисовывает к ней текст. Всё крайне просто и понятно, хотя на данный момент слишком захардкожено. :)

❯ Звоним!


Пришло время совершить первый звонок с нашей по настоящему кастомной прошивки. Набираем номерок и…



Да, всё работает и мы без проблем можем дозвониться :)

❯ Заключение


Конечно же, это далеко не весь функционал, необходимый любому современному смартфону. Здесь много чего еще нужно реализовать хотя бы для соответствия уровню бюджетных кнопочных телефонов: телефонную книгу, поддержку СМС/ММС, мультимедийный функционал с играми. Однако начало уже положено и самая необходимая часть модулей реализована. Этот проект очень занимательный для меня и я горд, что смог не на словах, а на деле показать вам, моим читателям, возможности моддинга совершенно NoName-устройств, без каких либо опознавательных знаков…

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

Показать полностью 23 2
2080

Рекомендую книгу Open Circuits

Хочу рекомендовать получить эстетическое удовольствие с книгой Open Circuits за авторством Eric Schlaepfer и Windell H. Oskay

Книга посвящена внутреннему устройству радиокомпонентов. Фотографии в разрезе, иногда рисунки поясняющие структуру, и немного текста — пояснения. К сожалению текст на английском языке, но если вы его не знаете — то потеряете не много. Основное достоинство книги — сочные фотографии.

Я тоже публиковал фотографии различной распиленной техники (но покрупнее) поэтому могу только восхищаться объемом и качеством работ. В конце книги есть немного о процессе съемок — распил, очистка. Мелкие предметы заливались и пилились в прозрачной смоле. Для макрофотографии производился стекинг (склейка стопки изображений с фокусом на разной глубине в программе, чтобы увеличить глубину резкости).

Хоть многое из показанного я уже видел, ибо за много лет творчества из любопытства многое расковырял. Особенно меня удивила эта фото:

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

Добиваться такой же звенящей резкости я пока не умею, впрочем и оборудование у меня сильно проще.

У книги есть сайт https://opencircuitsbook.com/. Второй автор книги Windell Oskay персонаж известный, я с ним столкнулся в интернете еще году наверное в 2008, у него сайт https://www.evilmadscientist.com/. Тогда у него на сайте был раздел с ссылками, и я прислал ссылку на мой сайт licrym.org и он ее добавил) Он делал проекты ЧПУ рисовалок по яйцам, рисовалок акварелью.

Некоторые объекты разрезаны в тонком слое. На фото динамик.

Книгу рекомендую к пролистыванию. Учитывая невозможность ее официального приобретения (ни оплатить, ни заказать. Только через посредников) я без зазрения совести опубликую pdf файл с ней у себя в телеграмм канале. Впрочем найти ее в электронном виде в рунете не сложно, достаточно погуглить название: "Open Circuits: The Inner Beauty of Electronic Components".

Показать полностью 12
8

Представлен российский «аналог iPhone» — смартфон Р-ФОН

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

И всё же это не значит, что не нужно пытаться. Нужно, ещё как нужно.

НО!!! Жирнющее НО!

Я очень надеюсь, что всё же инфа об этом фейк, потому что... Р-ФОН? Серьёзно? Ну то есть, да, я прекрасно понимаю, что у нас теперь есть кейс "Вкусно и точка", который наглядно демонстрирует, что с неймингом у нас беда, но я думал, что после этого меня уже ничто не удивит. Р-ФОН, хоспаде... Р-ФОН!

Показать полностью 6
84

Пожарная сигнализация из московского метро

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

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

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

Итак, сегодня поговорим про центральный блок управления системы пожаротушения «Игла». Узнаем, как он устроен и работает и можно ли ещё где-то его использовать. Традиционно будет много интересного.

❯ Суть такова


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

И вот в конце девяностых появилась «Игла» — автоматическая система обнаружения и тушения пожара, предназначенная специально для поездов метро и другого рельсового транспорта. Устройство оказалось достаточно удачным, более новые версии повсеместно эксплуатируются и сейчас. Именно об этой штуке сегодня и пойдёт речь.

❯ Из чего состоит «Игла»?


Увы, ко мне в руки попал только основной блок, полного комплекта нет, да и достать его вряд ли удастся. Поэтому пока что посмотрим фотографии с сайта компании Эпотос-К, производившей эти устройства (да, версия уже старая, но фотографии таки остались). Попутно разберёмся, как оно вообще работает.
В отличие от ППКОП с его зонами и шлейфами, устройство этой системы совершенно иное.

Итак, «Игла» состоит из трёх основных компонентов — центрального блока контроля и индикации (ЦБКИ), промежуточного центрального блока контроля (ПЦБК) и локальных блоков контроля (ЛБК).

ЦБКИ. Таких устройств два на весь поезд — по одному в передней и задней кабине. Данный блок служит пультом управления всей системой, в нём же хранятся настройки.

ПЦБК. Он устанавливается в каждом защищаемом вагоне. Задачей его является сбор данных с локальных блоков контроля и отслеживание их состояния.

ЛБК. Такой стоит для каждого защищаемого отсека. Это своего рода оконечное устройство, к нему подключаются датчики (дымовые или тепловые) и порошковый огнетушитель. Подобная система обладает множеством достоинств: отсутствие кучи проводов от датчиков, высокая надёжность, возможность легко определить место возгорания с точностью до вагона и отсека в нём. Также контролируется и температура колёсных букс, при перегреве выдаётся соответствующее оповещение и делается запись в журнале.

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



В некоторые отсеки ставятся обычные самосрабатывающие порошковые огнетушители (ОСП). Он представляет собой стеклянный баллон, который при повышении температуры взрывается, разбрасывая огнетушащий состав.

❯ Обзор оборудования


Так получилось, что ко мне в руки попал ЦБКИ от одной из первых версий «Иглы».

А вот и он. Девайс пережил немало жизненных потрясений и, наверное, сам успел стать свидетелем пожара, но всё ещё исправно работает. Увы, это не грязь, а облупившееся покрытие, если его оттереть, то пропадут все надписи. На передней части индикатор (разумеется, это ВЛИ), четыре клавиши со светодиодами, два индикатора (красный «Пожар» и жёлтый «Неисправность»). Отверстие слева в углу предназначено для разъёма для подключения устройства к компьютеру. Его никто не вырывал, в этой модификации блока он расположен на обратной стороне корпуса. В некоторых же — на лицевой стороне.

Обратная сторона. Четыре крепёжные шпильки и два разъёма. Большой, РМГ22Б10Ш1В1, служит для питания от бортовой сети и связи с ПЦБК, маленький, РС4ТВ — для конфигурации устройства при помощи ПК.

❯ Почему его не получится запустить?


Увы, посмотреть на систему в работе пока что не выйдет ввиду отсутствия у меня ПЦБК и ЛБК. Для минимального комплекта необходимо раздобыть хотя бы по одному блоку. Протокол обмена данными между ЦБКИ и ПЦБК также неизвестен.

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

❯ Внутренности


Что мы делаем, когда нам в руки попадает очередная интересная железка? Правильно, мы её разбираем.

Плата управления. На ней МК ATMega128A, Flash, КРЕНка, реле, чип RTC DS1307 с батарейкой. Большой синий блок — преобразователь питания, понижающий напряжение бортовой сети поезда до требуемого.

Снимем плату. На обратной её стороне почти ничего интересного — разъёмы и маленькая плата с кнопками.

Индикатор. В отличие от САВПЭ, где использовался тяжеленный блок индикации на базе отечественного ВЛИ, тут стоит готовый импортный модуль со своими цепями питания на борту. Перемычки намекают на управление по UART.

А вот и сам ВЛИ, две строки по десять символов. Он похож на экземпляры, используемые в дисплеях покупателя, но несколько меньше по размеру.

Интересно назначение этой маленькой платы. На ней распаян USB-UART FTDI FT232RL. Получается, разъём РС4ТВ, к которому идут отсюда провода, — USB.

Распиновка его оказалась вот такая:

  1. 5V

  2. D+

  3. D-

  4. GND

Под этой платой находится развязка интерфейса связи с ПЦБК, выполненная на четырёх транзисторных оптопарах. Любопытно, что две из них не задействованы. Подключены они через резисторы к портам МК. Скорее всего, это программная реализация UART, но выяснить точнее пока что невозможно — нечего подключить из периферии, чтобы потыкать обмен логическим анализатором.

❯ Первый запуск


Итак, как удалось выяснить, распиновка основного разъёма оказалась такая:

  1. -

  2. Плюс питания

  3. -

  4. -

  5. -

  6. -

  7. Цифровая земля

  8. Данные

  9. -

  10. Минус питания


Для запуска необходимо сравнительно высокое напряжение (около пятидесяти вольт). Я пробовал подавать меньше, но блоку было глубоко всё равно.

Изначально думал взять сорокавосьмивольтовый блок питания от какого-то PoE-свитча, но тут в щедрых закромах Родины отыскалась плата повышающего DC-DC преобразователя.

Я использовал его для проверки работоспособности ГРИ, но для получения меньшего напряжения он тоже подойдёт. Ток потребления очень низкий, так что даже такого питания вполне хватит.

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

После подачи питания блок издаёт писк и начинает тестирование систем.

Если всё нормально, он переходит в нормальный режим работы. Да, даже без подключенных комплектов девайс вполне работает и не падает в ошибку. Это полноценный блок управления, а не просто панель индикации.

Если ничего не нажимать, индикатор отключается для экономии ресурса. Нажатием четвёртой кнопки можно его активировать снова. Если же удерживать эту кнопку, то время начнёт отображаться постоянно, в половину яркости индикатора.

❯ Пароли


ЦБКИ имеет три меню, куда можно попасть, введя пароль.

Нажимаем две средние кнопки сразу, после чего требуется набрать код, состоящий из четырёх разных цифр. Всего есть 24 возможных комбинации (4*3*2*1), так как нажатия кнопок не повторяются.

Неизвестное меню, вход по коду 2314. Возможно, что это какая-то статистика по вагонам или настройки всего поезда.

Системные настройки, вход по коду 2134. Здесь есть два пункта — установка даты и времени, а также конфигурация ПЦБК, выдающее всё то же «Нет вагонов». Возможно, это дубль меню по 2314.

Установка даты и времени.

Журнал, вход по коду 3241. Здесь записываются все системные события — запуски/отключения, отказы, перегревы, потопы, пожары. Из него видно, что последний раз устройство включали почти два года назад.

❯ Вот как-то так

Увы, полноценно запустить этот блок не вышло ввиду отсутствия какой-либо периферии к нему, а использовать его в таком виде можно разве что как часы на ВЛИ.

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

Такие дела.

  • Автор: MaFrance351

  • Больше интересных статей в нашем блоге на Хабре. Недорогие сервера для ваших проектов — здесь.

Показать полностью 24
60

Домашний дымоуловитель

Много творческих людей, которые клеят, красят, паяют и при этом живут в небольших квартирах, сталкиваются с тем, что домашние часто ворчат, что мол пахнет, дымно и.т.д. Просмотрев рынок дымоуловителей я немного приохренел от цен. Получается что хорошую мощную, КОМПАКТНУЮ установку позволить себе может не каждый. Я просмотрел все бюджетные и не совсем модели. И решил сделать сам. Позвольте представить)

корпус из оцинкованной стали 0.7 мм.

Размеры 250х270х630. Внутри расположен блок с двумя съемными фильтрами (можно менять фильтры по мере их заполнения). Верхний фильтр от грубых частиц, нижний - угольный. Мотор мощностью 100 ватт, тихий и надежный. На корпусе расположен переключатель скорости. Также имеется гофрированная труба, растягивается до 2 метров.

Система проста и надежна как советский пылесос)

Название и логотип придумал за 2 минуты.

Конструктивная критика приветствуется !

Показать полностью 22
2022

Подключаем дисплей к любому одноплатнику с SPI: Большой мануал о поиске экранчиков для ваших проектов



Сейчас появилось достаточно много различных дешевых одноплатников с очень достойными характеристиками, которые вполне можно назвать экономичными и портативными. Однако очень часто встает вопрос вывода изображения на дисплей: к сожалению, в подобные устройства обычно ставят урезанные версии чипсетов без видеовыхода на обычные матрицы. Конечно в них практически всегда есть 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 и резистивным тачскрином.



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



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



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



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



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

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



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



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



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



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

От слов к делу — 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 версии) стоят копейки.



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



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



Если подключили все верно, то при включении одноплатника, Вы увидите подсветку.
Теперь для того, чтобы им управлять, нам нужно получить доступ к шине 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");

}

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

Линк на Pastebin, т.к код инита слишком большой.

Для передачи фреймбуфера, мы реализовываем отдельный метод, который разобьёт его на транзакции. В нашем случае, фреймбуфер занимает 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); }


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



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

Заключение


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

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

Показать полностью 14
Отличная работа, все прочитано!