diff --git a/create_sinus_tables.py b/create_sinus_tables.py index c4fe13b..d8e2929 100755 --- a/create_sinus_tables.py +++ b/create_sinus_tables.py @@ -45,7 +45,7 @@ uint8_t timeskew = 0; void sendOne(uint32_t *us, uint8_t *phaseShift) {{ uint32_t t; while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) {{ - OUT_PORT = pgm_read_byte(&SINUS1200[ (PS1200[*phaseShift] + t) % N_SINUS_1200 ]) >> 2; + OUT_PORT = (pgm_read_byte(&SINUS1200[ (PS1200[*phaseShift] + t) % N_SINUS_1200 ]) >> 4) | (OUT_PORT & 0xf0); }} *us += INV_BAUD + (timeskew == 0); timeskew = (timeskew + 1) % SKEW_ROUNDS; @@ -54,7 +54,7 @@ void sendOne(uint32_t *us, uint8_t *phaseShift) {{ void sendZero(uint32_t *us, uint8_t *phaseShift) {{ uint32_t t; while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) {{ - OUT_PORT = pgm_read_byte(&SINUS2200[ (PS2200[*phaseShift] + t) % N_SINUS_2200 ]) >> 2; + OUT_PORT = (pgm_read_byte(&SINUS2200[ (PS2200[*phaseShift] + t) % N_SINUS_2200 ]) >> 4) | (OUT_PORT & 0xf0); }} *phaseShift = (*phaseShift + 1) % N_SHIFTS; *us += INV_BAUD + (timeskew == 0); @@ -62,7 +62,7 @@ void sendZero(uint32_t *us, uint8_t *phaseShift) {{ }} void setZero() {{ - OUT_PORT = pgm_read_byte(&SINUS2200[0]) >> 2; + OUT_PORT = (pgm_read_byte(&SINUS2200[0]) >> 4) | (OUT_PORT & 0xf0); }} ''') diff --git a/include/afsk_sinus.hpp b/include/afsk_sinus.hpp index 1cdd81b..2e45b66 100644 --- a/include/afsk_sinus.hpp +++ b/include/afsk_sinus.hpp @@ -20,7 +20,7 @@ uint8_t timeskew = 0; void sendOne(uint32_t *us, uint8_t *phaseShift) { uint32_t t; while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) { - OUT_PORT = pgm_read_byte(&SINUS1200[ (PS1200[*phaseShift] + t) % N_SINUS_1200 ]) >> 2; + OUT_PORT = (pgm_read_byte(&SINUS1200[ (PS1200[*phaseShift] + t) % N_SINUS_1200 ]) >> 4) | (OUT_PORT & 0xf0); } *us += INV_BAUD + (timeskew == 0); timeskew = (timeskew + 1) % SKEW_ROUNDS; @@ -29,7 +29,7 @@ void sendOne(uint32_t *us, uint8_t *phaseShift) { void sendZero(uint32_t *us, uint8_t *phaseShift) { uint32_t t; while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) { - OUT_PORT = pgm_read_byte(&SINUS2200[ (PS2200[*phaseShift] + t) % N_SINUS_2200 ]) >> 2; + OUT_PORT = (pgm_read_byte(&SINUS2200[ (PS2200[*phaseShift] + t) % N_SINUS_2200 ]) >> 4) | (OUT_PORT & 0xf0); } *phaseShift = (*phaseShift + 1) % N_SHIFTS; *us += INV_BAUD + (timeskew == 0); @@ -37,6 +37,6 @@ void sendZero(uint32_t *us, uint8_t *phaseShift) { } void setZero() { - OUT_PORT = pgm_read_byte(&SINUS2200[0]) >> 2; + OUT_PORT = (pgm_read_byte(&SINUS2200[0]) >> 4) | (OUT_PORT & 0xf0); } diff --git a/include/eepromedit.hpp b/include/eepromedit.hpp deleted file mode 100644 index d58ffbe..0000000 --- a/include/eepromedit.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#include -#include - -#define ALPHABET_SIZE 37 -#define EEPROM_BASE 42 - -// 666 -// 4 5 -// 4 5 -// 333 -// 1 2 -// 1 2 -// 000 7 -uint8_t dispMap[ALPHABET_SIZE+1] = { 0x00, 0x77, 0x24, 0x6b, 0x6d, 0x3c, 0x5d, 0x5f, 0x64, 0x7f, 0x7d, 0x7e, 0x1f, 0x53, 0x2f, 0x5b, 0x5a, 0x57, 0x1e, 0x43, 0x25, 0x3c, 0x13, 0x76, 0x0e, 0x0f, 0x7a, 0x7c, 0x0a, 0x55, 0x1b, 0x07, 0x37, 0x3f, 0x49, 0x3d, 0x63 }; -char charMap[ALPHABET_SIZE] = { ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; -uint8_t *pinMap; - -uint8_t state = 0; -volatile bool advanceState = 0; -volatile bool advanceDigit = 0; - -void nextState() { - advanceState = true; -} - -void cycleDigit() { - advanceDigit = true; -} - -void showDigit(char digit, bool dot) { - // add 0 for SSID hexdump - if (digit < 10) { - digit += '0'; - } else if (digit < 16) { - digit += 'A' - 10; - } - uint8_t output = 0x08; - for (uint8_t i = 0; i < ALPHABET_SIZE; ++i) { - if (charMap[i] == digit) { - output = dispMap[i]; - break; - } - } - if (dot) { - output |= 0x80; - } - for (uint8_t i = 0; i < 8; ++i) { - digitalWrite(pinMap[i], (output >> i) & 1); - } -} - -void loadFromEeprom(char *str, uint8_t len, uint8_t eeprom_base) { - if (EEPROM.read(eeprom_base) != 0x42) { - for (uint8_t i = 0; i < len; ++i) { - EEPROM.update(eeprom_base+1+i, str[i]); - } - // Write EEPROM init flag last - EEPROM.write(eeprom_base, 0x42); - } - for (uint8_t i = 0; i < len; ++i) { - str[i] = EEPROM.read(eeprom_base+1+i); - } -} - -void clearDisplay() { - showDigit(' ', false); -} - -void display(char *str, uint8_t len) { - for (uint8_t i = 0; i < len; ++i) { - showDigit(str[i], false); - delay(1000); - } - clearDisplay(); -} - -void editor(char *str, uint8_t len, uint16_t eeprom_base) { - attachInterrupt(digitalPinToInterrupt(2), nextState, FALLING); - attachInterrupt(digitalPinToInterrupt(3), cycleDigit, FALLING); - while (true) { - if (advanceDigit) { - advanceDigit = false; - if (str[state] < 16) { - str[state] = (str[state] + 1) % 16; - } else { - for (uint8_t i = 0; i < ALPHABET_SIZE; ++i) { - if (charMap[i] == str[state]) { - str[state] = charMap[(i+1)%ALPHABET_SIZE]; - break; - } - } - } - } - if (advanceState) { - advanceState = false; - state++; - } - if (state >= len) { - for (uint8_t i = 0; i < len; ++i) { - EEPROM.update(eeprom_base+1+i, str[i]); - } - display(str, len); - return; - } else { - showDigit(str[state], true); - } - } -} - -void initEditor(char *str, uint8_t len, uint16_t eeprom_base, uint8_t *initPinMap) { - loadFromEeprom(str, len, eeprom_base); - pinMap = initPinMap; - for (uint8_t i = 0; i < 8; ++i) { - pinMode(pinMap[i], OUTPUT); - digitalWrite(pinMap[i], LOW); - } - pinMode(2, INPUT_PULLUP); - pinMode(3, INPUT_PULLUP); - delay(500); - if (!digitalRead(2)) { - while (!digitalRead(2)); - editor(str, len, eeprom_base); - } else { - display(str, len); - } -} diff --git a/platformio.ini b/platformio.ini index 86e19b5..3e4e34f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,36 +10,17 @@ [env:main] platform = atmelavr -board = uno -framework = arduino -build_flags = -DOUT_PORT=PORTC -DOUT_DDR=DDRC - -[env:mega] -platform = atmelavr -board = megaatmega2560 -framework = arduino -build_flags = -DOUT_PORT=PORTF -DOUT_DDR=DDRF - -[env:nano] -platform = atmelavr board = nanoatmega328 framework = arduino -build_flags = -DOUT_PORT=PORTC -DOUT_DDR=DDRC - -[env:one] -platform = atmelavr -board = uno -framework = arduino -build_flags = -DOUT_PORT=PORTD -DOUT_DDR=DDRD -DONE - -[env:zero] -platform = atmelavr -board = uno -framework = arduino -build_flags = -DOUT_PORT=PORTD -DOUT_DDR=DDRD -DZERO - -[env:alternate] -platform = atmelavr -board = megaatmega2560 -framework = arduino -build_flags = -DOUT_PORT=PORTF -DOUT_DDR=DDRF -DALT \ No newline at end of file +lib_deps = + https://github.com/marcoschwartz/LiquidCrystal_I2C +upload_protocol = custom +upload_flags = + -C + ; use "tool-avrdude-megaavr" for the atmelmegaavr platform + ${platformio.packages_dir}/tool-avrdude/avrdude.conf + -p + $BOARD_MCU + -c + usbtiny +upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i diff --git a/src/main.cpp b/src/main.cpp index bbb975d..a9cc673 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,25 +1,28 @@ -#include +#include #include -#include "afsk_sinus.hpp" -#include "eepromedit.hpp" +#include +#include +#include + +#include + +#define OUT_PORT PORTC +#define OUT_DDR DDRC +#include "afsk_sinus.hpp" + -#define MIN_INTERVAL 300000 // 5 min #define SEND_INVALID 0 #define DESTINATION "WIDE1 \x01" #define TXDELAY 32 #define FRAME_REPEAT 3 +#define EEPROM_OFFSET 0 + #define PTT 4 #define PTT_IND 13 -#define EDITOR_EEPROM_BASE 42 -char callsign[7] = { ' ', ' ', ' ', ' ', ' ', ' ', '\0' }; -uint8_t editorPinMap[8] = { 10, 9, 12, 8, 5, 7, 6, 11 }; - -uint32_t lastBroadcast = 0; - struct ax25 { uint8_t daddr[7]; uint8_t saddr[7]; @@ -27,26 +30,6 @@ struct ax25 { uint8_t proto; } __attribute__((packed)) frame; -enum AprsSymbol : uint8_t { - NONE, - AMBULANCE, - BUS, - FIRE_TRUCK, - BICYCLE, - YACHT, - HELICOPTER, - SMALL_AIRCRAFT, - SHIP, - CAR, - MOTORCYCLE, - BALLOON, - JEEP, - RECREATIONAL_VEHICLE, - TRUCK, - VAN -}; - - extern void sendOne(uint32_t *us, uint8_t *phaseShift); extern void sendZero(uint32_t *us, uint8_t *phaseShift); extern void setZero(); @@ -55,6 +38,7 @@ uint32_t m = 0; uint8_t phaseShift = 0; bool nrzi = true; uint8_t oneCount = 0; +uint32_t lastBroadcast = 0; void sendNrziBit(bool bit, bool zeroStuff) { if (!bit) { @@ -108,130 +92,629 @@ void sendBell202buf(uint8_t *buf, size_t len, bool sync) { } } + + +const PROGMEM uint8_t symbolId[80] = { + '!', '#', '$', '%', '&', '\'', '(', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'E', 'G', 'H', 'I', 'K', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y' +}; + +const PROGMEM char symbolReadable[80][17] = { + "Police, Sheriff", + "Digi", + "Phone", + "DX Cluster", + "HF Gateway", + "Small Aircraft", + "Mob Satellite GS", + "Snowmobile", + "Red Cross", + "Scouts", + "House QTH (VHF)", + "X", + "Dot", + "Num Circle (0)", + "Num Circle (1)", + "Num Circle (2)", + "Num Circle (3)", + "Num Circle (4)", + "Num Circle (5)", + "Num Circle (6)", + "Num Circle (7)", + "Num Circle (8)", + "Num Circle (9)", + "Fire", + "Campground", + "Motorcycle", + "Railroad Engine", + "Car", + "File Server", + "Hurricane Protct", + "Aid Station", + "BBS", + "Canoe", + "Eyeball", + "Grid Square (6c)", + "Hotel (blue bed)", + "TCP/IP", + "School", + "MacAPRS", + "NTS Station", + "Balloon", + "Police", + "Recreat. Vehicle", + "Space Shuttle", + "SSTV", + "Bus", + "ATV", + "National Weather", + "Helicopter", + "Yacht/Sail Boat", + "WinAPRS", + "Jogger", + "Triangle (DF)", + "PBBS", + "Large Aircraft", + "Weather Station", + "Dish Antenna", + "Ambulance", + "Bicycle", + "Dual Garage", + "Horse", + "Fire Truck", + "Glider", + "Hospital", + "IOTA", + "Jeep", + "Truck", + "Mic-Repeater", + "Node", + "Emergency OpCent", + "Rover", + "Grid Square 128m", + "Antenna", + "Ship (Powerboat)", + "Truck Stop", + "18-wheeler Truck", + "Van", + "Water Station", + "X-APRS (Unix)", + "Yagi at QTH" +}; + +const char emptyLine[] = " "; + +const uint8_t callsignAbcLen = 37; +const char callsignAbc[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +const uint8_t commentAbcLen = 94; +const uint8_t commentAbc[] = "\xff !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}"; + +struct __attribute__((packed)) EepromConfig { + uint8_t flag; + uint8_t srcCallsign[6]; + uint8_t srcSsid; + uint8_t marker; + uint16_t interval; + uint8_t commentLen; + uint8_t comment[32]; +}; + +struct EepromConfig config; + +struct NmeaRmc { + bool valid; + char time[10]; + char date[7]; + char latDeg[3]; + char latMin[8]; + char latDir; + char lonDeg[4]; + char lonMin[8]; + char lonDir; + char course[4]; + char speed[4]; +}; + +struct NmeaRmc rmc; + +LiquidCrystal_I2C lcd(0x27, 16, 2); + +volatile bool setPressed = false; +volatile bool abcPressed = false; +uint8_t page = 0; +uint32_t lastPressed = 0; +bool off = false; + + + + void initFrame() { memset(&frame, 0, sizeof(frame)); memcpy(&frame.daddr, DESTINATION, 6); - memcpy(&frame.saddr, callsign, 6); + memcpy(&frame.saddr, config.srcCallsign, 6); for (uint8_t i = 0; i < 7; ++i) { frame.daddr[i] <<=1; frame.saddr[i] <<=1; } frame.daddr[6] = (DESTINATION[6] << 1) | 0xe0; // command bit + reserved bits (AX.25 3.12.2) - frame.saddr[6] = (callsign[6] << 1) | 0xe1; // command bit + reserved bits + end bit + frame.saddr[6] = (config.srcSsid << 1) | 0xe1; // command bit + reserved bits + end bit frame.ctrl = 0x03; frame.proto = 0xf0; } - uint8_t mirrorByte(uint8_t byt) { return ((byt & 1) << 7) | ((byt & 2) << 5) | ((byt & 4) << 3) | ((byt & 8) << 1) | ((byt & 16) >> 1) | ((byt & 32) >> 3) | ((byt & 64) >> 5) | ((byt & 128) >> 7); } + + +void createDefaultConfig() { + config.flag = 1; + memset(&config.srcCallsign, ' ', 6); + config.srcSsid = 0; + config.marker = 'b'; + config.interval = 300; + config.commentLen = 0; + memset(&config.comment, ' ', 32); +} + +void writeEepromConfig() { + for (uint8_t i = 0; i < sizeof(struct EepromConfig); ++i) { + EEPROM.update(EEPROM_OFFSET+i, ((uint8_t*) &config)[i]); + } +} + +void loadEepromConfig() { + createDefaultConfig(); + if (EEPROM.read(EEPROM_OFFSET) != config.flag) { + writeEepromConfig(); + } + for (uint8_t i = 0; i < sizeof(struct EepromConfig); ++i) { + ((uint8_t*) &config)[i] = EEPROM.read(EEPROM_OFFSET+i); + } + initFrame(); +} + + +void printConfig() { + lcd.setCursor(0, 0); + for (uint8_t i = 0; i < 6; ++i) { + lcd.print((char) config.srcCallsign[i]); + } + lcd.print('-'); + lcd.print(config.srcSsid); + + lcd.setCursor(10, 0); + uint32_t seconds = config.interval; + if (seconds >= 3600) { + lcd.print(seconds / 3600); + lcd.print('h'); + seconds %= 3600; + } + if (seconds >= 60) { + lcd.print(seconds / 60); + lcd.print('m'); + seconds %= 60; + } + if (seconds > 0 || config.interval == 0) { + lcd.print(seconds); + lcd.print('s'); + } + lcd.print(emptyLine); + + char marker[17]; + for (uint8_t i = 0; i < 80; ++i) { + if (pgm_read_byte(&symbolId[i]) == config.marker) { + strncpy_P(marker, symbolReadable[i], 17); + lcd.setCursor(0, 1); + lcd.print(marker); + lcd.print(emptyLine); + break; + } + } + +} + +void printComment() { + lcd.setCursor(0, 0); + for (uint8_t i = 0; i < 16 && i < config.commentLen; ++i) { + lcd.print((char) config.comment[i]); + } + lcd.setCursor(0, 1); + for (uint8_t i = 0; i < 16 && i + 16 < config.commentLen; ++i) { + lcd.print((char) config.comment[i+16]); + } +} + +void printLocation() { + char lat[17]; + char lon[17]; + if (!rmc.valid) { + lcd.setCursor(0, 0); + lcd.print("No GPS fix! "); + lcd.setCursor(0, 1); + lcd.print(emptyLine); + } else { + snprintf(lat, 17, " %.2s\xdf %.7s' %c ", rmc.latDeg, rmc.latMin, rmc.latDir); + snprintf(lon, 17, "%.3s\xdf %.7s' %c ", rmc.lonDeg, rmc.lonMin, rmc.lonDir); + lcd.setCursor(0, 0); + lcd.print(lat); + lcd.setCursor(0, 1); + lcd.print(lon); + } +} + +void printDateTime() { + char dateTime[17]; + char speedCourse[17]; + snprintf(dateTime, 17, "%.2s.%.2s.%.2s %.2s:%.2sz", + rmc.date, rmc.date+2, rmc.date+4, + rmc.time, rmc.time+2); + uint16_t kph = (strtol(rmc.speed, NULL, 10) * 463) / 250; + uint16_t hdg = strtol(rmc.course, NULL, 10); + snprintf(speedCourse, 17, "% 3d km/h% 6d \xdf", + kph, hdg); + + lcd.setCursor(0, 0); + lcd.print(dateTime); + lcd.setCursor(0, 1); + lcd.print(speedCourse); +} + +void isrSet() { + setPressed = true; +} + +void isrAbc() { + abcPressed = true; +} + +void editConfig() { + uint8_t state = 0; + lcd.blink(); + while (true) { + if (setPressed) { + setPressed = false; + ++state; + } + if (state < 6) { // callsign + lcd.setCursor(state, 0); + if (abcPressed) { + abcPressed = false; + for (uint8_t i = 0; i < callsignAbcLen; ++i) { + if (callsignAbc[i] == config.srcCallsign[state]) { + config.srcCallsign[state] = callsignAbc[(i+1)%callsignAbcLen]; + break; + } + } + lcd.print((char) config.srcCallsign[state]); + lcd.setCursor(state, 0); + } + + } else if (state == 6) { // ssid + lcd.setCursor(7, 0); + if (abcPressed) { + abcPressed = false; + config.srcSsid = (config.srcSsid + 1) % 16; + lcd.print(config.srcSsid); + lcd.print(' '); + lcd.setCursor(7, 0); + } + + } else if (state == 7) { // interval + lcd.setCursor(10, 0); + if (abcPressed) { + abcPressed = false; + if (config.interval < 60) { + config.interval += 10; + } else if (config.interval < 300) { + config.interval += 60; + } else if (config.interval < 1800) { + config.interval += 300; + } else if (config.interval < 3600) { + config.interval += 600; + } else if (config.interval < 43200) { + config.interval += 3600; + } else { + config.interval = 0; + } + + uint16_t seconds = config.interval; + if (seconds >= 3600) { + lcd.print(seconds / 3600); + lcd.print('h'); + seconds %= 3600; + } + if (seconds >= 60) { + lcd.print(seconds / 60); + lcd.print('m'); + seconds %= 60; + } + if (seconds > 0 || config.interval == 0) { + lcd.print(seconds); + lcd.print('s'); + } + lcd.print(emptyLine); + lcd.setCursor(10, 0); + } + + } else if (state == 8) { // marker + lcd.setCursor(0, 1); + if (abcPressed) { + abcPressed = false; + char marker[17]; + for (uint8_t i = 0; i < 80; ++i) { + if (pgm_read_byte(&symbolId[i]) == config.marker) { + config.marker = pgm_read_byte(&symbolId[(i+1)%80]); + strncpy_P(marker, symbolReadable[(i+1)%80], 17); + lcd.setCursor(0, 1); + lcd.print(marker); + lcd.print(emptyLine); + break; + } + } + } + + } else { + lcd.noBlink(); + writeEepromConfig(); + initFrame(); + return; + } + } +} + +void editComment() { + uint8_t state = 0; + lcd.blink(); + while (true) { + if (setPressed) { + setPressed = false; + ++state; + if (state >= 32) { + config.commentLen = 32; + lcd.noBlink(); + writeEepromConfig(); + return; + } + if (config.comment[state-1] == 255U) { + config.commentLen = state-1; + memset(config.comment + state, '\xff', 32-state); + lcd.noBlink(); + writeEepromConfig(); + return; + } + } + lcd.setCursor(state%16, state/16); + if (abcPressed) { + abcPressed = false; + for (uint8_t i = 0; i < commentAbcLen; ++i) { + if (commentAbc[i] == config.comment[state]) { + config.comment[state] = commentAbc[(i+1)%commentAbcLen]; + break; + } + } + lcd.print((char) config.comment[state]); + lcd.setCursor(state%16, state/16); + } + } +}; + + +uint8_t nmeaState = 0; +char nmeaStr[256]; // NMEA-0183 messages should only be 82 characters, but the NEO-6M sends much longer messages +char *sptr; +void readNmeaRmc() { + + while (Serial.available()) { + int16_t byt = Serial.read(); + if (byt == '$') { + nmeaState = 1; + sptr = nmeaStr; + } + if (nmeaState == 1) { + *(sptr++) = (char) byt; + if (sptr >= nmeaStr + sizeof(nmeaStr)) { + nmeaState = 0; + } + if (byt == '*') { + *sptr = 0; + if (nmeaStr[3] == 'R' && nmeaStr[4] == 'M' && nmeaStr[5] == 'C') { + nmeaState = 2; + } else { + nmeaState = 0; + } + Serial.println(nmeaStr); + Serial.flush(); + } + } + if (nmeaState == 2) { + char buf[16]; + char *bptr = buf; + uint8_t counter = 0; + for (char *ptr = nmeaStr; ptr < sptr; ++ptr) { + if (*ptr == ',' || *ptr == '*') { + *bptr++ = 0; + if (counter == 1) { // time HHMMSS + if (bptr - buf > 6) { + memcpy(rmc.time, buf, 6); + } else { + memcpy(rmc.time, "??????", 6); + } + } else if (counter == 2 && bptr - buf > 1) { // valid + rmc.valid = buf[0] == 'A'; + } else if (counter == 3 && bptr - buf > 7) { // lat + memcpy(rmc.latDeg, buf, 2); + memcpy(rmc.latMin, buf+2, 5); + } else if (counter == 4 && bptr - buf > 1) { // lat dir + rmc.latDir = buf[0]; + } else if (counter == 5 && bptr - buf > 8) { // lon + memcpy(rmc.lonDeg, buf, 3); + memcpy(rmc.lonMin, buf+3, 5); + } else if (counter == 6 && bptr - buf > 1) { // lon dir + rmc.lonDir = buf[0]; + } else if (counter == 7) { // speed + memcpy(rmc.speed, "000", 3); + if (bptr - buf > 1) { + // only copy integer part of speed + char *dp; + for (dp = buf; *dp != '.' && dp < bptr; ++dp); + if (dp - buf > 3) { + dp = buf + 3; + } + memcpy(rmc.speed + 3-(dp-buf), buf, dp - buf); + } + } else if (counter == 8) { // course + memcpy(rmc.course, "000", 3); + if (bptr - buf > 1) { + // only copy integer part of course + char *dp; + for (dp = buf; *dp != '.' && dp < bptr; ++dp); + if (dp - buf > 3) { + dp = buf + 3; + } + memcpy(rmc.course + 3-(dp-buf), buf, dp - buf); + // APRS uses 000 for unknown heading, and 360 for 0 + if (!memcmp(rmc.course, "000", 3)) { + memcpy(rmc.course, "360", 3); + } + } + } else if (counter == 9) { // date DDMMYY + if (bptr - buf > 6) { + memcpy(rmc.date, buf, 6); + } else { + memcpy(rmc.date, "??????", 6); + } + } + bptr = buf; + counter++; + } else { + *bptr++ = *ptr; + if (bptr >= buf + 16) { + nmeaState = 0; + break; + } + } + } + uint32_t now = millis(); + if ((rmc.valid || SEND_INVALID) && ((now - lastBroadcast) / 1000 > config.interval || lastBroadcast == 0)) { + lastBroadcast = now; + char aprs[256]; + if (rmc.valid) { + snprintf(aprs, 256, "/%.2s%.4sz%.2s%.5s%c/%.3s%.5s%c%c%.3s/%.3s", + rmc.date, rmc.time, + rmc.latDeg, rmc.latMin, rmc.latDir, + rmc.lonDeg, rmc.lonMin, rmc.lonDir, + config.marker, rmc.course, rmc.speed); + } else { + snprintf(aprs, 256, "/012058z4700.00N/00700.00Eb000/000"); + } + memcpy(aprs+34, config.comment, config.commentLen); + aprs[34+config.commentLen] = 0; + Serial.println(aprs); + // AX.25 3.8 - FCS is sent MSB first + uint16_t fcs = 0xffff; + for (size_t i = 0; i < sizeof(frame); ++i) { + fcs = _crc_xmodem_update(fcs, mirrorByte(((uint8_t*) &frame)[i])); + } + for (int16_t i = 0; i < 34 + config.commentLen; ++i) { + fcs = _crc_xmodem_update(fcs, mirrorByte(aprs[i])); + } + fcs ^= 0xffff; + fcs = (mirrorByte(fcs & 0xff) << 8) | mirrorByte(fcs >> 8); + digitalWrite(PTT, HIGH); + digitalWrite(PTT_IND, HIGH); + sendFlag(true, TXDELAY+1); + for (uint8_t i = 0; i < FRAME_REPEAT; ++i) { + sendBell202buf((uint8_t*) &frame, sizeof(frame), false); + sendBell202buf((uint8_t*) aprs, 34 + config.commentLen, false); + sendBell202buf((uint8_t*) &fcs, 2, false); + sendFlag(false, 1); + } + setZero(); + nrzi = true; + digitalWrite(PTT, LOW); + digitalWrite(PTT_IND, LOW); + + } + nmeaState = 0; + } + } +} + void setup() { + Serial.begin(9600); + // ublox NEO-6M: Disable all NMEA sentences except for GPTXT and GPRMC + Serial.println("$PUBX,40,GLL,0,0,0,0,0,0*5C"); + Serial.println("$PUBX,40,GSV,0,0,0,0,0,0*59"); + Serial.println("$PUBX,40,GSA,0,0,0,0,0,0*4E"); + Serial.println("$PUBX,40,VTG,0,0,0,0,0,0*5E"); + Serial.println("$PUBX,40,GGA,0,0,0,0,0,0*5A"); + Serial.flush(); + // ublox NEO-6M: Switch to low-power on-off mode with peak current limiting - not sure if this actually works + Serial.print("\xB5\x62\x06\x3B\x2C\x00\x01\x06\x00\x00\x0E\x91\x00\x00\x60\xEA\x00\x00\x10\x27\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x2C\x01\x00\x00\x4F\xC1\x03\x00\x86\x02\x00\x00\xFE\x00\x00\x00\x64\x40\x01\x00\x01\x45"); + Serial.flush(); + OUT_DDR |= 0x0f; pinMode(PTT, OUTPUT); digitalWrite(PTT, LOW); pinMode(PTT_IND, OUTPUT); digitalWrite(PTT_IND, LOW); - initEditor(callsign, 7, EDITOR_EEPROM_BASE, editorPinMap); - OUT_DDR = 0x3f; + loadEepromConfig(); setZero(); nrzi = true; - initFrame(); - Serial.begin(9600); -#ifdef ALT - phaseShift = 0; - m = micros(); - while (true) { - sendOne(&m, &phaseShift); - sendZero(&m, &phaseShift); - } -#endif -#ifdef ONE - phaseShift = 0; - m = micros(); - while (true) { - sendOne(&m, &phaseShift); - } -#endif -#ifdef ZERO - phaseShift = 0; - m = micros(); - while (true) { - sendZero(&m, &phaseShift); - } -#endif -} - - -void readNmeaGll() { - uint8_t state = 0; - char str[83]; - char *sptr; - - while (state != 2) { - if (!Serial.available()) { - continue; - } - int16_t byt = Serial.read(); - if (state == 0) { - if (byt == '$') { - state = 1; - sptr = str; - *(sptr++) = (char) byt; - } - } else if (state == 1) { - *(sptr++) = (char) byt; - if (byt == '\n') { - state = 2; - *sptr = 0; - if (str[3] == 'R' && str[4] == 'M' && str[5] == 'C') { - if (SEND_INVALID || *(sptr-6) == 'A') { - state = 2; - } else { - state = 0; - } - } else { - state = 0; - } - Serial.print(str); - Serial.flush(); - } - } - if (sptr > str + sizeof(str)) { - state = 0; - } - } - uint32_t now = millis(); - if (now - lastBroadcast > MIN_INTERVAL || lastBroadcast == 0) { - lastBroadcast = now; - // AX.25 3.8 - FCS is sent MSB first - uint16_t fcs = 0xffff; - for (size_t i = 0; i < sizeof(frame); ++i) { - fcs = _crc_xmodem_update(fcs, mirrorByte(((uint8_t*) &frame)[i])); - } - for (char *c = str; c < sptr; ++c) { - fcs = _crc_xmodem_update(fcs, mirrorByte(*c)); - } - fcs ^= 0xffff; - fcs = (mirrorByte(fcs & 0xff) << 8) | mirrorByte(fcs >> 8); - digitalWrite(PTT, HIGH); - digitalWrite(PTT_IND, HIGH); - sendFlag(true, TXDELAY+1); - for (uint8_t i = i; i < FRAME_REPEAT; ++i) { - sendBell202buf((uint8_t*) &frame, sizeof(frame), false); - sendBell202buf((uint8_t*) str, sptr-str, false); - sendBell202buf((uint8_t*) &fcs, 2, false); - sendFlag(false, 1); - } - setZero(); - nrzi = true; - digitalWrite(PTT, LOW); - digitalWrite(PTT_IND, LOW); - } + pinMode(2, INPUT_PULLUP); + pinMode(3, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(2), isrSet, FALLING); + attachInterrupt(digitalPinToInterrupt(3), isrAbc, FALLING); + lcd.init(); + lcd.backlight(); + setPressed = false; + abcPressed = false; } void loop() { - readNmeaGll(); + readNmeaRmc(); + uint32_t now = millis(); + if (off) { + if (abcPressed || setPressed) { + off = false; + abcPressed = false; + setPressed = false; + lastPressed = now; + lcd.backlight(); + lcd.on(); + } + } else if (now - lastPressed > 30000) { + off = true; + lcd.off(); + lcd.noBacklight(); + } + if (abcPressed) { + abcPressed = false; + page = (page + 1) % 4; + lastPressed = now; + lcd.clear(); + } + if (page == 0) { + printConfig(); + if (setPressed) { + setPressed = false; + lastPressed = now; + editConfig(); + } + } else if (page == 1) { + printComment(); + if (setPressed) { + setPressed = false; + lastPressed = now; + editComment(); + lcd.clear(); + } + } else if (page == 2) { + printLocation(); + } else if (page == 3) { + printDateTime(); + } }