Skip to content

Instantly share code, notes, and snippets.

@NT7S
Last active January 16, 2020 07:07
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save NT7S/f22db15865f485c51139 to your computer and use it in GitHub Desktop.
Save NT7S/f22db15865f485c51139 to your computer and use it in GitHub Desktop.
Simple FSQ Beacon for Arduino driving the Si5351
//
// Simple FSQ 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.
//
// 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 "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
// Global variables
Si5351 si5351;
unsigned long freq = 7105350; // Base freq is 1350 Hz higher than dial freq in USB
uint8_t cur_tone = 0;
static uint8_t crc8_table[256];
char callsign[10] = "n0call";
char tx_buffer[40];
uint8_t callsign_crc;
// Global variables used in ISRs
volatile bool proceed = false;
// Define the structure of a varicode table
typedef struct fsq_varicode
{
uint8_t ch;
uint8_t var[2];
} Varicode;
// The FSQ varicode table, based on the FSQ Varicode V3.0
// document provided by Murray Greenman, ZL1BPU
const Varicode code_table[] PROGMEM =
{
{' ', {00, 00}}, // space
{'!', {11, 30}},
{'"', {12, 30}},
{'#', {13, 30}},
{'$', {14, 30}},
{'%', {15, 30}},
{'&', {16, 30}},
{'\'', {17, 30}},
{'(', {18, 30}},
{')', {19, 30}},
{'*', {20, 30}},
{'+', {21, 30}},
{',', {27, 29}},
{'-', {22, 30}},
{'.', {27, 00}},
{'/', {23, 30}},
{'0', {10, 30}},
{'1', {01, 30}},
{'2', {02, 30}},
{'3', {03, 30}},
{'4', {04, 30}},
{'5', {05, 30}},
{'6', {06, 30}},
{'7', {07, 30}},
{'8', {8, 30}},
{'9', {9, 30}},
{':', {24, 30}},
{';', {25, 30}},
{'<', {26, 30}},
{'=', {00, 31}},
{'>', {27, 30}},
{'?', {28, 29}},
{'@', {00, 29}},
{'A', {01, 29}},
{'B', {02, 29}},
{'C', {03, 29}},
{'D', {04, 29}},
{'E', {05, 29}},
{'F', {06, 29}},
{'G', {07, 29}},
{'H', {8, 29}},
{'I', {9, 29}},
{'J', {10, 29}},
{'K', {11, 29}},
{'L', {12, 29}},
{'M', {13, 29}},
{'N', {14, 29}},
{'O', {15, 29}},
{'P', {16, 29}},
{'Q', {17, 29}},
{'R', {18, 29}},
{'S', {19, 29}},
{'T', {20, 29}},
{'U', {21, 29}},
{'V', {22, 29}},
{'W', {23, 29}},
{'X', {24, 29}},
{'Y', {25, 29}},
{'Z', {26, 29}},
{'[', {01, 31}},
{'\\', {02, 31}},
{']', {03, 31}},
{'^', {04, 31}},
{'_', {05, 31}},
{'`', {9, 31}},
{'a', {01, 00}},
{'b', {02, 00}},
{'c', {03, 00}},
{'d', {04, 00}},
{'e', {05, 00}},
{'f', {06, 00}},
{'g', {07, 00}},
{'h', {8, 00}},
{'i', {9, 00}},
{'j', {10, 00}},
{'k', {11, 00}},
{'l', {12, 00}},
{'m', {13, 00}},
{'n', {14, 00}},
{'o', {15, 00}},
{'p', {16, 00}},
{'q', {17, 00}},
{'r', {18, 00}},
{'s', {19, 00}},
{'t', {20, 00}},
{'u', {21, 00}},
{'v', {22, 00}},
{'w', {23, 00}},
{'x', {24, 00}},
{'y', {25, 00}},
{'z', {26, 00}},
{'{', {06, 31}},
{'|', {07, 31}},
{'}', {8, 31}},
{'~', {00, 30}},
{127, {28, 31}}, // DEL
{13, {28, 00}}, // CR
{10, {28, 00}}, // LF
{0, {28, 30}}, // IDLE
{241, {10, 31}}, // plus/minus
{246, {11, 31}}, // division sign
{248, {12, 31}}, // degrees sign
{158, {13, 31}}, // multiply sign
{156, {14, 31}}, // pound sterling sign
{8, {27, 31}} // BS
};
// Define an upper bound on the number of glyphs. Defining it this
// way allows adding characters without having to update a hard-coded
// upper bound.
#define NGLYPHS (sizeof(code_table)/sizeof(code_table[0]))
// 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;
}
// This is the heart of the beacon. Given a character, it finds the
// appropriate glyph and sets output from the Si5351A to key the
// FSQ signal.
void encode_char(int ch)
{
uint8_t i, fch, vcode1, vcode2;
for(i = 0; i < NGLYPHS; i++)
{
// Check each element of the varicode table to see if we've found the
// character we're trying to send.
fch = pgm_read_byte(&code_table[i].ch);
if(fch == ch)
{
// Found the character, now fetch the varicode chars
vcode1 = pgm_read_byte(&(code_table[i].var[0]));
vcode2 = pgm_read_byte(&(code_table[i].var[1]));
// Transmit the appropriate tone per a varicode char
if(vcode2 == 0)
{
// If the 2nd varicode char is a 0 in the table,
// we are transmitting a lowercase character, and thus
// only transmit one tone for this character.
// Generate tone
encode_tone(vcode1);
while(!proceed) // Wait for the timer interrupt to fire
; // before continuing.
noInterrupts(); // Turn off interrupts; just good practice...
proceed = false; // reset the flag for the next character...
interrupts(); // and re-enable interrupts.
}
else
{
// If the 2nd varicode char is anything other than 0 in
// the table, then we need to transmit both
// Generate 1st tone
encode_tone(vcode1);
while(!proceed) // Wait for the timer interrupt to fire
; // before continuing.
noInterrupts(); // Turn off interrupts; just good practice...
proceed = false; // reset the flag for the next character...
interrupts(); // and re-enable interrupts.
// Generate 2nd tone
encode_tone(vcode2);
while(!proceed) // Wait for the timer interrupt to fire
; // before continuing.
noInterrupts(); // Turn off interrupts; just good practice...
proceed = false; // reset the flag for the next character...
interrupts(); // and re-enable interrupts.
}
break; // We've found and transmitted the char,
// so exit the for loop
}
}
}
void encode_tone(uint8_t tone)
{
cur_tone = ((cur_tone + tone + 1) % 33);
//Serial.println(cur_tone);
si5351.set_freq((freq * 100) + (cur_tone * TONE_SPACING), 0, SI5351_CLK0);
}
// Loop through the string, transmitting one character at a time.
void encode(char *str)
{
// Reset the tone to 0 and turn on the output
cur_tone = 0;
si5351.output_enable(SI5351_CLK0, 1);
digitalWrite(LED_PIN, HIGH);
//Serial.println("=======");
// Transmit BOT
noInterrupts();
TCNT1 = 0;
proceed = false;
interrupts();
encode_char(' '); // Send a space for the dummy character
while(!proceed);
noInterrupts();
proceed = false;
interrupts();
// Send another space
encode_char(' ');
while(!proceed);
noInterrupts();
proceed = false;
interrupts();
// Now send LF
encode_char(10);
while(!proceed);
noInterrupts();
proceed = false;
interrupts();
// Now do the rest of the message
while (*str != '\0')
{
encode_char(*str++);
}
// Turn off the output
si5351.output_enable(SI5351_CLK0, 0);
digitalWrite(LED_PIN, LOW);
}
static void init_crc8(void)
{
int i,j;
uint8_t crc;
for(i = 0; i < 256; i++)
{
crc = i;
for(j = 0; j < 8; j++)
{
crc = (crc << 1) ^ ((crc & 0x80) ? 0x07 : 0);
}
crc8_table[i] = crc & 0xFF;
}
}
uint8_t crc8(char * text)
{
uint8_t crc='\0';
uint8_t ch;
int i;
for(i = 0; i < strlen(text); i++)
{
ch = text[i];
crc = crc8_table[(crc) ^ ch];
crc &= 0xFF;
}
return crc;
}
void setup()
{
// Use the Arduino's on-board LED as a keying indicator.
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
//Serial.begin(57600);
// 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(-6190);
si5351.set_freq(freq * 100, 0, SI5351_CLK0);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Set for max power if desired
si5351.output_enable(SI5351_CLK0, 0); // Disable the clock initially
// Initialize the CRC table
init_crc8();
// Generate the CRC for the callsign
callsign_crc = crc8(callsign);
// We are building a directed message here, but you can do whatever.
// So apparently, FSQ very specifically needs " \b " in
// directed mode to indicate EOT. A single backspace won't do it.
sprintf(tx_buffer, "%s:%02x%s", callsign, callsign_crc, "beacon \b ");
// Set up Timer1 for interrupts at 6 Hz.
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 = BAUD_3; // Set up interrupt trigger count;
interrupts(); // Re-enable interrupts.
}
void loop()
{
// Beacon a call sign and a locator.
encode(tx_buffer);
// Wait 5 minutes between beacons.
delay(300000);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment