feat: use an LCD rather than a single 7segment digit
This commit is contained in:
parent
45d29a8391
commit
0fe613d223
5 changed files with 629 additions and 291 deletions
|
@ -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);
|
||||
}}
|
||||
|
||||
''')
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
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
|
||||
|
|
739
src/main.cpp
739
src/main.cpp
|
@ -1,25 +1,28 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <util/crc16.h>
|
||||
|
||||
#include "afsk_sinus.hpp"
|
||||
#include "eepromedit.hpp"
|
||||
#include <Arduino.h>
|
||||
#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 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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue