uint8_t symbols[] =
{
3,3,0,2,0,2,0,2,3,2,2,0,3,3,3,0,0,2,3,0,0,3,0,3,3,3,1,0,0,2,
2,2,2,0,1,0,2,3,0,1,2,0,2,0,0,2,3,0,3,3,2,2,1,3,2,3,2,0,0,1,
1,2,1,2,0,0,0,1,1,2,3,0,3,2,1,0,3,0,0,3,0,0,3,0,3,3,0,0,0,3,
1,2,1,2,3,0,2,0,1,0,2,2,0,2,1,2,0,3,0,2,3,3,1,2,1,3,2,0,1,3,
0,3,0,2,0,1,1,3,2,0,0,2,2,3,0,3,0,2,3,3,0,0,0,2,0,2,0,3,1,0,
3,0,3,3,2,0,2,3,1,0,0,2
};
// Sine wave generator using DDS techniques implementing WSPR beacon
// Jeff Whitlatch - ko7m
#include "avr/pgmspace.h"
// Single period sine wave table.
// Amplitudes are 0-255 with 128 as zero crossing. 256 samples per period.
PROGMEM prog_uchar sine256[] =
{
128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173,
176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215,
218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244,
245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255,
255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246,
245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220,
218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179,
176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131,
128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82,
79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40,
37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11,
10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,
0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,
10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35,
37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76,
79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124
};
uint8_t symbols[] =
{
3,3,0,2,0,2,0,2,3,2,2,0,3,3,3,0,0,2,3,0,0,3,0,3,3,3,1,0,0,2,
2,2,2,0,1,0,2,3,0,1,2,0,2,0,0,2,3,0,3,3,2,2,1,3,2,3,2,0,0,1,
1,2,1,2,0,0,0,1,1,2,3,0,3,2,1,0,3,0,0,3,0,0,3,0,3,3,0,0,0,3,
1,2,1,2,3,0,2,0,1,0,2,2,0,2,1,2,0,3,0,2,3,3,1,2,1,3,2,0,1,3,
0,3,0,2,0,1,1,3,2,0,0,2,2,3,0,3,0,2,3,3,0,0,0,2,0,2,0,3,1,0,
3,0,3,3,2,0,2,3,1,0,0,2
};
// Maximum number of symbols in a WSPR message
const uint32_t iSymbolMax = (sizeof(symbols) / sizeof(uint8_t));
// Useful macros for setting and resetting bits
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
// DDS frequency is chosen to be in the middle of the 1400-1600 Hz band Any value
// in this range is appropriate, but should be chosen to stay within the band while
// allowing for 1.4648 Hz above and 2.9296 Hz below plus sidebands.
int dds_frequency = 1500;
// The reference frequency is subject to the accuracy of the 16 MHz crystal oscillator.
const double ref_frequency = (16000000/510);
// The following formula will calculate the necessary tuning word for a given output freq
// tuning_word = pow(2,32) * dds_frequency / ref_frequency;
// These must all be marked as volatile as they are used in an interrupt service routine
volatile byte sine_table_index;
volatile uint32_t phase_accumulator;
volatile uint32_t tuning_word;
volatile uint16_t iSymbol;
volatile uint16_t cTone;
// Tuning word is set to the following multiplier times the tone frequency
const double multiplier = pow(2,32) / ref_frequency / 256;
// Tuning word delta for 1.4648 Hz delta between symbols
const double delta = 1.4648 * multiplier;
// Array of pre-calculated tuning words for each WSPR symbol spaced 1.4648 Hz apart.
// Arbitrarily chose symbol 2 for 1500 Hz as W1JT does not define it in his documentation.
volatile const uint32_t tuning_words[] =
{
(dds_frequency * multiplier - (delta * 2)),
(dds_frequency * multiplier - delta),
(dds_frequency * multiplier),
(dds_frequency * multiplier + delta)
};
// Setup
void setup()
{
// PWM output for timer2 is pin 10 on the ATMega2560
// If you use an ATMega328 (such as the UNO) you need to make this pin 11
// See https://spreadsheets.google.com/pub?key=rtHw_R6eVL140KS9_G8GPkA&gid=0
pinMode(10, OUTPUT); // Timer 2 PWM output on mega256 is pin 10
// Set up timer2 to a phase correct 32kHz clock
timer2Setup();
iSymbol = cTone = 0; // Set initial symbol and symbol length
// Calculate the tuning word for the first symbol before we enable interrupts
tuning_word = tuning_words[symbols[iSymbol]];
// disable interrupts to avoid timing distortion
//cbi (TIMSK0, TOIE0); // Disable timer 0. Breaks the delay() function
sbi (TIMSK2, TOIE2); // Enable timer 2.
}
// Nothing to do here. Everything is interrupt driven
void loop()
{
}
// Setup timer2 with prescaler = 1, PWM mode to phase correct PWM
// See th ATMega datasheet for all the gory details
void timer2Setup()
{
TIMSK2 = 0;
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
TCCR2B = _BV(CS20);
}
// Timer 2 interrupt service routine (ISR) is used to generate
// the timebase reference clock for the DDS generator at 32kHz.
ISR(TIMER2_OVF_vect)
{
// Keep track of how long this symbol has been playing and when to move to next symbol
// WSPR symbol length = 8192 / 12000 * 1000 ms = 682.6666 or 683 ms. This function is
// called about every 32 us. By keeping track of how many times this function is called
// we can calculate the length of each symbol
//
// Clock is 16000000 / 510 = 31372.5490 Hz = 31.875 us per tick
// .683 / 3.1875e-5 = 21427 counts. This may need be adjusted based on accuracy of
// main 16 MHz clock.
if (cTone++ >= 21427)
{
cTone = 0; // Reset tone length counter
iSymbol++; // Carry on with next symbol
}
// Update 24 bit phase accumulator and extract the sine table index from top 8 bits
phase_accumulator += tuning_word;
sine_table_index = phase_accumulator >> 16; // Use upper 8 bits as index
// Set current amplitude value for the sine wave being constructed.
OCR2A = pgm_read_byte_near(sine256 + sine_table_index);
// If we have sent the entire 162 symbol WSPR msg, kill the timer
// and reset to first symbol.
if (iSymbol >= iSymbolMax)
{
TIMSK2 = 0; // Disable timer 2 to stop tone
iSymbol = 0; // Reset back to beginning of symbol set
}
// Get the next tuning word based on the current symbol
tuning_word = tuning_words[symbols[iSymbol]];
}