Pull to refresh

«Привет, Хабр» на частоте 835 кГц

Reading time 5 min
Views 22K
Как-то раз в голове возникла мысль, а что бы сделать такое, чтобы скрестить старый радиоприемник в деревянном корпусе и современный контроллер для интернета-вещей ESP32? То ли с головой не так что-то, то ли делать мне нечего, но скрестить получилось. Не шаблонно, в целом, хотя судить вам, дорогие читатели Хабра).

За подробностями прошу под кат.

32>8266


В математике это абсурд, но в нашем случае это так. Функционал и возможности ESP32 куда больше ESP8266. Помимо бо́льшего числа GPIO, микроконтроллер мощнее функционально, к стандартному Wi-fi добавлен блютуз, ну и самое на мой взгляд правильное в нем — наличие двух каналов цифро-аналогового преобразования (ЦАП по-нашему DAC по импортному). Поэтому ничего сложного не составляет (не прибегая к разным выкрутасам) напрямую заставить контроллер быть генератором различных сигналов или даже говорить человечьим языком. Вот именно этот ЦАП нам и нужен.


В контроллере 2 канала ЦАП на 25 и 26 выводах. Как я понимаю, это для того, чтобы даже запилить стерео-звучание при необходимости. О контроллере ESP32 не мало информации, которую можно взять здесь или вот здесь, а для любителей технической документации вот ссылка на datasheet. В моем случае была платка разработчика серии WEMOS с экранчиком на борту, что само по себе достаточно удобно — можно сразу отлаживать с отображением информации на месте или выводить какие-то данные.



Удобная в целом вещь, хотя цена могла быть скромнее. Покупалось здесь.

Ближе к делу!


Имея на борту ЦАП мы можем смоделировать «чистый» синусоидальный сигнал определенной частоты. Анализ datasheet показал, что частота дискретизации ЦАП контроллера ESP32 может достигать порядка 13 МГц, поэтому нам остается понять, какой частоты синус мы можем получить в этом случае. Для этого необходимо аппроксимировать синус неким числом временны́х отчетов — чем больше отчетов взять, тем более качественно будет выглядеть сигнал, но при этом снижается предельная частота, которую можно получить в итоге. При этом амплитуда сигнала ограничивается разрядностью ЦАП и составляет 8 бит.



Аппроксимировать четырьмя или восьмью временны́ми отчетами синус будет не достаточно, а вот 16 — уже вполне пойдет.

int sintab[] = {0, 48, 90, 117, 127, 117, 90, 48, 0, -48, -90, -117, -127, -117, -90, -48};

Конечно, лучше бы 32 или 64 отчета взять, но тогда значительно падает частота итогового сигнала. Таким образом, при дискретизации в 16 временны́х отчетов и предельным диапазоном работы ЦАП в 13 МГц, можно получить ничего себе такой синус с частотой порядка 800 кГц (13 МГц/16 бит). А вот это уже попадает в диапазон средних волн бытовых радиоприемников, на который можно будет передавать полезный сигнал, к примеру, речь. Схема получается до жути простая. В настройке не нуждается)


К выводу 25 ЦАП мы подсоединяем отрезок провода (сантиметров 40), который выполняет роль антенны. Радиопередатчик практически готов!

Амплитудная модуляция


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



Мы должны изменять амплитуду несущей частоты (сверху) информационным сигналом (в центре), обеспечивая приемлемую глубину модуляции, которая в идеале должна быть равной 1 (или 100%), т.е. итоговый сигнал (снизу) представляет из себя постоянно изменяющийся по амплитуде высокочастотный сигнал. Так как реализовать глубину модуляции четко в 100 процентов не всегда удается — уровень этой величины в 50 процентов уже вполне приемлем (примерно как на рисунке). Качество звучания будет несколько хуже, но это не беда.

Привет, Хабр!


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


Так выглядит фраза «Привет, Хабр, частота 835 кГц». Из этого файла нам надо сформировать файл отчетов информационного сигнала, который можно сконвертировать по данной ссылке. Структура итогового выходного файла будет примерно такой

const unsigned int sampleRate = 44100; // столько отчетов идет в секунду
const unsigned int sampleCount = 289728;
// всего отчетов в звуковом файле. Поделив это на 44100 получаем длину аудифайла. Получается чуть больше 6 сек.
const signed char samples[] = {
-2, -4, -7, -10, -12, -13, -13, -14, -13, -12, -11, -11, -9, -8, -6, -5, 
-4, -2, -2, -3, -4, -6, -10, -15, -21, -27, -34, -39, -41, -40, -38, -34, 
-28, -22, -19, -15, -13, -13, -16, -18, -21, -24, -27, -29, -31, -33, -35, -36, 
-36, -37, -38, -39, -38, -37, -36, -33, -31, -30, -30, -31, -32, -34, -35, -35, 
-35, -35, -34, -34, -33, -32, -31, -31, -31, -31, -31, -31, -29, -27, -25, -22, 
......
и еще много много много цифр
......
-20, -18, -16, -14, -13, -11, -9, -8, -7, -6, -5, -4, -3, -3, -2, -1, 
0, 2, 6, 10, 14, 19, 23, 27, 28, 28, 26, 24, 21, 20, 20, 21, 
23, 25, 28, 30, 32, 34, 36, 37, 38, 39, 40, 41, 41, 42, 43, 43, 
42, 40, 38, 36, 35, 36, 38, 40, 42, 43, 43, 42, 39, 37, 34, 31, 
30, 29, 30, 31, 32, 33, 33, 32, 29, 26, 22, 19, 17, 15, 14, 15};

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

Остается только залить прошивку, подключить отрезок провода к 25 пину в виде антенны и, переведя приемник в АМ диапазон, настроиться на частоту в районе 800-850 кГц.



Мощность передатчика, конечно, мизерная. Поэтому антенну располагаем совсем рядом с приемником.

Собственно итоговый код программы занимает 70 строк. Преобразованный аудиофайл в виде файла отчетов sample.h можно скачать здесь. Важно не забыть скинуть этот файл в ту же папку, в которой находится сама программа.

Код программы
//подключаем аудиофайл отдельным модулем
#include "sample.h"

#include <soc/rtc.h>
#include "driver/i2s.h"

static const i2s_port_t i2s_num = (i2s_port_t)I2S_NUM_0; // i2s 

//настроим параметры i2s
static const i2s_config_t i2s_config = {
     .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
     .sample_rate = 1000000,  
     .bits_per_sample = (i2s_bits_per_sample_t)I2S_BITS_PER_SAMPLE_16BIT, 
     .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
     .communication_format = I2S_COMM_FORMAT_I2S_MSB,
     .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
     .dma_buf_count = 2,
     .dma_buf_len = 1024  
};

void setup() 
{
  rtc_clk_cpu_freq_set(RTC_CPU_FREQ_240M);              //установим максимальную частоту cpu
  i2s_driver_install(i2s_num, &i2s_config, 0, NULL);    //стартуем i2s 
  i2s_set_pin(i2s_num, NULL);                           //врубаем внупренний ЦАП
  i2s_set_sample_rates(i2s_num, 1000000);               

  //это финт, который позволяет использовать самую высокую частоту дискретизации ~ 13 МГц
  SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(0), I2S_CLKM_DIV_A_V, 1, I2S_CLKM_DIV_A_S);
  SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(0), I2S_CLKM_DIV_B_V, 1, I2S_CLKM_DIV_B_S);
  SET_PERI_REG_BITS(I2S_CLKM_CONF_REG(0), I2S_CLKM_DIV_NUM_V, 2, I2S_CLKM_DIV_NUM_S); 
  SET_PERI_REG_BITS(I2S_SAMPLE_RATE_CONF_REG(0), I2S_TX_BCK_DIV_NUM_V, 2, I2S_TX_BCK_DIV_NUM_S); 
}

//буфер для хранения модулированных выборок 
short buff[1024];
//синус, представленный в 16 значениях. при частоте дискретизации 13 МГц 
//дает несущую АМ сигнала порядка 800-850 кГц
int sintab[] = {0, 48, 90, 117, 127, 117, 90, 48, 0, -48, -90, -117, -127, -117, -90, -48};

unsigned long long pos = 0;         
unsigned int posLow = 0;
// попробуем где-то посерединке диапазона. К примеру, 835 кГц 
unsigned long long posInc = ((unsigned long long)sampleRate << 32) / 835000;  
void loop() 
{
  //заполняем буфер значениями выборок из звукового файла
  for(int i = 0; i < 1024; i+=16)
  {
    if(posLow >= sampleCount) posLow = sampleCount - 1;
    
    int s = samples[posLow] + 128;
    //собираем итоговый сигнал
    for(int j = 0; j < 16; j += 4)
    {          
      buff[i + j + 1] = (sintab[j + 0] * s + 0x8000);
      buff[i + j + 0] = (sintab[j + 1] * s + 0x8000);
      buff[i + j + 3] = (sintab[j + 2] * s + 0x8000);
      buff[i + j + 2] = (sintab[j + 3] * s + 0x8000);
    }
    pos += posInc;
    posLow = pos >> 32;
    if(posLow >= sampleCount)
      pos = posLow = 0;
  }
 // отправляем все с богом на выход
 i2s_write_bytes(i2s_num, (char*)buff, sizeof(buff), portMAX_DELAY);
}
   

Прошито и собрано в ARDUINO IDE. Как подружить ESP32 с ним, читаем, к примеру, здесь. Подробнее об использованном в проекте исходном коде читаем здесь.

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

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+35
Comments 10
Comments Comments 10

Articles