Skip to content

Instantly share code, notes, and snippets.

@flybd5
Last active July 29, 2024 19:24
Show Gist options
  • Save flybd5/27fd8bfbf66f409ad3150b6a8d1a6765 to your computer and use it in GitHub Desktop.
Save flybd5/27fd8bfbf66f409ad3150b6a8d1a6765 to your computer and use it in GitHub Desktop.
#include <serial-readline.h>
#include <BluetoothSerial.h>
// This code will implement an international morse code keyer using bluetooth.
// It requires an Arduino device and a relay (can be mechanical or solid state).
// The relay is powered from the Arduino. It is set as normally open. The
// trigger comes from one of the pio pins. On the relay you connect the NO
// and center contacts to a 1/4" male plug and connect that to the CW key
// jack on your radio. That's it.
// Written by Juan Jimenez, K1CPR, June 2023
// Released to the public domain. Use as you wish but please give credit.
const int relay_pin = 26; // Change to the PIO pin you use to trigger the relay
const int dotTime = 75; // Time for a dot in milliseconds, dashes/space are 3x this value
bool flag195 = false; // Flags for special characters
bool flag196 = false;
bool flag197 = false;
// BluetoothSerial SerialBT;
SerialLineReader reader(Serial);
void setup() {
Serial.begin(115200);
Serial.setTimeout(1000);
pinMode(relay_pin, OUTPUT);
// This will be the Bluetooth device name that will show up on your computer
// Change as you see fit.
// SerialBT.begin("JJCWKeyBT"); //Bluetooth device name
Serial.println("JJCWKeyBT running. Enter text to transmit.");
// If you want to require a PIN to pair to this device, define USE_PIN and put it here
#ifdef USE_PIN
SerialBT.setPin(pin);
Serial.println("Using PIN");
#endif
}
// For dot and dash the relay is activated to close, triggering the key on your radio
// Spacing between letters same as a dash.
void space() {
delay(dotTime * 3);
}
// Spacing between words is 7x the dot time
void wordSpace() {
delay(dotTime * 7);
}
// Define a dot
void dot() {
digitalWrite(relay_pin, HIGH);
delay(dotTime);
digitalWrite(relay_pin, LOW);
delay(dotTime);
}
// Define a dash
void dash() {
digitalWrite(relay_pin, HIGH);
delay(dotTime * 3);
digitalWrite(relay_pin, LOW);
delay(dotTime);
}
// Figure out which symbol to key and do it.
// This code handles everything, including international, except one symbol.
// The digraph ch was removed from the Spanish language but there are other
// languages that still use it. Shame on you, Microsoft!
void symbol(wchar_t theSymbol) {
if (isAlpha(theSymbol)) {
switch (toupper(theSymbol)) {
case 'A':
dot();
dash();
break;
case 'B':
dash();
dot();
dot();
dot();
break;
case 'C':
dash();
dot();
dash();
dot();
break;
case 'D':
dash();
dot();
dot();
break;
case 'E':
dot();
break;
case 'F':
dot();
dot();
dash();
dot;
break;
case 'G':
dash();
dash();
dot();
break;
case 'H':
dot();
dot();
dot();
dot();
break;
case 'I':
dot();
dot();
break;
case 'J':
dot();
dash();
dash();
dash();
break;
case 'K':
dash();
dot();
dash();
break;
case 'L':
dot();
dash();
dot();
dot();
break;
case 'M':
dash();
dash();
break;
case 'N':
dash();
dot();
break;
case 'O':
dash();
dash();
dash();
break;
case 'P':
dot();
dash();
dash();
dot();
break;
case 'Q':
dash();
dash();
dot();
dash();
break;
case 'R':
dot();
dash();
dot();
break;
case 'S':
dot();
dot();
dot();
break;
case 'T':
dash();
break;
case 'U':
dot();
dot();
dash();
break;
case 'V':
dot();
dot();
dot();
dash();
break;
case 'W':
dot();
dash();
dash();
break;
case 'X':
dash();
dot();
dot();
dash();
break;
case 'Y':
dash();
dot();
dash();
dash();
break;
case 'Z':
dash();
dash();
dot();
dot();
default:
// Do nothing
break;
}
} else if (isdigit(theSymbol)) {
switch (theSymbol) {
case '1':
dot();
dash();
dash();
dash();
dash();
break;
case '2':
dot();
dot();
dash();
dash();
dash();
break;
case '3':
dot();
dot();
dot();
dash();
dash();
break;
case '4':
dot();
dot();
dot();
dot();
dash();
break;
case '5':
dot();
dot();
dot();
dot();
dot();
break;
case '6':
dash();
dot();
dot();
dot();
dot();
break;
case '7':
dash();
dash();
dot();
dot();
dot();
break;
case '8':
dash();
dash();
dash();
dot();
dot();
break;
case '9':
dash();
dash();
dash();
dash();
dot();
break;
case '0':
dash();
dash();
dash();
dash();
dash();
break;
default:
// Do nothing
break;
}
} else if (wcschr(L".,?\\!/()&:;=+-_\"$@", theSymbol) != NULL) {
switch (theSymbol) {
case '.':
dot();
dash();
dot();
dash();
dot();
dash();
break;
case ',':
dash();
dash();
dot();
dot();
dash();
dash();
break;
case '?':
dot();
dot();
dash();
dash();
dot();
dot();
break;
case '\'':
dot();
dash();
dash();
dash();
dash();
dot();
break;
case '!':
dash();
dot();
dash();
dot();
dash();
dash();
break;
case '/':
dash();
dot();
dot();
dash();
dot();
break;
case '(':
dash();
dot();
dash();
dash();
dot();
break;
case ')':
dash();
dot();
dash();
dash();
dot();
dash();
break;
case '&':
dot();
dash();
dot();
dot();
dot();
break;
case ':':
dash();
dash();
dash();
dot();
dot();
dot();
break;
case ';':
dash();
dot();
dash();
dot();
dash();
dot();
break;
case '=':
dash();
dot();
dot();
dot();
dash();
break;
case '+':
dot();
dash();
dot();
dash();
dot();
break;
case '-':
dash();
dot();
dot();
dot();
dot();
dash();
break;
case '_':
dot();
dot();
dash();
dash();
dot();
dash();
break;
case '"':
dot();
dash();
dot();
dot();
dash();
dot();
break;
case '$':
dot();
dot();
dot();
dash();
dot();
dot();
dash();
break;
case '@':
dot();
dash();
dash();
dot();
dash();
dot();
break;
default:
// Do nothing
break;
}
} else if (flag195) {
switch (int(theSymbol)) {
// Don't forget the non-latin symbols
// Special case digraph ch is not implemented due to no input code possible
// Ä and ä
case 164:
case 132:
dot();
dash();
dot();
dash();
break;
// À and à
case 128:
case 160:
dot();
dash();
dash();
dot();
dash();
break;
// Ç and ç
case 135:
case 167:
dash();
dot();
dash();
dot();
dot();
break;
// Ð
case 144:
dot();
dot();
dash();
dash();
dot();
break;
// È and è
case 136:
case 168:
dot();
dash();
dot();
dot();
dash();
break;
// É and é
case 137:
case 169:
dot();
dot();
dash();
dot();
dot();
break;
// Ĵ and ĵ
case 180:
case 181:
dot();
dash();
dash();
dash();
dot();
break;
// Ñ and ñ
case 145:
case 177:
dash();
dash();
dot();
dash();
dash();
break;
// Ö and ö
case 150:
case 182:
dash();
dash();
dash();
dot();
break;
// þ
case 190:
dot();
dash();
dash();
dot();
dot();
break;
// Ü and ü
case 156:
case 188:
dot();
dot();
dash();
dash();
break;
default:
// Do nothing
break;
}
} else if (flag197) {
switch (int(theSymbol)) {
// Š and š
case 160:
case 161:
dot();
dot();
dot();
dash();
dot();
break;
default:
// Do nothing
break;
}
} else if (flag196) {
switch (int(theSymbol)) {
// Ĝ and ĝ
case 156:
case 157:
dash();
dash();
dot();
dash();
dot();
break;
default:
// Do nothing
break;
}
}
space();
}
// The main loop.
void loop() {
reader.poll();
if (reader.available()) {
int theLen = reader.len();
char text[theLen];
reader.read(text);
Serial.println(text);
// If the next character is int 195, 196 or 197 it indicates a special char
// It is skipped and the next char is sent for transmission. The flags tell
// symbol() in what switch statement to handle it.
// Otherwise, the char is sent for transmission.
for (int i = 0; i < theLen; i++) {
if (int(text[i]) == 195) {
flag195 = true;
symbol(text[++i]);
flag195 = false;
} else if (int(text[i]) == 197) {
flag197 = true;
symbol(text[++i]);
flag197 = false;
} else if (int(text[i]) == 196) {
flag197 = true;
symbol(text[++i]);
flag197 = false;
} else {
symbol(text[i]);
}
}
}
}
@flybd5
Copy link
Author

flybd5 commented Jul 29, 2024

Corrected problem with international and special chars. Code handles them correctly now, but I can only guarantee it works with Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment