This page covers talking to a Windows PC via the PS/2 port.
Sometimes its useful to be able to emulate a keyboard or a mouse -
that's what this code does. For example, it might be better to
have a device with a few buttons on it that provide the same functions
as a combination of keystrokes, rather than having a dedicated
keyboard.
The PS/2 port is used for Keyboards and Mice on Windows based PCs.
PCs still have a PS/2 port, although most Keyboards and Mice are
USB based now. A
device designed to work on the PS/2 port will also work
on USB via a PS/2-USB adapter. I have also tested this on a
Macintosh, using a PS/2 to USB adapter, and it works just fine.
I used information from this website here to figure out how to work with the PS/2 standard.
Some of the information from that website is included in the code comments.
These routines are written in Keil Micro C for the Silicon Labs C8051F005 MCU.
I've provided functions to send and receive data and commands on the PS/2 bus.
They work just fine in the application I'm using them for, although
error recovery is not fully implemented (I'm too confident ;) ).
You just need to fill in the blanks for your needs.
//Include the 8051 register declarations
#include <c8051f000.h>
#define
TRUE
1
#define
FALSE
0
#define
NORMAL
0
#define EXTENDED 1
#define BAT_SUCCESSFUL 0xAA
#define PS2_ERROR 0xFE
#define
PS2_ACK
0xFA
sbit NUM_LOCK_LED = P1^7;
sbit CAPS_LOCK_LED = P1^6;
sbit SCROLL_LOCK_LED = P1^5;
sbit ACTIVITY_LED = P1^4;
sbit PS2_DATA_OUT = P1^0;
sbit PS2_DATA_IN = P1^0;
sbit PS2_CLOCK = P1^1;
sbit TEST_SWITCH = P2^0;
//Prototypes
void ps2_send_data(unsigned char);
unsigned char ps2_receive_data(void);
void process_ps2_command(unsigned char);
void send_scan_code(unsigned char, unsigned char);
//Global variables
bit parity_error = FALSE;
unsigned char ps2_command;
void main(void)
{
unsigned char state = 0;
P1 = 0;
// do self test
NUM_LOCK_LED = TRUE;
CAPS_LOCK_LED = TRUE;
SCROLL_LOCK_LED = TRUE;
ACTIVITY_LED = TRUE;
delay_ms(500);
ps2_send_data(BAT_SUCCESSFUL);
while (TRUE)
{
// Poll the PS2 lines to see if the PC wants to send data.
// poll clock and data lines to
see if the host PC has performed the following 3 steps indicating it
wants to send a command
//1) Bring the Clock line low for at least 100 microseconds.
//2) Bring the Data line low.
//3) Release the Clock line.
if((PS2_CLOCK == 0) && (PS2_DATA_IN == 1)) state = 1;
if(((PS2_CLOCK == 0) && (PS2_DATA_IN == 0)) && state == 1) state = 2;
if(((PS2_CLOCK == 1) && (PS2_DATA_IN == 0)) && state == 2) state = 3;
if(state == 3) //PC has sent the start bit
{
delay_us(48);
ps2_command = ps2_receive_data();
process_ps2_command(ps2_command);
state = 0;
}
//test code to see if protocol works: send CTRL-ESC to activate Windows Start menu
//this is the same as pressing the Windows key on keyboards equiped with this button
//it corresponds to the Command Key on a Mac - eg Command-Q to quit a program
if (TEST_SWITCH == FALSE) //button on PORT2.0 pressed?
{
ps2_send_data(0x14); //CTRL down
delay_ms(10);
ps2_send_data(0x76); //ESC down
delay_ms(10);
ps2_send_data(0xF0);
//ESC up
ps2_send_data(0x76); //ESC up
ps2_send_data(0xF0); //CTRL up
ps2_send_data(0x14); //CTRL up
}
//your code here to "scan" keys and send the correct scan code out the PS/2 port
};
}
/************************************ PS/2 ROUTINES **********************************************/
//send scan codes out the PS2 port
void ps2_send_data(unsigned char scan_code)
{
/*
When the keyboard or mouse wants to send
information, it first checks the Clock line to make sure it's at a high
logic level. If it's not, the host is
inhibiting communication and the device must buffer
any to-be-sent data until the host releases Clock. The Clock line
must be continuously high for at least
50 microseconds before the device can begin to transmit its data.
The keyboard/mouse writes a bit on the Data line
when Clock is high, and it is read by the host when Clock is low.
* All data is transmitted one byte at a time and
each byte is sent in a frame consisting of 11 bits. These bits
are:
* 1 start bit. This is always 0
* 8 data bits, LSB first
* 1 parity bit (odd parity)
* 1 stop bit. This is always 1
*/
//PORT1.0 is the data out pin (PS2_DATA)
//PORT1.1 is the clock pin (PS2_CLOCK)
unsigned char k = 0;
unsigned char parity_count = 0;
unsigned char bit_mask = 0x01;
while ((PS2_CLOCK == 0) && (PS2_DATA_IN == 1)); //if bus in in an idle state, wait until freed
if((P1 & 0x03) && (scan_code != '\0')) //Clock and Data lines high?
{
last_byte = scan_code;
//set ports as outputs
P1 |= 0x03;
PS2_CLOCK = TRUE; //Set up data line while clock is high
delay_us(5); //wait at least 5uS after clock rises
//1 start bit. This is always 0
PS2_DATA_OUT = FALSE;
//toggle the clock pin
delay_us(48);
PS2_CLOCK = FALSE; //Host reads data line
delay_us(48);
PS2_CLOCK = TRUE; //Set up data line while clock is high
delay_us(5);
//8 data bits, LSB first
for (k = 0; k < 8; k++)
{
//determine whether to put a 0 or 1 on to the DATA pin (PB6)
if ((scan_code & bit_mask) > 0)
{
PS2_DATA_OUT = TRUE;
parity_count++;
}
//toggle the clock pin
delay_us(48);
PS2_CLOCK = FALSE; //Host reads data line
delay_us(48);
PS2_CLOCK = TRUE; //Set up data line while clock is high
delay_us(5);
PS2_DATA_OUT = FALSE;
//Shift
bit_mask right 1 bit
0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80
bit_mask = bit_mask << 1;
}
//1 parity bit (odd parity)
if ((parity_count ==
0) || (parity_count == 2) || (parity_count == 4) || (parity_count ==
6) || (parity_count == 8)) PS2_DATA_OUT = TRUE;
else PS2_DATA_OUT = FALSE;
//toggle the clock pin
delay_us(48);
PS2_CLOCK = FALSE; //Host reads data line
delay_us(48);
PS2_CLOCK = TRUE; //Set up data line while clock is high
delay_us(5);
//1 stop bit. This is always 1
PS2_DATA_OUT = TRUE;
//toggle the clock pin
delay_us(48);
PS2_CLOCK = FALSE; //Host reads data line
delay_us(48);
PS2_CLOCK = TRUE; //Set up data line while clock is high
delay_us(5);
}
P1 |= 0x03;
delay_us(100);
}
//receive commands from the host
unsigned char ps2_receive_data(void)
{
unsigned char parity = 0;
unsigned char m = 1;
unsigned char n = 1;
unsigned char parity_count = 0;
unsigned char received_data = 0;
unsigned char temp_data = 0;
PS2_DATA_IN = TRUE;;
//P1.0 input for data, P1.1 output for clocking
delay_us(48);
//8 data bits, LSB first
//bit 0
//toggle the clock pin
PS2_CLOCK = FALSE; //Host sets up data line while clock is low
delay_us(48);
PS2_CLOCK = TRUE; //Read data line while clock is high
//determine whether to put a 0 or 1 into received_data
temp_data = PS2_DATA_IN;
if (temp_data == 1) parity_count++;
received_data += temp_data;
temp_data = 0;
delay_us(48);
//bit 1 to 7
for (m = 1; m < 8; m++)
{
PS2_CLOCK = FALSE; //Host sets up data line while clock is low
delay_us(48);
PS2_CLOCK = TRUE; //Read data line while clock is high
//determine whether to put a 0 or 1 into received_data
temp_data = PS2_DATA_IN;
if (temp_data == 1) parity_count++;
temp_data = temp_data << n;
n++;
received_data += temp_data;
temp_data = 0;
delay_us(48);
}
//get
the
parity bit (odd
parity)
//toggle the clock pin
PS2_CLOCK = FALSE; //Host sets up data line while clock is low
delay_us(48);
PS2_CLOCK = TRUE; //Read data line while clock is high
//determine whether to put a 0 or 1 in to received_data
parity = PS2_DATA_IN;
//receive 1 stop bit. This is always 1
//toggle the clock pin
delay_us(48);
PS2_CLOCK = FALSE; //Host sets up data line while clock is low
delay_us(48);
PS2_CLOCK = TRUE; //Read data line while clock is high
/*
After the stop bit is received, the device will
acknowledge the received byte by bringing the Data line low and
generating one last clock pulse.
If the host does not release the Data line after the
11th clock pulse, the device will continue to generate clock pulses
until the the Data line is released
(the device will then generate an error.)
*/
delay_us(24);
PS2_DATA_OUT = FALSE;
delay_us(24);
PS2_CLOCK = FALSE;
delay_us(48);
PS2_DATA_OUT = TRUE;
delay_us(48);
PS2_CLOCK = TRUE;
delay_us(48);
switch(parity)
{
case 0:
if
((parity_count == 0) || (parity_count == 2) || (parity_count == 4) || (parity_count == 6) || (parity_count == 8)) parity_error = FALSE;
break;
case 1:
if
((parity_count == 1) || (parity_count == 3) || (parity_count == 5) || (parity_count == 7))
{
parity_error = TRUE;
ps2_send_data(PS2_ERROR);
}
break;
default:
parity_error = FALSE;
}
return received_data;
}
void send_scan_code(unsigned char _code, unsigned char mode)
{
switch(mode)
{
case NORMAL:
//send make scan code
ps2_send_data(_code);
break;
case SHIFT:
//send make scan code
ps2_send_data(0x12);
ps2_send_data(_code);
//then send break scan code
ps2_send_data(0xF0);
ps2_send_data(0x12);
break;
case CTRL:
break;
case EXTENDED:
//send make scan code
ps2_send_data(0xE0);
ps2_send_data(_code);
//then send break scan code
ps2_send_data(0xE0);
break;
ps2_send_data(0xF0);
ps2_send_data(_code);
}
}
void process_ps2_command(unsigned char command)
{
bit SetScanCode = FALSE;
bit SetLEDs = FALSE;
switch(command)
{
case(0xFF): //*0xFF (Reset) - Keyboard
responds with "ack" (0xFA), then enters "Reset" mode.
ps2_send_data(PS2_ACK);
//go into reset mode...
//do "self test" and tell host all ok
NUM_LOCK_LED = TRUE;
CAPS_LOCK_LED = TRUE;
SCROLL_LOCK_LED = TRUE;
ps2_send_data(BAT_SUCCESSFUL);
break;
case(0xFE):
//*0xFE (Resend) - Keyboard
responds by resending the last-sent byte. The exception to this
is if the last-sent byte was "resend" (0xFE).
//If
this is the case, the keyboard resends the last non-0xFE
byte. This command is used by the host to indicate an error in
reception.
ps2_send_data(PS2_ACK);
ps2_send_data(last_byte);
break;
//case(0xF7): //*0xF7 (Set All Keys
Typematic) - Keyboard responds with "ack" (0xFA).
// ps2_send_data(PS2_ACK);
//break;
//case(0xF6): // *0xF6 (Set Default) -
Load default typematic rate/delay (10.9cps / 500ms), key types (all
keys typematic/make/break), and scan code set (2).
//
ps2_send_data(PS2_ACK);
//break;
//case(0xF5): //*0xF5 (Disable) -
Keyboard stops scanning, loads default values (see "Set Default"
command), and waits further instructions.
//
ps2_send_data(PS2_ACK);
//break;
//case(0xF4): //*0xF4 (Enable) -
Re-enables keyboard after disabled using previous command.
//
ps2_send_data(PS2_ACK);
//break;
case(0xF2):
//*0xF2 (Read ID) - The
keyboard responds by sending a two-byte device ID of 0xAB, 0x83. (0xAB
is sent first, followed by 0x83.)
ps2_send_data(PS2_ACK);
ps2_send_data(0xAB);
ps2_send_data(0x83);
break;
case(0xF0):
//*0xF0 (Set Scan Code)
- Keyboard responds with "ack", then reads argument byte from the
host.
//This
argument byte may be 0x01, 0x02, or 0x03 to select scan code set 1, 2,
or 3, respectively.
//The keyboard responds to this argument byte with "ack".
//If the
argument byte is 0x00, the keyboard responds with "ack" followed by the
current scan code set.
SetScanCode = TRUE;
ps2_send_data(PS2_ACK);
break;
case(0xEE):
//*0xEE (Echo) - The keyboard
responds with "Echo" (0xEE).
ps2_send_data(0xEE);
break;
case(0xED):
//*0xED (Set/Reset LEDs) -
The host follows this command with one argument byte, that specifies
the state of the keyboard's
//Num Lock,
Caps Lock, and Scroll Lock LEDs. This argument byte is defined as
follows: 0,0,0,0,0,0,caps,num,scroll
SetLEDs = TRUE;
ps2_send_data(PS2_ACK);
break;
case(0x00):
if (SetScanCode == TRUE)
{
ps2_send_data(PS2_ACK);
ps2_send_data(0x02);
SetScanCode = FALSE;
}
else
{
NUM_LOCK_LED = FALSE;
CAPS_LOCK_LED = FALSE;
SCROLL_LOCK_LED = FALSE;
SetLEDs = FALSE;
ps2_send_data(PS2_ACK);
}
break;
case(0x01):
if(SetLEDs == TRUE)
{
NUM_LOCK_LED = FALSE;
CAPS_LOCK_LED = FALSE;
SCROLL_LOCK_LED = TRUE;
SetLEDs = FALSE;
}
ps2_send_data(PS2_ACK);
break;
case(0x02):
if (SetScanCode == FALSE) NUM_LOCK_LED = TRUE;
ps2_send_data(PS2_ACK);
break;
case(0x03):
if(SetLEDs == TRUE)
{
NUM_LOCK_LED = TRUE;
CAPS_LOCK_LED = FALSE;
SCROLL_LOCK_LED = TRUE;
SetLEDs = FALSE;
}
ps2_send_data(PS2_ACK);
break;
case(0x04):
if(SetLEDs == TRUE)
{
NUM_LOCK_LED = FALSE;
CAPS_LOCK_LED = TRUE;
SCROLL_LOCK_LED = FALSE;
SetLEDs = FALSE;
}
ps2_send_data(PS2_ACK);
break;
case(0x05):
if(SetLEDs == TRUE)
{
NUM_LOCK_LED = FALSE;
CAPS_LOCK_LED = TRUE;
SCROLL_LOCK_LED = TRUE;
SetLEDs = FALSE;
}
ps2_send_data(PS2_ACK);
break;
case(0x06):
if(SetLEDs == TRUE)
{
NUM_LOCK_LED = TRUE;
CAPS_LOCK_LED = TRUE;
SCROLL_LOCK_LED = FALSE;
SetLEDs = FALSE;
}
ps2_send_data(PS2_ACK);
break;
case(0x07):
if(SetLEDs == TRUE)
{
NUM_LOCK_LED = TRUE;
CAPS_LOCK_LED = TRUE;
SCROLL_LOCK_LED = TRUE;
}
ps2_send_data(PS2_ACK);
break;
default:
ps2_send_data(PS2_ACK);
}
}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ end of PS/2
routines
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^