Skip to content

Instantly share code, notes, and snippets.

@houmei
Created July 3, 2012 13:17
Show Gist options
  • Save houmei/3039650 to your computer and use it in GitHub Desktop.
Save houmei/3039650 to your computer and use it in GitHub Desktop.
PS/2Keyboard for Arduino Uno/Leonardo 20120703 test
/*
http://arduino.cc/playground/Main/PS2Keyboard
http://arduino.cc/playground/Main/PS2KeyboardExt2
http://msdn.microsoft.com/en-us/library/windows/hardware/gg463446.aspx
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "Arduino.h"
#include "PS2kybd.h"
#include "binary.h"
// Scancode FIFO
#define BUFFER_SIZE 64
static volatile uint8_t Buffer[BUFFER_SIZE];
static volatile uint8_t head, tail;
int ps2Keyboard_DataPin;
volatile uint8_t ps2Keyboard_CurrentBuffer;
volatile uint8_t ps2Keyboard_BufferPos;
// variables used to remember information about key presses
volatile bool ps2Keyboard_shift; // indicates shift key is pressed
volatile bool ps2Keyboard_ctrl; // indicates the ctrl key is pressed
volatile bool ps2Keyboard_alt; // indicates the alt key is pressed
volatile bool ps2Keyboard_extend; // remembers a keyboard extended char received
volatile bool ps2Keyboard_release; // distinguishes key presses from releases
volatile bool ps2Keyboard_caps_lock; // remembers shift lock has been pressed
// vairables used in sending command bytes to the keyboard, eg caps_lock light
volatile bool cmd_in_progress;
volatile int cmd_count;
uint8_t cmd_value;
volatile uint8_t cmd_ack_value;
uint8_t cmd_parity;
volatile bool cmd_ack_byte_ok;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// subroutines
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// odd parity
uint8_t odd_parity(uint8_t val) {
val ^= val>>4; val ^= val>>2; val ^= val>>1;
return (~val) & 1;
}
void v_modifier_initialize() {
ps2Keyboard_BufferPos = 0;
ps2Keyboard_shift = false;
ps2Keyboard_ctrl = false;
ps2Keyboard_alt = false;
ps2Keyboard_extend = false;
ps2Keyboard_release = false;
ps2Keyboard_caps_lock = false;
}
void v_initialize() {
v_modifier_initialize();
// reset all the global variables
ps2Keyboard_CurrentBuffer = 0;
cmd_in_progress = false;
cmd_count = 0;
cmd_value = 0;
cmd_ack_value = 1;
}
static inline uint8_t get_scan_code(void) {
uint8_t c, i;
i = tail;
if (i == head) return 0;
i++;
if (i >= BUFFER_SIZE) i = 0;
c = Buffer[i];
tail = i;
return c;
}
// The ISR for the external interrupt
void ps2interrupt(void)
{
static uint8_t bitcount=0;
static uint8_t incoming=0;
static uint32_t prev_ms=0;
uint32_t now_ms;
uint8_t n, val;
val = digitalRead(ps2Keyboard_DataPin);
now_ms = millis();
if (now_ms - prev_ms > 250) {
bitcount = 0;
incoming = 0;
}
prev_ms = now_ms;
n = bitcount - 1;
if (n <= 7) {
incoming |= (val << n);
}
bitcount++;
if (bitcount == 11) {
uint8_t i = head + 1;
if (i >= BUFFER_SIZE) i = 0;
if (i != tail) {
Buffer[i] = incoming;
head = i;
}
bitcount = 0;
incoming = 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void kbd_send_command(uint8_t val) {
// stop interrupt routine from receiving characters so that we can use it
// to send a byte
cmd_in_progress = true;
cmd_count = 0;
// set up the byte to shift out and initialise the ack bit
cmd_value = val;
cmd_ack_value = 1; // the kbd will clear this bit on receiving the byte
cmd_parity = odd_parity(val);
// set the data pin as an output, ready for driving
digitalWrite(ps2Keyboard_DataPin, HIGH);
pinMode(ps2Keyboard_DataPin, OUTPUT);
// drive clock pin low - this is going to generate the first
// interrupt of the shifting out process
pinMode(PS2_INT_PIN, OUTPUT);
digitalWrite(PS2_INT_PIN, LOW);
// wait at least one clock cycle (in case the kbd is mid transmission)
delayMicroseconds(60);
// set up the 0 start bit
digitalWrite(ps2Keyboard_DataPin, LOW);
// let go of clock - the kbd takes over driving the clock from here
digitalWrite(PS2_INT_PIN, HIGH);
pinMode(PS2_INT_PIN, INPUT);
// wait for interrupt routine to shift out byte, parity and receive ack bit
while (cmd_ack_value!=0) ;
// switch back to the interrupt routine receiving characters from the kbd
cmd_in_progress = false;
}
// val : bit_3=KANA_lock, bit_2=caps_lock, bit_1=num_lock, bit_0=scroll_lock
void kbd_set_lights(uint8_t val) {
// When setting the lights with the 0xED command the keyboard responds
// with an "ack byte", 0xFA. This is NOT the same as the "ack bit" that
// follows the succesful shifting of each command byte. See this web
// page for a good description of all this:
// http://www.beyondlogic.org/keyboard/keybrd.htm
cmd_ack_byte_ok = false; // initialise the ack byte flag
kbd_send_command(0xED); // send the command byte
while (!cmd_ack_byte_ok) ; // ack byte from keyboard sets this flag
kbd_send_command(val); // now send the data
}
// The ISR for the external interrupt
// This may look like a lot of code for an Interrupt routine, but the switch
// statements are fast and the path through the routine is only ever a few
// simple lines of code.
void ps2interruptss (void) {
int value = digitalRead(ps2Keyboard_DataPin);
// This is the code to send a byte to the keyboard. Actually its 12 bits:
// a start bit, 8 data bits, 1 parity, 1 stop bit, 1 ack bit (from the kbd)
if (cmd_in_progress) {
cmd_count++; // cmd_count keeps track of the shifting
switch (cmd_count) {
case 1: // start bit
digitalWrite(ps2Keyboard_DataPin,LOW);
break;
case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9:
// data bits to shift
digitalWrite(ps2Keyboard_DataPin,cmd_value&1);
cmd_value = cmd_value>>1;
break;
case 10: // parity bit
digitalWrite(ps2Keyboard_DataPin,cmd_parity);
break;
case 11: // stop bit
// release the data pin, so stop bit actually relies on pull-up
// but this ensures the data pin is ready to be driven by the kbd for
// for the next bit.
digitalWrite(ps2Keyboard_DataPin, HIGH);
pinMode(ps2Keyboard_DataPin, INPUT);
break;
case 12: // ack bit - driven by the kbd, so we read its value
cmd_ack_value = digitalRead(ps2Keyboard_DataPin);
cmd_in_progress = false; // done shifting out
}
return; // don't fall through to the receive section of the ISR
}
// receive section of the ISR
// shift the bits in
// if(ps2Keyboard_BufferPos > 0 && ps2Keyboard_BufferPos < 11) {
if(ps2Keyboard_BufferPos < 11) {
ps2Keyboard_CurrentBuffer |= (value << (ps2Keyboard_BufferPos - 1));
}
ps2Keyboard_BufferPos++; // keep track of shift-in position
if(ps2Keyboard_BufferPos == 11) { // a complete character received
//
switch (ps2Keyboard_CurrentBuffer) {
case 0xF0: { // key release char
ps2Keyboard_release = true;
ps2Keyboard_extend = false;
break;
}
case 0xFA: { // command acknowlegde byte
cmd_ack_byte_ok = true;
break;
}
case 0xE0: { // extended char set
ps2Keyboard_extend = true;
break;
}
case 0x12: // left shift
case 0x59: { // right shift
ps2Keyboard_shift = ps2Keyboard_release? false : true;
ps2Keyboard_release = false;
break;
}
case 0x11: { // alt key (right alt is extended 0x11)
ps2Keyboard_alt = ps2Keyboard_release? false : true;
ps2Keyboard_release = false;
break;
}
case 0x14: { // ctrl key (right ctrl is extended 0x14)
ps2Keyboard_ctrl = ps2Keyboard_release? false : true;
ps2Keyboard_release = false;
break;
}
case 0x58: { // caps lock key
if (!ps2Keyboard_release) {
ps2Keyboard_caps_lock = ps2Keyboard_caps_lock? false : true;
// allow caps lock code through to enable light on and off
//setfifo(ps2Keyboard_CurrentBuffer) ;
}
else {
ps2Keyboard_release = false;
}
break;
}
default: { // a real key
if (ps2Keyboard_release) { // although ignore if its just released
ps2Keyboard_release = false;
}
else { // real keys go into CharBuffer
//setfifo(ps2Keyboard_CurrentBuffer) ;
}
}
}
//setfifo(ps2Keyboard_CurrentBuffer) ; ///
ps2Keyboard_CurrentBuffer = 0;
ps2Keyboard_BufferPos = 0;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// PS2Keyboard
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PS2Keyboard::PS2Keyboard() {
// nothing to do here
}
void PS2Keyboard::reset() {
kbd_send_command(0xFF); // send the kbd reset code to the kbd: 3 lights
// should flash briefly on the kbd
v_initialize();
}
void PS2Keyboard::begin(int dataPin) {
v_initialize();
// Prepare the global variables
ps2Keyboard_DataPin = dataPin;
// initialize the pins
pinMode(PS2_INT_PIN, INPUT);
digitalWrite(PS2_INT_PIN, HIGH);
pinMode(dataPin, INPUT);
digitalWrite(dataPin, HIGH);
attachInterrupt(PS2_INT_NUM, ps2interrupt, FALLING);
#if 0
// Global Enable INT1 interrupt
EIMSK |= ( 1 << INT1);
// Falling edge triggers interrupt
EICRA |= (0 << ISC10) | (1 << ISC11);
#endif
}
bool PS2Keyboard::available() {
if (tail == head) return 0;
return 1 ;
}
// This routine allows a calling program to see if other other keys are held
// down when a character is received: ie <ctrl>, <alt>, <shift> or <shift_lock>
// Note that this routine must be called after available() has returned true,
// but BEFORE read(). The read() routine clears the buffer and allows another
// character to be received so these bits can change anytime after the read().
uint8_t PS2Keyboard::read_extra() {
return (ps2Keyboard_caps_lock<<3) | (ps2Keyboard_shift<<2) | (ps2Keyboard_alt<<1) | ps2Keyboard_ctrl;
}
uint8_t PS2Keyboard::rawread() {
return get_scan_code() ;
}
uint8_t PS2Keyboard::read() {
uint8_t result;
// result = getfifo() ; // read the raw data from the keyboard
// Use a switch for the code to character conversion.
// This is fast and actually only uses 4 bytes per simple line
switch (result) {
case 0x1C: result = 'a'; break;
case 0x32: result = 'b'; break;
case 0x21: result = 'c'; break;
case 0x23: result = 'd'; break;
case 0x24: result = 'e'; break;
case 0x2B: result = 'f'; break;
case 0x34: result = 'g'; break;
case 0x33: result = 'h'; break;
case 0x43: result = 'i'; break;
case 0x3B: result = 'j'; break;
case 0x42: result = 'k'; break;
case 0x4B: result = 'l'; break;
case 0x3A: result = 'm'; break;
case 0x31: result = 'n'; break;
case 0x44: result = 'o'; break;
case 0x4D: result = 'p'; break;
case 0x15: result = 'q'; break;
case 0x2D: result = 'r'; break;
case 0x1B: result = 's'; break;
case 0x2C: result = 't'; break;
case 0x3C: result = 'u'; break;
case 0x2A: result = 'v'; break;
case 0x1D: result = 'w'; break;
case 0x22: result = 'x'; break;
case 0x35: result = 'y'; break;
case 0x1A: result = 'z'; break;
// note that caps lock only used on a-z
case 0x41: result = ps2Keyboard_shift? '<' : ','; break;
case 0x49: result = ps2Keyboard_shift? '>' : '.'; break;
case 0x4A: result = ps2Keyboard_shift? '?' : '/'; break;
case 0x54: result = ps2Keyboard_shift? '{' : '['; break;
case 0x5B: result = ps2Keyboard_shift? '}' : ']'; break;
case 0x4E: result = ps2Keyboard_shift? '_' : '-'; break;
case 0x55: result = ps2Keyboard_shift? '+' : '='; break;
case 0x29: result = ' '; break;
case 0x45: result = ps2Keyboard_shift? ')' : '0'; break;
case 0x16: result = ps2Keyboard_shift? '!' : '1'; break;
case 0x1E: result = ps2Keyboard_shift? '@' : '2'; break;
case 0x26: result = ps2Keyboard_shift? '£' : '3'; break;
case 0x25: result = ps2Keyboard_shift? '$' : '4'; break;
case 0x2E: result = ps2Keyboard_shift? '%' : '5'; break;
case 0x36: result = ps2Keyboard_shift? '^' : '6'; break;
case 0x3D: result = ps2Keyboard_shift? '&' : '7'; break;
case 0x3E: result = ps2Keyboard_shift? '*' : '8'; break;
case 0x46: result = ps2Keyboard_shift? '(' : '9'; break;
case 0x0D: result = '\t'; break;
case 0x5A: result = '\n'; break;
case 0x66: result = PS2_KC_BKSP; break;
case 0x69: result = ps2Keyboard_extend? PS2_KC_END : '1'; break;
case 0x6B: result = ps2Keyboard_extend? PS2_KC_LEFT : '4'; break;
case 0x6C: result = ps2Keyboard_extend? PS2_KC_HOME : '7'; break;
case 0x70: result = ps2Keyboard_extend? PS2_KC_INS : '0'; break;
case 0x71: result = ps2Keyboard_extend? PS2_KC_DEL : '.'; break;
case 0x72: result = ps2Keyboard_extend? PS2_KC_DOWN : '2'; break;
case 0x73: result = '5'; break;
case 0x74: result = ps2Keyboard_extend? PS2_KC_RIGHT : '6'; break;
case 0x75: result = ps2Keyboard_extend? PS2_KC_UP : '8'; break;
case 0x76: result = PS2_KC_ESC; break;
case 0x79: result = '+'; break;
case 0x7A: result = ps2Keyboard_extend? PS2_KC_PGDN : '3'; break;
case 0x7B: result = '-'; break;
case 0x7C: result = '*'; break;
case 0x7D: result = ps2Keyboard_extend? PS2_KC_PGUP : '9'; break;
case 0x58:
// setting the keyboard lights is done here. Ideally it would be done
// in the interrupt routine itself and the key codes associated wth
// caps lock key presses would never be passed on as characters.
// However it would make the interrupt routine very messy with lots
// of extra state associated with the control of a caps_lock
// key code causing a cmd byte to transmit, causing an ack_byte to
// be received, then a data byte to transmit. Much easier done here.
// The downside, however, is that the light going on or off at the
// right time relies on the calling program to be checking for
// characters on a regular basis. If the calling program stops
// polling for characters at any point pressing the caps lock key
// will not change the state of the caps lock light while polling
// is not happening.
result = ps2Keyboard_caps_lock? PS2_KC_CLON : PS2_KC_CLOFF;
if (ps2Keyboard_caps_lock) kbd_set_lights(4);
else kbd_set_lights(0);
break;
// Reset the shift counter for unexpected values, to get back into sink
// This allows for hot plugging a keyboard in and out
default: delay(500); // but wait a bit in case part way through a shift
v_modifier_initialize();
} // end switch(result)
// shift a-z chars here (less code than in the switch statement)
if (((result>='a') && (result<='z')) &&
((ps2Keyboard_shift && !ps2Keyboard_caps_lock) ||
(!ps2Keyboard_shift && ps2Keyboard_caps_lock))) {
result = result + ('A'-'a');
}
return(result);
}
/*
PS2Keyboard.h - PS2Keyboard library
Copyright (c) 2007 Free Software Foundation. All right reserved.
Written by Christian Weichel <info@32leaves.net>
** Modified for use with Arduino 13 by L. Abraham Smith, <n3bah@microcompdesign.com> *
** Modified to include: shift, alt, caps_lock and caps_lock light by Bill Oldfield *
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
*/
#ifndef PS2Keyboard_h
#define PS2Keyboard_h
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
/*
* PS2 keyboard "make" codes to check for certain keys.
*/
// Give these codes that aren't used by anything else
// Making all the control key codes above 0x80 makes it simple to check for
// printable characters at the calling level.
#define PS2_KC_BKSP 0x80
#define PS2_KC_UP 0x81
#define PS2_KC_DOWN 0x82
#define PS2_KC_LEFT 0x83
#define PS2_KC_RIGHT 0x84
#define PS2_KC_PGDN 0x85
#define PS2_KC_PGUP 0x86
#define PS2_KC_END 0x87
#define PS2_KC_HOME 0x88
#define PS2_KC_INS 0x89
#define PS2_KC_DEL 0x8A
#define PS2_KC_ESC 0x8B
#define PS2_KC_CLON 0x8C // caps_lock on
#define PS2_KC_CLOFF 0x8D // caps_lock off
#include "binary.h"
typedef uint8_t boolean;
/*
* This PIN is hardcoded in the init routine later on. If you change this
* make sure you change the interrupt initialization as well.
PS2_INT_PIN 2 / attachInterrupt(0, ps2interrupt, FALLING);
PS2_INT_PIN 3 / attachInterrupt(1, ps2interrupt, FALLING);
*/
#define PS2_INT_PIN 2
#define PS2_INT_NUM 0
/**
* Purpose: Provides an easy access to PS2 keyboards
* Author: Christian Weichel
*/
class PS2Keyboard {
private:
int m_dataPin;
uint8_t m_charBuffer;
public:
/**
* This constructor does basically nothing. Please call the begin(int)
* method before using any other method of this class.
*/
PS2Keyboard();
/**
* Starts the keyboard "service" by registering the external interrupt.
* setting the pin modes correctly and driving those needed to high.
* The propably best place to call this method is in the setup routine.
*/
void begin(int dataPin);
/**
* Returns true if there is a char to be read, false if not.
*/
bool available();
/**
* Sends a reset command to the keyboard and re-initialises all the control
* variables within the PS2Keybaord code.
*/
void reset();
/**
* Returns the char last read from the keyboard. If the user has pressed two
* keys between calls to this method, only the later one will be availble. Once
* the char has been read, the buffer will be cleared.
* If there is no char availble, 0 is returned.
*/
uint8_t read();
uint8_t rawread();
/**
* Returns the status of the <ctrl> key, the <alt> key, the <shift> key and the
* caps_lock state. Note that shift and caps_lock are handled within the
* Ps2Keyboard code (and the return value from read() is already modified), but
* being able to read them here may be useful.
* This routine is optional BUT MUST ONLY be read after available() has returned
* true and BEFORE read() is called to retrieve the character. Reading it after
* the call to read() will return unpredictable values.
*/
uint8_t read_extra();
};
#endif
// 20120703 Arduino IDE1.0.1
// Arduino UNO R3 INT_NUM=0,PIN=2 / INT_NUM=1,PIN=3
// Arduino Leonardo INT_NUM=0,PIN=3 / INT_NUM=1,PIN=2 (wrong)
#define INT_NUM 1
#define INT_PIN 3
int led = 13;
volatile int state = LOW;
void setup()
{
pinMode(led, OUTPUT);
pinMode(INT_PIN, INPUT_PULLUP);
attachInterrupt(INT_NUM, blink, CHANGE);
}
void loop()
{
digitalWrite(led, state);
}
void blink()
{
state = !state;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment