Любительский пеленгатор


https://vhfdx.in.ua/index.php/pub/tech/28-lyubitel-skij-pelengator
В процессе подготовки к запускам воздушных шаров возникла задача определения направления на шар, который передает телеметрию по радиоканалу. Для этих целей был собран простой квазидопплеровский пеленгатор.

Доплеровский пеленгатор относится к фазовым пеленгаторам, которые извлекают информацию о направлении распространения  электромагнитной волны из пространственного расположения линий или поверхностей с одинаковой фазой. Принцип действия такого пеленгатора сводится к использованию обусловленной эффектом Допплера фазовой модуляции, возникающей при круговом вращении приемной антенны.  Относительное перемещение приемника и передатчика приводит к изменению частоты (фазы) принимаемых колебаний. В этом случае частота принимаемых колебаний меняется и отличается от частоты передатчика.

doppler.jpg

Обусловленное вращением антенны приращение фазы наводимой в ней ЭДС  отрицательно в промежутки времени, когда антенна удаляется от передатчика. В этом случае проекция вектора скорости движения антенны на линию OO' совпадает с направлением распространения радиоволны (рис. 1). Приращение фазы положительно, когда антенна приближается к передатчику, и равно нулю, когда антенна движется перпендикулярно направлению распространения.

Если принятые колебания усилить и подать на фазовый детектор, то при малых значениях R/? на выходе детектора можно получить колебания модулирующей частоты ?, фаза которых соответствует азимуту на пеленгуемую радиостанцию.

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

Конструкция такого квазидопплеровского пеленгатора была реализована с использованием дешевого SDR приемника, антенного коммутатора на микросхеме HMC253, переключением которого управляет контроллер Arduino Nano,  а обработка и отображение информации на экране обеспечивает программа, созданная в Gnuradio, установленного на микрокомпьютере под управлением одного из клонов ОС Linux (структурная схема представлена ниже). 

df-sh.jpg


Антенный коммутатор установлен на монтажной плате вместе с SMA разъемами для подключения антенн по ВЧ кабелям (см. фото платы ниже) и управляется сигналами, которые подаются с  Arduino Nano.
hmc253.jpg
 

Arduino получает аудио сигнал от микрокомпьютера, предварительно преобразованного из синусоидального сигнала в меандр с помощью триггера Шмитта на микросхеме 7414. Скетч (программа Arduino) вначале выполняет измерение входной частоты и определяет с помощью цифрового компаса начальный азимут установленной антенны. После этого скетч определяет по возрастанию переднего фронта меандра начало цикла коммутации антенн и формирует сигналы управления переключения антеннами (выводы 5-8 Arduino Nano), которые соответствуют 1/4 периода входного сигнала (для количества антенн равного 4).

Прототип антенного коммутатора представлен на фото ниже:

ant.jpg

Текст скетча:

Код


/* UKRHAB copyright (C) 2013 UY0LL Alexander Doschich
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU General Public License for more details.
*/

// Frequency counter sketch, for measuring frequencies low enough to execute an interrupt for each cycle
// Connect the frequency source to the INT0 pin (digital pin 2 on an Arduino Nano)
#include <digitalWriteFast.h>
#include <Wire.h>
#include <HMC5883L.h>

#define HEADING_MULTIPLIER 1

HMC5883L compass;
int ant, ant_prev, timer;
volatile unsigned long firstPulseTime;
volatile unsigned long lastPulseTime;
volatile unsigned long numPulses;
float freq, n_ant=4;

unsigned int latency;
unsigned int latencySum;
unsigned int sampleCount;
unsigned char timerLoadValue;

#define TIMER_CLOCK_FREQ 250000.0 //250kHz for /64 prescale from 16MHz

// interrupt service routine (ISR) is called when the timer interrupt
ISR(TIMER2_COMPA_vect)
  {
  switch(ant)
  {
  case 5: {  
  digitalWriteFast(6,HIGH);  
  digitalWriteFast(5,LOW);
  // EIMSK |= (1 << INT0); // Разрешить прерывание
  ant_prev = 5;
  ant++;
  SetupTimer2(freq*n_ant); // Разрешить прерывание
  break;
  }
  case 6: {
  digitalWriteFast(7,HIGH);
  digitalWriteFast(6,LOW);
  ant_prev++;
  ant++;
  SetupTimer2(freq*n_ant); // Разрешить прерывание
  break;
  }
  case 7: {
  digitalWriteFast(8,HIGH);
  digitalWriteFast(7,LOW);
  TIMSK2 = 0x00;
  break;
  }
  }
  }

//Setup Timer2.
//Configures the ATMegay168 8-Bit Timer2 to generate an interrupt at the specified frequency.
//Returns the time load value which must be loaded into TCNT2 inside your ISR routine.
//See the example usage below.
unsigned char SetupTimer2(float timeoutFrequency){
unsigned char timer;

//Calculate the timer load value
timer = (int)((TIMER_CLOCK_FREQ/(timeoutFrequency*1.02))+0.5); //the 0.5 is for rounding;

  //Timer2 Settings: Timer Prescaler /8, mode 0
  //Timmer clock = 16MHz/64 = 250khz or 4us
  //The /8 prescale gives us a good range to work with so we just hard code this for now.
  TCCR2B = 0<<CS22 | 1<<CS21 | 1<<CS20;
   
  // set WGM to CTC mode (010)
  // In this mode Timer2 counts up until it matches OCR2A
  // we need to use OCR2A instead of the overflow so we can interrupt
  // more often
  TCCR2A = _BV(WGM21);
   
  OCR2A = timer;

  // When the OCR2A register matches the Timer2 count, cause an interrupt
  TIMSK2 = _BV(OCIE2A);
}

void isr()
{
  unsigned long now = micros();
  if (numPulses == 1)
  {
  firstPulseTime = now;
  }
  else
  {
  lastPulseTime = now;
  }
  ++numPulses;
}

void audio()
{
  digitalWriteFast(5,HIGH);
  digitalWriteFast(8,LOW);
  ant_prev = 8;
  ant = 5;

  SetupTimer2(freq*n_ant); // Разрешить прерывание
}

// Measure the frequency over the specified sample time in milliseconds, returning the frequency in Hz
float readFrequency(unsigned int sampleTime)
{
  numPulses = 0; // prime the system to start a new reading
  attachInterrupt(0, isr, RISING); // enable the interrupt
  delay(sampleTime);
  detachInterrupt(0);
  return (numPulses < 3) ? 0 : (1000000.0 * (float)(numPulses - 2))/(float)(lastPulseTime - firstPulseTime);
}

void setupHMC5883L(){
  //Setup the HMC5883L, and check for errors
  int error;  
  error = compass.SetScale(1.3); //Set the scale of the compass.
  if(error != 0) Serial.println(compass.GetErrorText(error)); //check if there is an error, and print if so

  compass.SetMeasurementMode(Measurement_Continuous); // Set the measurement mode to Continuous
}

float getHeading(){
  //Get the reading from the HMC5883L and calculate the heading
  MagnetometerScaled scaled = compass.ReadScaledAxis(); //scaled values from compass.
  float heading = atan2(scaled.YAxis, scaled.XAxis);

  // Correct for when signs are reversed.
  if(heading < 0) heading += 2*PI;
  if(heading > 2*PI) heading -= 2*PI;

  return heading * RAD_TO_DEG; //radians to degrees
}

void setup()
{
  int i=0;
  float freq1=0, azimuth = 0;
  String azimuth_string;
   
  Wire.begin();
  Serial.begin(19200); // this is here so that we can print the result
   
  compass = HMC5883L(); //new instance of HMC5883L library
  setupHMC5883L(); //setup the HMC5883L
   
  pinMode(5, OUTPUT);  
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);  
  pinMode(8, OUTPUT);
  digitalWrite(5,0);
  digitalWrite(6,0);
  digitalWrite(7,0);
  digitalWrite(8,0);  
   
  while(i <= 19)
  {
  azimuth = azimuth + getHeading();
  i++;
  delay(100);
  }
  azimuth=azimuth/20;
   
// Serial.println(azimuth);
// The C command that reports azimuth

  Serial.print(F("Az= "));
  azimuth_string = String(int(azimuth/HEADING_MULTIPLIER), DEC);
  if (azimuth_string.length() == 1) {
  Serial.print(F("00"));
  } else {
  if (azimuth_string.length() == 2) {
  Serial.print(F("0"));
  }
  }
  Serial.print(azimuth_string);
  Serial.println("grad");

  while(readFrequency(1000) < 200) { delay(1000); }
  freq = readFrequency(1000);
  while( (int)freq != (int)freq1 ){
  freq1=freq;
  freq = readFrequency(1000);
  delay(1000);
  }
  Serial.print("Freq= ");
  Serial.print(freq);
  Serial.println("Hz");
  attachInterrupt(0, audio, RISING); // enable the interrupt

}

void loop()
{  

}

Принятый радиосигнал с антенного коммутатора поступает на SDR приемник и полученный поток IQ данных попадает через USB порт на компьютер для обработки и отображения результатов измерения. Программа обработки в виде флоуграфа Gnuradio представлена ниже (тект в файле df.grc):


df.png

Программа выполняет следующие задачи :
- формирует аудиосигнал управления переключением антенн;
- получает поток данных от SDR приемника и демодулирует узкополосную ЧМ;
- с помощью DSP выделяет из аудио сигнала частотную составляющую, соответствующую опорному сигналу антенного коммутатора;
- определяет разность фаз между опорным сигналом и частотной составляющей опорного сигнала в принятом радиосигнале;
- отображает спектр сигнала и направление на источник сигнала.

Пример отображения процесса измерений в прилагаемом видео:
http://vhfdx.in.ua/images/public/pbl/df/phase.flv

phase.jpg

  Портативный вариант комплекта, состоящего из микрокомпьютера и LCD экрана:
 
R-Pi.jpg


[ На главную] [ В раздел Пеленгация]