Skip to content

Instantly share code, notes, and snippets.

@speters
Created August 6, 2018 05:43
Show Gist options
  • Save speters/583fcf2e1852f3eb1f7a178a84620cce to your computer and use it in GitHub Desktop.
Save speters/583fcf2e1852f3eb1f7a178a84620cce to your computer and use it in GitHub Desktop.
/*
* Einfacher Navtex-Empfaenger
*
* von Martin Kuettner <[email protected]> 03/2016
*
*
* Portierung des 80C51 Assembler Programms aus "A NAVTEX Receiver for the DXer":
*
* Klaus Betke, Am Entengrund 7, D-26160 Bad Zwischenahn, Email: [email protected]
* 11-AUG-00 / 01-OCT-00
*
* Die Portierung in C lauffaehig auf einem ATmega32U4 (Arduino Mirco). Andere Arduino Modelle,
* welche auf dem ATmega32U4 basieren sollten das Programm auch verarbeiten koennen.
* Bitte auf eventuelle andere Konfiguration der Ausgangspins achten.
*
* Entwicklungsumgebung: Arduino v1.67
*
* Entfernt: Uhrzeitanzeige
* Da der ATmega32U4 keine Echtzeituhr besitzt und die Auswertung auf einem Raspberry PI mit GPS Modul
* geschieht, wird keine Uhr auf dem ATmega32U4 benoetigt.
*
* Das Programm ist optimierungsfaehig, ich habe es so geschrieben, dass ich den Quelltext auch
* ohne Kommentare verstehe. Deshalb wurde auch an einigen Stellen bewusst geschrieben (if ((byte & 0b111100) > 0))
* bzw. auf Verkuerzungen, wie i += 5 verzichtet!
*
* Wer moechte kann das alles gern optimieren :)
*
*/
// zum Copy&Paste fuers debugging
/*
digitalWrite(DebugPin, HIGH);
delayMicroseconds(100);
digitalWrite(DebugPin, LOW);
*/
/*
tobinstr(sirawdata,8,uart);
sprintf(uart,"%s\n",uart);
Serial1.write(uart);
*/
#include <EEPROM.h>
// Pin Definitionen
const byte DebugPin = 13; // Pin mit eingebauer LED auf dem Micro
const byte DataPin = 7; // Datenpin vom Empfaenger
const byte ErrorLEDPin = 12; // Wenn Error gesetzt wird
const byte SyncLEDPin = 11; // Wenn Daten empfangen werden
const byte Do518kHzLEDPin = 10; // Wenn auf 518kHz empfangen wird (Standard)
const byte Do518kHzSetPin = 8; // Ausgang fuer Teiler nach der PLL
const byte DataLEDPin = 9; // Daten LED .. zum blinkern...
const byte UpperLowerInPin = 4; // Eingangspin fuer Ausgabe Gross oder Klein
const byte FrequenzInPin = 6; // Eingangspin Umschaltung 490/518kHz
// Datenport - internes Register
bool InPort; // interne Datenvariable zur uebergabe
// Arbeiter
byte AA; // Arbeitsregister fuer externe Requests
unsigned int AB; // Arbeitsregister fuer DataPin Interrupt
byte AC; // Arbeitsregister fuer Puffer beim Bitholen
byte AD; // Arbeitsregister fuer Bitschieben bei Byteabholen
bool debug = LOW;
bool FF = LOW;
bool InWait;
// Sync & Clock
byte loopadjust = 255; // Zaehler zum Clockschieben-Ausloeser
byte loopgain = 128; // Regler, wie oft nachgeregelt werden soll
byte clock3200 = 0b00100000;// Taktgeber fuer 10ms Takt (3,2kHz / 32)
// Laufvariablen
byte i; // Runner fuer loops
// Daten
byte sirawdata; // rohdaten, die in Timerinterrupt geholt werden
byte bitsavailable; // Zaehler wie viel Bits verfuegbar sind (max 8)
byte error; // Fehlerzaehler fuer Empfang
byte RingBuffer[3]; // 3 Byte Ringpuffer zur Fehlervorwaertskorrektur
byte RXByte; // Empfangenes Byte auf dem RX Kanal
byte CharRingBuffer; // Zeichen, welches aus dem Ringbuffer stammt (zur Fehlerkorrektur)
byte CharRX; // Zeichen, welches empfangen wurde
char ReceivedChar; // Zeichen, was an die UART ausgegeben wird
// Konstanten, Steuerzeichen aus dem SITOR Code (alles, wo "1" in der LUT steht)
// diese Zeichen werden nicht ausgegeben, dienen nur zur internen Steuerung
const byte ALPHA = 0x0F;
const byte ALPHAUP = 0x7F;
const byte REP = 0x66;
const byte LRTS = 0x5A;
const byte FIGS = 0x36;
const byte LF = 0x6c;
const byte CR = 0x78;
const byte CHAR32 = 0x6A;
const byte SPACE = 0x5C;
const byte BETA = 0x33;
const byte BEL = 0x17;
const char errsymbol = '~'; // Fehlerzeichen ("0" in der LUT)
// Statusbits
bool Shifted = LOW; // Zahlen oder Buchstaben LUT nutzen?
bool Frequenz = HIGH; // Abbruchvariable, wenn Frequenz umgeschaltet wird
bool UpperLower = HIGH; // Abbruchvariable, wenn LUT umgeschaltet wird
bool sync = LOW; // Pruefvariable ob gueltige Sequenz ALPHA-REP-ALHPA ampfangen wurde
bool NotLostSync = HIGH;
// UART Puffer
char uart[255]; // Ausgangs Puffervariable fuer UART
// Es gibt verschiedene Zuordnungen - das ist die Europaeische Variante (ausser 0xd3 - da hab ich das $ gelassen)
// LUT - in Kleinbuchstaben
static const uint8_t lutLOWER[256] =
//0 1 2 3 4 5 6 7 8 9 a b c d e f
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 0
0, 0, 0, 0, 0, 0, 0, 'j', 0, 0, 0, 'f', 0, 'c', 'k', 0, // 1
0, 0, 0, 0, 0, 0, 0, 'w', 0, 0, 0, 'y', 0, 'p', 'q', 0, // 2
0, 0, 0, 1, 0, 'g', 1, 0, 0, 'm', 'x', 0, 'v', 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 'a', 0, 0, 0, 's', 0, 'i', 'u', 0, // 4
0, 0, 0, 'd', 0, 'r', 'e', 0, 0, 'n', 1, 0, ' ', 0, 0, 0, // 5
0, 0, 0, 'z', 0, 'l', 1, 0, 0, 'h', 1, 0, 1, 0, 0, 0, // 6
0, 'o', 'b', 0, 't', 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 8
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, '!', 0, ':', '(', 0, // 9
0, 0, 0, 0, 0, 0, 0, '2', 0, 0, 0, '6', 0, '0', '1', 0, // a
0, 0, 0, 1, 0, '&', 1, 0, 0, '.', '/', 0, '=', 0, 0, 0, // b
0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '\'', 0, '8', '7', 0, // c
0, 0, 0, '$', 0, '4', '3', 0, 0, ',', 1, 0, ' ', 0, 0, 0, // d
0, 0, 0, '+', 0, ')', 1, 0, 0, '#', 1, 0, 1, 0, 0, 0, // e
0, '9', '?', 0, '5', 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 // f
};
// LUT - in Grossbuchstaben
static const uint8_t lutUPPER[256] =
//0 1 2 3 4 5 6 7 8 9 a b c d e f
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 0
0, 0, 0, 0, 0, 0, 0, 'J', 0, 0, 0, 'F', 0, 'C', 'K', 0, // 1
0, 0, 0, 0, 0, 0, 0, 'W', 0, 0, 0, 'Y', 0, 'P', 'Q', 0, // 2
0, 0, 0, 1, 0, 'G', 1, 0, 0, 'M', 'X', 0, 'V', 0, 0, 0, // 3
0, 0, 0, 0, 0, 0, 0, 'A', 0, 0, 0, 'S', 0, 'I', 'U', 0, // 4
0, 0, 0, 'D', 0, 'R', 'E', 0, 0, 'N', 1, 0, ' ', 0, 0, 0, // 5
0, 0, 0, 'Z', 0, 'L', 1, 0, 0, 'H', 1, 0, 1, 0, 0, 0, // 6
0, 'O', 'B', 0, 'T', 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // 7
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 8
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, '!', 0, ':', '(', 0, // 9
0, 0, 0, 0, 0, 0, 0, '2', 0, 0, 0, '6', 0, '0', '1', 0, // a
0, 0, 0, 1, 0, '&', 1, 0, 0, '.', '/', 0, '=', 0, 0, 0, // b
0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '\'', 0, '8', '7', 0, // c
0, 0, 0, '$', 0, '4', '3', 0, 0, ',', 1, 0, ' ', 0, 0, 0, // d
0, 0, 0, '+', 0, ')', 1, 0, 0, '#', 1, 0, 1, 0, 0, 0, // e
0, '9', '?', 0, '5', 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 // f
};
// Initiales Setup der Register
void setup() {
// Pin als Interrupt, sfallende Flanke auf der Datenleitung
attachInterrupt(digitalPinToInterrupt(DataPin), EventOnInput, FALLING);
// Pin 13 (der mit LED) als Ausgang (zum Debug)
pinMode(DebugPin, OUTPUT);
// 2 Eingangspins fuer Frequenz und Zeichensatz
pinMode(UpperLowerInPin, INPUT);
pinMode(FrequenzInPin, INPUT);
// weitere Pins als Ausgang konfigurieren
pinMode(ErrorLEDPin, OUTPUT);
pinMode(SyncLEDPin, OUTPUT);
pinMode(Do518kHzLEDPin, OUTPUT);
pinMode(Do518kHzSetPin, OUTPUT);
pinMode(DataLEDPin, OUTPUT);
// Spielerrei...
for (i=0; i<5; i++) {
digitalWrite(Do518kHzLEDPin, HIGH);
delay(50);
digitalWrite(DataLEDPin, HIGH);
delay(50);
digitalWrite(SyncLEDPin, HIGH);
delay(50);
digitalWrite(ErrorLEDPin, HIGH);
delay(50);
digitalWrite(Do518kHzLEDPin, LOW);
delay(50);
digitalWrite(DataLEDPin, LOW);
delay(50);
digitalWrite(SyncLEDPin, LOW);
delay(50);
digitalWrite(ErrorLEDPin, LOW);
delay(50);
}
// UART - 9600 BAUD
Serial1.begin(9600);
//letzte Einstellungen aus dem EEProm holen
Frequenz = EEPROM.read(0);
UpperLower = EEPROM.read(1);
if (UpperLower == LOW) {
Serial1.write("---Lowercased---\n");
} else {
Serial1.write("---Uppercased---\n");
}
if (Frequenz == LOW) {
Serial1.write("---490kHz---\n");
digitalWrite(Do518kHzLEDPin, LOW);
digitalWrite(Do518kHzSetPin, LOW);
} else {
Serial1.write("---518kHz---\n");
digitalWrite(Do518kHzLEDPin, HIGH);
digitalWrite(Do518kHzSetPin, HIGH);
}
// Interrupt Timer Setup
DDRC |= (bit(7) | bit(6));
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS10); // Timer 1 ohne prescale (laeuft auf 16MHz)
OCR1A = 5000; // Nach 5000 Taktzyklen (also alle 312,5us | 16MHz) Interrupt ausloesen
TIMSK1 |= (1 << OCIE1A);
}
// Hauptprogramm
void loop() {
Serial1.write("---Loop Start---\n");
// wenn aenderung an Frequenz- oder Zeichensatz-Pin
if (AA > 0) {
if ((AA & 0b01) == 0b01) { // Zeichensatz umschalten
if (UpperLower == LOW) {
Serial1.write("---Lowercased---\n");
EEPROM.write(1, 0);
} else {
Serial1.write("---Uppercased---\n");
EEPROM.write(1, 1);
}
}
if ((AA & 0b10) == 0b10) { // zu empfangende Frequenz umschalten
if (Frequenz == LOW) {
Serial1.write("---490kHz---\n");
digitalWrite(Do518kHzLEDPin, LOW);
digitalWrite(Do518kHzSetPin, LOW);
EEPROM.write(0, 0);
} else {
Serial1.write("---518kHz---\n");
digitalWrite(Do518kHzLEDPin, HIGH);
digitalWrite(Do518kHzSetPin, HIGH);
EEPROM.write(0, 1);
}
NotLostSync = HIGH; // verhindern, dass Error LED angeht
}
AA = 0;
}
/*
* Error LED schalten, wenn kein EOT empfangen wurde.
* bleibt an, bis naechster Sync erreicht wurde.
* evtl. mal umbauen und ueber Bitclock einen int16 Zaehler runterlaufen lassen,
* der dann die LED nach einer gewissen Zeit abschaltet...
*/
if ((error > 16) || (NotLostSync == LOW)) {
digitalWrite(ErrorLEDPin, HIGH);
} else {
digitalWrite(ErrorLEDPin, LOW);
}
digitalWrite(DataLEDPin, LOW);
loopgain=96; // so lange kein Sync - oft nachregeln!
loopadjust=255; // Zaehler zuruecksetzen
Shifted = LOW; // Auf Buchstaben LUT stellen
NotLostSync = LOW;
sync = LOW; // kein sync
InWait = HIGH;
while (sync != HIGH) { // uC bleibt in dieser Schleife, bis ALPHA-REP-ALPHA kommt
while (GetOneBit() != ALPHA) { // Bitweise schauen, ob ALPHA empfangen wurde
CheckInput(); // Pruefe, ob sich an Zeichensatz-/Frequenz-Pin was geaendert hat
if (AA > 0) {
break;
}
}
InWait = LOW;
if (AA == 0) { // ueberspingen, wenn Konfig anders werden soll!
sync = HIGH; // sync high, wird low, wenn die naechsten Zeichen nicht passen
if (Get7Bits() != REP) { // 7 Bits abholen und auf REP Pruefen
sync = LOW;
} else {
RingBuffer[0] = REP; // Falls wahr, Ringpuffer befuellen
}
if (Get7Bits() != ALPHA) { // 7 Bits abholen und auf ALPHA Pruefen
sync = LOW;
}
}
if (AA > 0) { // while beenden, wenn Konfig anders werden soll!
break;
}
}
if (AA == 0) {
Serial1.write("\n---InSync---\n"); // Debug String
loopgain=8; // Sync da -> Reglung entschaerfen
error = 0; // Fehlerzaehler auf 0
digitalWrite(ErrorLEDPin, LOW); // Error aus
digitalWrite(SyncLEDPin, HIGH); // Sync LED an
RingBuffer[1] = 0; // Ringbuffer[1] leeren, damit evtl. altes Zeichen uebertragung nicht abbrechen laesst
/*
// debug - nur die Bitfolgen ausgeben - dazu muss der untere while-Teil jedoch auskommentiert werden!
tobinstr(CharRX,7,Get7Bits(););
sprintf(uart,"%s\n",CharRX);
Serial1.write(uart);
// debug Ende
*/
// ab hier immer 2 Zeichen abholen
while (error < 16) { // Schleife laeuft bis Abbuch, oder Fehler >= 32
RingBuffer[2] = RingBuffer[1];
RingBuffer[1] = RingBuffer[0];
RingBuffer[0] = Get7Bits(); // Ringpuffer schieben und mit neuem Zeichen befuellen
RXByte = Get7Bits(); // RX Puffer mit neuem Zeichen Fuellen
/*
* Der Empfang funktioniert so:
* Es wird um 3 Zeichen versetzt wiederholt gesendet.
* Damit kann man das als 2 ineinander gemultiplexte Kanaele sehen
* Trennt man das auf, schaut es so aus:
* - Startsequenz -
* ALPHA ALPHA ALPHA ALPHA K U T T _ I S
* REP REP REP K U T T _ I S T
* Der untere Kanal ist der Ringpuffer, der obere der Empfangskanal
* Es wird immer ein Zeichen aus dem oberen und unteren geholt und geschaut, was 3 Zeichen
* vorher auf dem anderen Kanal empfangen wurde
*/
if (RXByte != ALPHA) { // So lange ALPHA auf dem RXbyte steht, wird immer noch Synchronisation gesendet
ReceivedChar = ByteToASCII(); // Byte in Char wandeln
if ((ReceivedChar != 0) && (ReceivedChar != 1)) { // wenn gueltig ..
Serial1.write(ReceivedChar); // ..an UART ausgeben
if (FF == LOW) {
digitalWrite(DataLEDPin, LOW);
FF = HIGH;
} else {
digitalWrite(DataLEDPin, HIGH);
FF = LOW;
}
}
if (RingBuffer[2] == ALPHA) { // Abbruchbedingung, wenn uebertragung zu Ende ist
Serial1.write("\n---EOT---\n");
digitalWrite(ErrorLEDPin, LOW);
NotLostSync = HIGH;
error = 0;
break;
}
}
}
Serial1.write("\n---OutSync---\n");
digitalWrite(SyncLEDPin, LOW);
}
}
// Wandeln eines Bytes zu Char incl. Fehlerkorrektur und Auswertung von Steuerzeichen
char ByteToASCII() {
if (UpperLower == LOW) {
CharRX = lutLOWER[RXByte | (Shifted << 7)]; // RX Puffer in Char wandeln unter Beachtung des zu verwendeten Zeichensatzes
CharRingBuffer = lutLOWER[RingBuffer[2] | (Shifted << 7)]; // RingPuffer in Char wandeln unter Beachtung des zu verwendeten Zeichensatzes
} else {
CharRX = lutUPPER[RXByte | (Shifted << 7)]; // RX Puffer in Char wandeln unter Beachtung des zu verwendeten Zeichensatzes
CharRingBuffer = lutUPPER[RingBuffer[2] | (Shifted << 7)]; // RingPuffer in Char wandeln unter Beachtung des zu verwendeten Zeichensatzes
}
if ((CharRX == 0) && (CharRingBuffer == 0)) { // beide ungueltig :/
error++;
return errsymbol; // error erhoehen und Fehlerzeichen ausgeben
} else if ((CharRX == 0) && (CharRingBuffer != 0)) { // RX ungueltig aber RingBuffer OK
CharRX = CharRingBuffer; // Zeichen umkopieren
RXByte = RingBuffer[2]; // RXByte umkopieren fuer switch-Abfrage...
if (error > 0) {
error--; // Fehlerzaehler um eins erringern
}
}
if (CharRX == 1) { // wenn aus der LUT 1 kommt = Steuerzeichen
switch(RXByte) {
case LRTS: // benutze Zeichen
Shifted = LOW;
break;
case FIGS: // benutze Zahlen (offset 0x80h)
Shifted = HIGH;
break;
case BETA:
case ALPHA:
case ALPHAUP:
case REP:
case CR:
case BEL:
break; // Zeichen fuer Ausgabe irrelevant
case LF:
return '\n'; // Neue Zeile
break;
case CHAR32:
case SPACE:
return ' '; // Leerzeichen
default:
sprintf(uart,"Unknown: 0x%x\n",RXByte);
Serial1.write(uart);
break; // Zeichen unbekannt = Error
return 0;
}
} else {
return CharRX;
}
}
byte GetOneBit() {
while (bitsavailable == 0) {
delay(1);
}
if (AA == 0) {
switch(bitsavailable) { // je nach Puffergroesse Zeichen auswaehlen und zurueckgeben
case 7:
AC = AC << 1;
AC = AC | ((sirawdata & 0b00000001) << 6);
bitsavailable--;
break;
case 6:
AC = AC << 1;
AC = AC | ((sirawdata & 0b00000010) << 5);
bitsavailable--;
break;
case 5:
AC = AC << 1;
AC = AC | ((sirawdata & 0b00000100) << 4);
bitsavailable--;
break;
case 4:
AC = AC << 1;
AC = AC | ((sirawdata & 0b00001000) << 3);
bitsavailable--;
break;
case 3:
AC = AC << 1;
AC = AC | ((sirawdata & 0b00010000) << 2);
bitsavailable--;
break;
case 2:
AC = AC >> 1;
AC = AC | ((sirawdata & 0b00100000) << 1);
bitsavailable--;
break;
case 1:
AC = AC >> 1;
AC = AC | (sirawdata & 0b01000000);
bitsavailable--;
break;
case 0:
AC = 0;
break;
default:
bitsavailable = 0; // wenn Pufferueberlauf Puffer zuruecksetzen und Fehler auf UART ausgeben
AC = 0;
Serial1.write("ERROR! bitsavailable overflow!\n");
break;
}
AC = AC & 0b01111111; // oberstes BIT weg-und-en, nur fuer den Fall dass es gesetzt ist
return AC;
}
}
// 7 Bits in einem Rutsch holen und zurueckgeben
byte Get7Bits() {
i = 7;
AD = 0;
while (i > 0) {
AD = GetOneBit();
i--;
}
return AD;
}
// Interrupt, wenn fallende Flanke an DatenPin
void EventOnInput()
{
AB = loopadjust + loopgain; // Verstaerkung draufaddieren
loopadjust = AB & 0x00FF; // obere 8 Bit loeschen und zurueckschreiben (aus Ermangelung eines Carry-Flags)
if ((AB & 0xFF00) > 0) { // wenn ueber 255, dann nachregeln, sonst nicht
if (clock3200 < 16) { // wenn Takt zu langsam
clock3200++; // Zaehler vorregeln
} else if (clock3200 > 16) {
clock3200--; // sonst gegenregeln
}
}
}
// 3200kHz Timer Interrupt
ISR(TIMER1_COMPA_vect)
{
clock3200--; // Clock verkleinern .. es wird Faktor 32 ueberabgetastet
if (clock3200 == 0) {
InPort = digitalRead(DataPin);
/*
* wenn auf 490kHz, dann ist 1 und 0 vertauscht
* das liegt an der Referenzfrequenz, die bei 518kHz mit 518,4kHz leicht ueber, und
* bei 490kHz mit 489,6kHz leicht unter der Traegerfrequenz liegt.
* Dadurch wird das Signal invertiert (was man auch ueber eine andere LUT ausbuegeln koennte)
* geht auch mit InPort = InPort ^ 1; ... was solls ...
*/
if (Frequenz == LOW) {
if (InPort == LOW) {
InPort = HIGH;
} else {
InPort = LOW;
}
}
clock3200 = 0b00100000; // Zaehler zuruecksetzen
// Daten in Ringpuffer schieben und Zaehler erhoehen. Es konnen 8 Bit zwischengelagert werden (80ms)
sirawdata = ((sirawdata >> 1) | (InPort << 7)) & 0b11111111;
bitsavailable++;
}
}
// debugroutine, um INT zu binaer-String zu wandeln
void tobinstr(int value, int bitsCount, char* output)
{
int i;
output[bitsCount] = '\0';
for (i = bitsCount - 1; i >= 0; --i, value >>= 1)
{
output[i] = (value & 1) + '0';
}
}
// pruefe, ob sich am Frequenz- oder Zeichensatz-Pin was getan hat
void CheckInput() {
AA = 0;
if (digitalRead(UpperLowerInPin) != UpperLower) {
UpperLower = digitalRead(UpperLowerInPin);
AA = AA | 0b01;
}
if (digitalRead(FrequenzInPin) != Frequenz) {
Frequenz = digitalRead(FrequenzInPin);
AA = AA | 0b10;
}
}
@speters
Copy link
Author

speters commented Aug 6, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment