Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Arduino FSQ Telemetry transmitter using JTEncode library
//
// Simple FSQ telemetry beacon for Arduino, with the Etherkit Si5351A
// Breakout Board, by Jason Milldrum NT7S.
//
// Original code based on Feld Hell beacon for Arduino by Mark
// Vandewettering K6HX, adapted for the Si5351A by Robert
// Liesenfeld AK6L <ak6l@ak6l.org>. Timer setup
// code by Thomas Knutsen LA3PNA.
//
// Telemetry is sent in the following format:
// <temp>,<rel humidity>,<date><time>
//
// Uses the following libraries:
// Si5351Arduino https://github.com/etherkit/Si5351Arduino
// JTEncode https://github.com/etherkit/JTEncode
// TinyGPS++ http://arduiniana.org/libraries/tinygpsplus/
// DHT11Lib http://playground.arduino.cc/Main/DHT11Lib
// Time http://www.pjrc.com/teensy/td_libs_Time.html
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject
// to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#include <si5351.h>
#include <dht11.h>
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Time.h>
#include <JTEncode.h>
#include "Wire.h"
#define TONE_SPACING 879 // ~8.7890625 Hz
#define BAUD_2 7812 // CTC value for 2 baud
#define BAUD_3 5208 // CTC value for 3 baud
#define BAUD_4_5 3472 // CTC value for 4.5 baud
#define BAUD_6 2604 // CTC value for 6 baud
#define LED_PIN 13
#define DHT11PIN 2
// Class instantiation
Si5351 si5351;
dht11 DHT11;
JTEncode jtencode;
SoftwareSerial gpsSerial(10, 11); // RX, TX
TinyGPSPlus gps;
// Global variables
//unsigned long freq = 10145850; // Base freq is 1350 Hz higher than dial freq in USB
//unsigned long freq = 14105850; // Base freq is 1350 Hz higher than dial freq in USB
unsigned long freq = 7105850UL; // Base freq is 1350 Hz higher than dial freq in USB
//unsigned long freq = 3595850; // Base freq is 1350 Hz higher than dial freq in USB
//unsigned long freq = 28145850; // Base freq is 1350 Hz higher than dial freq in USB
char station_callsign[] = "nt7s";
char to_callsign[] = "nt7s";
const char device_id[] = "1";
static uint8_t tx_buffer[200];
time_t next_tx = 0;
uint16_t tx_interval = 120;
const int offset = 0; // Set UTC time zone
uint16_t ctc, tone_spacing;
uint8_t chk;
// Global variables used in ISRs
volatile bool proceed = false;
// Timer interrupt vector. This toggles the variable we use to gate
// each column of output to ensure accurate timing. Called whenever
// Timer1 hits the count set below in setup().
ISR(TIMER1_COMPA_vect)
{
proceed = true;
}
// Loop through the string, transmitting one character at a time.
void encode()
{
uint8_t i, symbol_count;
char telemetry[150] = "hai";
// Clear out the transmit buffer
memset(tx_buffer, 0, 200);
// Compose the telemetry string
// TODO: must check for buffer overrun here
if(chk == DHTLIB_OK)
{
sprintf(telemetry, "[%s-%s.tlm]%d,%d,%02u%02u%4u%02u%02u%02u",
station_callsign, device_id, DHT11.temperature, DHT11.humidity,
gps.date.day(), gps.date.month(), gps.date.year(),
gps.time.hour(), gps.time.minute(), gps.time.second());
}
Serial.println(telemetry);
// Build the symbol table
jtencode.fsq_dir_encode(station_callsign, to_callsign, '#', telemetry, tx_buffer);
// Reset the tone to the base frequency and turn on the output
si5351.output_enable(SI5351_CLK0, 1);
digitalWrite(LED_PIN, HIGH);
// Get the channel symbol count
uint8_t j = 0;
while(tx_buffer[j++] != 0xff)
{
}
symbol_count = j - 1;
for(j = 0; j < symbol_count; j++)
{
Serial.print(tx_buffer[j], HEX);
Serial.print(" ");
}
Serial.println();
// Iterate through the symbols and transmit
for(i = 0; i < symbol_count; i++)
{
si5351.set_freq((freq * 100) + (tx_buffer[i] * tone_spacing), 0, SI5351_CLK0);
proceed = false;
while(!proceed);
}
// Turn off the output
si5351.output_enable(SI5351_CLK0, 0);
digitalWrite(LED_PIN, LOW);
}
void setup()
{
// Use the Arduino's on-board LED as a keying indicator.
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Init serial for debug and GPS
Serial.begin(57600);
gpsSerial.begin(4800);
// Initialize the Si5351
// Change the 2nd parameter in init if using a ref osc other
// than 25 MHz
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0);
// Set CLK0 output
si5351.set_correction(0);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Set for max power if desired
si5351.output_enable(SI5351_CLK0, 0); // Disable the clock initially
ctc = BAUD_4_5;
tone_spacing = TONE_SPACING;
// Set up Timer1 for interrupts every symbol period.
noInterrupts(); // Turn off interrupts.
TCCR1A = 0; // Set entire TCCR1A register to 0; disconnects
// interrupt output pins, sets normal waveform
// mode. We're just using Timer1 as a counter.
TCNT1 = 0; // Initialize counter value to 0.
TCCR1B = (1 << CS12) | // Set CS12 and CS10 bit to set prescale
(1 << CS10) | // to /1024
(1 << WGM12); // turn on CTC
// which gives, 64 us ticks
TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt.
OCR1A = ctc; // Set up interrupt trigger count;
interrupts(); // Re-enable interrupts.
// Wait for valid time & date from GPS before proceeding
while(!gps.time.isValid() || !gps.date.isValid())
{
while(gpsSerial.available() > 0)
{
if(gps.encode(gpsSerial.read()))
{
// set the Time to the latest GPS reading
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
gps.date.day(), gps.date.month(), gps.date.year());
adjustTime(offset * SECS_PER_HOUR);
}
}
}
next_tx = now(); // Set next tx trigger to happen immediately
delay(1000);
}
void loop()
{
// Feed GPS data to TinyGPSPlus and mirror serial from GPS
while(gpsSerial.available() > 0)
{
if(gps.encode(gpsSerial.read()))
{
// process gps messages
// when TinyGPS reports new data...
if(gps.time.age() < 500)
{
// set the Time to the latest GPS reading
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
gps.date.day(), gps.date.month(), gps.date.year());
adjustTime(offset * SECS_PER_HOUR);
}
}
}
// Get the DHT11 data
do
{
chk = DHT11.read(DHT11PIN);
} while(chk);
// Send the telemetry if we have a good GPS fix
if(gps.time.isValid() && gps.date.isValid())
{
if(now() > next_tx)
{
next_tx = next_tx + tx_interval; // Set next tx trigger
encode();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.