#include
#include
// This is a bunch of #defines and variables for interfacing
// with a PS/2 keyboard input. Maybe when I’m less lazy, I’ll
// package this up in seperate files.
#define AT_KEY_LIFT 0xf0
#define AT_KEY_EXTENDED 0xe0
#define AT_KEY_SHIFT_L 0x12
#define AT_KEY_SHIFT_R 0x59
#define AT_KEY_CAPLK 0x58
#define AT_KEY_CTRL 0x14
#define AT_KEY_ALT 0x11
#define AT_KEY_NUMLK 0x77
#define AT_KEY_SCRLK 0x7e
#define AT_KEY_F1 0x05
#define AT_KEY_F2 0x06
#define AT_KEY_F3 0x04
#define AT_KEY_F4 0x0c
#define AT_KEY_F5 0x03
#define AT_KEY_F6 0x0b
#define AT_KEY_F7 0x83
#define AT_KEY_F8 0x0a
#define AT_KEY_F9 0x01
#define AT_KEY_F10 0x09
#define AT_KEY_F11 0x78
#define AT_KEY_F12 0x07
#define AT_HCMD_SETLEDS 0xed
#define AT_HCMD_ECHO 0xee
#define AT_HCMD_SETCODE 0xf0
#define AT_HCMD_SETRATE 0xf3
#define AT_HCMD_ENABLE 0xf4
#define AT_HCMD_DISABLE 0xf5
#define AT_HCMD_RESEND 0xfe
#define AT_HCMD_RESET 0xff
#define AT_CMD_ACK 0xfa
#define AT_CMD_TESTOK 0xaa
#define AT_CMD_ECHO 0xee
#define AT_CMD_RESEND 0xfe
#define AT_CMD_ERROR 0x00
#define AT_CMD_ERROR2 0xff
#define AT_LED_SCRLK 0x01
#define AT_LED_NUMLK 0x02
#define AT_LED_CAPLK 0x04
#define FLAG_ALT 0x01
#define FLAG_NUMLK 0x02
#define FLAG_CTRL 0x04
#define FLAG_CAPLK 0x08
#define FLAG_SHIFT 0x10
#define FLAG_EXT 0x20
#define FLAG_SCRLK 0x40
#define LR_FLAG_SHIFT_R 0x01
#define LR_FLAG_SHIFT_L 0x02
#define LR_FLAG_CTRL_R 0x04
#define LR_FLAG_CTRL_L 0x08
#define LR_FLAG_ALT_R 0x10
#define LR_FLAG_ALT_L 0x20
#define CHAR_NULL 0x00
#define CHAR_ESC 0x1b
#define CHAR_CR 0x0d
#define KEY_BUFFER_LEN 64
#define IS_ALPHA_SHIFTED(flags) (((flags & FLAG_SHIFT)==FLAG_SHIFT) ^ ((flags & FLAG_CAPLK)==FLAG_CAPLK))
volatile byte _bitNum = 0;
volatile byte _newByte = 0;
volatile byte _oldByte = 0;
volatile byte _parity = 0;
volatile byte _flags = 0;
volatile byte _lr_flags = 0;
volatile byte _extKey = 0;
volatile byte _bufferWriteIndex = 0;
volatile byte _bufferReadIndex = 0;
volatile byte _keyBuffer[KEY_BUFFER_LEN];
volatile byte _flagBuffer[KEY_BUFFER_LEN];
// alpha_ps2_to_ascii simply translates from scan code to ascii
// where ascii_value = array_index + 0x40 (or array_index + 0x60)
byte alpha_ps2_to_ascii[] = { // and morse code
0x1c, B01100000, // a
0x32, B10001000, // b
0x21, B10101000, // c
0x23, B10010000, // d
0x24, B01000000, // e
0x2b, B00101000, // f
0x34, B11010000, // g
0x33, B00001000, // h
0x43, B00100000, // i
0x3b, B01111000, // j
0x42, B10110000, // k
0x4b, B01001000, // l
0x3a, B11100000, // m
0x31, B10100000, // n
0x44, B11110000, // o
0x4d, B01101000, // p
0x15, B11011000, // q
0x2d, B01010000, // r
0x1b, B00010000, // s
0x2c, B11000000, // t
0x3c, B00110000, // u
0x2a, B00011000, // v
0x1d, B01110000, // w
0x22, B10011000, // x
0x35, B10111000, // y
0x1a, B11001000 }; // z
// other_ps2_to_ascii is filled with a bunch of non-alpha characters
// the format of the array is as follows:
// { scan_code, ascii, ascii_if_shifted, morse, morse_if_shifted }
byte other_ps2_to_ascii[] = { // and morse code
0x76, CHAR_ESC, CHAR_ESC, 0, 0, // ESC
0x29, ‘ ‘, ‘ ‘, 0, 0,
0x0E, ‘`’, ‘~’, 0, 0,
0x16, ‘1’, ‘!’, B01111100, B00000001, // 1, ERR
0x1E, ‘2’, ‘@’, B00111100, B01101010,
0x26, ‘3’, ‘#’, B00011100, 0,
0x25, ‘4’, ‘$’, B00001100, B01000100, // 4, AS
0x2E, ‘5’, ‘%’, B00000100, 0,
0x36, ‘6’, ‘^’, B10000100, 0,
0x3D, ‘7’, ‘&’, B11000100, 0,
0x3E, ‘8’, ‘*’, B11100100, 0,
0x46, ‘9’, ‘(‘, B11110100, B10110100, // 9, KN
0x45, ‘0’, ‘)’, B11111100, 0,
0x4E, ‘-‘, ‘_’, B00010110, B00010110, // SK, SK
0x55, ‘=’, ‘+’, B10001100, B01010100, // BT, AR
0x66, 0x08, 0x08, B00000001, B00000001, // ERR
0x0D, 0x09, 0x09, 0, 0,
0x54, ‘[‘, ‘{‘, 0, 0,
0x5B, ‘]’, ‘}’, 0, 0,
0x5D, ‘\\’, ‘|’, 0, 0,
0x4C, ‘;’, ‘:’, B10001100, B10001100, // BT
0x52, ‘\”, ‘”‘, 0, 0,
0x5A, 0x0d, 0x0d, B10001100, B10001100, // BT
0x41, ‘,’, ‘<', B11001110, B11001110,
0x49, '.', '>‘, B01010110, B01010110,
0x4A, ‘/’, ‘?’, B10010100, B00110010,
0x7c, ‘*’, ‘*’, 0, 0,
0x7b, ‘-‘, ‘-‘, 0, 0,
0x79, ‘+’, ‘+’, 0, 0,
0x6c, ‘7’, ‘7’, B11000100, B11000100,
0x75, ‘8’, ‘8’, B11100100, B11100100,
0x7d, ‘9’, ‘9’, B11110100, B11110100,
0x6b, ‘4’, ‘4’, B00001100, B00001100,
0x73, ‘5’, ‘5’, B00000100, B00000100,
0x74, ‘6’, ‘6’, B10000100, B10000100,
0x69, ‘1’, ‘1’, B01111100, B01111100,
0x72, ‘2’, ‘2’, B00111100, B00111100,
0x7a, ‘3’, ‘3’, B00011100, B00011100,
0x70, ‘0’, ‘0’, B11111100, B11111100,
0x71, ‘.’, ‘.’, B01010110, B01010110,
0xff, 0xff, 0xff, 0xff, 0xff };
int txPin = 13; // used to key the transmitter and/or light an LED 🙂
int dataPin = 4; // PS/2 data pin
int clkPin = 2; // PS/2 clock pin
int pwmPin = 5; // speaker
#define CHAR_BUFFER_SIZE 20
// msgBuffer stores both ASCII chars and their equivalent morse bytes
// so 2 bytes are required for every character in the message
#define MSG_BUFFER_SIZE 200 // MSG_BUFFER_SIZE/2 == character max message size
byte msgBuffer1[MSG_BUFFER_SIZE];
// I neglected to notice how little RAM is on my Arduino
#ifdef _LOTS_OF_RAM
byte msgBuffer2[MSG_BUFFER_SIZE];
byte msgBuffer3[MSG_BUFFER_SIZE];
byte msgBuffer4[MSG_BUFFER_SIZE];
#endif //_LOTS_OF_RAM
byte charBuffer[CHAR_BUFFER_SIZE];
byte morseBuffer[CHAR_BUFFER_SIZE];
byte charBufStart = 0;
byte morseBufStart = 0;
byte charCount = 0;
byte morseCount = 0;
volatile unsigned long m_millis = 0;
volatile unsigned int m_delay = 0;
volatile unsigned int m_dit_ms = 0;
volatile unsigned int m_dah_ms = 0;
volatile byte m_wpm = 0;
volatile byte m_byte = 0;
volatile byte m_volume = 0;
// VOLUME_MULTIPLIER is multiplied by the input volume (0-9)
// to calculate the output PWM level. This really only works
// if you’re using a piezo speaker connected directly to the
// output pin and have VOLUME_MULTIPLIER set to 1
#define VOLUME_MULTIPLIER 1
// define a crude critical section method
#define ENTER_CRITSEC() byte sreg = SREG; cli();
#define EXIT_CRITSEC() SREG = sreg;
// setup the service routine for the timer 2 interrupt
ISR(TIMER2_OVF_vect)
{
sendMorseAsync();
}
void setup()
{
pinMode( txPin, OUTPUT );
pinMode( pwmPin, OUTPUT );
pinMode( dataPin, INPUT );
// Setup our ISR for the keyboard’s clock line. We want
// ps2_clk_isr to be called whenever the clock transitions HIGH->LOW
attachInterrupt( clkPin – 2, ps2_clk_isr, FALLING );
// Set up timer2. This is kind of cryptic.
// Refer to the ATMega datasheet (if you dare).
TCNT2 = 0;
TCCR2A = 0;
TIMSK2 = (1<
{
Serial.print( “Invalid speed!\n” );
}
else
{
setMorseSpeed( wpm, 3 );
Serial.print( “Speed set to ” );
Serial.print( wpm, DEC );
Serial.println( ” wpm.” );
}
}
else if( key == AT_KEY_F2 ) // F2 == set volume
{
unsigned int vol = 0;
Serial.print( “Enter volume (0-9): ” );
getInputNumber( &vol );
Serial.println( “” );
if( vol > 9 )
{
Serial.print( “Invalid volume!\n” );
}
else
{
m_volume = vol * VOLUME_MULTIPLIER;
Serial.print( “Volume set to ” );
Serial.print( vol, DEC );
Serial.println( “.” );
}
}
else if( key == AT_KEY_F3 ) // F3 == set weight
{
unsigned int weight = 0;
Serial.print( “Enter weight (1-5): ” );
getInputNumber( &weight );
Serial.println( “” );
if( weight == 0 || weight > 5 )
{
Serial.print( “Invalid weight!\n” );
}
else
{
setMorseSpeed( m_wpm, weight );
Serial.print( “Weight set to ” );
Serial.print( weight, DEC );
Serial.println( “.” );
}
}
else if( key == AT_KEY_F5 ) // F5 == play a message
// shift-F5 == record a message
{
msgPlayRecord( (byte*)msgBuffer1, flags & FLAG_SHIFT );
}
#ifdef _LOTS_OF_RAM
else if( key == AT_KEY_F6 ) // play or record message
{
msgPlayRecord( (byte*)msgBuffer2, flags & FLAG_SHIFT );
}
else if( key == AT_KEY_F7 ) // play or record message
{
msgPlayRecord( (byte*)msgBuffer3, flags & FLAG_SHIFT );
}
else if( key == AT_KEY_F8 ) // play or record message
{
msgPlayRecord( (byte*)msgBuffer4, flags & FLAG_SHIFT );
}
#endif //_LOTS_OF_RAM
else if( ch != CHAR_NULL )
{
// looks like a valid, non-command keypress
// throw it onto our queue — it’ll get “sent” later
enqCharAndMorse( ch, mb );
}
ENTER_CRITSEC();
if( charCount > morseCount )
{
// I’ve got this whacky buffering scheme in here because
// I intend to hook this up to a 20×4 LCD display some day.
// We pull morse bytes off the queue without pulling the
// corresponding ASCII characters. This code allows us to
// “catch up” by dropping the first char in the queue.
charCount–;
charBufStart++;
charBufStart %= CHAR_BUFFER_SIZE;
}
EXIT_CRITSEC();
}
void msgPlayRecord( byte *pmsg, byte record )
{
byte ch, mb, x = 0;
if( record )
{
Serial.println( “\nRecording message:” );
while( true )
{
while( CHAR_NULL == ( ch = getKeyboardCharAndMorse( NULL, NULL, &mb ) ) )
{
delay( 100 );
}
if( ch == 0x08 ) // backspace
{
if( x > 0 ) x-=2;
}
else if( ch == CHAR_CR || ch == CHAR_ESC )
{
Serial.println( “\nMessage recorded.” );
pmsg[x] = 0;
pmsg[x+1] = 0;
return;
}
else
{
pmsg[x++] = ch;
pmsg[x++] = mb;
if( x == MSG_BUFFER_SIZE )
{
x -= 2;
}
else
{
Serial.print( ch, BYTE );
}
}
}
}
else // play
{
Serial.println( “\nPlaying Message:” );
while( !(pmsg[x]==0 && pmsg[x+1]==0) )
{
// ESC will abort message playback
if( CHAR_ESC == getKeyboardCharAndMorse( NULL, NULL, NULL ) )
{
break;
}
Serial.print( pmsg[x] );
// Put the next message character onto the queue to be sent
enqCharAndMorse( pmsg[x], pmsg[x+1] );
// Wait for the character to be sent.
while( morseCount > 0 )
{
delay( m_dit_ms );
}
// Remove the character from the buffer
// see comment on weird buffering in loop() above
ENTER_CRITSEC();
if( charCount > morseCount )
{
charCount–;
charBufStart++;
charBufStart %= CHAR_BUFFER_SIZE;
}
EXIT_CRITSEC();
x += 2;
}
Serial.println( “\nMessage complete.” );
}
}
// getInputNumber is a cheap and dirty method for getting a number
// from the user via the keyboard.
boolean getInputNumber( unsigned int *pnum )
{
char ch, input[6];
byte x = 0;
while( true )
{
while( CHAR_NULL == ( ch = getKeyboardCharAndMorse( NULL, NULL, NULL ) ) )
{
delay( 100 );
}
if( ch == 0x08 )// backspace
{
if( x > 0 ) x–;
}
else if( ch == 0x0d ) // carriage return
{
*pnum = 0;
for( byte b = 0; b < x; b++ )
{
*pnum *= 10;
*pnum += input[b] - '0';
}
return true;
}
else if( ch >= ‘0’ && ch <= '9' )
{
input[x++] = ch;
if( x > sizeof(input) – 1 )
{
x = sizeof(input) – 1;
}
}
Serial.print( ch, BYTE );
}
return false;
}
// enqCharAndMorse just tosses an ASCII char and the corresponding
// morse byte onto the respective queues
void enqCharAndMorse( byte ch, byte mb )
{
ENTER_CRITSEC();
if( charCount != CHAR_BUFFER_SIZE )
{
byte x = (charBufStart+charCount)%CHAR_BUFFER_SIZE;
charBuffer[x] = ch;
morseBuffer[x] = mb;
charCount++;
morseCount++;
}
EXIT_CRITSEC();
}
// deqCharAndMorse pulls an ASCII char and the corresponding
// morse byte off of the respective queues
byte deqCharAndMorse( byte *pch, byte *pmb )
{
byte rc = 0;
ENTER_CRITSEC();
if( charCount != 0 )
{
*pch = charBuffer[charBufStart];
*pmb = morseBuffer[charBufStart];
charBufStart++;
charBufStart %= CHAR_BUFFER_SIZE;
charCount–;
rc = 1;
}
EXIT_CRITSEC();
return rc;
}
// deqMorseByte pulls a morse byte off of the queue. Note that the
// corresponding ASCII character remains on the charBuffer queue.
byte deqMorseByte( byte *pmb )
{
byte rc = 0;
ENTER_CRITSEC();
if( morseCount != 0 )
{
*pmb = morseBuffer[morseBufStart];
morseBufStart++;
morseBufStart %= CHAR_BUFFER_SIZE;
morseCount–;
rc = 1;
}
EXIT_CRITSEC();
return rc;
}
// getKeyboardCharAndMorse is a jack-of-all-trades function that
// returns the ASCII character input from the keyboard while also
// providing the raw scan code, the keyboard flags and the morse
// byte that corresponds the character.
byte getKeyboardCharAndMorse( byte *raw_key, byte *raw_flags, byte *pmb )
{
if( _bufferReadIndex == _bufferWriteIndex )
{
return CHAR_NULL;
}
byte k = _keyBuffer[ _bufferReadIndex ];
byte f = _flagBuffer[ _bufferReadIndex ];
_bufferReadIndex += 1;
_bufferReadIndex %= KEY_BUFFER_LEN;
if( raw_key ) *raw_key = k;
if( raw_flags ) *raw_flags = f;
return convertKeyToChar( k, f, pmb );
}
// convertKeyToChar is the work horse behind getKeyboardCharAndMorse
byte convertKeyToChar( byte key, byte flags, byte *pmb )
{
byte i, c;
// is it a letter?
for( i = 0; i < 52; i+=2 )
{
if( key == alpha_ps2_to_ascii[i] )
{
c = i/2 + 0x41;
if( !IS_ALPHA_SHIFTED( flags ) )
{
c += 0x20;
}
if( pmb )
{
*pmb = alpha_ps2_to_ascii[i+1];
}
return c;
}
}
// must be something else
for( i = 0; other_ps2_to_ascii[i] != 0xff; i+=5 )
{
if( key == other_ps2_to_ascii[i] )
{
i += 1;
if( flags & FLAG_SHIFT )
{
i += 1;
}
if( pmb )
{
*pmb = other_ps2_to_ascii[i+2];
}
return other_ps2_to_ascii[i];
}
}
return 0;
}
void setMorseSpeed( byte wpm, byte weight )
{
if( wpm < 5 || wpm > 40 )
{
return;
}
m_wpm = wpm;
m_dit_ms = 1200 / wpm;
m_dah_ms = (( 3 + weight ) * m_dit_ms) / 2;
}
// sendMorseAsync does the actual “sending” of morse code.
// It’s made much more complicated by the fact that it’s
// happening asynchronously on the timer interrupt.
void sendMorseAsync()
{
// if m_delay is set (non-zero) then we’re in the middle of
// a dit, a dah or some space
if( m_delay > 0 )
{
if( ( millis() – m_millis ) < m_delay )
{
return;
}
// we've waited the appropriate time now make sure
// that the key is off
digitalWrite( txPin, LOW );
analogWrite( pwmPin, 0 );
// the value ( m_dit_ms - 1 ) is used for the amount of
// delay between dits and dahs within a character
if( m_delay != ( m_dit_ms - 1 ) )
{
// add some "intra-dit/dah" delay here
m_delay = m_dit_ms - 1;
m_millis = millis();
return;
}
m_byte <<= 1;
if( m_byte == 0x80 )
{
// we're at the end of the morse character
// add some intra-character delay
m_byte = 0;
// note that the actual delay we want is 3 * m_dit_ms
// however, the (m_dit_ms - 1) delay will also be added
// by the code above, so here we just specify 2 * m_dit_ms
m_delay = 2 * m_dit_ms;
m_millis = millis();
return;
}
m_delay = 0;
}
if( m_byte == 0 && !deqMorseByte( (byte*)&m_byte ) )
{
// nothing in the queue
return;
}
if( m_byte == 0 )
{
// add some intra-word space
m_delay = 2 * m_dah_ms;
m_millis = millis();
return;
}
digitalWrite( txPin, HIGH );
analogWrite( pwmPin, m_volume );
m_delay = m_byte & 0x80 ? m_dah_ms : m_dit_ms;
m_millis = millis();
}
// ps2_clk_isr reads the raw input from the keyboard.
// It's called whenever the keyboard's clock line transitions
// from HIGH to LOW. Read all about the protocol at Beyond Logic.
// http://www.beyondlogic.org/keyboard/keybrd.htm
void ps2_clk_isr()
{
if( _bitNum == 0 )
{
// the start bit is on the data pin
// ignore it and wait for the first bit of data
_bitNum += 1;
return;
}
byte bit = (byte)digitalRead( dataPin );
_parity += bit;
// bits 1-8 contain the data we want
if( _bitNum < 9 )
{
// store the bits in the _newByte global variable
_newByte >>= 1;
_newByte |= bit << 7;
_bitNum +=1;
return;
}
// bit 9 is the parity bit
// I'm ignoring it here
if( _bitNum == 9 )
{
if( !(_parity & 1) )
{
// parity error
}
_bitNum += 1;
return;
}
// We've received a whole byte of data.
// Now process it.
processKeyboardByte();
_bitNum = 0;
_parity = 0;
_oldByte = _newByte;
_newByte = 0;
}
// processKeyboardByte takes raw
void processKeyboardByte()
{
byte b;
// the keyboard may be sending a command
// check for and process commands first
switch( _newByte )
{
case AT_CMD_ACK: // ACK of host command
case AT_CMD_TESTOK: // self-test completed
case AT_CMD_ECHO: // echo response
case AT_CMD_RESEND: // resend
case AT_CMD_ERROR: // error
case AT_CMD_ERROR2: // error
return;
}
// The rest of this function just processes keypresses and sets flags.
// I assume it can be optimized quite a bit more.
switch( _newByte )
{
case AT_KEY_NUMLK:
if( _oldByte != AT_KEY_LIFT )
_flags ^= FLAG_NUMLK;
break;
case AT_KEY_SCRLK:
if( _oldByte != AT_KEY_LIFT )
_flags ^= FLAG_SCRLK;
break;
case AT_KEY_CAPLK:
if( _oldByte != AT_KEY_LIFT )
_flags ^= FLAG_CAPLK;
break;
case AT_KEY_SHIFT_L:
if( _oldByte == AT_KEY_LIFT )
{
_lr_flags &= ~LR_FLAG_SHIFT_L;
}
else
{
_lr_flags |= LR_FLAG_SHIFT_L;
}
break;
case AT_KEY_SHIFT_R:
if( _oldByte == AT_KEY_LIFT )
{
_lr_flags &= ~LR_FLAG_SHIFT_R;
}
else
{
_lr_flags |= LR_FLAG_SHIFT_R;
}
break;
case AT_KEY_ALT:
b = ( _flags & FLAG_EXT ) ? LR_FLAG_ALT_R : LR_FLAG_ALT_L;
_flags &= ~FLAG_EXT;
if( _oldByte == AT_KEY_LIFT )
{
_lr_flags &= ~b;
}
else
{
_lr_flags |= b;
}
break;
case AT_KEY_CTRL:
b = ( _flags & FLAG_EXT ) ? LR_FLAG_CTRL_R : LR_FLAG_CTRL_L;
_flags &= ~FLAG_EXT;
if( _oldByte == AT_KEY_LIFT )
{
_lr_flags &= ~b;
}
else
{
_lr_flags |= b;
}
break;
case AT_KEY_EXTENDED:
_flags |= FLAG_EXT;
break;
case AT_KEY_LIFT:
break;
default:
if( _oldByte != AT_KEY_LIFT )
{
if( _lr_flags & (LR_FLAG_SHIFT_R | LR_FLAG_SHIFT_L) )
{
_flags |= FLAG_SHIFT;
}
else
{
_flags &= ~FLAG_SHIFT;
}
if( _lr_flags & (LR_FLAG_ALT_R | LR_FLAG_ALT_L) )
{
_flags |= FLAG_ALT;
}
else
{
_flags &= ~FLAG_ALT;
}
if( _lr_flags & (LR_FLAG_CTRL_R | LR_FLAG_CTRL_L) )
{
_flags |= FLAG_CTRL;
}
else
{
_flags &= ~FLAG_CTRL;
}
// this is a keypress we need to record in the buffer
// we also record the current flags along with it
_keyBuffer[_bufferWriteIndex] = _newByte;
_flagBuffer[_bufferWriteIndex] = _flags;
_bufferWriteIndex += 1;
_bufferWriteIndex %= KEY_BUFFER_LEN;
// the FLAG_EXT flag only applies to one keypress
// since we've recorded the keypress, we also clear the flag now
_flags &= ~FLAG_EXT;
}
}
}
[/sourcecode]