I’ve been trying to do a bit more Arduino programming and interfacing lately. Nothing too difficult, but just trying to expand my junkbox and my skills so that I can quickly prototype new ideas and expand the kind of projects that I can tackle in the limited time that I seem to have for playing around.
I have written programs to use an Arduino to blink or beep Morse code before, but the programs were meant to control beacons, where they might just send the same message over and over, which I just compiled into the program. Today’s experiment did two things: it reads and buffers keystrokes from an old IBM PS/2 keyboard which I rescued from exile in my garage, and it actually keys my ham radio transmitter (a Yaesu FT-817ND) using a 4N31 optoisolator.
Most keyboards that you get today are interfaced via the Universal Serial Bus (USB). That’s kind of cool, but it’s also a bit of a travesty, since they require a lot more code (and a lot more knowledge) to interface what is ultimately a pretty simple device. Back in the old IBM AT days, keyboards were equipped with a large 5 pin DIN connector, and would transmit “scan codes” represent each character along a simple serial bus. The IBM PS/2 shrunk the connector to a six pin mini-DIN connector, but retained almost all the other features.
But if you want to do a tidy job, you’ll need some extra bits and pieces to hook them to your Arduino. Luckily, Sparkfun has a couple of cheap bits that will make it easy. I ordered some 6 pin MiniDIN connectors from them, along with a tiny $0.95 breakout board which you can tack together along with some header pins to make a little breadboard friendly connector. You need four connections: 5V, ground, and a clock and data line, which I connected to pins 3 and 4 on my Arduino.
You’ll also need a library. Yes, you could roll your own, but that would have added hours to this simple project. Instead, I chose this one, more or less at random from a number of possibilities. It seemed to work fine, and compiled properly even with my Arduino 1.0 setup. The code actually comes from the guys who make the Teensy, but the code works fine on regular Arduinos.
Then, I wrote the code. I wanted the user to be able to type ahead of the sending, so I needed to implement a ring buffer to store chars (currently up to 128) which I coded up from scratch in the simplest way possible. The way I used to do Morse sending was to set the output pin to be HIGH, then delay(), then set it to LOW. But we want to process new keystrokes and add them to the buffer as soon as you can. So, I implemented my own “delay” function, which loops to find new characters and inserts them in the buffer while waiting for the timeout. This seems to work pretty well.
Without further explanation, here’s the code:
[sourcecode lang=”cpp”]
#include <PS2Keyboard.h>
// _____ _ _ _ _ __ __
// |_ _| |_ ___ /_\ _ _ __| |_ _(_)_ _ ___ | \/ |___ _ _ ___ ___
// | | | ‘ \/ -_) / _ \| ‘_/ _` | || | | ‘ \/ _ \ | |\/| / _ \ ‘_(_-</ -_)
// |_| |_||_\___| /_/ \_\_| \__,_|\_,_|_|_||_\___/ |_| |_\___/_| /__/\___|
//
// _ __
// | |/ /___ _ _ ___ _ _
// | ‘ </ -_) || / -_) ‘_|
// |_|\_\___|\_, \___|_|
// |__/
//
// Version for the PS2 Keyboard
// using the library from http://www.pjrc.com/teensy/td_libs_PS2Keyboard.html
//
// Written by Mark VandeWettering K6HX
//
// This is just a quick Morse keyer program.
//
////////////////////////////////////////////////////////////////////////
//
// Here is a queue to store the characters that I’ve typed.
// To simplify the code, it can store a maximum of QUEUESIZE-1 characters
// before it fills up. What is a byte wasted between friends?
//
////////////////////////////////////////////////////////////////////////
#define QUEUESIZE (128)
#define QUEUEMASK (QUEUESIZE-1)
int aborted = 0 ;
int qhead = 0 ;
int qtail = 0 ;
char queue[QUEUESIZE] ;
void
queueadd(char ch)
{
queue[qtail++] = ch ;
qtail &= QUEUEMASK ;
}
void
queueadd(char *s)
{
while (*s)
queueadd(*s++) ;
}
char
queuepop()
{
char ch ;
ch = queue[qhead++] ;
qhead &= QUEUEMASK ;
return ch ;
}
int
queuefull()
{
return (((qtail+1)%QUEUEMASK) == qhead) ;
}
int
queueempty()
{
return (qhead == qtail) ;
}
void
queueflush()
{
qhead = qtail ;
}
////////////////////////////////////////////////////////////////////////
int pin = 13 ; // blink the LED for now…
int tpin = 10 ; // tone pin
#define WPM (20)
int ditlen = 1200 / WPM ;
PS2Keyboard kbd ;
inline void
ps2poll()
{
char ch ;
while (kbd.available()) {
if (queuefull()) {
Serial.print("") ;
} else {
switch (ch=kbd.read()) {
case ‘\033’:
queueflush() ;
Serial.flush() ;
Serial.println("== FLUSH ==") ;
aborted = 1 ;
break ;
case ‘%’:
queueadd("CQ CQ CQ DE K6HX K6HX K6HX K\r\n") ;
break ;
default:
queueadd(ch) ;
break ;
}
}
}
}
void
mydelay(unsigned long ms)
{
unsigned long t = millis() ;
while (millis()-t < ms)
ps2poll() ;
}
#define FREQ (700)
void
scale()
{
long f = 220L ;
int i ;
for (i=0; i<=12; i++) {
tone(tpin, (int)f) ;
f *= 1059L ;
f /= 1000L ;
Serial.println(f) ;
delay(300) ;
}
noTone(tpin) ;
}
void
dit()
{
digitalWrite(pin, HIGH) ;
tone(tpin, FREQ) ;
mydelay(ditlen) ;
digitalWrite(pin, LOW) ;
noTone(tpin) ;
mydelay(ditlen) ;
}
void
dah()
{
digitalWrite(pin, HIGH) ;
tone(tpin, FREQ) ;
mydelay(3*ditlen) ;
digitalWrite(pin, LOW) ;
noTone(tpin) ;
mydelay(ditlen) ;
}
void
lspace()
{
mydelay(2*ditlen) ;
}
void
space()
{
mydelay(4*ditlen) ;
}
void
setup()
{
pinMode(pin, OUTPUT) ;
pinMode(tpin, OUTPUT) ;
Serial.begin(9600) ;
kbd.begin(4, 3) ;
Serial.println("Morse Code Keyboard by K6HX") ;
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
char ltab[] = {
0b101, // A
0b11000, // B
0b11010, // C
0b1100, // D
0b10, // E
0b10010, // F
0b1110, // G
0b10000, // H
0b100, // I
0b10111, // J
0b1101, // K
0b10100, // L
0b111, // M
0b110, // N
0b1111, // O
0b10110, // P
0b11101, // Q
0b1010, // R
0b1000, // S
0b11, // T
0b1001, // U
0b10001, // V
0b1011, // W
0b11001, // X
0b11011, // Y
0b11100 // Z
} ;
char ntab[] = {
0b111111, // 0
0b101111, // 1
0b100111, // 2
0b100011, // 3
0b100001, // 4
0b100000, // 5
0b110000, // 6
0b111000, // 7
0b111100, // 8
0b111110 // 9
} ;
void
sendcode(char code)
{
int i ;
for (i=7; i>= 0; i–)
if (code & (1 << i))
break ;
for (i–; i>= 0; i–) {
if (code & (1 << i))
dah() ;
else
dit() ;
}
lspace() ;
}
void
send(char ch)
{
if (isalpha(ch)) {
if (islower(ch)) ch = toupper(ch) ;
sendcode(ltab[ch-‘A’]) ;
} else if (isdigit(ch))
sendcode(ntab[ch-‘0’]) ;
else if (ch == ‘ ‘ || ch == ‘\r’ || ch == ‘\n’)
space() ;
else if (ch == ‘.’)
sendcode(0b1010101) ;
else if (ch == ‘,’)
sendcode(0b1110011) ;
else if (ch == ‘!’)
sendcode(0b1101011) ;
else if (ch == ‘?’)
sendcode(0b1001100) ;
else if (ch == ‘/’)
sendcode(0b110010) ;
else if (ch == ‘+’)
sendcode(0b101010) ;
else if (ch == ‘-‘)
sendcode(0b1100001) ;
else if (ch == ‘=’)
sendcode(0b110001) ;
else if (ch == ‘@’) // hardly anyone knows this!
sendcode(0b1011010) ;
else
return ; // ignore anything else
if (!aborted) {
Serial.print(ch) ;
if (ch == 13) Serial.print((char) 10) ;
}
aborted = 0 ;
}
////////////////////////////////////////////////////////////////////////
void
loop()
{
ps2poll() ;
if (!queueempty())
send(queuepop()) ;
}
[/sourcecode]
To key the transmitter, I used a 4N31 optoisolator. This code uses pin 13 to key the transmitter, so I wired it to pin 1 on the optoisolator, passing through a 1K current limiting resistor, and pin 2 ground. Pins 4 and 5 are wired to the tip and shield of a 3.5mm socket, which I then connected to the FT-817 key input using a 3.5mm stereo cable I had lying around. I could have used a little NPN transistor to do the keying, but the optoisolator keeps any current from the radio from circulating in the Arduino (and vice versa) which is a nice little insurance that nothing will zap either my radio or my microcontroller.
Here’s the resulting project:
Feel free to drop me a line if you find this code to be useful. Let me know how you adapted it to do something cool in your shack or lab.
Addendum: Friends Atdiy and Whisker of the tymkrs empire made a similar project a while ago, except that they used a board based upon a Propeller board, which does a lot of what my project does as well. If you like the Propeller instead (and if you’ve been following my blog and reading about WA0UWH’s beacon, why wouldn’t you?) you might check out their video: