An Arduino powered IBM PS/2 Morse Keyboard

January 21, 2012 | Amateur Radio, Arduino | By: Mark VandeWettering

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:

#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()) ;
}

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:



Share Button
Be Sociable, Share!

Comments

Comment from Ricardo – CT2GQV
Time 1/22/2012 at 4:01 am

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!

Comment from John Reed
Time 2/8/2012 at 12:00 pm

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

Comment from Keith Daniel
Time 3/12/2012 at 6:05 am

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

Comment from scruss
Time 5/24/2012 at 6:19 pm

Mark, K3NG has made a very full-featured Arduino keyer that also supports an AT keyboard: https://radioartisan.wordpress.com/arduino-cw-keyer/

Write a comment






+ 3 = six