Содержание статьи
Помню, в далеком 2006 году я впервые увидел магнитолу, отображающую название принимаемой станции. Тогда меня это, прямо скажем, озадачило. В то время вершиной моего радиоэлектронного искусства был приемник на К174ХА34 (TDA7021), а там такого и близко не было. И если принцип отображения частоты я мог понять — типа цифровая шкала или синтезатор, — то отображение названия просто сносило крышу. Сегодня мы обсудим, как это делается, и соберем актуальный вещательный приемник.
Про радиоприемник на RDA5807 мы говорили и тогда использовали недокументированный режим stand alone, в этот раз мы задействуем все возможности этой замечательной микросхемы.
Самый низ
Рассмотрим структуру демодулированного FM-сигнала. Сначала идет аудиосигнал суммы двух стереоканалов, поэтому если такой сигнал подать прямо на наушники, то в них будет вполне себе слышен монофонический звук, а все прочие сигналы обрежутся АЧХ наушников и нашего уха. Такое положение дел обеспечивает обратную совместимость с устаревшими типами приемников, вплоть до сверхрегенератора. Дальше идет пилот: тон 19 кГц для восстановления поднесущих стереосигнала (38 кГц) и RDS (57 кГц), потом DSB (амплитудная модуляция с подавленной поднесущей), модулированный сигнал с разностью двух стереоканалов. Выше уже идет RDS-сигнал, это тоже DSB-модулированный сигнал.
Частоты эти не случайны: 38 кГц — это вторая гармоника пилот‑тона, а 57 кГц — третья. Дальше идут сигналы RDS2, но о них мы поговорим в другой раз, все равно наш чип работать с RDS2 не умеет.

Собственно, поднесущая RDS-сигнала промодулирована дифференциальным манчестерским кодом, который и содержит цифровые данные.

Данные передаются группами по 104 бита с битрейтом 1187,5 бит/c (57 кГц / 48), каждая группа содержит 26 бит и состоит из 16-битного слова и 10 бит контрольной суммы.

Но эта низкоуровневая жесть скорее для общего развития, так как всем этим за нас займется микросхема RDA5807, которая дает в наше распоряжение сразу четыре слова RDS-группы, и это определенно к лучшему. На этом пока отложим RDS и поговорим о железе.
Схема и конструкция
Со схемой тут, конечно, посложнее, чем в режиме stand alone, но все равно она достаточно проста.

Нам, само собой, понадобится контроллер — я остановился на STM32F103C8T6, вернее даже на плате BluePill. Еще понадобится дисплей TFT 128 × 128 ST7735. Модуля на RDA5807 у меня не было, впрочем, «модуль» — это громко сказано: там из обвеса кварц и пара кондеров, и все это добро легко размещается на плате адаптера SOIC в DIP. Ну и в завершение нужен энкодер для управления. Все просто до предела: на SPI-шину вешаем дисплей, на I2C вешаем RDA5807, подключаем энкодер, готово. В сборе получается как‑то так.

Из подводных камней тут могут возникнуть разве что наводки от шины экрана на антенну, но это проявляется только при слабом сигнале станции и лечится подключением антенны через кусок коаксиального кабеля, чтобы отодвинуть ее от шины. Мощности звуковых выходов RDA5807 для наушников хватает с большим запасом. Однако если захочется подключить динамики, то советую обратить внимание на стереоусилитель TDA4863, так как на нем удобно собрать УЗЧ с усилением по напряжению 1, что позволит не ставить физический регулятор громкости, а ограничиться программным.

Прошивка
Есть очень хорошая статья в двух частях, посвященная работе с RDA5807: часть 1, часть 2. Но повторять мы ее не будем, скорее станем ориентироваться на нее, как на один из возможных концептов. Да и железо у меня совсем другое.
Инициализация портов — это тривиально, энкодер на прерываниях и работу с дисплеем мы здесь, пожалуй, опустим. А вот работа с самой RDA5807 — это интересно. Использовать будем, как и раньше, libopenCM3, но переписывать монструозную библиотеку для «Ардуино» мы не станем, а реализуем только необходимые нам функции.
www
Все исходники и бинарники проекта можно найти на GitHub.
Чтение и запись в регистры
Поскольку чип естественным образом управляется через регистры, то без функции чтения и записи в эти самые регистры и говорить не о чем. В доступных даташитах кратко описан способ коммуникации, предполагающий чтение и запись регистров строго группами. Причем чтение идет с адреса 0Ah
, запись с адреса 02h
, и читать их можно вплоть до адреса 3Ah
. Для этого надо стучаться по адресу 0x10
на шине I2C.
Самое забавное, что в мануале описаны только 16 регистров, причем начиная с 02h
. Пытливые радиолюбители разнюхали еще один способ коммуникации с рандомным доступом, там уже можно читать и записывать любой регистр. Такой режим реализуется, если стучаться по адресу 0x11
на шине I2C. Любопытно, что этот способ показан лишь на картинке в мануале, причем на китайском языке, а в тексте об этом ни слова. Китайцы такое любят.

Итак, в чипе 16 шестнадцатибитных регистров. Реализуем запись и чтение рандомного регистра, а сверх того — последовательное чтение шести регистров, но об этом позже. Первое удобно для управления чипом.
#define RDA5807I2C I2C1#define RDA5807ADDR_RANDOMACSESS 0x11uint16_t RDA5807_read_random_register(uint8_t registr){ uint8_t temp[2]; i2c_transfer7(RDA5807I2C,RDA5807ADDR_RANDOMACSESS,®istr,1,temp,2); return (uint16_t)(temp[0]<<8|temp[1]);}void RDA5807_write_random_register(uint8_t registr, uint16_t data){ uint8_t temp[3]={registr,(uint8_t)(data>>8),(uint8_t)(data&0xff)}; i2c_transfer7(RDA5807I2C,RDA5807ADDR_RANDOMACSESS,temp,3,0,0);}
Теперь мы можем читать и писать регистры, можно запускать чип!
Инициализация
Тут все проще, чем кажется: достаточно выставить нужные биты в регистре 02h
, причем если хочется быстро проверить работоспособность схемы, то достаточно записать в регистр 0xC101
.
...RDA5807_write_random_register((0x02, 0xC101); // set ENABLE, DHIZ, DMUTE, SEEK...

Это включит чип, отключит MUTE, включит выходной каскад аудиоусилителя и запустит автопоиск станции. Через пару секунд радио запоет. Попробовали, проверили, теперь можно и по‑серьезному сделать. Автопоиск я использовать не буду, мне нравится крутить ручки, для того я и подключил энкодер. Поэтому бит SEEK
выставлять не будем, зато добавим баса (бит BASS
), включим альтернативный метод демодуляции (бит NEW_METHOD
) и включим RDS (бит RDS_EN
).
Если верить мануалу, альтернативный метод демодуляции немного поднимет чувствительность. Заодно выберем расширенный диапазон 76–108 МГц. Большого смысла в этом нет, так как у нас нет станций на участке 76–87 МГц, но будем считать это заделом на будущее.
Ну и соответственно, нам нужны функции для управления частотой и громкостью, чтобы два раза не вставать. За громкость отвечают младшие 8 бит регистра 05h
. Чтобы не затирать остальные данные в регистре перед установкой громкости, считываем старое значение, благо наш рандомный доступ к регистрам это позволяет. За частоту отвечает регистр 08h
, частота указывается в килогерцах относительно начала диапазона, в нашем случае это 76 000 кГц.
#define RDA5807_DHIZ 1<<15#define RDA5807_DMUTE 1<<14#define RDA5807_BASS 1<<12#define RDA5807_ENABLE 1#define RDA5807_SEEK 1<<8#define RDA5807_RDS_EN 1<<3#define RDA5807_NEW_METHOD 1<<2#define RDA5807_WWBAND 0b10<<2void RDA5807_init(void){ uint16_t temp=RDA5807_ENABLE|RDA5807_BASS|RDA5807_DHIZ|RDA5807_DMUTE|RDA5807_RDS_EN|RDA5807_NEW_METHOD; RDA5807_write_random_register(0x2, temp); temp=RDA5807_read_random_register(0x3); // Регистр 3h, биты 2, 3, выбирается диапазон 76–108 МГц temp|=RDA5807_WWBAND; RDA5807_write_random_register(0x3, temp); // Включаем direct freq uint16_t temp=RDA5807_read_random_register(0x7); temp|=1; RDA5807_write_random_register(0x7, temp); //RDA5807_set_freq(89100);}void RDA5807_set_freq(uint32_t freq){ temp=(uint16_t)(freq-76000); RDA5807_write_random_register(0x8, temp);}void RDA5807_set_vol(uint8_t vol){ if(vol>15)vol=15; uint16_t temp=RDA5807_read_random_register(0x5); temp=temp&0xFFF0|(uint16_t)vol; RDA5807_write_random_register(0x5, temp);}
RDS
Как мы уже говорили, декодер RDS-сигнала в RDA5807 аппаратный, поэтому от нас требуется только анализировать с некоторой периодичностью содержимое блоков A—D. Однако здесь есть один нюанс: это радио, и качество сигнала обычно не очень, поэтому крайне полезно считать биты, указывающие на количество ошибок в блоках A—D. Я предлагаю установить требование к полному отсутствию ошибок, а сверх того применить валидацию пакетов, как в упомянутой выше статье.
Ошибки все равно пролезут, но это хотя бы будет не так раздражать. Для удобства обработки, наверное, имеет смысл соединить четыре слова блока в одну 64-битную переменную, тогда битовые операции сильно упростятся и будут нагляднее, но эта идея пришла мне в голову, когда все уже было реализовано и отлажено, и переделывать я не стал.
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее