суббота, 16 апреля 2016 г.

Панорамный анализатор спектра с прямым доступом к чипу RTL-SDR

Вот уже как два года прошло с анонса первой версии нашего панорамного анализатора спектра на хабе RTL-SDR.com и год как открыл API для прямого доступа к чипу RTL2832/R820T с примером получения выборок одиночной полосы и отрисовки спектра [7] в своем приложении без использования прокладки RTL_POWER. Но судя по поступающим вопросам у пользователей API возникают определенные трудности с реализацией панорамного режима через библиотеку 'rtlsdr.dll'. А ведь задача "яйца выеденного" не стоит, достаточно взять листочек в руки и разрисовать алгоритм оконного накопления и визуализации...

Если вкратце, то следует выполнить следующие шаги:
  1. Задаться минимальной Fmin и максимальной Fmax частотой сканирования (просмотра), скажем от 138 до 153 МГц.
  2. Задать полосу пропускания приемного тракта, определяемую битрейтом потока через USB порт (максимум polosa = 3.2 МГц).
  3. Задаться количеством точек преобразования бабочки БПФ для одной полосы, скажем N=4096, а по сути дискретность шага частоты в одиночной полосе STEP = polosa / N.
  4. Расчитать количество точек CNT, которые поместятся в суммарной полосе = (разнице максимальной и минимальной частот сканирования) / ширину одиночной полосы пропускания * количество точек преобразования N.
  5. Инициализировать чип (см. API). 
  6. Произвести расчет точек центральной частоты каждой из накапливаемых полос. Особенностью задания частоты при одиночном сканирования является то, что задается центральная частота, а отсчеты идут в диапазоне частот равным ± половины полосы пропускания от центральной частоты. Таким образом, для реализации панорамного режима требуется в бесконечном цикле - перебирать точки центральной частоты всех полос, получать выборки каждой из одиночных полос и накапливать их до тех пор, пока не будут перебраны все полосы. Количество полос есть округление по п.4 без умножения на количество точек в одиночной полосе N. Понятно, что первая центральная частота будет со сдвигом половины пропускания от минимальной частоты сканирования Fmin, а все последующие будут идти со сдвигом на целую полосу пропускания. В цикле это будет выглядеть так: Fo = round(Fmin +  ((polosa/2)*cnt)), где первоначальное значение переменной cnt = 1, идущей с шагом = 2.
  7. Передать выборки в процедуру БПФ (тут можно варьировать - либо накапливать выборки и потом FFT, либо сначала FFT, а накапливать куски спектра, последнее быстрее). 
  8. Отобразить накопленное.
Реализация алгоритма инициализации (в терминах Delphi):
 dongle_count:= rtlsdr_get_device_count();
 for i:= 0 to dongle_count-1 do begin
  memo1.Lines.Add('Имя: '+ rtlsdr_get_device_name(i));
  rtlsdr_get_device_usb_strings(i,
                                          @manufact,
                                          @product,
                                          @serial);
 end;

 // пробуем открыть 0-й
 if dongle_count >0 then begin
  i:= rtlsdr_open(@dev, 0);
  if i <> -1 then begin
   // чип
   tp:= rtlsdr_get_tuner_type(dev);

   // задаем полосу 3.2 MHz
   polosa:= 3200000;
   rtlsdr_set_sample_rate(dev, polosa);

   // задаем коэфф-т усиления 36.4 дБ = 364 (*10)
   rtlsdr_set_tuner_gain_mode(dev, 364);   
   // задаем смещение синтезатора PPM
   rtlsdr_set_freq_correction(dev, 2700);
   // шаг по частоте, Hz
   step:= df/BufSize;

   // набиваем точками заранее, чтобы управлять их положением
   series1.Clear;
   setlength(mas, 0);
   for i:= 0 to round(((maxf-minf)/polosa)*BufSize)-1 do
    series1.AddXY(0,0, '', clred);
   //создаем параллельный поток
   hwnd:= CreateThread(nil, 0, @TTH, nil, 0, ThreadID);

   chart1.BottomAxis.Automatic:= false;
   chart1.BottomAxis.Maximum:= maxf;
   chart1.BottomAxis.Minimum:= minf;
  end;
 end;
Также добавим возможность циклической записи данных буфера BUF размером 8*16384 байт в файл 'sdr.dat' фиксированного размера, скажем 2Gb (при достижении конца файла, указатель перекинем на начало файла):
var mem: tfilestream;
// создаем файловый поток
mem:= tfilestream.Create('sdr.dat', fmcreate);
mem.size:= 2000*1024*1024; //2Gb
freeandnil(mem);
// а теперь открываем для записи-чтения и убираем монопольный доступ
mem:= tfilestream.Create('sdr.dat', fmOpenReadWrite or fmShareDenyNone);
mem.Seek(0, soFromBeginning);
Обработчик потока и накопление:
uses API_RTL_SDR, TypInfo, math;

type tmas = record
 f, d: double;
end;

const
    BufSize = 4096; // количество точек FFT
    BUF_LENGTH = (8 * 16384);
var i, outi, gain, h, hwnd: integer;
    tp: rtlsdr_tuner;
    buf: array[0..BUF_LENGTH] of byte;
    flag: boolean = false;

    ThreadID: dword;
    step: double;
    f, df: longword;

    minf: integer   = 138000000;
    maxf: integer   = 153000000;
    polosa: integer =   3200000;
    cnt: integer = 1;
    cnt2: integer = 0;
    mas: array of tmas;


procedure TTH;
var k,kk, nf: integer;
    d: double;

    bpf_max: double;
    a : array [0..20000] of double;
    b : array [0..20000] of double;
    dim:array of double;
    qcos:array [0..10000] of double;
    qsin:array [0..10000] of double;
    bpf:array [0..20000] of double;   
begin
 while (not FLAG) do begin
  // сканер
  f:= round(minf + ((polosa/2)*cnt));
  // следующий блок частот
  cnt:= cnt + 2;

  // визуализация
  if (f>=maxf) then begin
   cnt:= 1; cnt2:= 0;

   for i:= 0 to form1.Series1.Count-1 do begin
    if (mas[i].f<10000) then begin
     mas[i].f:= mas[length(mas)-1].f;
     mas[i].d:= mas[length(mas)-1].d;
    end;
    form1.Series1.XValue[i]:= mas[i].f;
    form1.Series1.YValue[i]:= mas[i].d;
   end;
   setlength(mas, 0);
  end else try
  // задаем центральную частоту для сканирования, Hz
  rtlsdr_set_center_freq(dev, f);
  // задаем полосу
  rtlsdr_set_sample_rate(dev, polosa);
  // сброс буфера
  rtlsdr_reset_buffer(dev);


  // выборки буфера BUF размером BUF_LENGTH
  h:= rtlsdr_read_sync(dev, @buf, outi, @i);
  if (i < outi)or (h=-1) then begin
   flag:= true;
   form1.button1.Enabled:= true;
  end;
  // теперь циклическая запись в файловый поток
  mem.Write(buf, BUF_LENGTH);
  if (mem.Position+BUF_LENGTH)>mem.size then
   mem.Position:= 0;
  // подчищаем
  setlength(dim, 0);
  fillchar(a,sizeof(a),0);
  fillchar(b,sizeof(b),0);
  fillchar(qcos,sizeof(qcos),0);
  fillchar(qsin,sizeof(qsin),0);
  fillchar(bpf,sizeof(bpf),0);
 
  // FFT
  for i:= 0 to length(buf)-1 do begin
   setlength(dim, length(dim)+1);
   dim[length(dim)-1]:= buf[i];
  end;
 nf:= BufSize;
 quadr(false,nf,qcos,qsin,dim);
 for i:=0 to nf - 1 do begin
   a[i]:= dim[i];
   b[i]:= 0
 end;
 fft(a,b,nf,4,1);
 for i:= 0 to BufSize-1 do begin
    bpf[i]:= sqrt(a[i]*a[i] + b[i]*b[i]);
    if bpf[i] = 0 then bpf[i]:= 1e-100;
    bpf[i]:= (20*log10(bpf[i]/BufSize))
 end;
 // нормировка-
  bpf_max:= -1000;
  for i:= 0 to BufSize - 1 do //search-
   if bpf[i]> bpf_max then
    bpf_max:= bpf[i];
  for i:= 0 to BufSize - 1 do //search-
   if bpf_max>=0 then
    bpf[i]:= bpf[i]- bpf_max;
  // накопление кусков спектра/полос
  for i:= 0 to BufSize -1 do begin
   setlength(mas, length(mas)+1);
   mas[length(mas)-1].f:= f - (polosa div 2) + i*step;
   mas[length(mas)-1].d:= bpf[i];
  end;
  inc(cnt2);

 except end;

 end;
end;
Подключаем донгл, запускаем панорамный анализатор спектра (138..153 МГц) и жмякаем тангенту (по ссылке [7] с обрезкой границ полос для удаления амплитудных минимумов и возможностью циклической записи/воспроизведения RAW отсчетов в/из файла или памяти):



Видеодемонстрация (realtime)




Тематические ссылки
  1. Анонс нашего проекта на официальном ресурсе RTL-SDR
  2. Открыл API доступа к RTL-SDR чипу DVB-тюнера под Delphi
  3. Панорамный анализатор спектра из DVB-T тюнера
  4. Обновление 'RTL-SDR Panoramic Spectrum Analyzer' 
  5. Панорамный анализатор спектра с прямым доступом к чипу RTL-SDR
  6. Ресурсы проекта панорамного FFT анализатора и циклической записи/воспроизведения выборок в файл на SourceForge
  7. Циклическая запись/воспроизведение в панорамном FFT анализаторе с прямым доступом к чипу

Комментариев нет:

Отправить комментарий

В комментариях уважайте собеседника, внимательно читайте посты и не додумывайте. Просьбы и предложения из разряда: «можно ваш Skype/Viber/телефон», «напишите мне в vk/FB», а также другие им подобные — игнорируются. Выход новых версий ПО, внешняя ссылка, переставшая работать с течением времени и т.п. не является основанием для претензий. Желающие спокойно подискутировать и высказаться — Welcome. Желающие спонсировать блог — Donate. Нарушение этих простых правил ведет к бану и удалению комментариев без предупреждения.