/* * Einfacher Navtex-Empfaenger * * von Martin Kuettner 03/2016 * * * Portierung des 80C51 Assembler Programms aus "A NAVTEX Receiver for the DXer": * * Klaus Betke, Am Entengrund 7, D-26160 Bad Zwischenahn, Email: betke@itap.de * 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 // 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; } }