An Arduino powered IBM PS/2 Morse Keyboard

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:



9 thoughts on “An Arduino powered IBM PS/2 Morse Keyboard

  1. Ricardo - CT2GQV

    You just reminded me the hours spent learning DOS, Windows 3.0, Pascal and C on one of those keyboards with the companion PS/2 system. One of the best keyboards ever made for sure, unbreakable! Unfortunately the PS/2 mouse needed frequent cleaning 🙂 Nice project!

  2. John Reed

    Thanks for posting your Arduino/Ham projects, I plan to build a similar setup and I have a beginner electronics question if you have the time… I cannot find a 4N31 optocoupler, could I substitute a 4N32?

    I’ve found a comparison chart here: http://dspace.dial.pipex.com/isocom/6leyiw.htm

    I notice the “Min Current Transfer Ratio” is 10x for the 4N32. Think that would matter for the 817 keyer?

    Any suggestions appreciated.

    Thanks,

    John
    K6JCR

  3. Keith Daniel

    Thanks ! Just setting off looking for ideas to roll my own when I found your excellent page. Very best de VE2KXD.

  4. KG5GFV Ron

    Mark I built it works great. Any suggestions on how to add front panel cw speed control ?

  5. Don

    I am a newbie to the UNO and would like a parts lists and schematic and will try it out.

    Thank you for providing this online as I was searching for keyboard CW sending.

  6. alan forrest

    hi mark nice project. I was wondering if you could send part list and schematic’s
    best 73 alan m0cvu

  7. Carl, DL8PI

    Dear om Mark,

    sorry, but I have to tell you that the Arduino compiler version 1.69 gives error messages during compilation.
    The system expects int expression in the following section of the program:

    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 ;

    The system stops the compilation at the command :

    queueadd(“CQ CQ CQ DE K6HX K6HX K6HX K\r\n”) ;

    This command seems to be to long and the sign “%” is not accepted.

    Please let me know whether I have to use another version of compiler ( 1.05 or later).

    Vy 73 es best dx yr Carl, DL8PI (Bonn, Germany)

  8. Dan Lyke

    Carl, I suspect you’re trying to compile it with a C compiler rather than a C++ compiler? The compiler should be finding the prototype for “void
    queueadd(char *s)”, and instead seems to be finding the one for “void
    queueadd(char ch)” (or no queueadd() function prototyped at all).

Comments are closed.