feat: use an LCD rather than a single 7segment digit

This commit is contained in:
s3lph 2023-05-05 23:53:15 +02:00
parent 45d29a8391
commit 0fe613d223
Signed by: s3lph
GPG key ID: 0AA29A52FB33CFB5
5 changed files with 629 additions and 291 deletions

View file

@ -45,7 +45,7 @@ uint8_t timeskew = 0;
void sendOne(uint32_t *us, uint8_t *phaseShift) {{ void sendOne(uint32_t *us, uint8_t *phaseShift) {{
uint32_t t; uint32_t t;
while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) {{ 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); *us += INV_BAUD + (timeskew == 0);
timeskew = (timeskew + 1) % SKEW_ROUNDS; 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) {{ void sendZero(uint32_t *us, uint8_t *phaseShift) {{
uint32_t t; uint32_t t;
while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) {{ 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; *phaseShift = (*phaseShift + 1) % N_SHIFTS;
*us += INV_BAUD + (timeskew == 0); *us += INV_BAUD + (timeskew == 0);
@ -62,7 +62,7 @@ void sendZero(uint32_t *us, uint8_t *phaseShift) {{
}} }}
void setZero() {{ void setZero() {{
OUT_PORT = pgm_read_byte(&SINUS2200[0]) >> 2; OUT_PORT = (pgm_read_byte(&SINUS2200[0]) >> 4) | (OUT_PORT & 0xf0);
}} }}
''') ''')

View file

@ -20,7 +20,7 @@ uint8_t timeskew = 0;
void sendOne(uint32_t *us, uint8_t *phaseShift) { void sendOne(uint32_t *us, uint8_t *phaseShift) {
uint32_t t; uint32_t t;
while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) { 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); *us += INV_BAUD + (timeskew == 0);
timeskew = (timeskew + 1) % SKEW_ROUNDS; 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) { void sendZero(uint32_t *us, uint8_t *phaseShift) {
uint32_t t; uint32_t t;
while ((t = micros() - *us) < INV_BAUD + (timeskew == 0)) { 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; *phaseShift = (*phaseShift + 1) % N_SHIFTS;
*us += INV_BAUD + (timeskew == 0); *us += INV_BAUD + (timeskew == 0);
@ -37,6 +37,6 @@ void sendZero(uint32_t *us, uint8_t *phaseShift) {
} }
void setZero() { void setZero() {
OUT_PORT = pgm_read_byte(&SINUS2200[0]) >> 2; OUT_PORT = (pgm_read_byte(&SINUS2200[0]) >> 4) | (OUT_PORT & 0xf0);
} }

View file

@ -1,126 +0,0 @@
#include <Arduino.h>
#include <EEPROM.h>
#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);
}
}

View file

@ -10,36 +10,17 @@
[env:main] [env:main]
platform = atmelavr 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 board = nanoatmega328
framework = arduino framework = arduino
build_flags = -DOUT_PORT=PORTC -DOUT_DDR=DDRC lib_deps =
https://github.com/marcoschwartz/LiquidCrystal_I2C
[env:one] upload_protocol = custom
platform = atmelavr upload_flags =
board = uno -C
framework = arduino ; use "tool-avrdude-megaavr" for the atmelmegaavr platform
build_flags = -DOUT_PORT=PORTD -DOUT_DDR=DDRD -DONE ${platformio.packages_dir}/tool-avrdude/avrdude.conf
-p
[env:zero] $BOARD_MCU
platform = atmelavr -c
board = uno usbtiny
framework = arduino upload_command = avrdude $UPLOAD_FLAGS -U flash:w:$SOURCE:i
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

View file

@ -1,25 +1,28 @@
#include <Arduino.h> #include <avr/pgmspace.h>
#include <util/crc16.h> #include <util/crc16.h>
#include "afsk_sinus.hpp" #include <Arduino.h>
#include "eepromedit.hpp" #include <Wire.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>
#define OUT_PORT PORTC
#define OUT_DDR DDRC
#include "afsk_sinus.hpp"
#define MIN_INTERVAL 300000 // 5 min
#define SEND_INVALID 0 #define SEND_INVALID 0
#define DESTINATION "WIDE1 \x01" #define DESTINATION "WIDE1 \x01"
#define TXDELAY 32 #define TXDELAY 32
#define FRAME_REPEAT 3 #define FRAME_REPEAT 3
#define EEPROM_OFFSET 0
#define PTT 4 #define PTT 4
#define PTT_IND 13 #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 { struct ax25 {
uint8_t daddr[7]; uint8_t daddr[7];
uint8_t saddr[7]; uint8_t saddr[7];
@ -27,26 +30,6 @@ struct ax25 {
uint8_t proto; uint8_t proto;
} __attribute__((packed)) frame; } __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 sendOne(uint32_t *us, uint8_t *phaseShift);
extern void sendZero(uint32_t *us, uint8_t *phaseShift); extern void sendZero(uint32_t *us, uint8_t *phaseShift);
extern void setZero(); extern void setZero();
@ -55,6 +38,7 @@ uint32_t m = 0;
uint8_t phaseShift = 0; uint8_t phaseShift = 0;
bool nrzi = true; bool nrzi = true;
uint8_t oneCount = 0; uint8_t oneCount = 0;
uint32_t lastBroadcast = 0;
void sendNrziBit(bool bit, bool zeroStuff) { void sendNrziBit(bool bit, bool zeroStuff) {
if (!bit) { if (!bit) {
@ -108,119 +92,543 @@ 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() { void initFrame() {
memset(&frame, 0, sizeof(frame)); memset(&frame, 0, sizeof(frame));
memcpy(&frame.daddr, DESTINATION, 6); memcpy(&frame.daddr, DESTINATION, 6);
memcpy(&frame.saddr, callsign, 6); memcpy(&frame.saddr, config.srcCallsign, 6);
for (uint8_t i = 0; i < 7; ++i) { for (uint8_t i = 0; i < 7; ++i) {
frame.daddr[i] <<=1; frame.daddr[i] <<=1;
frame.saddr[i] <<=1; frame.saddr[i] <<=1;
} }
frame.daddr[6] = (DESTINATION[6] << 1) | 0xe0; // command bit + reserved bits (AX.25 3.12.2) 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.ctrl = 0x03;
frame.proto = 0xf0; frame.proto = 0xf0;
} }
uint8_t mirrorByte(uint8_t byt) { uint8_t mirrorByte(uint8_t byt) {
return ((byt & 1) << 7) | ((byt & 2) << 5) | ((byt & 4) << 3) | ((byt & 8) << 1) 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); | ((byt & 16) >> 1) | ((byt & 32) >> 3) | ((byt & 64) >> 5) | ((byt & 128) >> 7);
} }
void setup() {
pinMode(PTT, OUTPUT);
digitalWrite(PTT, LOW); void createDefaultConfig() {
pinMode(PTT_IND, OUTPUT); config.flag = 1;
digitalWrite(PTT_IND, LOW); memset(&config.srcCallsign, ' ', 6);
initEditor(callsign, 7, EDITOR_EEPROM_BASE, editorPinMap); config.srcSsid = 0;
OUT_DDR = 0x3f; config.marker = 'b';
setZero(); config.interval = 300;
nrzi = true; 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(); 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() { void printConfig() {
uint8_t state = 0; lcd.setCursor(0, 0);
char str[83]; for (uint8_t i = 0; i < 6; ++i) {
char *sptr; lcd.print((char) config.srcCallsign[i]);
}
lcd.print('-');
lcd.print(config.srcSsid);
while (state != 2) { lcd.setCursor(10, 0);
if (!Serial.available()) { uint32_t seconds = config.interval;
continue; 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(); int16_t byt = Serial.read();
if (state == 0) {
if (byt == '$') { if (byt == '$') {
state = 1; nmeaState = 1;
sptr = str; sptr = nmeaStr;
*(sptr++) = (char) byt;
} }
} else if (state == 1) { if (nmeaState == 1) {
*(sptr++) = (char) byt; *(sptr++) = (char) byt;
if (byt == '\n') { if (sptr >= nmeaStr + sizeof(nmeaStr)) {
state = 2; nmeaState = 0;
}
if (byt == '*') {
*sptr = 0; *sptr = 0;
if (str[3] == 'R' && str[4] == 'M' && str[5] == 'C') { if (nmeaStr[3] == 'R' && nmeaStr[4] == 'M' && nmeaStr[5] == 'C') {
if (SEND_INVALID || *(sptr-6) == 'A') { nmeaState = 2;
state = 2;
} else { } else {
state = 0; nmeaState = 0;
} }
} else { Serial.println(nmeaStr);
state = 0;
}
Serial.print(str);
Serial.flush(); Serial.flush();
} }
} }
if (sptr > str + sizeof(str)) { if (nmeaState == 2) {
state = 0; 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(); uint32_t now = millis();
if (now - lastBroadcast > MIN_INTERVAL || lastBroadcast == 0) { if ((rmc.valid || SEND_INVALID) && ((now - lastBroadcast) / 1000 > config.interval || lastBroadcast == 0)) {
lastBroadcast = now; 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 // AX.25 3.8 - FCS is sent MSB first
uint16_t fcs = 0xffff; uint16_t fcs = 0xffff;
for (size_t i = 0; i < sizeof(frame); ++i) { for (size_t i = 0; i < sizeof(frame); ++i) {
fcs = _crc_xmodem_update(fcs, mirrorByte(((uint8_t*) &frame)[i])); fcs = _crc_xmodem_update(fcs, mirrorByte(((uint8_t*) &frame)[i]));
} }
for (char *c = str; c < sptr; ++c) { for (int16_t i = 0; i < 34 + config.commentLen; ++i) {
fcs = _crc_xmodem_update(fcs, mirrorByte(*c)); fcs = _crc_xmodem_update(fcs, mirrorByte(aprs[i]));
} }
fcs ^= 0xffff; fcs ^= 0xffff;
fcs = (mirrorByte(fcs & 0xff) << 8) | mirrorByte(fcs >> 8); fcs = (mirrorByte(fcs & 0xff) << 8) | mirrorByte(fcs >> 8);
digitalWrite(PTT, HIGH); digitalWrite(PTT, HIGH);
digitalWrite(PTT_IND, HIGH); digitalWrite(PTT_IND, HIGH);
sendFlag(true, TXDELAY+1); sendFlag(true, TXDELAY+1);
for (uint8_t i = i; i < FRAME_REPEAT; ++i) { for (uint8_t i = 0; i < FRAME_REPEAT; ++i) {
sendBell202buf((uint8_t*) &frame, sizeof(frame), false); sendBell202buf((uint8_t*) &frame, sizeof(frame), false);
sendBell202buf((uint8_t*) str, sptr-str, false); sendBell202buf((uint8_t*) aprs, 34 + config.commentLen, false);
sendBell202buf((uint8_t*) &fcs, 2, false); sendBell202buf((uint8_t*) &fcs, 2, false);
sendFlag(false, 1); sendFlag(false, 1);
} }
@ -228,10 +636,85 @@ void readNmeaGll() {
nrzi = true; nrzi = true;
digitalWrite(PTT, LOW); digitalWrite(PTT, LOW);
digitalWrite(PTT_IND, 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);
loadEepromConfig();
setZero();
nrzi = true;
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() { 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();
}
} }