Get Adobe Flash player
FacebookTwitterGoogle+
English Arabic French German Italian Portuguese Russian Spanish

Did you know?

Recently graduated engineers have higher average starting salaries than their counterparts with other degrees. These salaries vary by branch of engineering and level of education.
 

Help us stay online:

small donate

PS2 data and clock signalsAdding PC keyboard to your hardware design and connecting microcontroller to it is both useful and COOL!
In this project we will explain how this is done and provide complete source code and step by step tutorial how to do this with AVR microcontroller.

 

 


This project uses interrupts to interface a PS/2 keyboard with AVR ATmega8535, but code can be very easily adjusted for any microcontroller. The simplicity of the protocol and the asynchronous nature of the clock generated by the keyboard make this a great simple start for getting familiar with interrupts and interrupt handlers. If you have no experience with external interrupts, see AVR external interrupts - INT0 example.
First to say few words on how PS/2 port communicates.
All signals are generated by keyboard, AVR only reads pin state at pins defined as inputs.
Here is PS/2 connector pinout:


PS2MaleConnector pinoutps2 connector

  • Pin 1 Data

Pin 2 Not Connected

Pin 3 GND

Pin 4 Vcc (+5V)

Pin 5 Clock

Pin 6 Not Connected

 

The easiest way to connect it is to find PS/2 female connector or take one from old broken PC motherboard.
PS2 female and male conectors
You need to use four pins and we connected four wires to these pins: Vcc (red), GND (black), Data (white), Clock (yellow).

Connection for PS2 tastature female with AVR

These signals are very fast so simple pooling pin state would be too slow and we could lose some data. Because of this, we use external interrupt INT0 for clock falling edge detection and readout of key data.

PS2 data and clock signals

On picture above we can see clock frequency is around 10 kHz. Yellow line is clock and blue one is data.

Each data packet consists of 11 bits. These are 1 start bit, 8 data bits, parity and stop bit. This is illustrated on picture below.
waveform3
When we capture byte from keyboard, we have Keyboard Scan Code. These are codes that give us information which key is pressed or released. Table of all Keyboard Scan Codes is given below:

 

KEY

MAKE

BREAK

-----

KEY

MAKE

BREAK

-----

KEY

MAKE

BREAK

A

1C

F0,1C

 

9

46

F0,46

 

[

54

FO,54

B

32

F0,32

 

`

0E

F0,0E

 

INSERT

E0,70

E0,F0,70

C

21

F0,21

 

-

4E

F0,4E

 

HOME

E0,6C

E0,F0,6C

D

23

F0,23

 

=

55

FO,55

 

PG UP

E0,7D

E0,F0,7D

E

24

F0,24

 

\

5D

F0,5D

 

DELETE

E0,71

E0,F0,71

F

2B

F0,2B

 

BKSP

66

F0,66

 

END

E0,69

E0,F0,69

G

34

F0,34

 

SPACE

29

F0,29

 

PG DN

E0,7A

E0,F0,7A

H

33

F0,33

 

TAB

0D

F0,0D

 

U ARROW

E0,75

E0,F0,75

I

43

F0,43

 

CAPS

58

F0,58

 

L ARROW

E0,6B

E0,F0,6B

J

3B

F0,3B

 

L SHFT

12

FO,12

 

D ARROW

E0,72

E0,F0,72

K

42

F0,42

 

L CTRL

14

FO,14

 

R ARROW

E0,74

E0,F0,74

L

4B

F0,4B

 

L GUI

E0,1F

E0,F0,1F

 

NUM

77

F0,77

M

3A

F0,3A

 

L ALT

11

F0,11

 

KP /

E0,4A

E0,F0,4A

N

31

F0,31

 

R SHFT

59

F0,59

 

KP *

7C

F0,7C

O

44

F0,44

 

R CTRL

E0,14

E0,F0,14

 

KP -

7B

F0,7B

P

4D

F0,4D

 

R GUI

E0,27

E0,F0,27

 

KP +

79

F0,79

Q

15

F0,15

 

R ALT

E0,11

E0,F0,11

 

KP EN

E0,5A

E0,F0,5A

R

2D

F0,2D

 

APPS

E0,2F

E0,F0,2F

 

KP .

71

F0,71

S

1B

F0,1B

 

ENTER

5A

F0,5A

 

KP 0

70

F0,70

T

2C

F0,2C

 

ESC

76

F0,76

 

KP 1

69

F0,69

U

3C

F0,3C

 

F1

05

F0,05

 

KP 2

72

F0,72

V

2A

F0,2A

 

F2

06

F0,06

 

KP 3

7A

F0,7A

W

1D

F0,1D

 

F3

04

F0,04

 

KP 4

6B

F0,6B

X

22

F0,22

 

F4

0C

F0,0C

 

KP 5

73

F0,73

Y

35

F0,35

 

F5

03

F0,03

 

KP 6

74

F0,74

Z

1A

F0,1A

 

F6

0B

F0,0B

 

KP 7

6C

F0,6C

0

45

F0,45

 

F7

83

F0,83

 

KP 8

75

F0,75

1

16

F0,16

 

F8

0A

F0,0A

 

KP 9

7D

F0,7D

2

1E

F0,1E

 

F9

01

F0,01

 

]

5B

F0,5B

3

26

F0,26

 

F10

09

F0,09

 

;

4C

F0,4C

4

25

F0,25

 

F11

78

F0,78

 

'

52

F0,52

5

2E

F0,2E

 

F12

07

F0,07

 

,

41

F0,41

6

36

F0,36

 

PRNT
SCRN

E0,12,
E0,7C 

E0,F0,
7C,E0,
F0,12 

 

.

49

F0,49

7

3D

F0,3D

 

SCROLL

7E

F0,7E

 

/

4A

F0,4A

8

3E

F0,3E

 

PAUSE

E1,14,77,
E1,F0,14,
F0,77

-NONE-

 

 

 

 

 

 

And you can download this table along with some extra codes for special function buttons at this link.
In this example we implemented conversion of Keyboard Scan Codes to their ASCII codes. As you can see, any key sends its code when it is pressed or byte 0xf0 followed by taster code when it is released.We will use this to detect whether shift taster is pressed or not. Since scan codes for ‘a’ and ‘A’ are the same we have to detect when shift is pressed.
External interrupt on falling edge of clock signal is used. Usage of external interrupts on AVR is explained more at AVR external interrupts - INT0 example.Reception of data from keyboard is done in INT0 ISR.
Here is code that does this:

interrupt [EXT_INT0] void ext_int0_isr(void)
{
// Place your code here
   // function entered at falling edge of the kbd clock signal
   // if data bit is the next bit to be read
   // (bit 3 to 10 is data, start, stop & parity bis are ignored
   if((bitcount < 11) && (bitcount > 2)) {
      data = (data >> 1);      
      if (KBD_DATA==1)         // if next bit is 1   
         data = data | 0x80;   // store a '1'
      else
         data = data & 0x7f; // else store a '0'  
   }      
   if(--bitcount == 0) {      // all bits received ?      
           decode(data);       // decode received byte
         bitcount = 11;      // reset bit counter    
    }
        
}

It is very simple code that receives bit by bit and adds it to appropriate place in previously declared data variable. Also, start, parity and stop bits are ignored so only scan code will be placed in data variable.When received, scan code needs to be converted into ASCII value.To be able to do this we defined constant strings that contains all ASCII codes and its keyboard scan codes. Here are those strings:

// keyboard scan codes (without & with shift key pressed)
flash unsigned char unshifted[67][2] = {
//0x0d,9,
0x0e,'`',0x15,'q',0x16,'1',0x1a,'z',0x1b,'s',0x1c,'a',0x1d,'w',0x1e,'2',0x21,'c',0x22,'x',0x23,'d',0x24,'e',
0x25,'4',0x26,'3',0x29,' ',0x2a,'v',0x2b,'f',0x2c,'t',0x2d,'r',0x2e,'5',0x31,'n',0x32,'b',0x33,'h',0x34,'g',
0x35,'y',0x36,'6',0x39,',',0x3a,'m',0x3b,'j',0x3c,'u',0x3d,'7',0x3e,'8',0x41,',',0x42,'k',0x43,'i',0x44,'o',
0x45,'0',0x46,'9',0x49,'.',0x4a,'/',0x4b,'l',0x4c,';',0x4d,'p',0x4e,'-',0x52,'`',0x54,'[',0x55,'=',0x5a,13,
0x5b,']',0x5d,'/',0x61,'<',0x66,8,  0x69,'1',0x6b,'4',0x6c,'7',0x70,'0',0x71,',',0x72,'2',0x73,'5',0x74,'6',
0x75,'8',0x79,'+',0x7a,'3',0x7b,'-',0x7c,'*',0x7d,'9',0,0 };
flash unsigned char shifted[67][2] = {
//0x0d,9,
0x0e,'`',0x15,'Q',0x16,'!',0x1a,'Z',0x1b,'S',0x1c,'A',0x1d,'W',0x1e,'@',0x21,'C',0x22,'X',0x23,'D',0x24,'E',
0x25,'$',0x26,'#',0x29,' ',0x2a,'V',0x2b,'F',0x2c,'T',0x2d,'R',0x2e,'%',0x31,'N',0x32,'B',0x33,'H',0x34,'G',
0x35,'Y',0x36,'^',0x39,'L',0x3a,'M',0x3b,'J',0x3c,'U',0x3d,'&',0x3e,'*',0x41,'<',0x42,'K',0x43,'I',0x44,'O',
0x45,')',0x46,'(',0x49,'>',0x4a,'?',0x4b,'L',0x4c,':',0x4d,'P',0x4e,'_',0x52,'"',0x54,'{',0x55,'+',0x5a,13,
0x5b,'}',0x5d,'|',0x61,'>',0x66,8,  0x69,'1',0x6b,'4',0x6c,'7',0x70,'0',0x71,',',0x72,'2',0x73,'5',0x74,'6',
0x75,'8',0x79,'+',0x7a,'3',0x7b,'-',0x7c,'*',0x7d,'9',0,0 };

Conversion of keyboard scan code to ASCII code is done with function decode(data).
Here is its source code:

//***********************************************
// decode scan code
void decode(unsigned char sc) {
    static unsigned char is_up=0, shift = 0, mode = 0;
   unsigned char i;
 
   if (!is_up) {
      switch (sc) {
         case 0xF0 :// The up-key identifier
            is_up = 1;
            break;
         case 0x12 :// Left SHIFT
            shift = 1;
            break;
         case 0x59 :// Right SHIFT
            shift = 1;
            break;
         case 0x05 :// F1
            if(mode == 0)
               mode = 1;// Enter scan code mode
            if(mode == 2)
               mode = 3;// Leave scan code mode
            break;
         default:
            if(mode == 0 || mode == 3) {// If ASCII mode            
               if(!shift) {// If shift not pressed, do a table look-up
                  for(i = 0; unshifted[i][0]!=sc && unshifted[i][0]; i++);
                     if (unshifted[i][0] == sc) {
                        putchar(unshifted[i][1]);                     
                     }                                       
               }
               else {// If shift pressed
                  for(i = 0; shifted[i][0]!=sc && shifted[i][0]; i++);
                     if (shifted[i][0] == sc) {
                        putchar(shifted[i][1]);
                     }
               }
            }
            else putchar(sc);   // scan code mode (debugging mode)                        
            break;
      }
   }
   else {
      is_up = 0;// Two 0xF0 in a row not allowed
      switch (sc) {
         case 0x12 :// Left SHIFT
            shift = 0;
            break;
         case 0x59 :// Right SHIFT
            shift = 0;
            break;
         case 0x05 :// F1 --  F1 puts you in debugging mode
               // pressing F1 again gets you out of debugging mode
               // in debugging mode hex code of the scan codes
               // are stored in the buffer instead of their ascii codes      
            if(mode == 1)
            mode = 2;
            if(mode == 3)
               mode = 0;
            break;      
      }
   }
}                                               
//***********************************************

It is simple state machine that keeps track if shift key is pressed or not and finds appropriate ASCII code for every received keyboard scan code received from PS/2 port.
By pressing F1 key you can choose to send scan codes directly to UART without conversion. This is done for you to understand better how this conversion is done.
Because of its simplicity whole code is done in interrupt, so while(1) loop is empty and can execute other tasks. Implementation in interrupt guarantees that no data will be lost.
Here is video showing how this example works in practice:

You can download complete project for connection of AVR with PS/2 tastature and source code from this link.

Below is provided source code with key lines for this example highlighted:

/*****************************************************
This program was produced by the
CodeWizardAVR V2.04.4a Advanced
Automatic Program Generator
© Copyright 1998-2009 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com
Project : 
Version : 
Date    : 12/26/2011
Author  : 
Company : 
Comments:
Chip type               : ATmega8535
Program type            : Application
AVR Core Clock frequency: 8.000000 MHz
Memory model            : Small
External RAM size       : 0
Data Stack size         : 128
*****************************************************/
#include <mega8535.h>
#include <delay.h>
// Standard Input/Output functions
#include <stdio.h>
#define KBD_DATA PIND.3
// External Interrupt 0 service routine
unsigned char data,bitcount=11;   // holds the received scan code
// keyboard scan codes (without & with shift key pressed)
flash unsigned char unshifted[67][2] = {
//0x0d,9,
0x0e,'`',0x15,'q',0x16,'1',0x1a,'z',0x1b,'s',0x1c,'a',0x1d,'w',0x1e,'2',0x21,'c',0x22,'x',0x23,'d',0x24,'e',
0x25,'4',0x26,'3',0x29,' ',0x2a,'v',0x2b,'f',0x2c,'t',0x2d,'r',0x2e,'5',0x31,'n',0x32,'b',0x33,'h',0x34,'g',
0x35,'y',0x36,'6',0x39,',',0x3a,'m',0x3b,'j',0x3c,'u',0x3d,'7',0x3e,'8',0x41,',',0x42,'k',0x43,'i',0x44,'o',
0x45,'0',0x46,'9',0x49,'.',0x4a,'/',0x4b,'l',0x4c,';',0x4d,'p',0x4e,'-',0x52,'`',0x54,'[',0x55,'=',0x5a,13,
0x5b,']',0x5d,'/',0x61,'<',0x66,8,  0x69,'1',0x6b,'4',0x6c,'7',0x70,'0',0x71,',',0x72,'2',0x73,'5',0x74,'6',
0x75,'8',0x79,'+',0x7a,'3',0x7b,'-',0x7c,'*',0x7d,'9',0,0 };
flash unsigned char shifted[67][2] = {
//0x0d,9,
0x0e,'`',0x15,'Q',0x16,'!',0x1a,'Z',0x1b,'S',0x1c,'A',0x1d,'W',0x1e,'@',0x21,'C',0x22,'X',0x23,'D',0x24,'E',
0x25,'$',0x26,'#',0x29,' ',0x2a,'V',0x2b,'F',0x2c,'T',0x2d,'R',0x2e,'%',0x31,'N',0x32,'B',0x33,'H',0x34,'G',
0x35,'Y',0x36,'^',0x39,'L',0x3a,'M',0x3b,'J',0x3c,'U',0x3d,'&',0x3e,'*',0x41,'<',0x42,'K',0x43,'I',0x44,'O',
0x45,')',0x46,'(',0x49,'>',0x4a,'?',0x4b,'L',0x4c,':',0x4d,'P',0x4e,'_',0x52,'"',0x54,'{',0x55,'+',0x5a,13,
0x5b,'}',0x5d,'|',0x61,'>',0x66,8,  0x69,'1',0x6b,'4',0x6c,'7',0x70,'0',0x71,',',0x72,'2',0x73,'5',0x74,'6',
0x75,'8',0x79,'+',0x7a,'3',0x7b,'-',0x7c,'*',0x7d,'9',0,0 };
                       
//***********************************************
// decode scan code
void decode(unsigned char sc) {
    static unsigned char is_up=0, shift = 0, mode = 0;
   unsigned char i;
 
   if (!is_up) {
      switch (sc) {
         case 0xF0 :// The up-key identifier
            is_up = 1;
            break;
         case 0x12 :// Left SHIFT
            shift = 1;
            break;
         case 0x59 :// Right SHIFT
            shift = 1;
            break;
         case 0x05 :// F1
            if(mode == 0)
               mode = 1;// Enter scan code mode
            if(mode == 2)
               mode = 3;// Leave scan code mode
            break;
         default:
            if(mode == 0 || mode == 3) {// If ASCII mode            
               if(!shift) {// If shift not pressed, do a table look-up
                  for(i = 0; unshifted[i][0]!=sc && unshifted[i][0]; i++);
                     if (unshifted[i][0] == sc) {
                        putchar(unshifted[i][1]);                     
                     }                                       
               }
               else {// If shift pressed
                  for(i = 0; shifted[i][0]!=sc && shifted[i][0]; i++);
                     if (shifted[i][0] == sc) {
                        putchar(shifted[i][1]);
                     }
               }
            }
            else putchar(sc);   // scan code mode (debugging mode)                        
            break;
      }
   }
   else {
      is_up = 0;// Two 0xF0 in a row not allowed
      switch (sc) {
         case 0x12 :// Left SHIFT
            shift = 0;
            break;
         case 0x59 :// Right SHIFT
            shift = 0;
            break;
         case 0x05 :// F1 --  F1 puts you in debugging mode
               // pressing F1 again gets you out of debugging mode
               // in debugging mode hex code of the scan codes
               // are stored in the buffer instead of their ascii codes      
            if(mode == 1)
            mode = 2;
            if(mode == 3)
               mode = 0;
            break;      
      }
   }
}                                               
//***********************************************
interrupt [EXT_INT0] void ext_int0_isr(void)
{
// Place your code here
   // function entered at falling edge of the kbd clock signal
   // if data bit is the next bit to be read
   // (bit 3 to 10 is data, start, stop & parity bis are ignored
   if((bitcount < 11) && (bitcount > 2)) {
      data = (data >> 1);      
      if (KBD_DATA==1)         // if next bit is 1   
         data = data | 0x80;   // store a '1'
      else
         data = data & 0x7f; // else store a '0'  
   }      
   if(--bitcount == 0) {      // all bits received ?      
           decode(data);       // decode received byte
         bitcount = 11;      // reset bit counter    
    }
        
}
#ifndef RXB8
#define RXB8 1
#endif
#ifndef TXB8
#define TXB8 0
#endif
#ifndef UPE
#define UPE 2
#endif
#ifndef DOR
#define DOR 3
#endif
#ifndef FE
#define FE 4
#endif
#ifndef UDRE
#define UDRE 5
#endif
#ifndef RXC
#define RXC 7
#endif
#define FRAMING_ERROR (1<<FE)
#define PARITY_ERROR (1<<UPE)
#define DATA_OVERRUN (1<<DOR)
#define DATA_REGISTER_EMPTY (1<<UDRE)
#define RX_COMPLETE (1<<RXC)
// USART Receiver buffer
#define RX_BUFFER_SIZE 8
char rx_buffer[RX_BUFFER_SIZE];
#if RX_BUFFER_SIZE<256
unsigned char rx_wr_index,rx_rd_index,rx_counter;
#else
unsigned int rx_wr_index,rx_rd_index,rx_counter;
#endif
// This flag is set on USART Receiver buffer overflow
bit rx_buffer_overflow;
// USART Receiver interrupt service routine
interrupt [USART_RXC] void usart_rx_isr(void)
{
char status,data;
status=UCSRA;
data=UDR;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
   {
   rx_buffer[rx_wr_index]=data;
   if (++rx_wr_index == RX_BUFFER_SIZE) rx_wr_index=0;
   if (++rx_counter == RX_BUFFER_SIZE)
      {
      rx_counter=0;
      rx_buffer_overflow=1;
      };
   };
}
#ifndef _DEBUG_TERMINAL_IO_
// Get a character from the USART Receiver buffer
#define _ALTERNATE_GETCHAR_
#pragma used+
char getchar(void)
{
char data;
while (rx_counter==0);
data=rx_buffer[rx_rd_index];
if (++rx_rd_index == RX_BUFFER_SIZE) rx_rd_index=0;
#asm("cli")
--rx_counter;
#asm("sei")
return data;
}
#pragma used-
#endif
void main(void)
{
// Declare your local variables here
// Input/Output Ports initialization
// Port A initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTA=0x00;
DDRA=0x00;
// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTB=0x00;
DDRB=0x00;
// Port C initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTC=0x00;
DDRC=0x00;
// Port D initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTD=0x00;
DDRD=0x00;
// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0 output: Disconnected
TCCR0=0x00;
TCNT0=0x00;
OCR0=0x00;
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer1 Stopped
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer2 Stopped
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
// External Interrupt(s) initialization
// INT0: On
// INT0 Mode: Falling Edge
// INT1: Off
// INT2: Off
GICR|=0x40;
MCUCR=0x02;
MCUCSR=0x00;
GIFR=0x40;
// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x00;
// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud Rate: 9600
UCSRA=0x00;
UCSRB=0x98;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x33;
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
// Global enable interrupts
#asm("sei")
while (1)
      {
      };
}