morse keyboard sketch

#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< 40 )
{
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]

Advertisements
%d bloggers like this: