Datenübertragung im Subaudiobereich

Im Amateurfunk wird es auf den frequenzmodulierten VHF und UHF-Bändern immer enger. Die Bandbreiten schrumpfen. Wenn man also mal eine Datenübertragung parallel zum Träger und der Sprachsignale übertragen will (ähnlich RDS im UKW-Rundfunk), bleibt eigentlich nur noch der Bereich unter 300 Hertz. Normalerweise ist dieser Bereich für CTCSS-Töne “reserviert”. Also nutzen wir sie doch experimentell… Sprachfrequenzen werden im Audiobereich 300 … 2700 Hz übertragen. Da stört sich also nix.

Hier eine Idee, wie man z.B. einen Arduino Nano beibringen könnte Daten in diesem Bereich dem Trägersignal hinzuzusetzen. Die eingebaute Tone()-Funktion erzeugt 5V-Rechtecksignale von 31 … 65535 Hz. So also mal schnell ein Arduino-Sketch niedergeschrieben und in den Nano geschoben:

Fakt ist: Die tone()-Funktion des Arduino kann nur unsigned int Werte annehmen. Bis hinunter zu 31 Hz kann ein Signal unterhalb 300 Hz erzeugt werden. Aber eben nur ganzzahlige Werte. Ein CTCSS-Pilotton von 88,5 Hz ist demnach nicht darstellbar. Was bleibt ist die Erzeugung der 10-fachen Frequenz und dann mit einem Teiler-IC durch 10 zu teilen. Das kann dann ein CD4018 erledigen, also 885 : 10 = 88,5.

Hier die Idee:

CD 4018 Beschaltung als Teiler durch 10

CD 4018 Beschaltung als Teiler durch 10

Hier der aktuelle Arduino-Sketch:

/*
  Dieser Sketch wurde mit dem Arduino Nano entwickelt
  und sollte aber auch auf allen anderen Arduinotypen lauffähig sein.
  Ich hatte eben keinen anderen, die Dinger sind so saubillig geworden...
  Natürlich kann man immer etwas verbessern, also tu es, ich tu auch was
  
  Carsten Koch, DL8AAP - Letzter Stand 22.9.2016
*/

// Der Timer muss eingebunden werden
#include "TimerOne.h"

//            0   1   2   3   4   5   6   7   8   9  Beg End
int T[12] = {915,693,710,719,744,770,797,825,854,885,670,2541}; // CTCSS-Frequenzen mal 10 in Hertz, werden später durch Hardware durch 10 geteilt
int dur = 180;                                                  // Dauer eines Datenbits in ms
int dur_roger = 120;                                            // Dauer des Rogerbeep in ms
int freq_roger = 3000;                                          // Frequenz des Rogerbeep in Hertz
int dur_break = 500;                                            // Pause zum nächsten Datenwort in ms
int pin_id = 10;                                                // Ausgabepin für die zu sendende String-ID
int pin_roger = 11;                                             // Ausgabepin für den Rogerbeep
String ID = "1234567890";                                       // zu sendende Ziffern
String zeichen = "";                                            // Puffervariable
int x;                                                          // Puffervariable
int pttPin = 7;                                                 // Pin 7 ist HIGH, schaltet nach Masse auf LOW als PTT gedrückt
int ledPin = 13;                                                // Visuelle Darstellung des PTT-Status
long intervall = 10000;                                         // Timerintervall in µsek
int send_id = 1;                                                // 0=String-ID aus, 1=String-ID an
int send_roger = 1;                                             // 0=Rogerbeep aus, 1=Rogerbeep an
int play = 0;                                                   // Zusatzvariable für den Rogerbeep

void setup() {
  pinMode(pttPin, INPUT_PULLUP);             // pttPin wird als Input mit einem Pullup-Widerstand auf HIGH gesetzt
  pinMode(ledPin, OUTPUT);                   // ledPin wird als Output gesetzt
  Timer1.initialize(intervall);              // Der Timer wird mit dem Timerintervall gesetzt
  Timer1.attachInterrupt(switch_ptt_state);  // Die Funktion switch_ptt_state wird als Timer-Funktion gesetzt
}

void loop() {
  // In der loop() wird der ID-String an der pin_id ausgegeben
  // aber nur, wenn der Schalter send_id auf 1 steht
  if (send_id == 1) {
    start_id();
    for(int i = 0; i<ID.length(); i++) {
      zeichen = ID[i];
      x = zeichen.toInt();
      play_id(T[x]);
    }
    end_id();
    pause_id();
  }
}

// Weitere eigene Funktionen

void switch_ptt_state() {
  // Diese Funktion wird vorrangig vor der loop()-Funktion aufgerufen
  //digitalWrite(ledPin,digitalRead(ledPin)^1);
  if (digitalRead(pttPin) == LOW) {
    // Wenn die PTT getastet wird = LOW, dann sind wir im TX-Modus
    //ptt = tx;
    digitalWrite(ledPin,HIGH); // Lass die TX-Lampe brennen
    play = 1; // Um den Rogerbeep später auszulösen, wird play auf 1 gesetzt
  }
  else {
    //ptt = rx;
    // Sobald wir im RX Modus sind und der Rogerbeep gesendet werden soll, dann tu das
    if (play==1) {
      // Wenn der Rogerbeep gesendet werden soll,
      if (send_roger == 1) {
        // Dann unterbreche den Pin bei dem, was er grad spielt
        noTone(pin_id);
        // und spiele den Rogerbeep
        play_roger(); 
      }
      // Der Rogerbeep wurde erzeugt, play kann wieder auf 0 gesetzt werden
      play = 0;
    }
    // Schalte die TX-Lampe aus
    digitalWrite(ledPin,LOW);
  }
}

void start_id() {
  // Startsignal für Datenwort
  // wird doppelt so lange gespielt, wie ein Datenbit
  tone(pin_id,T[10],dur*2);
  delay(dur*2+10);
}

void play_id(unsigned int f) {
  // Datenbit eines Datenworts
  // f=übergebene Frequenz des Tones
  tone(pin_id,f,dur);
  delay(dur+10);
}

void end_id() {
  // Endsignal für Datenwort
  // wird doppelt so lange gespielt, wie ein Datenbit
  tone(pin_id,T[11],dur*2);
  delay(dur*2+10);
}

void pause_id() {
  // Pause zum nächsten Datenwort
  noTone(pin_id);
  delay(dur_break);
}

void play_roger() {
  // Rogerbeep
  tone(pin_roger,freq_roger,dur_roger);
  delay(dur+10);
}

Danach durchläuft das Signal einen aktiven Tiefpassfilter 4. Ordnung mit einer Grenzfrequenz von 300 Hz nach vorheriger Verstärkung von 3 dB. Das Signal des Rogerbeeps (ca. 1 … 3 kHz) wird daran vorbei geschleust. Damit werden dann alle Obertöne des Rechtecksignals unterdrückt. Im Idealfall bleibt ein (relativ) sauberer Sinus als Nutzsignal.

Auf http://www.analog.com/designtools/en/filterwizard/ kann man sich seinen Filter zusammen stellen. Die Ergebnisse für meine Zwecke sehen dann in der Theorie wie folgt aus:

TP-Filter Vorgaben

TP-Filter Vorgaben 3dB Verstärkung mit 300 Hz Grenzfrequenz, 40 dB Dämpfung bei 1 kHz

TP-Filter 4. Ordnung nach Sallen Key

TP-Filter 4. Ordnung nach Sallen Key

TP-Filter 2 x 2. Ordnung

TP-Filter 2 x 2. Ordnung

TP-Filter zu erwartende Kurve

TP-Filter zu erwartende Kurve

Eine entsprechende Schaltung habe ich mal mit LTSpice simuliert. Dabei habe ich die Werte eines TL084 OP benutzt und komme auf die gleichen Filterergebnisse.

300 Hz Tiefpassfilter in der Spice-Simulation

300 Hz Tiefpassfilter in der Spice-Simulation

250 Hz-Signal - rot=Input, grün = Output,

250 Hz-Signal – rot=Input, grün = Output,

Das 500 Hz-Signal (grün) wird schon deutlich vom Eingangssignal (rot) gedämpft

Das 500 Hz-Signal (grün) wird schon deutlich vom Eingangssignal (rot) gedämpft

Deutlich ist die Dämpfung des Signals von fast 50 dB bei 1 kHz zu erkennen.

Deutlich ist die Dämpfung des Signals von fast 50 dB bei 1 kHz zu erkennen.

Blockschaltbild Subaudio-Encoder

Blockschaltbild Subaudio-Encoder mit Rogerbeep

Soweit die Theorie…

Wohlgemerkt, es handelt sich hier zunächst erstmal um einen Encoder. Um das Decodieren kümmern wir uns an anderer Stelle.

Nun geht’s an die Praxis!

Kommentar verfassen