Instantly share code, notes, and snippets.
Created
October 8, 2019 22:13
-
Star
4
(4)
You must be signed in to star a gist -
Fork
3
(3)
You must be signed in to fork a gist
-
Save hsiboy/1c41457c5ef071a7655f99375660b287 to your computer and use it in GitHub Desktop.
Simple Navtex receiver - https://youtu.be/SwL_ZQ_iBIM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Simple Navtex receiver - https://youtu.be/SwL_ZQ_iBIM | |
* | |
* by Martin Kuettner <[email protected]> 03/2016 | |
* | |
* | |
* Porting the 80C51 Assembler Program from "A NAVTEX Receiver for the DXer": | |
* | |
* Klaus Betke, Am Entengrund 7, D-26160 Bad Zwischenahn, Email: [email protected] | |
* 11-AUG-00/01-OCT-00 | |
* | |
* The port in C can be run on an ATmega32U4 (Arduino Mirco). | |
* Other Arduino models, which are based on the ATmega32U4 should also be able to process the program. | |
* Please pay attention to any other configuration of the output pins. | |
* | |
* Development environment: Arduino v1.67 | |
* | |
* Removes: clock display | |
* Since the ATmega32U4 has no real-time clock and the evaluation happens on a Raspberry PI with GPS module | |
* , no clock on the ATmega32U4 is required. | |
* | |
* The program is optimizable, I have written it so that I have the source code too | |
* understand without comments. Therefore, it was also written in some places (if ((byte & 0b111100)> 0)) | |
* or on shortening, like i + = 5 omitted! | |
* | |
* Who would like to optimize all this :) | |
* | |
*/ | |
// to copy and paste for debugging | |
/* | |
digitalWrite (DebugPin, HIGH); | |
delay microseconds (100); | |
digitalWrite (DebugPin, LOW); | |
*/ | |
/* | |
tobinstr (sirawdata, 8, UART); | |
sprintf (UART, "% s \ n", UART); | |
Serial1.write (UART); | |
*/ | |
#include <EEPROM.h> | |
// pin definitions | |
const byte DebugPin = 13; // Pin with built-in LED on the Arduino | |
const byte DataPin = 7; // Data pin from the receiver | |
const byte ErrorLEDPin = 12; // if error is set | |
const byte SyncLEDPin = 11; // on data received | |
const byte Do518kHzLEDPin = 10; // when received at 518kHz (default) | |
const byte Do518kHzSetPin = 8; // output for divider after the PLL | |
const byte DataLEDPin = 9; // Data LED .. to blink ... | |
const byte UpperLowerInPin = 4; // Input pin for output large or small | |
const byte FrequencyInPin = 6; // Input pin switching 490kHz / 518kHz | |
// data port - internal register | |
bool InPort; // internal data variable for transfer | |
// workers | |
byte AA; // work register for external requests | |
unsigned int AB; // work register for DataPin interrupt | |
byte AC; // working register for bitholes in buffer | |
byte AD; // working register for bit shifting for byte fetching | |
bool debug = LOW; | |
bool FF = LOW; | |
bool inWait; | |
// Sync & Clock | |
byte loopadjust = 255; // Counter to clock slide-trigger | |
byte loopgain = 128; // Regulator, how often should be readjusted | |
byte clock3200 = 0b00100000; // clock for 10ms clock (3,2kHz / 32) | |
// run variables | |
byte i; // Runner for loops | |
// Dates | |
byte sirawdata; // raw data fetched in timer interrupt | |
byte bitsavailable; // Count how many bits are available (max 8) | |
byte error; // error counter for reception | |
byte RingBuffer [3]; // 3-byte ring buffer for error pre-correction | |
byte RXByte; // Received byte on the RX channel | |
byte CharRingBuffer; // character, which comes from the ring buffer (for error correction) | |
byte CharRX; // character, which was received | |
char ReceivedChar; // sign, what is output to the UART | |
// constants, control characters from the SITOR code (everything where "1" is in the LUT) | |
// these characters are not output, are for internal control only | |
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 = '~'; // error sign ("0" in the LUT) | |
// status bits | |
bool Shifted = LOW; // use numbers or letters LUT? | |
bool frequency = HIGH; // Abort variable when frequency is switched | |
bool UpperLower = HIGH; // Abort variable when LUT is toggled | |
bool sync = LOW; // whether valid sequence ALPHA-REP-ALHPA was received | |
bool NotLostSync = HIGH; | |
// UART buffer | |
char uart [255]; // output buffer variable for UART | |
// There are different mappings - this is the European variant (except 0xd3 - I left that $) | |
// LUT - in lowercase letters | |
static const uint8_t lutLOWER [256] = | |
// 0 1 2 3 4 5 6 7 8 9 abcdef | |
{ | |
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 capital letters | |
static const uint8_t lutUPPER [256] = | |
// 0 1 2 3 4 5 6 7 8 9 abcdef | |
{ | |
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 | |
}; | |
// Initial setup of the registers | |
void setup () { | |
// Pin as interrupt, falling edge on the data line | |
attachInterrupt (digitalPinToInterrupt (DataPin), EventOnInput, FALLING); | |
// pin 13 (the one with LED) as output (for debug) | |
pinMode (DebugPin, OUTPUT); | |
// 2 input pins for frequency and character set | |
pinMode (UpperLowerInPin, INPUT); | |
pinMode (Frequency InPin, INPUT); | |
// configure further pins as output | |
pinMode (ErrorLEDPin, OUTPUT); | |
pinMode (SyncLEDPin, OUTPUT); | |
pinMode (Do518kHzLEDPin, OUTPUT); | |
pinMode (Do518kHzSetPin, OUTPUT); | |
pinMode (DataLEDPin, OUTPUT); | |
// boot sequence ... | |
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); | |
// get last settings from the EEProm | |
Frequency = EEPROM.read (0); | |
UpperLower = EEPROM.read (1); | |
if (UpperLower == LOW) { | |
Serial1.write ( "--- Lower Cased ---\n"); | |
} else { | |
Serial1.write ( "--- Upper Cased --- \n"); | |
} | |
if (frequency == LOW) { | |
Serial1.write ( "--- 490 kHz --- \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 without prescale (runs at 16MHz) | |
OCR1A = 5000; // Trigger after 5000 clock cycles (ie all 312.5us | 16MHz) interrupt | |
TIMSK1 | = (1 << OCIE1A); | |
} | |
// main program | |
void loop () { | |
Serial1.write ("--- Loop Start --- \n"); | |
// if change to frequency or character set pin | |
if (AA> 0) { | |
if ((AA & 0b01) == 0b01) {// Switch character set | |
if (UpperLower == LOW) { | |
Serial1.write ( "--- Lower Cased \n"); | |
EEPROM.write (1, 0); | |
} else { | |
Serial1.write ( "--- Upper Cased --- \n"); | |
EEPROM.write (1, 1); | |
} | |
} | |
if ((AA & 0b10) == 0b10) {// switch to the frequency to be received | |
if (frequency == LOW) { | |
Serial1.write ( "--- 490 kHz --- \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; // Prevent Error LED | |
} | |
AA = 0; | |
} | |
/* | |
* Error LED switch if no EOT was received. | |
* stays on until the next sync is reached. | |
* maybe rebuild and run over bitclock an int16 counter, | |
* then turns off the LED after a while ... | |
*/ | |
if ((error> 16) || (NotLostSync == LOW)) { | |
digitalWrite (ErrorLEDPin, HIGH); | |
} else { | |
digitalWrite (ErrorLEDPin, LOW); | |
} | |
digitalWrite (DataLEDPin, LOW); | |
loopgain = 96; // as long as no sync - often readjust! | |
loopadjust = 255; // Reset counter | |
Shifted = LOW; // Set to Letters LUT | |
NotLostSync = LOW; | |
sync = LOW; // no sync | |
InWait = HIGH; | |
while (sync! = HIGH) {// uC stays in this loop until ALPHA-REP-ALPHA comes | |
while (GetOneBit ()! = ALPHA) {// Bitwise to see if ALPHA was received | |
Check Input (); // Check if something has changed on charset / frequency pin | |
if (AA> 0) { | |
break; | |
} | |
} | |
InWait = LOW; | |
if (AA == 0) {// skip over if config should change! | |
sync = HIGH; // sync high, turns low if the next characters do not match | |
if (Get7Bits ()! = REP) {// pick 7 bits and check for REP | |
sync = LOW; | |
} else { | |
RingBuffer [0] = REP; // If true, fill ring buffers | |
} | |
if (Get7Bits ()! = ALPHA) {// pick 7 bits and check for ALPHA | |
sync = LOW; | |
} | |
} | |
if (AA> 0) {// stop while config changes! | |
break; | |
} | |
} | |
if (AA == 0) { | |
Serial1.write ( "\n --- InSync --- \n"); // debug string | |
loopgain = 8; // Sync da -> disable regulation | |
error = 0; // error count to 0 | |
digitalWrite (ErrorLEDPin, LOW); // error off | |
digitalWrite (SyncLEDPin, HIGH); // Sync LED on | |
RingBuffer [1] = 0; // Empty the ring buffer [1] so that you can not cancel the old character transfer | |
/* | |
// debug - just output the bit strings - but the lower while part has to be commented out! | |
tobinstr (CharRX, 7, Get7Bits ();); | |
sprintf (UART, "% s \ n", CharRX); | |
Serial1.write (UART); | |
// debug the end | |
*/ | |
// always pick up 2 characters from here | |
while (error <16) {// loop goes to logoff, or error> = 32 | |
RingBuffer [2] = RingBuffer [1]; | |
RingBuffer [1] = RingBuffer [0]; | |
RingBuffer [0] = Get7Bits (); // Move the ring buffer and fill it with a new character | |
RXByte = Get7Bits (); // RX Fill buffer with new character | |
/* | |
* The reception works like this: | |
* It is sent repeatedly offset by 3 characters. | |
* Thus one can see that as 2 in one another multiplexed channels | |
* If you break that up, it looks like this: | |
* - start sequence - | |
* ALPHA ALPHA ALPHA ALPHA KUTT _ IS | |
* REP REP REP KUTT _ IS | |
* The lower channel is the ring buffer, the upper one is the receive channel | |
* There is always a character from the top and bottom fetched and looked, which is 3 characters | |
* was previously received on the other channel | |
*/ | |
if (RXByte! = ALPHA) {// As long as ALPHA is on the RXbyte, synchronization will still be sent | |
ReceivedChar = ByteToASCII (); // convert bytes to char | |
if ((ReceivedChar! = 0) && (ReceivedChar! = 1)) {// if valid .. | |
Serial1.write (ReceivedChar); // give it to UART | |
if (FF == LOW) { | |
digitalWrite (DataLEDPin, LOW); | |
FF = HIGH; | |
} else { | |
digitalWrite (DataLEDPin, HIGH); | |
FF = LOW; | |
} | |
} | |
if (RingBuffer [2] == ALPHA) {// Termination condition when transmission is over | |
Serial1.write ( "\n --- EOT --- \n"); | |
digitalWrite (ErrorLEDPin, LOW); | |
NotLostSync = HIGH; | |
error = 0; | |
break; | |
} | |
} | |
} | |
Serial1.write ( "\n --- OutSync --- \n"); | |
digitalWrite (SyncLEDPin, LOW); | |
} | |
} | |
// Convert a byte to char incl. Error correction and evaluation of control characters | |
char ByteToASCII () { | |
if (UpperLower == LOW) { | |
CharRX = lutLOWER [RXByte | (Shifted << 7)]; // Convert RX buffers to char, taking into account the character set to be used | |
CharRingBuffer = lutLOWER [RingBuffer [2] | (Shifted << 7)]; // Convert RingBuffer to Char observing the character set to be used | |
} else { | |
CharRX = lutUPPER [RXByte | (Shifted << 7)]; // Convert RX buffers to char, taking into account the character set to be used | |
CharRingBuffer = lutUPPER [RingBuffer [2] | (Shifted << 7)]; // Convert RingBuffer to Char observing the character set to be used | |
} | |
if ((CharRX == 0) && (CharRingBuffer == 0)) {// both invalid: / | |
error ++; | |
return errsymbol; // raise error and return error | |
} else if ((CharRX == 0) && (CharRingBuffer! = 0)) {// RX invalid but RingBuffer OK | |
CharRX = CharRingBuffer; // copy characters | |
RXByte = ring buffer [2]; // RXByte umkopieren for switch query ... | |
if (error> 0) { | |
error--; // decrement error counters by one | |
} | |
} | |
if (CharRX == 1) {// if out of the LUT 1 = control character | |
switch (RXByte) { | |
case LRTS: // use characters | |
Shifted = LOW; | |
break; | |
case FIGS: // use numbers (offset 0x80h) | |
Shifted = HIGH; | |
break; | |
case BETA: | |
Case ALPHA: | |
case ALPHAUP: | |
case REP: | |
case CR: | |
case BEL: | |
break; // Characters for output irrelevant | |
Case LF: | |
return '\n'; // New line | |
break; | |
case CHAR32: | |
case SPACE: | |
return ''; // spaces | |
default: | |
sprintf (uart, "Unknown: 0x% x \n", RXByte); | |
Serial1.write (UART); | |
break; // character unknown = Error | |
return 0; | |
} | |
} else { | |
return CharRX; | |
} | |
} | |
byte GetOneBit () { | |
while (bitsavailable == 0) { | |
delay (1); | |
} | |
if (AA == 0) { | |
switch (bitsavailable) {// select characters according to the size of the buffer and return them | |
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; // if buffer overflow clears buffers and returns error on UART | |
AC = 0; | |
Serial1.write ("ERROR! Bitsavailable overflow! \n"); | |
break; | |
} | |
AC = AC 0 01111111; // top BIT away-and-en, just in case it's set | |
return AC; | |
} | |
} | |
// Get 7 bits in one go and return | |
byte Get7Bits () { | |
i = 7; | |
AD = 0; | |
while (i> 0) { | |
AD = GetOneBit (); | |
i--; | |
} | |
return AD; | |
} | |
// Interrupt if falling edge on DataPin | |
void EventOnInput () | |
{ | |
AB = loopadjust + loopgain; // Add reinforcements | |
loopadjust = AB & 0x00FF; // delete upper 8 bits and write back (due to lack of a carry flag) | |
if ((AB & 0xFF00)> 0) { // if over 255, then readjust, otherwise not | |
if (clock3200 <16) { // if the clock is too slow | |
clock3200 ++; // Precheck meter | |
} else if (clock3200> 16) { | |
clock3200--; // otherwise rules | |
} | |
} | |
} | |
// 3200kHz timer interrupt | |
ISR (TIMER1_COMPA_vect) | |
{ | |
clock3200--; // Decrease clock .. factor 32 is oversampled | |
if (clock3200 == 0) { | |
InPort = digitalRead (DataPin); | |
/* | |
* if at 490kHz, then 1 and 0 are reversed | |
* This is due to the reference frequency, which at 518kHz with 518.4kHz slightly over, and | |
* at 490kHz with 489.6kHz is slightly below the centre frequency. | |
* This will invert the signal (which could be done by another LUT) | |
* also works with InPort = InPort ^ 1; ... what the hell ... | |
*/ | |
if (frequency == LOW) { | |
if (InPort == LOW) { | |
InPort = HIGH; | |
} else { | |
InPort = LOW; | |
} | |
} | |
clock3200 = 0b00100000; // Reset counter | |
// Move data to ring buffer and increase counters. 8 bits can be stored temporarily (80ms) | |
sirawdata = ((sirawdata >> 1) | (InPort << 7)) & 0b11111111; | |
bitsavailable ++; | |
} | |
} | |
// debug routine to convert INT to binaer-String | |
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'; | |
} | |
} | |
// check if something has happened on the frequency or character set pin | |
void CheckInput () { | |
AA = 0; | |
if (digitalRead (UpperLowerInPin)! = UpperLower) { | |
UpperLower = digitalRead (UpperLowerInPin); | |
AA = AA | 0b01; | |
} | |
if (digitalRead (frequencyInPin)! = frequency) { | |
Frequency = digitalRead (FrequencyInPin); | |
AA = AA | 0b10; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment