Кто сейчас вообще слушает радио? Может показаться, что в современном мире не осталось места для радио, однако мы сумели отыскать несколько кейсов его реального использования и немного поиграли с этим. В этой статье мы хотели бы рассказать, как не будучи радиотехниками, можно реализовать механизм мобильного управления радиовещанием, а заодно раскрыть техническую составляющего происходящего в видео. История этого проекта сложилась довольно неординарно: мы хотели в короткие сроки реализовать управляемый с мобильного устройства fm-передатчик приемлемой дальности, связав полностью готовые компоненты между собой и особо не погружаясь в радиотехнику, но в итоге получили опыт hardware-reversing.  Кратко наш путь выглядит так: Часто для того, чтобы понять, как нужно делать что-то правильно, нужно просто начать это делать. Мы начали с поиска существующих проектов в GitHub по управлению fm-передачей (google: site:github.com "fm transmitter"). И сразу же вышли на живой проект — https://github.com/markondej/fm_transmitter, позволяющий вещать радио посредством подключенного к GPCLK(General Purpose Clock) пину Raspberry PI провода. Протестировав работоспособность проекта на реальном радио и услышав свою мелодию вместо оригинальной радиостанции, на минуту показалось, что дело сделано, и задачу в Jira вот-вот можно будет закрывать. Оставалось только придумать интерфейс взаимодействия с RPi через мобильное приложение. Выбрали классический в мире IoT подход: WiFi AP + веб-приложение. Во вселенной Opensource был быстро найден подходящий проект rpi-hostap, позволяющий в пару кликов развернуть WiFi Access Point с нужной конфигурацией на Raspbian и с веб-приложением внутри локальной сети AP (со статическим IP), к которому бы стучалось iOS-приложение, управляющее радио. Веб-приложение реализовано на базе Python-фреймворка Flask и содержит следующие HTTP-эндпоинты для управления радиовещанием: Этот интерфейс не предполагалось использовать в многопользовательском режиме, однако для надежности все методы были защищены общим threading.Lock-объектом: одновременно можно было вызывать только какой-то один из методов. После появления HTTP-интерфейса уже можно было общаться с RPi множеством различных способов. Нашей конечной целью было управление fm-вещанием через iPhone: 
Структура iOS приложения На этом этапе у нас был полностью готовый прототип fm-трансмиттера с интерфейсом, позволяющим его легко тестировать в разных локациях. До текущего момента все тесты проходили в помещении офиса, что давало дальность вещания до 20 метров. Но после серии уличных тестов выяснилось, что даже 5-метровый радиус получается гарантировать не всегда…  Мы стали пытаться увеличить дальность вещания путем удлинения GPIO-провода, отходящего от GPCLK-пина. Увеличение дальности оказалось незначительным. Для еще большего увеличения дальности решили попробовать стереоскопическую антенну. Чтобы её использовать, был необходим переход с GPIO пина на контур антенны. В этот момент мы впервые достали паяльник, но пока не подозревали, что станем с ним неразлучны до конца проекта. Это дало результат как минимум не хуже предыдущего, но теперь стало удобнее эксплуатировать антенну. Было очевидно, что GPCLK-пин Rasberry "не вытянет" нужной мощности вещания без дополнительной помощи. После небольшого ресерча выяснилось, что такая "помощь" существует — SPF5189Z. Это усилитель сигнала, использующий внешнее питание. Добавили этот компонент в разрыв между переходником и антенной: 
Усилитель SPF5189Z для антенны с питанием от USB и переходником на GPIO Снова разогрели паяльник для пайки переходника питания усилителя через USB с того же Powerbank, с которого питался RPi, после чего разместили все в картонной коробке для удобства эксплуатации. На этот раз дальность вещания была увеличена с нестабильных 10 метров до стабильных 15 метров на улице, чего едва хватало. Вся эта конструкция проработала ровно 1 день, пока не вышел из строя усилитель. В этот момент пришло осознание, что мы далеко отошли от исходной идеи сборки устройства из готовых компонентов и что без минимального погружения в радиоэлектронику не обойтись. "Уровень 0" привнес интерфейс управления, независимый от железа, поэтому мы могли смело отказаться от использования проекта fm_transmitter и поискать другой способ организации fm-вещания.  У нас не было желания строить собственный велосипед из fm-вещателя и усилителя, поэтому мы решили отыскать готовую систему и немного её "подпилить" под себя. Интернет-разведка привела нас к китайской компании CZERF, занимающейся беспроводными технологиями. В ассортименте продуктов, выпускаемой данной компанией, было обнаружено интересное устройство — fm-трансмиттер на 7 ватт с кнопочным управлением (2 кнопки на выставление частоты ▲/▼ и кнопка питания) и audio-jack'ом — CZERF-7C (официальный сайт кидается 500 ошибкой). Такая мощность позволила бы в десятки раз увеличить дальность в условиях улицы. Задача сводилась к связыванию проприетарного прибора малоизвестной китайской компании с RPi. После получения прибора на руки, сразу же его разобрали и приступили к поверхностному анализу основной платы. За несколько минут с помощью мультиметра удалось установить связь между дорожками кнопок и микроконтроллером, связывающего дисплей, кнопки и радиомодуль воедино на этом устройстве:  
Внутренности прибора (1 — радиомодуль, 2 — микроконтроллер) Под рукой уже был разогретый Raspberry PI с "Уровня 0", поэтому мы пришли к идее имитации нажатий на кнопки устройства с помощью GPIO-реле. Это реле предоставляло очень удобный интерфейс управления — достаточно насадить реле на GPIO-шину RPi, после чего посылать сигнал на один из пинов GPIO Raspberry, как нужное реле замыкает цепь подобно оригинальной кнопке: Потребовалась серьезная переработка веб-приложения (без изменения HTTP-интерфейса): теперь при вызове /setfreq(установка частоты) нужно было запускать фоновую задачу по "нащелкиванию" нужной частоты, а при вызове /start(запуск проигрывания) — дожидаться выставления частоты и запускать фоновое аудио, которое бы по audio-jack уходило бы с RPi в радиомодуль (до этого аудио-сэмплы читались из файла без непосредственного проигрывания на RPi).
За дальность вещания пришлось расплатиться скоростью выставления частоты — за одну имитацию нажатия на управляющие кнопки происходило изменение частоты на 0.1Mhz. Если, например, нужно с частоты 90Mhz переключиться на 105Mhz, потребовалось бы ждать (105-90)*10*(0.03*2)=9 секунд. Вдобавок к этому появилось щелканье реле на протяжении всего времени переключения частоты и необходимость запоминания частоты программно, так как из кода не было никакой связи с реальной("железной") частотой на устройстве. Но тем не менее самой большой проблемой оказалось другое — наводки. 7-ваттная антенна fm-вещателя оказывала на реле крайне негативное воздействие: в какие-то редкие моменты могли происходить случайные переключения частоты вверх или вниз из-за наводок. С учетом отсутствия обратной связи с реальной частотой на устройстве, код мог "думать", что нужная частота "нащелкана", когда на самом деле нет. С этого момента наводки, как и паяльник, сопровождали нас до заключительной стадии проекта. 
Управление прибором с RPi Relay Board Было решено начать избавляться от кома образовавшихся проблем постепенно и методично. Начали с того, что к текущему моменту надоело больше всего — с устранения треска реле. Мы отыскали несколько элементов, функции которых аналогичны реле: Твердотельное реле с интерфейсом, подобным RPi Relay Board, найти не получилось. Стало понятно, что так или иначе придется мастерить собственную схему управления, поэтому оптрон оказался самым подходящим решением: "тихий", компактный, дешевый и легкодоступный. Принцип его работы крайне прост: при подаче тока на светодиод открывается выходной транзистор и замыкает ножки 4 и 3 для протекания тока в цепи. Набросали макет платы с 2 кнопками (кнопки частоты) на базе оптронов для замены реле: 
Схема управления v0 И достаточно быстро спаяли нулевую версию схемы на проводах: 
Схема управления v0: реализация Наводки на переключение никуда не делись, но зато переключение частоты теперь происходит беззвучно. В этот момент работа казалась практически завершенной, однако настоящее приключение только начиналось. Наводки были последней крупной проблемой. На этом этапе не было никаких сомнений, что провода есть основной субъект наводок. Поэтому все силы были брошены на разработку полноценной платы управления, которая схематично была практически аналогична "схеме на проводах". В схему платы лишь было добавлено управление кнопкой питания fm-трансмиттера (тоже через оптрон), чтобы можно было управлять fm-вещанием с помощью веб-приложения на RPi (эндпоинт /start). Схема первой версии платы получилась такая: 
Схема управления v1 Проектировалась плата в пакете DipTrace. На выходе эта программа создает файл в формате .dip, который можно прямиком отдавать большинству производителей печатных плат под заказ. Мы заказали печать у компании Резонит и получили готовую плату уже через день: 
Плата управления v1 Эта плата по нашим ожиданиям должна была устранить влияние помех. Для еще большей защиты от помех мы соорудили экранированный провод от этой платы до управляющих пинов RPi на основе кабеля типа CCC-4G. Плата проектировалась с учетом возможности размещения внутри корпуса устройства "вторым этажом", который бы управлял "первым этажом", где располагался контроллер: 
"Второй этаж" для перехвата управления с первого А для удобства эксплуатации этой системы управления радиовещанием мы решили спроектировать и распечатать корпус на 3d-принтере. Он получился у нас двухкомпонентный:  Внутренний каркас и внешнюю оболочку можно было бы распечатать монолитной деталью, однако в таком случае резко возрастало бы время печати — до трех суток. Заказав детали раздельно, получилось распечатать все за сутки: 
Мобильный fm-вещатель в собранном виде В итоге, запустив стресс-тест на несколько тысяч итераций переключения, мы словили по его завершению несоответствие программной частоты с той, что была реально выставлена на устройстве. Дальнейший разбор показал, что теперь частота переключалась не всегда. Наводки, провоцирующие произвольное переключение частоты, ушли с переходом на плату, но появились другие — теперь частота переключалась не всегда, когда было нужно в ходе вещания.  Провели командный брейншторм, общий посыл которого был таков: "нужно найти способ устранения наводок, не требующий еще большего углубления в радиотехнику". Идеальным вариантом было бы обойтись уже полученными в ходе проделанной работы знаниями. И такой способ нашелся. Мы вспомнили, что к микроконтроллеру, связывающему кнопки и дисплей с радиомодулем, ведут целых 10 ножек, в то время, как мы знали назначение только 5 из них (3 кнопки, земля и питание) Управлять любой другой из них через оптрон — уже знакомый нам способ. Одна из ножек наверняка должна отвечать за питание самого радиомодуля, ответственного за вещание. Достали мультиметр и быстро вычислили эту ножку. Оказалось, можно, не отключая питание всего устройства, включать/выключать только вещание, когда это нужно. А значит у нас появилась возможность выставлять частоту до активного вещания, что позволит избавиться от остаточных наводок "непереключения". После пары экспериментов выяснилось, что память(текущая fm-частота) находится в микроконтроллере, а не в радиомодуле, поэтому, отключая питание радиомодуля, мы можем спокойно "нащелкать" нужную частоту на контроллере, после чего уже итоговая частота будет отправлена в радиомодуль. Мы модифицировали первую версию платы, впаяв недостающий оптрон прямо на пластину платы, чтобы быстро провести тесты и оценить влияние наводок: 
Плата управления v1.5 Испытания показали, что наводка на выставление частоты пропала, однако появилась наводка на кнопку питания устройства. Проявлялась она достаточно интересным образом: при имитации нажатия на кнопку питания, прибор включался на мгновение и затем сразу же выключался. Избавиться от этого негативного эффекта получилось с помощью подбора времени замыкания (удерживания) кнопки питания в 0.05 секунд — за такое время наводка не успевала оказывать эффект.  Отказаться от управления кнопкой питания, оставив только управление вещанием, нельзя: при подаче питания на радиомодуль через оптрон вещание не начнется до тех пор, пока микроконтроллер не отправит какую-нибудь команду в радиомодуль, а отправка происходит либо при смене частоты (которую нельзя менять в ходе вещания) либо при "перезагрузке". Перезагрузка же — это быстрое включение и выключение устройства путем имитации нажатия на кнопку питания. Избавиться от сомнительного таймаута на удерживание кнопки помог фильтрующий конденсатор, который позволил "срезать" наводку на кнопке питания. После этого стресс-тест на несколько тысяч итераций переключения прошел успешно, и мы спроектировали вторую версию платы с дополнительным оптроном, управляющим вещанием: 
Схема управления v2 И заказали ее у производителя: 
Плата управления v2 + фильтрующие конденсаторы на всех кнопках К этому моменту устройство было уже существенно модифицировано: все его кнопки управлялись программно с RPi и даже была добавлена дополнительная "кнопка" на управление вещанием. Проект бы можно было считать завершенным, но ненадежный способ переключения частоты не давал покоя: было медленно, оставался риск возвращения наводок. Мы обратили внимание, что наводки влияют только на те элементы, на пути которых встречаются кнопки. Так, на включение/выключение вещания, которым управлял только наш оптрон, наводки не влияли.  В итоге было решено окончательно погрязнуть в азах радиоэлектроники, чтобы стабилизировать работу устройства, отказаться от "нащелкивания" частоты и окунуться в мир hardware-reversing'а. Набив шишек на предыдущих этапах, захотелось разобраться, как же работает наше устройство на более низком уровне. Интерфейс кнопок оказался слишком проблемным, а чутье подсказывало, что кнопки — это лишь интерфейс пользователя для некоторого низкоуровневого интерфейса. Оставалось лишь найти этот низкоуровневый интерфейс. Так как устройство CZERF-7C является проприетарным (мануала по внутренней работе платы найти нигде не удалось), пришлось выковыривать информацию о его работе самостоятельно.  К текущему моменту у нас остались неизвестными 4 ножки платы микроконтроллера. Настало время разобраться, что на них происходит при переключении частоты.   Для этого одного мультиметра уже будет недостаточно. Подключимся поочередно щупами осциллографа к неизвестным ножкам (землю осциллографа нужно подключить к земле самого устройства) и будем зажимать кнопки переключения частоты вверх или вниз, чтобы инициализировать обмен данными между микроконтроллером и радиомодулем. Сразу же натыкаемся на уже известную нам проблему наводок — но теперь на осциллогаф. Пришлось приобрести экранирующую медную сетку и замотать в неё антенну fm-трансмиттера в ходе исследования сигнала осциллографом. На ножке №1 никакой активности нет, на остальных же получилось засечь сигналы, которые посылает микроконтроллер в радиомодуль сразу же после нажатия на кнопки выставления частоты. 
Анализ пинов 2 и 3 
Анализ пинов 3 и 4 Захваченные сигналы возникают синхронно по времени, что очень похоже на некий протокол. Хоть устройство CZERF-7C и является проприетарным, отдельные его части открыты. Так, микроконтроллер, который управлял радиомодулем этого устройства, оказался программируемым STC11F02E с публичным datasheet, на основе которого получилось установить протокол — SPI. Это и есть искомый низкоуровневый интерфейс.  Кратко SPI работает так: В этом устройстве считывание происходит по переднему фронту (Mode 0). В посылке можно увидеть, что данные меняются по заднему фронту, значит задний фронт не используется для считывания данных — по протоколу данные не могут меняться при считывающем фронте. Лучше всего это видно на этом захвате сигнала: 
Анализ режима работы SPI Зеленый сигнал(внизу) — это MOSI, желтый(вверху) — CLK. Исходя из этого, можно установить назначение оставшихся 3 из 4 ножек: ножка 2 — это CS, 3 — CLK, 4 — MOSI. Перехватим несколько посылок с разными заведомо известными частотами: Всего в посылке передается 16 бит. Можно заметить, что у этих сообщений не меняется хвостовая чаcть — последние 5 бит = 10010
 Скорее всего это команда, которая устанавливает частоту, а данные передаются в первых 11 битах. Рассмотрим первые 11 бит двух соседних частот: 102.4Mhz и 102.6Mhz. Можно заметить что разница есть только в одном бите, что идет в самом начале последовательности — скорее всего здесь находятся младшие биты значения частоты, ведь мы сравниваем соседние частоты. Попробуем переписать эту битовую последовательность в обратном порядке, справа налево: Поигравшись с различными системами исчисления, удалось быстро выявить корреляцию реальной частоты с передаваемыми данными. Лучше всего это видно на примере десятичной системы: Для контроля, убеждаемся, что 92.9 — это 929: reverse(110000101110) = 011101000011 = 929. Мы собрали достаточно информации для того, чтобы попробовать самостоятельно выставить нужную частоту новым методом. Осталось лишь "откусить" ножки CS, CLK, MOSI от головного микроконтроллера, забрав управление себе, и отладить отправку собственной посылки. Легче всего отладка будет протекать на максимально простом устройстве (RPi плохо подходит для этих целей). Подключаем Arduino к плате CZERF-7C:  И пишем небольшой скетч, позволяющий отправить с Arduino посылку в режиме SPI: Код до безобразия прост, но здесь стоит подробнее остановиться на режимах работы SPI. Для SPI есть 4 режима работы. Они отличаются фронтом считывания (передний или задний, на картинке ниже они показаны стрелками) и начиная с какого уровня стартует сигнал CLK (высокий или низкий)  Ранее мы выяснили, что CZERF-7C работает в режиме Mode 0(CPOL = 0, CPHA = 0), поэтому в настройках SPI: SPI.setDataMode(SPI_MODE0) Запустив скетч на Arduino, каждые 20 секунд начала устанавливаться частота 102.4Mhz. Можно поменять константу freq, и частота после перезагрузки скетча будет меняться на неё. Отладив механизм отправки на Arduino, можно возвращаться к Rpi, на котором крутится веб-приложение по управлению радио. В случае с Arduino нам не требовалась гальваническая развязка в виде оптронов, так как мы сумели запитать Arduino от того же пина, от которого питается микроконтроллер CZERF-7C. Но в случае с RPi без развязки не обойтись, поэтому схема немного усложнилась: 
Схема управления v2.5 Мы собрали эту схему "на коленке" и набросали небольшой прототип для отправки посылки с Raspberry PI, аналогичный Arduino-скетчу выше: Для организации работы через оптроны нужно поменять режим работы SPI на Mode 2(CPOL = 1, CPHA = 0). Это связно с тем, что сигнал на оптронах инвертируется. По этой же причине мы по линии MOSI отправляем инвертированные значения [~0x00, ~0x32] = 11111111 11001101 вместо 00000000 00110010 Подсоединяем RPi к плате трансмиттера, запускаем и… ничего не работает. Снова беремся за осциллограф смотреть, какой сигнал у нас формируется после оптронов. Чтобы посмотреть сигнал, нужно снова оборачивать в экран антенну… И в этом случаем все начинает работать. Значит наводки от антенны искажают нашу SPI-посылку. Придется воспользоваться старым приемом выключения вещания для выставления частоты. Алгоритм получается следующий: Теперь частота выставляется только в половине случаев… Здесь-то стало понятно, почему разработчики CZERF-7C выбрали нетипичную полярность CS.  При выключении радиомодуля, он начинал подпитываться от ножек CS, CLK, MOSI через встроенные защитные диоды. По этой причине разработчики fm-трансмиттера, все линии SPI вне посылки сообщений притягивают к низкому уровню (pull-down). У контроллера SPI в RPi линия MOSI после отправки сообщения выставлялся в низкий уровень, а так как сигнал на оптронах инвертируется — на линии MOSI радиомодуля оказывался высокий уровень, который "паразитно" подпитывал радиомодуль и не давал ему полноценно выключаться. Когда же подавалось полноценное питание на радиомодуль, он начинал сразу вещать на частоте, которую ему установили до этого и тем самым опять создавать наводки на линии SPI от RPi. Казалось бы, нужно просто выставлять MOSI в высокий уровень после каждой отправки сообщения, но это оказалось не так-то просто. Настройки SPI в RPi не позволяли этого сделать, вдобавок если линию MOSI инициализировать как обычный GPIO пин, то переставал работать аппаратный SPI до перезагрузки RPi. Самым простым и быстрым решением было самим реализовать программный SPI через прямое выставление уровней на ножках GPIO. К этому времени мы уже были экспертами по протоколу SPI: Все заработало как надо, и мы были готовы делать третью версию платы управления. Проблемы нестабильной работой прибора в результате наводок были полностью ликвидированы. Мы знали 90% всех функций микроконтроллера, управляющего радиомодулем, а поэтому были уверены, что его можно полностью заменить своей платой с собственным софтом. Оставалось только выяснить назначение последней ножки(№1) микроконтроллера и дело в шляпе. Функций у CZERF-7C было не так-то много, поэтому можно было применить "брутфорс", вычислив функцию последней ножки методом перебора. У этого прибора был режим настройки, в который можно было попасть, зажав кнопку power при подключении питания. В этом режиме помимо выставления границ частоты можно было задавать силу вещания: 1/7 ватт. Оказалось, что на этой ножке была логическая единица при выборе силы в 1 ватт и логический ноль при выборе 7 ватт. Можно было бы просто откусить эту ножку, и дело с концом, но мы решили добавить еще пару оптронов в финальную версию платы: для включения low(1ватт) и high(7ватт) режимов на всякий случай — места у нас теперь в корпусе прибора было предостаточно. С учетом полноты информации, проектируем новую версию платы: 
Схема управления v3 Теперь мы могли быстро и бесшумно выставлять частоту без хранения состояния, контролировать силу и управлять ходом вещания. Проект можно было считать завершенным. Также мы добавили светодиоды для более простой отладки: 
Финальная плата управления вместо оригинального контроллера, кнопок и дисплея Hardware-reversing быстро принес свои плоды: мы сумели избавиться от всех проблем, которые привнесли предыдущие уровни. Оборачиваясь назад, сложно поверить, на что мы рассчитывали в начале, а сейчас — сложно представить развитие этой истории каким-либо иным образом. Этот по-настоящему "венчурный" для нас проект показал, что в любой новой сфере можно разобраться за адекватное время при наличии интереса у команды, толерантности к ошибкам и открытости новым идеям. Компания