Last active
January 16, 2022 18:25
-
-
Save NT7S/3a85c57af3808d14c390 to your computer and use it in GitHub Desktop.
Feld Hell beacon for Arduino and Si5351
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Simple Feld Hell beacon for Arduino, with the Etherkit Si5351a breakout | |
// board, by Jason Milldrum NT7S. | |
// | |
// Original Feld Hell code for Arduino by Mark Vandewettering K6HX, adapted | |
// for the Si5351A by Robert Liesenfeld AK6L <ak6l@ak6l.org>. Timer setup | |
// code by Thomas Knutsen LA3PNA. | |
// | |
// | |
// Copyright (c) 2015 Robert Liesenfeld AK6L <ak6l@ak6l.org> | |
// | |
// 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 some variables. | |
Si5351 si5351; | |
long freq = 14063000; | |
int ledPin = 13; | |
// This variable must be defined 'volatile' to avoid some compiler | |
// optimizations, which would render the interrupt timing useless. | |
volatile bool proceed = false; | |
// Define the structure of a glyph | |
typedef struct glyph { | |
char ch ; | |
word col[7] ; | |
} Glyph ; | |
// Build the Feld Hell "font". This is declared PROGMEM to save | |
// memory on the Arduino; this data will be stored in program | |
// flash instead of loaded in memory. This makes accesses slower | |
// and more complicated, but this is a small price to pay for the | |
// memory savings. | |
const Glyph glyphtab[] PROGMEM = { | |
{' ', {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{'A', {0x07fc, 0x0e60, 0x0c60, 0x0e60, 0x07fc, 0x0000, 0x0000}}, | |
{'B', {0x0c0c, 0x0ffc, 0x0ccc, 0x0ccc, 0x0738, 0x0000, 0x0000}}, | |
{'C', {0x0ffc, 0x0c0c, 0x0c0c, 0x0c0c, 0x0c0c, 0x0000, 0x0000}}, | |
{'D', {0x0c0c, 0x0ffc, 0x0c0c, 0x0c0c, 0x07f8, 0x0000, 0x0000}}, | |
{'E', {0x0ffc, 0x0ccc, 0x0ccc, 0x0c0c, 0x0c0c, 0x0000, 0x0000}}, | |
{'F', {0x0ffc, 0x0cc0, 0x0cc0, 0x0c00, 0x0c00, 0x0000, 0x0000}}, | |
{'G', {0x0ffc, 0x0c0c, 0x0c0c, 0x0ccc, 0x0cfc, 0x0000, 0x0000}}, | |
{'H', {0x0ffc, 0x00c0, 0x00c0, 0x00c0, 0x0ffc, 0x0000, 0x0000}}, | |
{'I', {0x0ffc, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{'J', {0x003c, 0x000c, 0x000c, 0x000c, 0x0ffc, 0x0000, 0x0000}}, | |
{'K', {0x0ffc, 0x00c0, 0x00e0, 0x0330, 0x0e1c, 0x0000, 0x0000}}, | |
{'L', {0x0ffc, 0x000c, 0x000c, 0x000c, 0x000c, 0x0000, 0x0000}}, | |
{'M', {0x0ffc, 0x0600, 0x0300, 0x0600, 0x0ffc, 0x0000, 0x0000}}, | |
{'N', {0x0ffc, 0x0700, 0x01c0, 0x0070, 0x0ffc, 0x0000, 0x0000}}, | |
{'O', {0x0ffc, 0x0c0c, 0x0c0c, 0x0c0c, 0x0ffc, 0x0000, 0x0000}}, | |
{'P', {0x0c0c, 0x0ffc, 0x0ccc, 0x0cc0, 0x0780, 0x0000, 0x0000}}, | |
{'Q', {0x0ffc, 0x0c0c, 0x0c3c, 0x0ffc, 0x000f, 0x0000, 0x0000}}, | |
{'R', {0x0ffc, 0x0cc0, 0x0cc0, 0x0cf0, 0x079c, 0x0000, 0x0000}}, | |
{'S', {0x078c, 0x0ccc, 0x0ccc, 0x0ccc, 0x0c78, 0x0000, 0x0000}}, | |
{'T', {0x0c00, 0x0c00, 0x0ffc, 0x0c00, 0x0c00, 0x0000, 0x0000}}, | |
{'U', {0x0ff8, 0x000c, 0x000c, 0x000c, 0x0ff8, 0x0000, 0x0000}}, | |
{'V', {0x0ffc, 0x0038, 0x00e0, 0x0380, 0x0e00, 0x0000, 0x0000}}, | |
{'W', {0x0ff8, 0x000c, 0x00f8, 0x000c, 0x0ff8, 0x0000, 0x0000}}, | |
{'X', {0x0e1c, 0x0330, 0x01e0, 0x0330, 0x0e1c, 0x0000, 0x0000}}, | |
{'Y', {0x0e00, 0x0380, 0x00fc, 0x0380, 0x0e00, 0x0000, 0x0000}}, | |
{'Z', {0x0c1c, 0x0c7c, 0x0ccc, 0x0f8c, 0x0e0c, 0x0000, 0x0000}}, | |
{'0', {0x07f8, 0x0c0c, 0x0c0c, 0x0c0c, 0x07f8, 0x0000, 0x0000}}, | |
{'1', {0x0300, 0x0600, 0x0ffc, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{'2', {0x061c, 0x0c3c, 0x0ccc, 0x078c, 0x000c, 0x0000, 0x0000}}, | |
{'3', {0x0006, 0x1806, 0x198c, 0x1f98, 0x00f0, 0x0000, 0x0000}}, | |
{'4', {0x1fe0, 0x0060, 0x0060, 0x0ffc, 0x0060, 0x0000, 0x0000}}, | |
{'5', {0x000c, 0x000c, 0x1f8c, 0x1998, 0x18f0, 0x0000, 0x0000}}, | |
{'6', {0x07fc, 0x0c66, 0x18c6, 0x00c6, 0x007c, 0x0000, 0x0000}}, | |
{'7', {0x181c, 0x1870, 0x19c0, 0x1f00, 0x1c00, 0x0000, 0x0000}}, | |
{'8', {0x0f3c, 0x19e6, 0x18c6, 0x19e6, 0x0f3c, 0x0000, 0x0000}}, | |
{'9', {0x0f80, 0x18c6, 0x18cc, 0x1818, 0x0ff0, 0x0000, 0x0000}}, | |
{'*', {0x018c, 0x0198, 0x0ff0, 0x0198, 0x018c, 0x0000, 0x0000}}, | |
{'.', {0x001c, 0x001c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{'?', {0x1800, 0x1800, 0x19ce, 0x1f00, 0x0000, 0x0000, 0x0000}}, | |
{'!', {0x1f9c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{'(', {0x01e0, 0x0738, 0x1c0e, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{')', {0x1c0e, 0x0738, 0x01e0, 0x0000, 0x0000, 0x0000, 0x0000}}, | |
{'#', {0x0330, 0x0ffc, 0x0330, 0x0ffc, 0x0330, 0x0000, 0x0000}}, | |
{'$', {0x078c, 0x0ccc, 0x1ffe, 0x0ccc, 0x0c78, 0x0000, 0x0000}}, | |
{'/', {0x001c, 0x0070, 0x01c0, 0x0700, 0x1c00, 0x0000, 0x0000}}, | |
} ; | |
// 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(glyphtab)/sizeof(glyphtab[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 toggles output from the Si5351A to key the | |
// Feld Hell signal. | |
void encodechar(int ch) { | |
int i, x, y, fch; | |
word fbits; | |
for (i=0; i<NGLYPHS; i++) { | |
// Check each element of the glyphtab to see if we've found the | |
// character we're trying to send. | |
fch = pgm_read_byte(&glyphtab[i].ch); | |
if (fch == ch) { | |
// Found the character, now fetch the pattern to be transmitted, | |
// one column at a time. | |
for (x=0; x<7; x++) { | |
fbits = pgm_read_word(&(glyphtab[i].col[x])); | |
// Transmit (or do not tx) one 'pixel' at a time; characters | |
// are 7 cols by 14 rows. | |
for (y=0; y<14; y++) { | |
if (fbits & (1<<y)) { | |
si5351.output_enable(SI5351_CLK0, 1); | |
digitalWrite(ledPin, HIGH); | |
} else { | |
si5351.output_enable(SI5351_CLK0, 0); | |
digitalWrite(ledPin, LOW); | |
} | |
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 | |
} | |
} | |
} | |
// Loop through the string, transmitting one character at a time. | |
void encode(char *str) { | |
while (*str != '\0') | |
encodechar(*str++) ; | |
} | |
void setup() { | |
// Use the Arduino's on-board LED as a keying indicator. | |
pinMode(ledPin, OUTPUT); | |
digitalWrite(ledPin, LOW); | |
// 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 to 14.063000 MHz with no correction factor; | |
// see _____ for how to determine your board's correction factor. | |
// Setting the correction factor appropriately will reduce slant | |
// in the Feld Hell signal. | |
si5351.set_correction(0); | |
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 | |
// Set up Timer1 for interrupts at 245 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<<CS10) | // Set CS10 bit; this specifies no pre-scaling | |
(1 << WGM12); // of the system clock. | |
TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt. | |
OCR1A = 0xFF1A; // Set up interrupt trigger count; | |
// 16MHz clock / 0xFF1A counts == 245.00045938Hz | |
interrupts(); // Re-enable interrupts. | |
} | |
void loop() { | |
// Beacon a call sign and a locator. | |
encode("N0CALL AB12CD"); | |
// Wait 5 seconds between beacons. | |
delay(5000); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment