LightAPRS-W-1.0/LightAPRS-W-pico-balloon/LightAPRS-W-pico-balloon.ino

990 wiersze
30 KiB
C++
Czysty Wina Historia

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <Arduino.h>
#include <Wire.h>
#include <SoftwareSerial.h>
#include <avr/wdt.h>
#include <GEOFENCE.h> // Modified version of https://github.com/TomasTT7/TT7F-Float-Tracker/blob/master/Software/ARM_GEOFENCE.c
#include <LibAPRS.h> //Modified version of https://github.com/markqvist/LibAPRS
#include <TinyGPS++.h> //https://github.com/mikalhart/TinyGPSPlus
#include <LowPower.h> //https://github.com/rocketscream/Low-Power
#include <Adafruit_BMP085.h>//https://github.com/adafruit/Adafruit-BMP085-Library
#include <si5351.h> //https://github.com/etherkit/Si5351Arduino
#include <JTEncode.h> //https://github.com/etherkit/JTEncode (JT65/JT9/JT4/FT8/WSPR/FSQ Encoder Library)
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time
#include <TimerThree.h> //https://github.com/PaulStoffregen/TimerThree/
#define RfPDPin 19
#define GpsVccPin 18
#define RfPwrHLPin 21
#define RfPttPin 20
#define SiVccPin 0
#define BattPin A2
#define PIN_DRA_RX 22
#define PIN_DRA_TX 23
#define ADC_REFERENCE REF_3V3
#define OPEN_SQUELCH false
#define GpsON digitalWrite(GpsVccPin, LOW)//PNP
#define GpsOFF digitalWrite(GpsVccPin, HIGH)
#define RfON digitalWrite(RfPDPin, HIGH)
#define RfOFF digitalWrite(RfPDPin, LOW)
#define RfPwrHigh pinMode(RfPwrHLPin, INPUT)
#define RfPwrLow pinMode(RfPwrHLPin, OUTPUT);digitalWrite(RfPwrHLPin, LOW)
#define RfPttON digitalWrite(RfPttPin, HIGH)//NPN
#define RfPttOFF digitalWrite(RfPttPin, LOW)
#define SiON digitalWrite(SiVccPin, HIGH)//NPN
#define SiOFF digitalWrite(SiVccPin, LOW)
#define AprsPinInput pinMode(12,INPUT);pinMode(13,INPUT);pinMode(14,INPUT);pinMode(15,INPUT)
#define AprsPinOutput pinMode(12,OUTPUT);pinMode(13,OUTPUT);pinMode(14,OUTPUT);pinMode(15,OUTPUT)
//#define DEVMODE // Development mode. Uncomment to enable for debugging.
//****************************** APRS CONFIG **********************************
char CallSign[7]="NOCALL"; //DO NOT FORGET TO CHANGE YOUR CALLSIGN
int8_t CallNumber=11; //SSID http://www.aprs.org/aprs11/SSIDs.txt
char Symbol='O'; // '/O' for balloon, '/>' for car, for more info : http://www.aprs.org/symbols/symbols-new.txt
bool alternateSymbolTable = false ; //false = '/' , true = '\'
char comment[50] = "http://www.lightaprs.com"; // Max 50 char
char StatusMessage[50] = "LightAPRS-W by TA2NHP & TA2MUN";
//*****************************************************************************
uint16_t BeaconWait=50; //seconds sleep for next beacon (HF or VHF). This is optimized value, do not change this if possible.
uint16_t BattWait=60; //seconds sleep if super capacitors/batteries are below BattMin (important if power source is solar panel)
float BattMin=4.5; // min Volts to wake up.
float DraHighVolt=6.0;// min Volts for radio module (DRA818V) to transmit (TX) 1 Watt, below this transmit 0.5 Watt.
float GpsMinVolt=4.0; //min Volts for GPS to wake up. (important if power source is solar panel)
float WsprBattMin=4.5; //min Volts for HF mradio module to transmit (TX) ~10 mW
//****************************** HF CONFIG *************************************
char hf_call[7] = "NOCALL"; //DO NOT FORGET TO CHANGE YOUR CALLSIGN
//#define WSPR_DEFAULT_FREQ 10140200UL //30m band
#define WSPR_DEFAULT_FREQ 14097100UL //20m band
//#define WSPR_DEFAULT_FREQ 18106100UL //17M band
//#define WSPR_DEFAULT_FREQ 21096100UL //15m band
//#define WSPR_DEFAULT_FREQ 24926100UL //12M band
//#define WSPR_DEFAULT_FREQ 28126100UL //10m band
//for all bands -> http://wsprnet.org/drupal/node/7352
// Supported modes, default HF mode is WSPR
enum mode {MODE_JT9, MODE_JT65, MODE_JT4, MODE_WSPR, MODE_FSQ_2, MODE_FSQ_3,
MODE_FSQ_4_5, MODE_FSQ_6, MODE_FT8};
enum mode cur_mode = MODE_WSPR; //default HF mode
//supported other modes freq for 20m
#define JT9_DEFAULT_FREQ 14078700UL
#define FT8_DEFAULT_FREQ 14075000UL
#define JT65_DEFAULT_FREQ 14078300UL
#define JT4_DEFAULT_FREQ 14078500UL
#define FSQ_DEFAULT_FREQ 7105350UL // Base freq is 1350 Hz higher than dial freq in USB
//*******************************************************************************
//****************************** APRS SETTINGS *********************************
//do not change WIDE path settings below if you don't know what you are doing :)
uint8_t Wide1=1; // 1 for WIDE1-1 path
uint8_t Wide2=1; // 1 for WIDE2-1 path
/**
Airborne stations above a few thousand feet should ideally use NO path at all, or at the maximum just WIDE2-1 alone.
Due to their extended transmit range due to elevation, multiple digipeater hops are not required by airborne stations.
Multi-hop paths just add needless congestion on the shared APRS channel in areas hundreds of miles away from the aircraft's own location.
NEVER use WIDE1-1 in an airborne path, since this can potentially trigger hundreds of home stations simultaneously over a radius of 150-200 miles.
*/
uint8_t pathSize=2; // 2 for WIDE1-N,WIDE2-N ; 1 for WIDE2-N
boolean autoPathSizeHighAlt = true; //force path to WIDE2-N only for high altitude (airborne) beaconing (over 1.000 meters (3.280 feet))
boolean beaconViaARISS = false; //there are no iGates in some regions (such as North Africa, Oceans, etc) so try to beacon via ARISS (International Space Station) https://www.amsat.org/amateur-radio-on-the-iss/
// Send aprs high precision position extension (adds 5 bytes to beacon messaage)
boolean send_aprs_enhanced_precision = true;
boolean radioSetup = false;
boolean aliveStatus = true; //for tx status message on first wake-up just once.
static char telemetry_buff[100];// telemetry buffer
uint16_t TxCount = 1; //increase +1 after every APRS transmission
//*******************************************************************************
//****************************** HF SETTINGS *********************************
#define JT9_TONE_SPACING 174 // ~1.74 Hz
#define JT65_TONE_SPACING 269 // ~2.69 Hz
#define JT4_TONE_SPACING 437 // ~4.37 Hz
#define WSPR_TONE_SPACING 146 // ~1.46 Hz
#define FSQ_TONE_SPACING 879 // ~8.79 Hz
#define FT8_TONE_SPACING 625 // ~6.25 Hz
#define JT9_DELAY 576 // Delay value for JT9-1
#define JT65_DELAY 371 // Delay in ms for JT65A
#define JT4_DELAY 229 // Delay value for JT4A
#define WSPR_DELAY 683 // Delay value for WSPR
#define FSQ_2_DELAY 500 // Delay value for 2 baud FSQ
#define FSQ_3_DELAY 333 // Delay value for 3 baud FSQ
#define FSQ_4_5_DELAY 222 // Delay value for 4.5 baud FSQ
#define FSQ_6_DELAY 167 // Delay value for 6 baud FSQ
#define FT8_DELAY 159 // Delay value for FT8
#define CORRECTION -3700 // Change this for your ref osc
// Global variables
unsigned long hf_freq;
char hf_message[13] = "NOCALL AA00";//for non WSPR modes, you don't have to change this, updated by hf_call and GPS location
char hf_loc[] = "AA00"; //for WSPR, updated by GPS location. You don't have to change this.
uint8_t dbm = 10;
uint8_t tx_buffer[255];
uint8_t symbol_count;
uint16_t tone_delay, tone_spacing;
volatile bool proceed = false;
boolean HFSent = false;
//*******************************************************************************
//****************************** GPS SETTINGS *********************************
int16_t GpsResetTime=1800; // timeout for reset if GPS is not fixed
// GEOFENCE
uint32_t GEOFENCE_APRS_frequency = 144390000; //default frequency before geofencing. This variable will be updated based on GPS location.
uint32_t GEOFENCE_no_tx = 0;
boolean arissModEnabled = false; //do not change this, temp value.
boolean GpsFirstFix=false; //do not change this
boolean ublox_high_alt_mode_enabled = false; //do not change this
int16_t GpsInvalidTime=0; //do not change this
//********************************************************************************
Si5351 si5351(0x60);
TinyGPSPlus gps;
Adafruit_BMP085 bmp;
JTEncode jtencode;
void setup() {
wdt_enable(WDTO_8S);
analogReference(INTERNAL2V56);
pinMode(RfPDPin, OUTPUT);
pinMode(GpsVccPin, OUTPUT);
pinMode(RfPwrHLPin, OUTPUT);
pinMode(RfPttPin, OUTPUT);
pinMode(BattPin, INPUT);
pinMode(PIN_DRA_TX, INPUT);
pinMode(SiVccPin, OUTPUT);
RfOFF;
GpsOFF;
RfPwrLow;
RfPttOFF;
SiOFF;
AprsPinInput;
Serial.begin(57600); //Arduino serial
Serial1.begin(9600); //GPS serial
#if defined(DEVMODE)
Serial.println(F("Start"));
#endif
APRS_init(ADC_REFERENCE, OPEN_SQUELCH);
APRS_setCallsign(CallSign,CallNumber);
APRS_setDestination("APLIGA", 0);
APRS_setMessageDestination("APLIGA", 0);
APRS_setPath1("WIDE1", Wide1);
APRS_setPath2("WIDE2", Wide2);
APRS_useAlternateSymbolTable(alternateSymbolTable);
APRS_setSymbol(Symbol);
//increase following value (for example to 500UL) if you experience packet loss/decode issues.
APRS_setPreamble(350UL);
APRS_setPathSize(pathSize);
AprsPinInput;
bmp.begin();
}
void loop() {
wdt_reset();
if (readBatt() > BattMin) {
if (aliveStatus) {
#if defined(DEVMODE)
Serial.println(F("Sending"));
#endif
sendStatus();
#if defined(DEVMODE)
Serial.println(F("Status sent"));
#endif
aliveStatus = false;
while (readBatt() < BattMin) {
sleepSeconds(BattWait);
}
}
updateGpsData(1000);
gpsDebug();
if(gps.location.isValid() && gps.location.age()<1000){
GpsInvalidTime=0;
}else{
GpsInvalidTime++;
if(GpsInvalidTime > GpsResetTime){
GpsOFF;
ublox_high_alt_mode_enabled = false; //gps sleep mode resets high altitude mode.
wdt_reset();
delay(1000);
GpsON;
GpsInvalidTime=0;
}
}
if ((gps.location.age() < 1000 || gps.location.isUpdated()) && gps.location.isValid()) {
if (gps.satellites.isValid() && gps.satellites.value() >= 3) {
GpsOFF;
GpsFirstFix = true;
ublox_high_alt_mode_enabled = false; //gps sleep mode resets high altitude mode.
#if defined(DEVMODE)
Serial.println(F("Before WSPR Check"));
#endif
// preparations for HF starts one minute before TX time at minute 3, 7, 13, 17, 23, 27, 33, 37, 43, 47, 53 or 57. No APRS TX during this period...
if (readBatt() > WsprBattMin && timeStatus() == timeSet && ((minute() % 10 == 3) || (minute() % 10 == 7)) ) {
GridLocator(hf_loc, gps.location.lat(), gps.location.lng());
sprintf(hf_message,"%s %s",hf_call,hf_loc);
#if defined(DEVMODE)
Serial.println(F("Digital HF Mode Prepearing"));
Serial.println(hf_loc);
#endif
HFSent = false;
//HF transmission starts at minute 4, 8, 14, 18, 24, 28, 34, 38, 44, 48, 54 or 58
while (((minute() % 10 != 4) || (minute() % 10 != 8)) && second() != 0) {
wdt_reset();
}
#if defined(DEVMODE)
Serial.println(F("Digital HF Mode Sending"));
#endif
encode();
HFSent = true;
#if defined(DEVMODE)
Serial.println(F("Digital HF Mode Sent"));
#endif
} else {
updateTelemetry();
//APRS frequency isn't the same for the whole world. (for pico balloon only)
if (!radioSetup || TxCount == 200) {
configureFreqbyLocation();
}
if(!arissModEnabled && autoPathSizeHighAlt && gps.altitude.feet()>3000){
//force to use high altitude settings (WIDE2-n)
APRS_setPathSize(1);
} else {
//use default settings
APRS_setPathSize(pathSize);
}
//in some countries Airborne APRS is not allowed. (for pico balloon only)
if (isAirborneAPRSAllowed()) {
sendLocation();
}
freeMem();
Serial.flush();
//If two minutes time slot is close, sleep less than default.
if (timeStatus() == timeSet && ((minute() % 10 == 2) || (minute() % 10 == 6))){
sleepSeconds(60 - second());
} else {
sleepSeconds(BeaconWait);
}
}
} else {
#if defined(DEVMODE)
Serial.println(F("Not enough sattelites"));
#endif
}
}
} else {
sleepSeconds(BattWait);
}
}
void aprs_msg_callback(struct AX25Msg *msg) {
//do not remove this function, necessary for LibAPRS.
}
void sleepSeconds(int sec) {
if (GpsFirstFix){//sleep gps after first fix
GpsOFF;
ublox_high_alt_mode_enabled = false;
}
RfOFF;
RfPttOFF;
SiOFF;
#if defined(DEVMODE)
Serial.flush();
#endif
wdt_disable();
for (int i = 0; i < sec; i++) {
if (readBatt() < GpsMinVolt){
GpsOFF;
ublox_high_alt_mode_enabled = false;
}
LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_ON);
}
wdt_enable(WDTO_8S);
wdt_reset();
}
boolean isAirborneAPRSAllowed() {
float tempLat = gps.location.lat();
float tempLong = gps.location.lng();
GEOFENCE_position(tempLat, tempLong);
boolean airborne = true;
if (GEOFENCE_no_tx == 1) {
airborne = false;
}
return airborne;
}
boolean inARISSGeoFence(float tempLat, float tempLong) {
boolean ariss = false;
//North Africa
if(tempLat>0 && tempLat<32 && tempLong>0 && tempLong<32){ariss = true;}
//North Pacific
if(tempLat>28 && tempLat<50 && tempLong>-180 && tempLong<-130){ariss = true;}
//North Atlantic
if(tempLat>25 && tempLat<42 && tempLong>-60 && tempLong<-33){ariss = true;}
return ariss;
}
void configureFreqbyLocation() {
float tempLat = gps.location.lat();
float tempLong = gps.location.lng();
if(beaconViaARISS && inARISSGeoFence(tempLat, tempLong)) {
APRS_setPath1("ARISS", Wide1);
APRS_setPath2("WIDE2", Wide2);
APRS_setPathSize(2);
configDra818("145.8250");
arissModEnabled = true;
} else {
GEOFENCE_position(tempLat,tempLong);
float dividedFreq = GEOFENCE_APRS_frequency / 1000000.f;
char aprsFreq_buff[9];
dtostrf(dividedFreq, 8, 4, aprsFreq_buff);
configDra818(aprsFreq_buff);
arissModEnabled = false;
}
radioSetup = true;
}
byte configDra818(char *freq)
{
SoftwareSerial Serial_dra(PIN_DRA_RX, PIN_DRA_TX);
Serial_dra.begin(9600);
RfON;
char ack[3];
int n;
delay(2000);
char cmd[50];
#if defined(DEVMODE)
Serial.println(F("Config DRA.."));
#endif
sprintf(cmd, "AT+DMOSETGROUP=0,%s,%s,0000,4,0000", freq, freq);
Serial_dra.println(cmd);
ack[2] = 0;
while (ack[2] != 0xa)
{
if (Serial_dra.available() > 0) {
ack[0] = ack[1];
ack[1] = ack[2];
ack[2] = Serial_dra.read();
}
}
Serial_dra.end();
RfOFF;
pinMode(PIN_DRA_TX, INPUT);
#if defined(DEVMODE)
if (ack[0] == 0x30) Serial.println(F("Frequency updated...")); else Serial.println(F("Frequency update error!"));
#endif
return (ack[0] == 0x30) ? 1 : 0;
}
void updatePosition(int high_precision, char *dao) {
// Convert and set latitude NMEA string Degree Minute Hundreths of minutes ddmm.hh[S,N].
char latStr[10];
RawDegrees rawDeg = gps.location.rawLat();
uint32_t min_nnnnn;
char lat_dao = 0;
min_nnnnn = rawDeg.billionths * 0.006;
if ( ((min_nnnnn / (high_precision ? 1 : 100)) % 10) >= 5 && min_nnnnn < (6000000 - ((high_precision ? 1 : 100)*5)) ) {
// round up. Avoid overflow (59.999999 should never become 60.0 or more)
min_nnnnn = min_nnnnn + (high_precision ? 1 : 100)*5;
}
sprintf(latStr, "%02u%02u.%02u%c", (unsigned int ) (rawDeg.deg % 100), (unsigned int ) ((min_nnnnn / 100000) % 100), (unsigned int ) ((min_nnnnn / 1000) % 100), rawDeg.negative ? 'S' : 'N');
if (dao)
dao[0] = (char) ((min_nnnnn % 1000) / 11) + 33;
APRS_setLat(latStr);
// Convert and set longitude NMEA string Degree Minute Hundreths of minutes ddmm.hh[E,W].
char lonStr[10];
rawDeg = gps.location.rawLng();
min_nnnnn = rawDeg.billionths * 0.006;
if ( ((min_nnnnn / (high_precision ? 1 : 100)) % 10) >= 5 && min_nnnnn < (6000000 - ((high_precision ? 1 : 100)*5)) ) {
min_nnnnn = min_nnnnn + (high_precision ? 1 : 100)*5;
}
sprintf(lonStr, "%03u%02u.%02u%c", (unsigned int ) (rawDeg.deg % 1000), (unsigned int ) ((min_nnnnn / 100000) % 100), (unsigned int ) ((min_nnnnn / 1000) % 100), rawDeg.negative ? 'W' : 'E');
if (dao) {
dao[1] = (char) ((min_nnnnn % 1000) / 11) + 33;
dao[2] = 0;
}
APRS_setLon(lonStr);
}
void updateTelemetry() {
sprintf(telemetry_buff, "%03d", gps.course.isValid() ? (int)gps.course.deg() : 0);
telemetry_buff[3] = '/';
sprintf(telemetry_buff + 4, "%03d", gps.speed.isValid() ? (int)gps.speed.knots() : 0);
telemetry_buff[7] = '/';
telemetry_buff[8] = 'A';
telemetry_buff[9] = '=';
//sprintf(telemetry_buff + 10, "%06lu", (long)gps.altitude.feet());
//fixing negative altitude values causing display bug on aprs.fi
float tempAltitude = gps.altitude.feet();
if (tempAltitude>0){
//for positive values
sprintf(telemetry_buff + 10, "%06lu", (long)tempAltitude);
} else{
//for negative values
sprintf(telemetry_buff + 10, "%06d", (long)tempAltitude);
}
telemetry_buff[16] = ' ';
sprintf(telemetry_buff + 17, "%03d", TxCount);
telemetry_buff[20] = 'T';
telemetry_buff[21] = 'x';
telemetry_buff[22] = 'C';
telemetry_buff[23] = ' '; float tempC = bmp.readTemperature();//-21.4;//
dtostrf(tempC, 6, 2, telemetry_buff + 24);
telemetry_buff[30] = 'C';
telemetry_buff[31] = ' '; float pressure = bmp.readPressure() / 100.0; //Pa to hPa
dtostrf(pressure, 7, 2, telemetry_buff + 32);
telemetry_buff[39] = 'h';
telemetry_buff[40] = 'P';
telemetry_buff[41] = 'a';
telemetry_buff[42] = ' ';
dtostrf(readBatt(), 5, 2, telemetry_buff + 43);
telemetry_buff[48] = 'V';
telemetry_buff[49] = ' ';
sprintf(telemetry_buff + 50, "%02d", gps.satellites.isValid() ? (int)gps.satellites.value() : 0);
telemetry_buff[52] = 'S';
telemetry_buff[53] = ' ';
sprintf(telemetry_buff + 54, "%s", comment);
// APRS PRECISION AND DATUM OPTION http://www.aprs.org/aprs12/datum.txt ; this extension should be added at end of beacon message.
// We only send this detailed info if it's likely we're interested in, i.e. searching for landing position
if (send_aprs_enhanced_precision && gps.altitude.feet() < 10000L && strlen(telemetry_buff) < sizeof(telemetry_buff) - 1 - 5 - 1) /* room for " !wAB!\0" */ {
char dao[3];
updatePosition(1, dao);
sprintf(telemetry_buff + strlen(telemetry_buff), " !w%s!", dao);
} else {
updatePosition(0, NULL);
}
#if defined(DEVMODE)
Serial.println(telemetry_buff);
#endif
}
void sendLocation() {
#if defined(DEVMODE)
Serial.println(F("Location sending with comment"));
#endif
if ((readBatt() > DraHighVolt) && (readBatt() < 10)) RfPwrHigh; //DRA Power 1 Watt
else RfPwrLow; //DRA Power 0.5 Watt
int hh = gps.time.hour();
int mm = gps.time.minute();
int ss = gps.time.second();
char timestamp_buff[7];
sprintf(timestamp_buff, "%02d", gps.time.isValid() ? (int)gps.time.hour() : 0);
sprintf(timestamp_buff + 2, "%02d", gps.time.isValid() ? (int)gps.time.minute() : 0);
sprintf(timestamp_buff + 4, "%02d", gps.time.isValid() ? (int)gps.time.second() : 0);
timestamp_buff[6] = 'h';
AprsPinOutput;
RfON;
delay(2000);
RfPttON;
delay(1000);
APRS_sendLocWtTmStmp(telemetry_buff, strlen(telemetry_buff), timestamp_buff);
delay(50);
while (digitalRead(1)) {;} //LibAprs TX Led pin PB1
delay(50);
RfPttOFF;
RfOFF;
AprsPinInput;
#if defined(DEVMODE)
Serial.println(F("Location sent with comment"));
#endif
TxCount++;
}
void sendStatus() {
if ((readBatt() > DraHighVolt) && (readBatt() < 10)) RfPwrHigh; //DRA Power 1 Watt
else RfPwrLow; //DRA Power 0.5 Watt
AprsPinOutput;
RfON;
delay(2000);
RfPttON;
delay(1000);
APRS_sendStatus(StatusMessage, strlen(StatusMessage));
delay(50);
while (digitalRead(1)) {;} //LibAprs TX Led pin PB1
delay(50);
RfPttOFF;
RfOFF;
AprsPinInput;
#if defined(DEVMODE)
Serial.println(F("Status sent"));
#endif
TxCount++;
}
static void updateGpsData(int ms)
{
GpsON;
if(!ublox_high_alt_mode_enabled){
//enable ublox high altitude mode
delay(100);
setGPS_DynamicModel6();
#if defined(DEVMODE)
Serial.println(F("ublox DynamicModel6 enabled..."));
#endif
ublox_high_alt_mode_enabled = true;
}
while (!Serial1) {
delayMicroseconds(1); // wait for serial port to connect.
}
unsigned long start = millis();
unsigned long bekle = 0;
do
{
while (Serial1.available() > 0) {
char c;
c = Serial1.read();
gps.encode(c);
bekle = millis();
}
if (gps.time.isValid())
{
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(), NULL, NULL, NULL);
}
if (bekle != 0 && bekle + 10 < millis())break;
} while (millis() - start < ms);
}
float readBatt() {
float R1 = 560000.0; // 560K
float R2 = 100000.0; // 100K
float value = 0.0;
do {
value = analogRead(BattPin);
delay(5);// ilk açılışta hatalı ölçüm yapmaması için
value = analogRead(BattPin);
//value = value - 8; // direnç tolerans düzeltmesi
value = (value * 2.56) / 1024.0;
value = value / (R2 / (R1 + R2));
} while (value > 16.0);
return value ;
}
void Timer3Tick(void)
{
proceed = true;
}
void encode()
{
wdt_reset();
SiON;
delay(20);
si5351.init(SI5351_CRYSTAL_LOAD_0PF, 0, CORRECTION);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Set for max power if desired
si5351.output_enable(SI5351_CLK0, 0);
uint8_t i;
switch(cur_mode)
{
case MODE_JT9:
hf_freq = JT9_DEFAULT_FREQ;
symbol_count = JT9_SYMBOL_COUNT; // From the library defines
tone_spacing = JT9_TONE_SPACING;
tone_delay = JT9_DELAY;
break;
case MODE_JT65:
hf_freq = JT65_DEFAULT_FREQ;
symbol_count = JT65_SYMBOL_COUNT; // From the library defines
tone_spacing = JT65_TONE_SPACING;
tone_delay = JT65_DELAY;
break;
case MODE_JT4:
hf_freq = JT4_DEFAULT_FREQ;
symbol_count = JT4_SYMBOL_COUNT; // From the library defines
tone_spacing = JT4_TONE_SPACING;
tone_delay = JT4_DELAY;
break;
case MODE_WSPR:
hf_freq = WSPR_DEFAULT_FREQ;
symbol_count = WSPR_SYMBOL_COUNT; // From the library defines
tone_spacing = WSPR_TONE_SPACING;
tone_delay = WSPR_DELAY;
break;
case MODE_FT8:
hf_freq = FT8_DEFAULT_FREQ;
symbol_count = FT8_SYMBOL_COUNT; // From the library defines
tone_spacing = FT8_TONE_SPACING;
tone_delay = FT8_DELAY;
break;
case MODE_FSQ_2:
hf_freq = FSQ_DEFAULT_FREQ;
tone_spacing = FSQ_TONE_SPACING;
tone_delay = FSQ_2_DELAY;
break;
case MODE_FSQ_3:
hf_freq = FSQ_DEFAULT_FREQ;
tone_spacing = FSQ_TONE_SPACING;
tone_delay = FSQ_3_DELAY;
break;
case MODE_FSQ_4_5:
hf_freq = FSQ_DEFAULT_FREQ;
tone_spacing = FSQ_TONE_SPACING;
tone_delay = FSQ_4_5_DELAY;
break;
case MODE_FSQ_6:
hf_freq = FSQ_DEFAULT_FREQ;
tone_spacing = FSQ_TONE_SPACING;
tone_delay = FSQ_6_DELAY;
break;
}
set_tx_buffer();
// Now transmit the channel symbols
if(cur_mode == MODE_FSQ_2 || cur_mode == MODE_FSQ_3 || cur_mode == MODE_FSQ_4_5 || cur_mode == MODE_FSQ_6)
{
uint8_t j = 0;
while(tx_buffer[j++] != 0xff);
symbol_count = j - 1;
}
// Reset the tone to the base frequency and turn on the output
si5351.output_enable(SI5351_CLK0, 1);
uint32_t timer_period= tone_delay;
timer_period*=1000;
Timer3.initialize(timer_period);
Timer3.attachInterrupt(Timer3Tick);
Timer3.restart();
for(i = 0; i < symbol_count; i++)
{
si5351.set_freq((hf_freq * 100) + (tx_buffer[i] * tone_spacing), SI5351_CLK0);
proceed = false;
while (!proceed);
wdt_reset();
}
Timer3.stop();
// Turn off the output
si5351.output_enable(SI5351_CLK0, 0);
SiOFF;
}
void set_tx_buffer()
{
// Clear out the transmit buffer
memset(tx_buffer, 0, 255);
// Set the proper frequency and timer CTC depending on mode
switch(cur_mode)
{
case MODE_JT9:
jtencode.jt9_encode(hf_message, tx_buffer);
break;
case MODE_JT65:
jtencode.jt65_encode(hf_message, tx_buffer);
break;
case MODE_JT4:
jtencode.jt4_encode(hf_message, tx_buffer);
break;
case MODE_WSPR:
jtencode.wspr_encode(hf_call, hf_loc, dbm, tx_buffer);
break;
case MODE_FT8:
jtencode.ft8_encode(hf_message, tx_buffer);
break;
case MODE_FSQ_2:
case MODE_FSQ_3:
case MODE_FSQ_4_5:
case MODE_FSQ_6:
jtencode.fsq_dir_encode(hf_call, "n0call", ' ', "hello world", tx_buffer);
break;
}
}
void GridLocator(char *dst, float latt, float lon) {
int o1, o2;
int a1, a2;
float remainder;
// longitude
remainder = lon + 180.0;
o1 = (int)(remainder / 20.0);
remainder = remainder - (float)o1 * 20.0;
o2 = (int)(remainder / 2.0);
// latitude
remainder = latt + 90.0;
a1 = (int)(remainder / 10.0);
remainder = remainder - (float)a1 * 10.0;
a2 = (int)(remainder);
dst[0] = (char)o1 + 'A';
dst[1] = (char)a1 + 'A';
dst[2] = (char)o2 + '0';
dst[3] = (char)a2 + '0';
dst[4] = (char)0;
}
void freeMem() {
#if defined(DEVMODE)
Serial.print(F("Free RAM: ")); Serial.print(freeMemory()); Serial.println(F(" byte"));
#endif
}
void gpsDebug() {
#if defined(DEVMODE)
Serial.println();
Serial.println(F("Sats HDOP Latitude Longitude Fix Date Time Date Alt Course Speed Card Chars Sentences Checksum"));
Serial.println(F(" (deg) (deg) Age Age (m) --- from GPS ---- RX RX Fail"));
Serial.println(F("-----------------------------------------------------------------------------------------------------------------"));
printInt(gps.satellites.value(), gps.satellites.isValid(), 5);
printInt(gps.hdop.value(), gps.hdop.isValid(), 5);
printFloat(gps.location.lat(), gps.location.isValid(), 11, 6);
printFloat(gps.location.lng(), gps.location.isValid(), 12, 6);
printInt(gps.location.age(), gps.location.isValid(), 5);
printDateTime(gps.date, gps.time);
printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2);
printFloat(gps.course.deg(), gps.course.isValid(), 7, 2);
printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2);
printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.value()) : "*** ", 6);
printInt(gps.charsProcessed(), true, 6);
printInt(gps.sentencesWithFix(), true, 10);
printInt(gps.failedChecksum(), true, 9);
Serial.println();
#endif
}
static void printFloat(float val, bool valid, int len, int prec)
{
#if defined(DEVMODE)
if (!valid)
{
while (len-- > 1)
Serial.print('*');
Serial.print(' ');
}
else
{
Serial.print(val, prec);
int vi = abs((int)val);
int flen = prec + (val < 0.0 ? 2 : 1);
flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
for (int i = flen; i < len; ++i)
Serial.print(' ');
}
#endif
}
static void printInt(unsigned long val, bool valid, int len)
{
#if defined(DEVMODE)
char sz[32] = "*****************";
if (valid)
sprintf(sz, "%ld", val);
sz[len] = 0;
for (int i = strlen(sz); i < len; ++i)
sz[i] = ' ';
if (len > 0)
sz[len - 1] = ' ';
Serial.print(sz);
#endif
}
static void printDateTime(TinyGPSDate &d, TinyGPSTime &t)
{
#if defined(DEVMODE)
if (!d.isValid())
{
Serial.print(F("********** "));
}
else
{
char sz[32];
sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
Serial.print(sz);
}
if (!t.isValid())
{
Serial.print(F("******** "));
}
else
{
char sz[32];
sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
Serial.print(sz);
}
printInt(d.age(), d.isValid(), 5);
#endif
}
static void printStr(const char *str, int len)
{
#if defined(DEVMODE)
int slen = strlen(str);
for (int i = 0; i < len; ++i)
Serial.print(i < slen ? str[i] : ' ');
#endif
}
//following GPS code from : https://github.com/HABduino/HABduino/blob/master/Software/habduino_v4/habduino_v4.ino
void setGPS_DynamicModel6()
{
int gps_set_sucess=0;
uint8_t setdm6[] = {
0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x06,
0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00,
0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xDC };
while(!gps_set_sucess)
{
sendUBX(setdm6, sizeof(setdm6)/sizeof(uint8_t));
gps_set_sucess=getUBX_ACK(setdm6);
}
}
void sendUBX(uint8_t *MSG, uint8_t len) {
Serial1.flush();
Serial1.write(0xFF);
delay(500);
for(int i=0; i<len; i++) {
Serial1.write(MSG[i]);
}
}
boolean getUBX_ACK(uint8_t *MSG) {
uint8_t b;
uint8_t ackByteID = 0;
uint8_t ackPacket[10];
unsigned long startTime = millis();
// Construct the expected ACK packet
ackPacket[0] = 0xB5; // header
ackPacket[1] = 0x62; // header
ackPacket[2] = 0x05; // class
ackPacket[3] = 0x01; // id
ackPacket[4] = 0x02; // length
ackPacket[5] = 0x00;
ackPacket[6] = MSG[2]; // ACK class
ackPacket[7] = MSG[3]; // ACK id
ackPacket[8] = 0; // CK_A
ackPacket[9] = 0; // CK_B
// Calculate the checksums
for (uint8_t ubxi=2; ubxi<8; ubxi++) {
ackPacket[8] = ackPacket[8] + ackPacket[ubxi];
ackPacket[9] = ackPacket[9] + ackPacket[8];
}
while (1) {
// Test for success
if (ackByteID > 9) {
// All packets in order!
return true;
}
// Timeout if no valid response in 3 seconds
if (millis() - startTime > 3000) {
return false;
}
// Make sure data is available to read
if (Serial1.available()) {
b = Serial1.read();
// Check that bytes arrive in sequence as per expected ACK packet
if (b == ackPacket[ackByteID]) {
ackByteID++;
}
else {
ackByteID = 0; // Reset and look again, invalid order
}
}
}
}