Here is my CW beacon using a Arduino Nano and Si5351 Clock Generator Board. It needs a Low Pass Filter for the band you intend to program it for. 10mw output, drifts slightly as it warms up. This one is set to send every 10 seconds. For UK Licencing on a remote site it needs a way of remotely switching it off, I use a sim card relay which responds to a simple text message
#include <Wire.h>
#include <si5351.h>
#include <ctype.h>
Si5351 si;
// ===== USER SETTINGS =====
const double BEACON_FREQ_HZ = 144.414000e6; // Hz
const char MESSAGE[] = "DE G0XBU/B IO83XG";
const uint8_t WPM = 15; // CW speed
const unsigned REPEAT_SEC = 10; // gap between repeats
const int32_t CAL_PPM = -17000; // freq correction later if needed
// ==========================
// Library uses frequency in hundredths of Hz
const uint64_t BEACON_FREQ_CENTI_HZ = (uint64_t)(BEACON_FREQ_HZ * 100.0);
const uint8_t CLK = SI5351_CLK0;
unsigned dit_ms;
// Morse lookup table
struct Morse {
char c; const char* pat;
};
const Morse MORSE_TABLE[] = {
{'A', ".-"}, {'B', "-..."}, {'C', "-.-."}, {'D', "-.."}, {'E', "."},
{'F', "..-."}, {'G', "--."}, {'H', "...."}, {'I', ".."}, {'J', ".---"},
{'K', "-.-"}, {'L', ".-.."}, {'M', "--"}, {'N', "-."}, {'O', "---"},
{'P', ".--."}, {'Q', "--.-"}, {'R', ".-."}, {'S', "..."}, {'T', "-"},
{'U', "..-"}, {'V', "...-"}, {'W', ".--"}, {'X', "-..-"}, {'Y', "-.--"},
{'Z', "--.."},
{'0', "-----"}, {'1', ".----"}, {'2', "..---"}, {'3', "...--"},
{'4', "....-"}, {'5', "....."}, {'6', "-...."}, {'7', "--..."},
{'8', "---.."}, {'9', "----."},
{'/', "-..-."}, {'?', "..--.."}, {'.', ".-.-.-"}, {',', "--..--"},
{'=', "-...-"}, {'+', ".-.-."}, {'-', "-....-"}, {'@', ".--.-."},
{' ', " "} // word space marker
};
const char* lookupMorse(char ch) {
char u = toupper((unsigned char)ch);
for (auto &m : MORSE_TABLE) {
if (m.c == u) return m.pat;
}
return ""; // unknown = skip
}
void keyOn() {
// Keying by enabling CLK0
si.output_enable(CLK, 1);
}
void keyOff() {
si.output_enable(CLK, 0);
}
void sendDit() { keyOn(); delay(dit_ms); keyOff(); delay(dit_ms); }
void sendDah() { keyOn(); delay(3 * dit_ms); keyOff(); delay(dit_ms); }
void sendChar(const char* pat) {
if (pat[0] == ' ') {
// Word gap = 7 dits total; we've already had 1 dit from previous element gap
delay(7 * dit_ms);
return;
}
for (const char* p = pat; *p; ++p) {
if (*p == '.') sendDit();
else if (*p == '-') sendDah();
}
// Inter-character gap: 3 dits total; we've already had 1 from last element off-time
delay(2 * dit_ms);
}
void sendMessage(const char* msg) {
for (const char* p = msg; *p; ++p) {
const char* pat = lookupMorse(*p);
if (*pat) sendChar(pat);
}
}
void setup() {
Serial.begin(115200);
Wire.begin();
bool ok = si.init(SI5351_CRYSTAL_LOAD_8PF, 25000000UL, 0);
if (!ok) {
Serial.println("Si5351 init FAILED");
} else {
Serial.println("Si5351 init OK");
}
if (CAL_PPM != 0) {
si.set_correction(CAL_PPM, SI5351_PLL_INPUT_XO);
}
si.set_freq(BEACON_FREQ_CENTI_HZ, CLK);
si.drive_strength(CLK, SI5351_DRIVE_8MA);
si.output_enable(CLK, 0); // start keyed off
// WPM timing: dit = 1200 / WPM (ms)
dit_ms = 1200 / WPM;
Serial.println("CW beacon ready.");
}
void loop() {
Serial.println("Sending beacon...");
sendMessage(MESSAGE);
keyOff();
// Wait until next cycle
for (unsigned s = 0; s < REPEAT_SEC; ++s) {
delay(1000);
}
}