воскресенье, 16 апреля 2017 г.

Циклическая запись/воспроизведение в панорамном FFT анализаторе с прямым доступом к RTL-SDR




Предпосылки реализации...

Циклическая непрерывная запись (например выборок с АЦП тюнера SDR) необходима в следующих случаях:
  1. Ограниченность ресурсов железа (памяти и/или харда).
  2. Требуется лишь отследить некоторое событие (сигнал) на ограниченном временном участке, когда нет необходимости вести архивацию на протяжении всего времени работы и заранее неизвестно время наступления этого события. 
Самое простое и логичное - использовать последовательную запись плавающим окном длиной равной размерности получаемого буфера данных (сэмпла) на фиксированном участке памяти. При достижении границы памяти, перемещать указатель для записи на начало и продолжать запись, тем самым затирая самые старые (ранее записанные) данные. По сути запись по кругу. В случае ограниченности RAM можно использовать файловые потоки через класс TFileStream и писать сразу на винчестер в режиме немонопольного доступа, имея для работы сторонних приложений готовый файл. Если же поток принимаемых данных в единицу времени слишком велик, а быстродействие и ресурсы винчестера* ограничены, то следует воспользоваться записью непосредственно в RAM, используя класс TMemoryStream.
* Следует отметить, что современные SSD самой распространенной емкости 240 ГБ базируются на ячейках TLC NAND, выполненные по 16-нм технологии, гарантирующей ~60-ти терабайтный ресурс записи в течение трех лет.
Практика

Данный сабж является логическим продолжением материала с тем лишь отличием, что запись и воспроизведение реализованы одним условием в потоке получения выборок по логическому флагу flag_read. Использование метода Write() производит запись буфера по начальному адресу и сдвигает каждый раз указатель на величину размерности этого буфера. К примеру, мы начали записывать с нулевого адреса буфер размерностью BUF_LENGTH. При этом будут заполнены ячейки памяти с нулевой по BUF_LENGTH-1, а указатель сместится на адрес BUF_LENGTH. При последующем вызове Write() заполнятся ячейки с адреса BUF_LENGTH по BUF_LENGTH*2-1 и т.д.
...
procedure TTH;
var k,i, kus: integer;
    d: double;

    bpf_max,signoise, re,druc, druc2: double;
    exp,mant,lab:string;

    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 if not stop then begin
  // сканер
  f:= round(minf + ((polosa/2)*cnt));
  // следующий блок частот
  cnt:= cnt + 2;

  // визуализация
  if (f>=maxf) then begin
   cnt:= 1; 
   prev:= mem.position;

   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
   
  if not flag_read then begin
   // задаем центральную частоту для сканирования, Hz
   rtlsdr_set_center_freq(dev, f);
   // задаем полосу
   rtlsdr_set_sample_rate(dev, polosa);
   // сброс буфера
   rtlsdr_reset_buffer(dev);

   // квадратуры

   h:= rtlsdr_read_sync(dev, @buf, outi, @ii);
   if (ii < outi)or (h=-1) then begin
    flag:= true;
   end;

   // теперь циклическая запись в файловый поток
   mem.Write(buf,BUF_LENGTH);
  end else begin // циклическое чтение из потока
   if flg then begin
    flg:= false;
    mem.Position:= 0;
   end;
   mem.Read(buf, BUF_LENGTH);
   //mem.ReadBuffer(buf, BUF_LENGTH);
  end;

  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);

   for i:= 0 to length(buf)-1 do begin
    setlength(dim, length(dim)+1);
    dim[length(dim)-1]:= buf[i];
   end;

   quadr(false,BufSize,qcos,qsin,dim);
   for i:=0 to BufSize - 1 do begin
    a[i]:= dim[i];
    b[i]:= 0
   end;

   fft(a,b,BufSize,4{5},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 max-
    if bpf[i]> bpf_max then
     bpf_max:= bpf[i];
   for i:= 0 to BufSize - 1 do begin 
    if bpf_max>=0 then
     bpf[i]:= bpf[i]- bpf_max;
   end;

   // отсекаем окрестность на границах сканируемого участка
   // избавляемся от палок
   kus:= 4;
   for i:= 0+kus to BufSize -1-kus do begin // отсекаем зеркалку
    setlength(mas, length(mas)+1);

    mas[length(mas)-1].d:= bpf[i];
    mas[length(mas)-1].f:= f - (polosa div 2) + i*step;
   end;

  except end;
 end; end;
end;
...
Запуск воспроизведения:
...
 stop:= false;

 ii:= 0;
 cnt:= 1;
 flg:= true;
 flag_read:= true;
...
Для того, чтобы не создавать файл дампа каждый раз при перезапуске утилиты и сохранить предыдущую сессию, в событие инициализации добавлено условие на проверку существования этого файла и файл открывается на запись/чтение в режиме немономопольного доступа:
{Также добавим возможность циклической записи данных буфера BUF
 размером 8*16384 байт в файл 'sdrdat' фиксированного размера, скажем 2Gb (при достижении конца файла, указатель перекинем на начало файла) }
 // создаем файловый поток
 if not fileexists('sdr.dat') then begin
  mem:= tfilestream.Create('sdr.dat', fmcreate);
  mem.size:= 2000*1024*1024; //2Gb
  freeandnil(mem);
 end;
 // а теперь открываем для записи-чтения и убираем монопольный доступ
 mem:= tfilestream.Create('sdr.dat', fmOpenReadWrite or fmShareDenyNone);
 mem.Seek(0, soFromBeginning);


забрать сорцы

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

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

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