mirror of
https://github.com/smartykit/apple1.git
synced 2024-09-28 18:55:05 +00:00
1017 lines
33 KiB
C++
1017 lines
33 KiB
C++
|
/* Version V1.0.9
|
||
|
PS2KeyAdvanced.cpp - PS2KeyAdvanced library
|
||
|
Copyright (c) 2007 Free Software Foundation. All right reserved.
|
||
|
Written by Paul Carpenter, PC Services <sales@pcserviceselectronics.co.uk>
|
||
|
Created September 2014
|
||
|
Updated January 2016 - Paul Carpenter - add tested on Due and tidy ups for V1.5 Library Management
|
||
|
January 2020 Fix typos, correct keyboard reset status improve library.properties
|
||
|
and additional platform handling and some documentation
|
||
|
March 2020 Add SAMD1 as recognised support as has been tested by user
|
||
|
Improve different architecture handling
|
||
|
November 2020 Add support for STM32 from user Hiabuto-de
|
||
|
Tested on STM32Duino-Framework and PlatformIO on STM32F103C8T6 and an IBM Model M
|
||
|
July 2021 Add workaround for ESP32 issue with Silicon (hardware) from user submissions
|
||
|
|
||
|
IMPORTANT WARNING
|
||
|
|
||
|
If using a DUE or similar board with 3V3 I/O you MUST put a level translator
|
||
|
like a Texas Instruments TXS0102 or FET circuit as the signals are
|
||
|
Bi-directional (signals transmitted from both ends on same wire).
|
||
|
|
||
|
Failure to do so may damage your Arduino Due or similar board.
|
||
|
|
||
|
Test History
|
||
|
September 2014 Uno and Mega 2560 September 2014 using Arduino V1.6.0
|
||
|
January 2016 Uno, Mega 2560 and Due using Arduino 1.6.7 and Due Board
|
||
|
Manager V1.6.6
|
||
|
|
||
|
|
||
|
Assumption - Only ONE keyboard added to one Arduino
|
||
|
- No stream support
|
||
|
|
||
|
This is for a LATIN style keyboard using Scan code set 2. See various
|
||
|
websites on what different scan code sets use. Scan Code Set 2 is the
|
||
|
default scan code set for PS2 keyboards on power up.
|
||
|
|
||
|
Fully featured PS2 keyboard library to provide
|
||
|
All keys as a keycode (A-Z and 0-9 as ASCII equivalents)
|
||
|
All function and movement keys supported even multi-lingual
|
||
|
Parity checking of data sent/received on receive request keyboard resend
|
||
|
Resends data when needed handles keyboard protocol for RESEND and ECHO
|
||
|
Functions for get and set of
|
||
|
Scancode set in use READ only
|
||
|
LED and LOCK control
|
||
|
ReadID
|
||
|
Reset keyboard
|
||
|
Send ECHO
|
||
|
Handles NUM, _CAPS and SCROLL lock keys to LEDs
|
||
|
Handles NUM/SCROLL internally
|
||
|
|
||
|
Returns an uint16_t containing
|
||
|
Make/Break status
|
||
|
CAPS status
|
||
|
SHIFT, CTRL, ALT, ALT GR, GUI keys
|
||
|
Flag for function key not a displayable/printable character
|
||
|
8 bit key code
|
||
|
|
||
|
Code Ranges(bottom byte of uint16_t) see PS2KeyAdvanced.h for details
|
||
|
0 invalid/error
|
||
|
1-1F Functions (_CAPS, _SHIFT, _ALT, Enter, DEL... )
|
||
|
1A-1F Functions with ASCII control code
|
||
|
(DEL, BS, TAB, ESC, ENTER, SPACE)
|
||
|
20-60 Printable characters noting
|
||
|
0-9 = 0x30 to 0x39 as ASCII
|
||
|
A to Z = 0x41 to 0x5A as upper case ASCII type codes
|
||
|
8B Extra European key
|
||
|
61-A0 Function keys and other special keys (plus F2 and F1 less 8B)
|
||
|
61-78 F1 to F24
|
||
|
79-8A Multimedia
|
||
|
8C-8E ACPI power
|
||
|
91-A0 and F2 and F1 - Special multilingual
|
||
|
A8-FF Keyboard communications commands (note F2 and F1 are special
|
||
|
codes for special multi-lingual keyboards)
|
||
|
|
||
|
By using these ranges it is possible to perform detection of any key and do
|
||
|
easy translation to ASCII/UTF-8 avoiding keys that do not have a valid code.
|
||
|
|
||
|
Top Byte is 8 bits denoting as follows with defines for bit code
|
||
|
|
||
|
Define name bit description
|
||
|
PS2_BREAK 15 1 = Break key code
|
||
|
(MSB) 0 = Make Key code
|
||
|
PS2_SHIFT 14 1 = Shift key pressed as well (either side)
|
||
|
0 = NO shift key
|
||
|
PS2_CTRL 13 1 = Ctrl key pressed as well (either side)
|
||
|
0 = NO Ctrl key
|
||
|
PS2_CAPS 12 1 = Caps Lock ON
|
||
|
0 = Caps lock OFF
|
||
|
PS2_ALT 11 1 = Left Alt key pressed as well
|
||
|
0 = NO Left Alt key
|
||
|
PS2_ALT_GR 10 1 = Right Alt (Alt GR) key pressed as well
|
||
|
0 = NO Right Alt key
|
||
|
PS2_GUI 9 1 = GUI key pressed as well (either)
|
||
|
0 = NO GUI key
|
||
|
PS2_FUNCTION 8 1 = FUNCTION key non-printable character (plus space, tab, enter)
|
||
|
0 = standard character key
|
||
|
|
||
|
Error Codes
|
||
|
Most functions return 0 or 0xFFFF as error, other codes to note and
|
||
|
handle appropriately value in bottom byte
|
||
|
0xAA keyboard has reset and passed power up tests
|
||
|
will happen if keyboard plugged in after code start
|
||
|
0xFC Keyboard General error or power up fail
|
||
|
|
||
|
It is responsibility of your programme to deal with converting special cases
|
||
|
like <_CTRL>+<ENTER> sends a special code to something else. A better method
|
||
|
is to use PS2KeyMap library and add your own table to that library. If you
|
||
|
wish to do that make a NEW library called SOMETHING different NOT a variant
|
||
|
or revision of this one, as you are changing base functionality
|
||
|
|
||
|
See PS2KeyCode.h for codes from the keyboard this library uses to decode.
|
||
|
(may disappear in updates do not rely on this file or definitions)
|
||
|
|
||
|
See PS2KeyAvanced.h for returned definitions of Keys and accessible
|
||
|
definitions
|
||
|
|
||
|
See PS2KeyMap.h for tables currently supported
|
||
|
|
||
|
To get the key as ASCII/UTF-8 single byte character conversion requires use
|
||
|
of PS2KeyMap library AS WELL.
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Lesser General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2.1 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Lesser General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Lesser General Public
|
||
|
License along with this library; if not, write to the Free Software
|
||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
*/
|
||
|
#include <Arduino.h>
|
||
|
// Internal headers for library defines/codes/etc
|
||
|
#include "PS2KeyAdvanced.h"
|
||
|
#include "PS2KeyCode.h"
|
||
|
#include "PS2KeyTable.h"
|
||
|
|
||
|
|
||
|
// Private function declarations
|
||
|
void send_bit( void );
|
||
|
void send_now( uint8_t );
|
||
|
int16_t send_next( void );
|
||
|
void ps2_reset( void );
|
||
|
uint8_t decode_key( uint8_t );
|
||
|
void pininput( uint8_t );
|
||
|
void set_lock( );
|
||
|
|
||
|
/* Constant control functions to flags array
|
||
|
in translated key code value order */
|
||
|
#if defined( PS2_REQUIRES_PROGMEM )
|
||
|
const uint8_t PROGMEM control_flags[] = {
|
||
|
#else
|
||
|
const uint8_t control_flags[] = {
|
||
|
#endif
|
||
|
_SHIFT, _SHIFT, _CTRL, _CTRL,
|
||
|
_ALT, _ALT_GR, _GUI, _GUI
|
||
|
};
|
||
|
|
||
|
// Private Variables
|
||
|
volatile uint8_t _ps2mode; /* _ps2mode contains
|
||
|
_PS2_BUSY bit 7 = busy until all expected bytes RX/TX
|
||
|
_TX_MODE bit 6 = direction 1 = TX, 0 = RX (default)
|
||
|
_BREAK_KEY bit 5 = break code detected
|
||
|
_WAIT_RESPONSE bit 4 = expecting data response
|
||
|
_E0_MODE bit 3 = in E0 mode
|
||
|
_E1_MODE bit 2 = in E1 mode
|
||
|
_LAST_VALID bit 1 = last sent valid in case we receive resend
|
||
|
and not sent anything */
|
||
|
|
||
|
/* volatile RX buffers and variables accessed via interrupt functions */
|
||
|
volatile uint16_t _rx_buffer[ _RX_BUFFER_SIZE ]; // buffer for data from keyboard
|
||
|
volatile uint8_t _head; // _head = last byte written
|
||
|
uint8_t _tail; // _tail = last byte read (not modified in IRQ ever)
|
||
|
volatile int8_t _bytes_expected;
|
||
|
volatile uint8_t _bitcount; // Main state variable and bit count for interrupts
|
||
|
volatile uint8_t _shiftdata;
|
||
|
volatile uint8_t _parity;
|
||
|
|
||
|
/* TX variables */
|
||
|
volatile uint8_t _tx_buff[ _TX_BUFFER_SIZE ]; // buffer for keyboard commands
|
||
|
volatile uint8_t _tx_head; // buffer write pointer
|
||
|
volatile uint8_t _tx_tail; // buffer read pointer
|
||
|
volatile uint8_t _last_sent; // last byte if resend requested
|
||
|
volatile uint8_t _now_send; // immediate byte to send
|
||
|
volatile uint8_t _response_count; // bytes expected in reply to next TX
|
||
|
volatile uint8_t _tx_ready; // TX status for type of send contains
|
||
|
/* _HANDSHAKE 0x80 = handshaking command (ECHO/RESEND)
|
||
|
_COMMAND 0x01 = other command processing */
|
||
|
|
||
|
/* Output key buffering */
|
||
|
uint16_t _key_buffer[ _KEY_BUFF_SIZE ]; // Output Buffer for translated keys
|
||
|
uint8_t _key_head; // Output buffer WR pointer
|
||
|
uint8_t _key_tail; // Output buffer RD pointer
|
||
|
uint8_t _mode = 0; // Mode for output buffer contains
|
||
|
/* _NO_REPEATS 0x80 No repeat make codes for _CTRL, _ALT, _SHIFT, _GUI
|
||
|
_NO_BREAKS 0x08 No break codes */
|
||
|
|
||
|
// Arduino settings for pins and interrupts Needed to send data
|
||
|
uint8_t PS2_DataPin;
|
||
|
uint8_t PS2_IrqPin;
|
||
|
|
||
|
// Key decoding variables
|
||
|
uint8_t PS2_led_lock = 0; // LED and Lock status
|
||
|
uint8_t PS2_lockstate[ 4 ]; // Save if had break on key for locks
|
||
|
uint8_t PS2_keystatus; // current CAPS etc status for top byte
|
||
|
|
||
|
|
||
|
/*------------------ Code starts here -------------------------*/
|
||
|
|
||
|
/* The ISR for the external interrupt
|
||
|
To receive 11 bits - start 8 data, ODD parity, stop
|
||
|
To send data calls send_bit( )
|
||
|
Interrupt every falling incoming clock edge from keyboard */
|
||
|
void ps2interrupt( void )
|
||
|
{
|
||
|
// Workaround for ESP32 SILICON error see extra/Porting.md
|
||
|
#ifdef PS2_ONLY_CHANGE_IRQ
|
||
|
if( digitalRead( PS2_IrqPin ) )
|
||
|
return;
|
||
|
#endif
|
||
|
if( _ps2mode & _TX_MODE )
|
||
|
send_bit( );
|
||
|
else
|
||
|
{
|
||
|
static uint32_t prev_ms = 0;
|
||
|
uint32_t now_ms;
|
||
|
uint8_t val, ret;
|
||
|
|
||
|
val = digitalRead( PS2_DataPin );
|
||
|
/* timeout catch for glitches reset everything */
|
||
|
now_ms = millis( );
|
||
|
if( now_ms - prev_ms > 250 )
|
||
|
{
|
||
|
_bitcount = 0;
|
||
|
_shiftdata = 0;
|
||
|
}
|
||
|
prev_ms = now_ms;
|
||
|
_bitcount++; // Now point to next bit
|
||
|
switch( _bitcount )
|
||
|
{
|
||
|
case 1: // Start bit
|
||
|
_parity = 0;
|
||
|
_ps2mode |= _PS2_BUSY; // set busy
|
||
|
break;
|
||
|
case 2:
|
||
|
case 3:
|
||
|
case 4:
|
||
|
case 5:
|
||
|
case 6:
|
||
|
case 7:
|
||
|
case 8:
|
||
|
case 9: // Data bits
|
||
|
_parity += val; // another one received ?
|
||
|
_shiftdata >>= 1; // right _SHIFT one place for next bit
|
||
|
_shiftdata |= ( val ) ? 0x80 : 0; // or in MSbit
|
||
|
break;
|
||
|
case 10: // Parity check
|
||
|
_parity &= 1; // Get LSB if 1 = odd number of 1's so parity bit should be 0
|
||
|
if( _parity == val ) // Both same parity error
|
||
|
_parity = 0xFD; // To ensure at next bit count clear and discard
|
||
|
break;
|
||
|
case 11: // Stop bit lots of spare time now
|
||
|
if( _parity >= 0xFD ) // had parity error
|
||
|
{
|
||
|
send_now( PS2_KC_RESEND ); // request resend
|
||
|
_tx_ready |= _HANDSHAKE;
|
||
|
}
|
||
|
else // Good so save byte in _rx_buffer
|
||
|
{
|
||
|
// Check _SHIFTed data for commands and action
|
||
|
ret = decode_key( _shiftdata );
|
||
|
if( ret & 0x2 ) // decrement expected bytes
|
||
|
_bytes_expected--;
|
||
|
if( _bytes_expected <= 0 || ret & 4 ) // Save value ??
|
||
|
{
|
||
|
val = _head + 1;
|
||
|
if( val >= _RX_BUFFER_SIZE )
|
||
|
val = 0;
|
||
|
if( val != _tail )
|
||
|
{
|
||
|
// get last byte to save
|
||
|
_rx_buffer[ val ] = uint16_t( _shiftdata );
|
||
|
// save extra details
|
||
|
_rx_buffer[ val ] |= uint16_t( _ps2mode ) << 8;
|
||
|
_head = val;
|
||
|
}
|
||
|
}
|
||
|
if( ret & 0x10 ) // Special command to send (ECHO/RESEND)
|
||
|
{
|
||
|
send_now( _now_send );
|
||
|
_tx_ready |= _HANDSHAKE;
|
||
|
}
|
||
|
else
|
||
|
if( _bytes_expected <= 0 ) // Receive data finished
|
||
|
{
|
||
|
// Set mode and status for next receive byte
|
||
|
_ps2mode &= ~( _E0_MODE + _E1_MODE + _WAIT_RESPONSE + _BREAK_KEY );
|
||
|
_bytes_expected = 0;
|
||
|
_ps2mode &= ~_PS2_BUSY;
|
||
|
send_next( ); // Check for more to send
|
||
|
}
|
||
|
}
|
||
|
_bitcount = 0; // end of byte
|
||
|
break;
|
||
|
default: // in case of weird error and end of byte reception re-sync
|
||
|
_bitcount = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Decode value received to check for errors commands and responses
|
||
|
NOT keycode translate yet
|
||
|
returns bit Or'ing
|
||
|
0x10 send command in _now_send (after any saves and decrements)
|
||
|
0x08 error abort reception and reset status and queues
|
||
|
0x04 save value ( complete after translation )
|
||
|
0x02 decrement count of bytes to expected
|
||
|
|
||
|
Codes like EE, AA and FC ( Echo, BAT pass and fail) treated as valid codes
|
||
|
return code 6
|
||
|
*/
|
||
|
uint8_t decode_key( uint8_t value )
|
||
|
{
|
||
|
uint8_t state;
|
||
|
|
||
|
state = 6; // default state save and decrement
|
||
|
|
||
|
// Anything but resend received clear valid value to resend
|
||
|
if( value != PS2_KC_RESEND )
|
||
|
_ps2mode &= ~( _LAST_VALID );
|
||
|
|
||
|
// First check not a valid response code from a host command
|
||
|
if( _ps2mode & _WAIT_RESPONSE )
|
||
|
if( value < 0xF0 )
|
||
|
return state; // Save response and decrement
|
||
|
|
||
|
// E1 Pause mode special case just decrement
|
||
|
if( _ps2mode & _E1_MODE )
|
||
|
return 2;
|
||
|
|
||
|
switch( value )
|
||
|
{
|
||
|
case 0: // Buffer overrun Errors Reset modes and buffers
|
||
|
case PS2_KC_OVERRUN:
|
||
|
ps2_reset( );
|
||
|
state = 0xC;
|
||
|
break;
|
||
|
case PS2_KC_RESEND: // Resend last byte if we have sent something
|
||
|
if( ( _ps2mode & _LAST_VALID ) )
|
||
|
{
|
||
|
_now_send = _last_sent;
|
||
|
state = 0x10;
|
||
|
}
|
||
|
else
|
||
|
state = 0;
|
||
|
break;
|
||
|
case PS2_KC_ERROR: // General error pass up but stop any sending or receiving
|
||
|
_bytes_expected = 0;
|
||
|
_ps2mode = 0;
|
||
|
_tx_ready = 0;
|
||
|
state = 0xE;
|
||
|
break;
|
||
|
case PS2_KC_KEYBREAK: // break Code - wait the final key byte
|
||
|
_bytes_expected = 1;
|
||
|
_ps2mode |= _BREAK_KEY;
|
||
|
state = 0;
|
||
|
break;
|
||
|
case PS2_KC_ECHO: // Echo if we did not originate echo back
|
||
|
state = 4; // always save
|
||
|
if( _ps2mode & _LAST_VALID && _last_sent != PS2_KC_ECHO )
|
||
|
{
|
||
|
_now_send = PS2_KC_ECHO;
|
||
|
state |= 0x10; // send _command on exit
|
||
|
}
|
||
|
break;
|
||
|
case PS2_KC_BAT: // BAT pass
|
||
|
_bytes_expected = 0; // reset as if in middle of something lost now
|
||
|
state = 4;
|
||
|
break;
|
||
|
case PS2_KC_EXTEND1: // Major extend code (PAUSE key only)
|
||
|
if( !( _ps2mode & _E1_MODE ) ) // First E1 only
|
||
|
{
|
||
|
_bytes_expected = 7; // seven more bytes
|
||
|
_ps2mode |= _E1_MODE;
|
||
|
_ps2mode &= ~_BREAK_KEY; // Always a make
|
||
|
}
|
||
|
state = 0;
|
||
|
break;
|
||
|
case PS2_KC_EXTEND: // Two byte Extend code
|
||
|
_bytes_expected = 1; // one more byte at least to wait for
|
||
|
_ps2mode |= _E0_MODE;
|
||
|
state = 0;
|
||
|
break;
|
||
|
}
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Send data to keyboard
|
||
|
Data pin direction should already be changed
|
||
|
Start bit would be already set so each clock setup for next clock
|
||
|
parity and _bitcount should be 0 already and busy should be set
|
||
|
|
||
|
Start bit setting is due to bug in attachinterrupt not clearing pending interrupts
|
||
|
Also no clear pending interrupt function */
|
||
|
void send_bit( void )
|
||
|
{
|
||
|
uint8_t val;
|
||
|
|
||
|
_bitcount++; // Now point to next bit
|
||
|
switch( _bitcount )
|
||
|
{
|
||
|
case 1:
|
||
|
#if defined( PS2_CLEAR_PENDING_IRQ )
|
||
|
// Start bit due to Arduino bug
|
||
|
digitalWrite( PS2_DataPin, LOW );
|
||
|
break;
|
||
|
#endif
|
||
|
case 2:
|
||
|
case 3:
|
||
|
case 4:
|
||
|
case 5:
|
||
|
case 6:
|
||
|
case 7:
|
||
|
case 8:
|
||
|
case 9:
|
||
|
// Data bits
|
||
|
val = _shiftdata & 0x01; // get LSB
|
||
|
digitalWrite( PS2_DataPin, val ); // send start bit
|
||
|
_parity += val; // another one received ?
|
||
|
_shiftdata >>= 1; // right _SHIFT one place for next bit
|
||
|
break;
|
||
|
case 10:
|
||
|
// Parity - Send LSB if 1 = odd number of 1's so parity should be 0
|
||
|
digitalWrite( PS2_DataPin, ( ~_parity & 1 ) );
|
||
|
break;
|
||
|
case 11: // Stop bit write change to input pull up for high stop bit
|
||
|
pininput( PS2_DataPin );
|
||
|
break;
|
||
|
case 12: // Acknowledge bit low we cannot do anything if high instead of low
|
||
|
if( !( _now_send == PS2_KC_ECHO || _now_send == PS2_KC_RESEND ) )
|
||
|
{
|
||
|
_last_sent = _now_send; // save in case of resend request
|
||
|
_ps2mode |= _LAST_VALID;
|
||
|
}
|
||
|
// clear modes to receive again
|
||
|
_ps2mode &= ~_TX_MODE;
|
||
|
if( _tx_ready & _HANDSHAKE ) // If _HANDSHAKE done
|
||
|
_tx_ready &= ~_HANDSHAKE;
|
||
|
else // else we finished a command
|
||
|
_tx_ready &= ~_COMMAND;
|
||
|
if( !( _ps2mode & _WAIT_RESPONSE ) ) // if not wait response
|
||
|
send_next( ); // check anything else to queue up
|
||
|
_bitcount = 0; // end of byte
|
||
|
break;
|
||
|
default: // in case of weird error and end of byte reception re-sync
|
||
|
_bitcount = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Takes a byte sets up variables and starts the data sending processes
|
||
|
Starts the actual byte transmission
|
||
|
calling code must make sure line is idle and able to send
|
||
|
Whilst this function adds long delays the process of the delays
|
||
|
will STOP the interrupt source (keyboard) externally when clock held low
|
||
|
_tx_ready contains 2 flags checked in this order
|
||
|
_HANDSHAKE command sent as part of receiving e.g. ECHO, RESEND
|
||
|
_COMMAND other commands not part of receiving
|
||
|
Main difference _bytes_expected is NOT altered in _HANDSHAKE mode
|
||
|
in command mode we update _bytes_expected with number of response bytes
|
||
|
*/
|
||
|
void send_now( uint8_t command )
|
||
|
{
|
||
|
_shiftdata = command;
|
||
|
_now_send = command; // copy for later to save in last sent
|
||
|
#if defined( PS2_CLEAR_PENDING_IRQ )
|
||
|
_bitcount = 0; // AVR/SAM ignore extra interrupt
|
||
|
#else
|
||
|
_bitcount = 1; // Normal processors
|
||
|
#endif
|
||
|
_parity = 0;
|
||
|
_ps2mode |= _TX_MODE + _PS2_BUSY;
|
||
|
|
||
|
// Only do this if sending a command not from Handshaking
|
||
|
if( !( _tx_ready & _HANDSHAKE ) && ( _tx_ready & _COMMAND ) )
|
||
|
{
|
||
|
_bytes_expected = _response_count; // How many bytes command will generate
|
||
|
_ps2mode |= _WAIT_RESPONSE;
|
||
|
}
|
||
|
|
||
|
// STOP interrupt handler
|
||
|
// Setting pin output low will cause interrupt before ready
|
||
|
detachInterrupt( digitalPinToInterrupt( PS2_IrqPin ) );
|
||
|
// set pins to outputs and high
|
||
|
digitalWrite( PS2_DataPin, HIGH );
|
||
|
pinMode( PS2_DataPin, OUTPUT );
|
||
|
digitalWrite( PS2_IrqPin, HIGH );
|
||
|
pinMode( PS2_IrqPin, OUTPUT );
|
||
|
// Essential for PS2 spec compliance
|
||
|
delayMicroseconds( 10 );
|
||
|
// set Clock LOW
|
||
|
digitalWrite( PS2_IrqPin, LOW );
|
||
|
// Essential for PS2 spec compliance
|
||
|
// set clock low for 60us
|
||
|
delayMicroseconds( 60 );
|
||
|
// Set data low - Start bit
|
||
|
digitalWrite( PS2_DataPin, LOW );
|
||
|
// set clock to input_pullup data stays output while writing to keyboard
|
||
|
pininput( PS2_IrqPin );
|
||
|
// Restart interrupt handler
|
||
|
attachInterrupt( digitalPinToInterrupt( PS2_IrqPin ), ps2interrupt, FALLING );
|
||
|
// wait clock interrupt to send data
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Send next byte/command from TX queue and start sending
|
||
|
Must be ready to send and idle
|
||
|
Assumes commands consist of 1 or more bytes and wait for response then may or
|
||
|
not be followed by further bytes to send with or without response
|
||
|
Checks
|
||
|
1/ Buffer empty return empty buffer
|
||
|
2/ Busy return busy (will be checked by interrupt routines later)
|
||
|
3/ Read next byte (next byte to send)
|
||
|
4/ Check if following byte(s) are command/data or response
|
||
|
|
||
|
Returns 1 if started transmission or queued
|
||
|
-134 if already busy
|
||
|
-2 if buffer empty
|
||
|
|
||
|
Note PS2_KEY_IGNORE is used to denote a byte(s) expected in response */
|
||
|
int16_t send_next( void )
|
||
|
{
|
||
|
uint8_t i;
|
||
|
int16_t val;
|
||
|
|
||
|
val = -1;
|
||
|
// Check buffer not empty
|
||
|
i = _tx_tail;
|
||
|
if( i == _tx_head )
|
||
|
return -2;
|
||
|
|
||
|
// set command bit in _tx_ready as another command to do
|
||
|
_tx_ready |= _COMMAND;
|
||
|
|
||
|
// Already item waiting to be sent or sending interrupt routines will call back
|
||
|
if( _tx_ready & _HANDSHAKE )
|
||
|
return -134;
|
||
|
|
||
|
// if busy let interrupt catch and call us again
|
||
|
if( _ps2mode & _PS2_BUSY )
|
||
|
return -134;
|
||
|
|
||
|
// Following only accessed when not receiving or sending protocol bytes
|
||
|
// Scan for command response and expected bytes to follow
|
||
|
_response_count = 0;
|
||
|
do
|
||
|
{
|
||
|
i++;
|
||
|
if( i >= _TX_BUFFER_SIZE )
|
||
|
i = 0;
|
||
|
if( val == -1 )
|
||
|
val = _tx_buff[ i ];
|
||
|
else
|
||
|
if( _tx_buff[ i ] != PS2_KEY_IGNORE )
|
||
|
break;
|
||
|
else
|
||
|
_response_count++;
|
||
|
_tx_tail = i;
|
||
|
}
|
||
|
while( i != _tx_head );
|
||
|
// Now know what to send and expect start the actual wire sending
|
||
|
send_now( val );
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Send a byte to the TX buffer
|
||
|
Value in buffer of PS2_KEY_IGNORE signifies wait for response,
|
||
|
use one for each byte expected
|
||
|
|
||
|
Returns -4 - if buffer full (buffer overrun not written)
|
||
|
Returns 1 byte written when done */
|
||
|
int send_byte( uint8_t val )
|
||
|
{
|
||
|
uint8_t ret;
|
||
|
|
||
|
ret = _tx_head + 1;
|
||
|
if( ret >= _TX_BUFFER_SIZE )
|
||
|
ret = 0;
|
||
|
if( ret != _tx_tail )
|
||
|
{
|
||
|
_tx_buff[ ret ] = val;
|
||
|
_tx_head = ret;
|
||
|
return 1;
|
||
|
}
|
||
|
return -4;
|
||
|
}
|
||
|
|
||
|
|
||
|
// initialize a data pin for input
|
||
|
void pininput( uint8_t pin )
|
||
|
{
|
||
|
#ifdef INPUT_PULLUP
|
||
|
pinMode( pin, INPUT_PULLUP );
|
||
|
#else
|
||
|
digitalWrite( pin, HIGH );
|
||
|
pinMode( pin, INPUT );
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
void ps2_reset( void )
|
||
|
{
|
||
|
/* reset buffers and states */
|
||
|
_tx_head = 0;
|
||
|
_tx_tail = 0;
|
||
|
_tx_ready = 0;
|
||
|
_response_count = 0;
|
||
|
_head = 0;
|
||
|
_tail = 0;
|
||
|
_bitcount = 0;
|
||
|
PS2_keystatus = 0;
|
||
|
PS2_led_lock = 0;
|
||
|
_ps2mode = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
uint8_t key_available( )
|
||
|
{
|
||
|
int8_t i;
|
||
|
|
||
|
i = _head - _tail;
|
||
|
if( i < 0 )
|
||
|
i += _RX_BUFFER_SIZE;
|
||
|
return uint8_t( i );
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Translate PS2 keyboard code sequence into our key code data
|
||
|
PAUSE key (_E1_MODE) is de_ALT with as special case, and
|
||
|
command responses not translated
|
||
|
|
||
|
Called from read function as too long for in interrupt
|
||
|
|
||
|
Returns 0 for no valid key or processed internally ignored or similar
|
||
|
0 for empty buffer
|
||
|
*/
|
||
|
uint16_t translate( void )
|
||
|
{
|
||
|
uint8_t index, length, data;
|
||
|
uint16_t retdata;
|
||
|
|
||
|
// get next character
|
||
|
// Check first something to fetch
|
||
|
index = _tail;
|
||
|
// check for empty buffer
|
||
|
if( index == _head )
|
||
|
return 0;
|
||
|
index++;
|
||
|
if( index >= _RX_BUFFER_SIZE )
|
||
|
index = 0;
|
||
|
_tail = index;
|
||
|
// Get the flags byte break modes etc in this order
|
||
|
data = _rx_buffer[ index ] & 0xFF;
|
||
|
index = ( _rx_buffer[ index ] & 0xFF00 ) >> 8;
|
||
|
|
||
|
// Catch special case of PAUSE key
|
||
|
if( index & _E1_MODE )
|
||
|
return PS2_KEY_PAUSE + _FUNCTION;
|
||
|
|
||
|
// Ignore anything not actual keycode but command/response
|
||
|
// Return untranslated as valid
|
||
|
if( ( data >= PS2_KC_BAT && data != PS2_KC_LANG1 && data != PS2_KC_LANG2 )
|
||
|
|| ( index & _WAIT_RESPONSE ) )
|
||
|
return ( uint16_t )data;
|
||
|
|
||
|
// Gather the break of key status
|
||
|
if( index & _BREAK_KEY )
|
||
|
PS2_keystatus |= _BREAK;
|
||
|
else
|
||
|
PS2_keystatus &= ~_BREAK;
|
||
|
|
||
|
retdata = 0; // error code by default
|
||
|
// Scan appropriate table
|
||
|
if( index & _E0_MODE )
|
||
|
{
|
||
|
length = sizeof( extended_key ) / sizeof( extended_key[ 0 ] );
|
||
|
for( index = 0; index < length; index++ )
|
||
|
#if defined( PS2_REQUIRES_PROGMEM )
|
||
|
if( data == pgm_read_byte( &extended_key[ index ][ 0 ] ) )
|
||
|
{
|
||
|
retdata = pgm_read_byte( &extended_key[ index ][ 1 ] );
|
||
|
#else
|
||
|
if( data == extended_key[ index ][ 0 ] )
|
||
|
{
|
||
|
retdata = extended_key[ index ][ 1 ];
|
||
|
#endif
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
length = sizeof( single_key ) / sizeof( single_key[ 0 ] );
|
||
|
for( index = 0; index < length; index++ )
|
||
|
#if defined( PS2_REQUIRES_PROGMEM )
|
||
|
if( data == pgm_read_byte( &single_key[ index ][ 0 ] ) )
|
||
|
{
|
||
|
retdata = pgm_read_byte( &single_key[ index ][ 1 ] );
|
||
|
#else
|
||
|
if( data == single_key[ index ][ 0 ] )
|
||
|
{
|
||
|
retdata = single_key[ index ][ 1 ];
|
||
|
#endif
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
// trap not found key
|
||
|
if( index == length )
|
||
|
retdata = 0;
|
||
|
/* valid found values only */
|
||
|
if( retdata > 0 )
|
||
|
{
|
||
|
if( retdata <= PS2_KEY_CAPS )
|
||
|
{ // process lock keys need second make to turn off
|
||
|
if( PS2_keystatus & _BREAK )
|
||
|
{
|
||
|
PS2_lockstate[ retdata ] = 0; // Set received a break so next make toggles LOCK status
|
||
|
retdata = PS2_KEY_IGNORE; // ignore key
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( PS2_lockstate[ retdata ] == 1 )
|
||
|
retdata = PS2_KEY_IGNORE; // ignore key if make and not received break
|
||
|
else
|
||
|
{
|
||
|
PS2_lockstate[ retdata ] = 1;
|
||
|
switch( retdata )
|
||
|
{
|
||
|
case PS2_KEY_CAPS: index = PS2_LOCK_CAPS;
|
||
|
// Set CAPS lock if not set before
|
||
|
if( PS2_keystatus & _CAPS )
|
||
|
PS2_keystatus &= ~_CAPS;
|
||
|
else
|
||
|
PS2_keystatus |= _CAPS;
|
||
|
break;
|
||
|
case PS2_KEY_SCROLL: index = PS2_LOCK_SCROLL;
|
||
|
break;
|
||
|
case PS2_KEY_NUM: index = PS2_LOCK_NUM;
|
||
|
break;
|
||
|
}
|
||
|
// Now update PS2_led_lock status to match
|
||
|
if( PS2_led_lock & index )
|
||
|
{
|
||
|
PS2_led_lock &= ~index;
|
||
|
PS2_keystatus |= _BREAK; // send as break
|
||
|
}
|
||
|
else
|
||
|
PS2_led_lock |= index;
|
||
|
set_lock( );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
if( retdata >= PS2_KEY_L_SHIFT && retdata <= PS2_KEY_R_GUI )
|
||
|
{ // Update bits for _SHIFT, _CTRL, _ALT, _ALT GR, _GUI in status
|
||
|
#if defined( PS2_REQUIRES_PROGMEM )
|
||
|
index = pgm_read_byte( &control_flags[ retdata - PS2_KEY_L_SHIFT ] );
|
||
|
#else
|
||
|
index = control_flags[ retdata - PS2_KEY_L_SHIFT ];
|
||
|
#endif
|
||
|
if( PS2_keystatus & _BREAK )
|
||
|
PS2_keystatus &= ~index;
|
||
|
else
|
||
|
// if already set ignore repeats if flag set
|
||
|
if( ( PS2_keystatus & index ) && ( _mode & _NO_REPEATS ) )
|
||
|
retdata = PS2_KEY_IGNORE; // ignore repeat _SHIFT, _CTRL, _ALT, _GUI
|
||
|
else
|
||
|
PS2_keystatus |= index;
|
||
|
}
|
||
|
else
|
||
|
// Numeric keypad ONLY works in numlock state or when _SHIFT status
|
||
|
if( retdata >= PS2_KEY_KP0 && retdata <= PS2_KEY_KP_DOT )
|
||
|
if( !( PS2_led_lock & PS2_LOCK_NUM ) || ( PS2_keystatus & _SHIFT ) )
|
||
|
#if defined( PS2_REQUIRES_PROGMEM )
|
||
|
retdata = pgm_read_byte( &scroll_remap[ retdata - PS2_KEY_KP0 ] );
|
||
|
#else
|
||
|
retdata = scroll_remap[ retdata - PS2_KEY_KP0 ];
|
||
|
#endif
|
||
|
// Sort break code handling or ignore for all having processed the _SHIFT etc status
|
||
|
if( ( PS2_keystatus & _BREAK ) && ( _mode & _NO_BREAKS ) )
|
||
|
return ( uint16_t )PS2_KEY_IGNORE;
|
||
|
// Assign Function keys _mode
|
||
|
if( ( retdata <= PS2_KEY_SPACE || retdata >= PS2_KEY_F1 ) && retdata != PS2_KEY_EUROPE2 )
|
||
|
PS2_keystatus |= _FUNCTION;
|
||
|
else
|
||
|
PS2_keystatus &= ~_FUNCTION;
|
||
|
}
|
||
|
return ( retdata | ( (uint16_t)PS2_keystatus << 8 ) );
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Build command to send lock status
|
||
|
Assumes data is within range */
|
||
|
void set_lock( )
|
||
|
{
|
||
|
send_byte( PS2_KC_LOCK ); // send command
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait ACK
|
||
|
send_byte( PS2_led_lock ); // send data from internal variable
|
||
|
if( ( send_byte( PS2_KEY_IGNORE ) ) ) // wait ACK
|
||
|
send_next( ); // if idle start transmission
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Send echo command to keyboard
|
||
|
returned data in keyboard buffer read as keys */
|
||
|
void PS2KeyAdvanced::echo( void )
|
||
|
{
|
||
|
send_byte( PS2_KC_ECHO ); // send command
|
||
|
if( ( send_byte( PS2_KEY_IGNORE ) ) ) // wait data PS2_KC_ECHO
|
||
|
send_next( ); // if idle start transmission
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Get the ID used in keyboard
|
||
|
returned data in keyboard buffer read as keys */
|
||
|
void PS2KeyAdvanced::readID( void )
|
||
|
{
|
||
|
send_byte( PS2_KC_READID ); // send command
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait ACK
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait data
|
||
|
if( ( send_byte( PS2_KEY_IGNORE ) ) ) // wait data
|
||
|
send_next( ); // if idle start transmission
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Get the current Scancode Set used in keyboard
|
||
|
returned data in keyboard buffer read as keys */
|
||
|
void PS2KeyAdvanced::getScanCodeSet( void )
|
||
|
{
|
||
|
send_byte( PS2_KC_SCANCODE ); // send command
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait ACK
|
||
|
send_byte( 0 ); // send data 0 = read
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait ACK
|
||
|
if( ( send_byte( PS2_KEY_IGNORE ) ) ) // wait data
|
||
|
send_next( ); // if idle start transmission
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns the current status of Locks */
|
||
|
uint8_t PS2KeyAdvanced::getLock( )
|
||
|
{
|
||
|
return( PS2_led_lock );
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Sets the current status of Locks and LEDs */
|
||
|
void PS2KeyAdvanced::setLock( uint8_t code )
|
||
|
{
|
||
|
code &= 0xF; // To allow for rare keyboards with extra LED
|
||
|
PS2_led_lock = code; // update our lock copy
|
||
|
PS2_keystatus &= ~_CAPS; // Update copy of _CAPS lock as well
|
||
|
PS2_keystatus |= ( code & PS2_LOCK_CAPS ) ? _CAPS : 0;
|
||
|
set_lock( );
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Set library to not send break key codes
|
||
|
1 = no break codes
|
||
|
0 = send break codes */
|
||
|
void PS2KeyAdvanced::setNoBreak( uint8_t data )
|
||
|
{
|
||
|
_mode &= ~_NO_BREAKS;
|
||
|
_mode |= data ? _NO_BREAKS : 0;
|
||
|
}
|
||
|
|
||
|
/* Set library to not repeat make codes for _CTRL, _ALT, _GUI, _SHIFT
|
||
|
1 = no repeat codes
|
||
|
0 = send repeat codes */
|
||
|
void PS2KeyAdvanced::setNoRepeat( uint8_t data )
|
||
|
{
|
||
|
_mode &= ~_NO_REPEATS;
|
||
|
_mode |= data ? _NO_REPEATS : 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Resets keyboard when reset has completed
|
||
|
keyboard sends AA - Pass or FC for fail */
|
||
|
void PS2KeyAdvanced::resetKey( )
|
||
|
{
|
||
|
send_byte( PS2_KC_RESET ); // send command
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait ACK
|
||
|
if( ( send_byte( PS2_KEY_IGNORE ) ) ) // wait data PS2_KC_BAT or PS2_KC_ERROR
|
||
|
send_next( ); // if idle start transmission
|
||
|
// LEDs and KeyStatus Reset too... to match keyboard
|
||
|
PS2_led_lock = 0;
|
||
|
PS2_keystatus = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Send Typematic rate/delay command to keyboard
|
||
|
First Parameter rate is 0 - 0x1F (31)
|
||
|
0 = 30 CPS
|
||
|
0x1F = 2 CPS
|
||
|
default in keyboard is 0xB (10.9 CPS)
|
||
|
Second Parameter delay is 0 - 3 for 0.25s to 1s in 0.25 increments
|
||
|
default in keyboard is 1 = 0.5 second delay
|
||
|
Returned data in keyboard buffer read as keys
|
||
|
|
||
|
Error returns 0 OK
|
||
|
-5 parameter error
|
||
|
*/
|
||
|
int PS2KeyAdvanced::typematic( uint8_t rate, uint8_t delay )
|
||
|
{
|
||
|
if( rate > 31 || delay > 3 )
|
||
|
return -5;
|
||
|
send_byte( PS2_KC_RATE ); // send command
|
||
|
send_byte( PS2_KEY_IGNORE ); // wait ACK
|
||
|
send_byte( ( delay << 5 ) + rate ); // Send values
|
||
|
if( ( send_byte( PS2_KEY_IGNORE ) ) ) // wait ACK
|
||
|
send_next( ); // if idle start transmission
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Returns count of available processed key codes
|
||
|
|
||
|
If processed key buffer (_key_buffer) buffer returns max count
|
||
|
else processes input key code buffer until
|
||
|
either input buffer empty
|
||
|
or output buffer full
|
||
|
returns actual count
|
||
|
|
||
|
Returns 0 buffer empty
|
||
|
1 to buffer size less 1 as 1 to full buffer
|
||
|
|
||
|
As with other ring buffers here when pointers match
|
||
|
buffer empty so cannot actually hold buffer size values */
|
||
|
uint8_t PS2KeyAdvanced::available( )
|
||
|
{
|
||
|
int8_t i, idx;
|
||
|
uint16_t data;
|
||
|
|
||
|
// check output queue
|
||
|
i = _key_head - _key_tail;
|
||
|
if( i < 0 )
|
||
|
i += _KEY_BUFF_SIZE;
|
||
|
while( i < ( _KEY_BUFF_SIZE - 1 ) ) // process if not full
|
||
|
if( key_available( ) ) // not check for more keys to process
|
||
|
{
|
||
|
data = translate( ); // get next translated key
|
||
|
if( data == 0 ) // unless in buffer is empty
|
||
|
break;
|
||
|
if( ( data & 0xFF ) != PS2_KEY_IGNORE
|
||
|
&& ( data & 0xFF ) > 0 )
|
||
|
{
|
||
|
idx = _key_head + 1; // point to next space
|
||
|
if( idx >= _KEY_BUFF_SIZE ) // loop to front if necessary
|
||
|
idx = 0;
|
||
|
_key_buffer[ idx ] = data; // save the data to out buffer
|
||
|
_key_head = idx;
|
||
|
i++; // update count
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
break; // exit nothing coming in
|
||
|
return uint8_t( i );
|
||
|
}
|
||
|
|
||
|
|
||
|
/* read a decoded key from the keyboard buffer
|
||
|
returns 0 for empty buffer */
|
||
|
uint16_t PS2KeyAdvanced::read( )
|
||
|
{
|
||
|
uint16_t result;
|
||
|
uint8_t idx;
|
||
|
|
||
|
if( ( result = available( ) ) )
|
||
|
{
|
||
|
idx = _key_tail;
|
||
|
idx++;
|
||
|
if( idx >= _KEY_BUFF_SIZE ) // loop to front if necessary
|
||
|
idx = 0;
|
||
|
_key_tail = idx;
|
||
|
result = _key_buffer[ idx ];
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
|
||
|
PS2KeyAdvanced::PS2KeyAdvanced( )
|
||
|
{
|
||
|
// nothing to do here, begin( ) does it all
|
||
|
}
|
||
|
|
||
|
|
||
|
/* instantiate class for keyboard */
|
||
|
void PS2KeyAdvanced::begin( uint8_t data_pin, uint8_t irq_pin )
|
||
|
{
|
||
|
/* PS2 variables reset */
|
||
|
ps2_reset( );
|
||
|
|
||
|
PS2_DataPin = data_pin;
|
||
|
PS2_IrqPin = irq_pin;
|
||
|
|
||
|
// initialize the pins
|
||
|
pininput( PS2_IrqPin ); /* Setup Clock pin */
|
||
|
pininput( PS2_DataPin ); /* Setup Data pin */
|
||
|
|
||
|
// Start interrupt handler
|
||
|
attachInterrupt( digitalPinToInterrupt( irq_pin ), ps2interrupt, FALLING );
|
||
|
}
|