Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PS/2 Protocol Nightmare!

Status
Not open for further replies.

bsodmike

New Member
Wow, today has been a complete waste of my time, but let me detail it out for you.

I spent 7hrs (straight) working on the PS/2 code to have my 16F877 communicate with the mouse. I spent the first hour adding a 'breakout' board to shift from the 16F648 over to the 877, this was quite relaxing. I started holding my head and tearing my hair out somewhere at around the 4th hour, when WEE the darn thing generated a clock signal.

To my horror it was a MESSED UP signal, the frames starting block would vary in length (between 150 - 250us) randomly and only 9 pulses were coming out of the M$ mouse...so I switched to a different mouse - wee clean signal, and I could count around 11 pulses (low) between the start and end of each frame.

The biggest problem I'm having is that everything is mostly visible at around 100uS timescale, but things fly off the screen fast (and even with run/hold) not too easy to keep track of when you have Tx/Rx stuff to check.

I've literally re-coded the Tx routine and it halts in the Rx routine as the mouse doesn't seem to 'acknowledge' the Txed data.

Code:
/*------------------ Function Prototypes -----------------*/
extern short int calcparity(char parcel);
extern void ps2_transmit(char packet);
extern int ps2_receive(void);
extern char ps2_init(void);
extern void setdataclock(int datastate, int clockstate);
extern short int ps2_receive_packet(void);
/*--------------------------------------------------------*/

/*---------------------- Definitions ---------------------*/
//#define settlingdelay 7 								// Arbitrary guess
#define bitset(var,bitno) ((var) |= (1 << (bitno)))    	// C alternative to bsf
#define bitclr(var,bitno) ((var) &= ~(1 << (bitno)))   	// C alternative to bcf
#define bittst(var,bitno) (var & (1 << (bitno)))       	// C alternative to btfss/btfsc
/*--------------------------------------------------------*/

/*-------------- Global Veriable Declaration -------------*/
char ps2_data_in;
int packet3[3];
/*--------------------------------------------------------*/

/*-------------- Mouse I/O Pin Assignments ---------------*/
static bit CLK		@ ((unsigned)&PORTB*8+0); // Connected to pin 5 of PS/2 mouse
static bit DATA		@ ((unsigned)&PORTB*8+4); // Connected to pin 1 of PS/2 mouse
/*--------------------------------------------------------*/

void setdataclock(int datastate, int clockstate)
{
//RB0-CLK, RB4-DATA
    if(datastate)
        bitset(TRISB,4);//set TRIS register bit associated with data line to input
    else
    {
        DATA=0;
        bitclr(TRISB,4);//set TRIS register bit associated wtih data line to output
    }
    
    if(clockstate)
        bitset(TRISB,0);//set TRIS register bit associated with clock line to input
    else
    {
        CLK=0;
        bitclr(TRISB,0);//set TRIS register bit associated wtih clock line to output
    }
}

short int calcparity(char parcel)
{
	short int i;
    short int parity = 1;

	for (i=0;i<7;++i)
	{
		if (bittst(parcel,i) == 1)parity = parity^1;
	}
    return parity;
}

void ps2_transmit(char packet)
{

    short int parity, i;
 	snd_cls();snd_string("Inside ps2_Tx");
    Delay(2);
	ps2_data_in=0;

//	TRISD = 0x00;
//	PORTD = 0x00;

    do
	{ 
		parity = calcparity(packet);      

		setdataclock(1,0);          //pull clock low
        DelayMs_100(3);               //must be greater than 100ms
        setdataclock(0,0);          //pull data low
		DelayMs_100(3);
        setdataclock(0,1);          //release clock - device will start generating clock

		while(!CLK);             //wait for rising edge
        while(CLK);               //wait for falling edge
		setdataclock(0,1);

		//while(1){snd_cls;snd_string("Code halted.");}

			for (i=0;i<7;++i)
			{
			while(!CLK);             //wait for rising edge
        	while(CLK);               //wait for falling edge			 
			if (bittst(packet,i)){setdataclock(1,1);}else{setdataclock(0,1);}
			}

		//while(1){snd_cls;snd_string("Code halted.");}

        while(!CLK){}
        while(CLK){}
		if (parity){setdataclock(1,1);}else{setdataclock(0,1);}
        while(!CLK){}
        while(CLK){}
		setdataclock(1,1);          //set stop bit and release Data line
        while(CLK || DATA){}         //wait for device to pull clock & data low (ACK)
        while((!CLK) || (!DATA)){}       //wait for device to release clock & data
		snd_cls;snd_string("ACK OK!");

        while(!ps2_receive()){} //wait for acknowledge byte from mouse
        //hextoascii(ps2_data_in); << Fix! This is for BoostC!
/*

todo: bitwise hex to ascii conversion...

*/
    
    }while(ps2_data_in!=0xFA);   
	snd_cls();
	snd_string("Tx OK!");
    Delay(2);
}

int ps2_receive(void)
{
    int i;
    char packet;

	snd_cls();snd_string("Inside ps2_Rx");
    packet=0;
    setdataclock(1,0); //pull clock line low to inhibit communication
    DelayMs_100(3); //must be greater than 100ms
    setdataclock(1,1); //release clock line, data line ready for input

	while((CLK)||(DATA)); //wait for data AND clock to be low
	snd_cls();snd_string("after while");

		for (i=0;i<7; ++i)
		{
		    while(!CLK){} //wait for clock high
    		while(CLK){} //wait for clock low (falling edge);
			if (DATA){bitset(packet,0);}else{bitclr(packet,0);}
		}

    while(!CLK){} 
    while(CLK){}//We do not perform a parity check on the data!
    while(!CLK){}
    while(CLK){} //wait for last 2 clock pulses (parity and stop)
	snd_cls();
	snd_string("Rx OK!");
    setdataclock(1,0); //pull clock line low to inhibit communication
    ps2_data_in=packet;	
    return 1;
}

short int ps2_receive_packet(void)
{
	int a;
    packet3[0]=0;
    packet3[1]=0;
    packet3[2]=0;
    for(a=0; a<10; a++) //try 5 times to receive 1st data byte
    {
        if(ps2_receive())
            break;
        if(a==9)
            return 0;
    }
    packet3[0]=ps2_data_in;
    if(!ps2_receive())
        return 0;
    packet3[1]=ps2_data_in;
    if(!ps2_receive())
        return 0;
    packet3[2]=ps2_data_in;
    return 1;
}

//function returns 0 if mouse initializes properly
//returns 1 if mouse error (0xFC) is received
//returns 2 if other code received
char ps2_init(void)
{
    char temp;
	setdataclock(1,1);
	snd_cls();
	snd_string("Inside ps2_int()");
    Delay(2);	

    ps2_transmit(0xFF);     //reset command
    while(!ps2_receive()){} //mouse init results
    temp=ps2_data_in;
    while(!ps2_receive()){} //mouse ID
    if(temp==0xAA)          //0xAA = mouse initialized OK (self test)
    {
		snd_cls();
		snd_string("0xAA Received OK");
        Delay(2);
		ps2_transmit(0xF3);
        ps2_transmit(10);
        ps2_transmit(0xF4);//enable data reporting, begin operation
    }
    else if(temp==0xFC)     //0xFC = mouse had init error
	{
		snd_cls();
		snd_string("0xFC Init Error!");
		Delay(2);
        return 1;
	}else
	{
		snd_cls();
		snd_string("Unknown Error!");
        Delay(2);
		return 2;           //code other than 0xAA or 0xFC returned by mouse
	}
    return 0;        
}

Notes:

Using PICC-Lite v9.50PL1. The setdataclock() allows easy swapping of state of the lines. There is a 330ohm resistor on both DATA and CLK to prevent shorting the mouse *if* by mistake a '1' is outputted (should never happen). As they are in open-collector mode and PortB pull-ups are enabled to make it high, simply set as input. For low, write 0 to pin and set as output!

I also write output to the lcd now and then to let me know where in the code it is...

----------------------------------------

What you can do to help me:

A) Anyone out there that has IBM's official Tech Ref for the PS/2 protocol, wow you'd be a life saver here!

B) Once I post the code, please say anything you like, all comments/hints are needed as I'm starting to see a bright light at the end of the tunnel, oh wait it's an ultrabright LED ... (ohh boy, see I'm going crazy now :p)

C) Detail to me in steps (with required delays) the requirements for Tx and Rx with PS/2? I've seen all; the online sites to see about PS/2 (the Alan Chapswke, and few others...) and still not much luck I'm afraid.

--------------------------------------

Question: Would it make more sense to use interrupts on the clock like to detect each clock transition OR stick to polling such as:

while(!clock); // wait for high
while(clock); // wait for low
---do stuff

??

--------------------------------------

Thanks guys, your help will mean that I won't decide to kill my self buy hanging my self with an oscillator probe (haha..ok I'm kidding but who knows :p)

A very tired and depressed,
--Mike
 
The magazine EPE did an article (and a couple of followup projects) on PS2 mice and keyboards, the code can be downloaded from their website - however, it is (obviously) in assembler!.
 
Dear Nigel,

Thanks for your reply - yea but I'd really be much happier to achieve this with C. I'm gonna take a break now (as I'm dead tired) and gonna port the LCD code over to BoostC to see how much 'better' this compiler is, WAY too many people recommend it! :)

Thanks, Mike
 
bsodmike said:
Dear Nigel,

Thanks for your reply - yea but I'd really be much happier to achieve this with C. I'm gonna take a break now (as I'm dead tired) and gonna port the LCD code over to BoostC to see how much 'better' this compiler is, WAY too many people recommend it! :)

You could always 'down grade' the assembler code to C?. PIC assembler is simple enough to understand, so it should be easy to convert it to any language?.
 
WOW: Thanks Nigel, that code is perfect. Only thing I'm concerned about is the fact that I'm running a 20Mhz clock - this (could) mess things up but I'll attempt to stick to the timing guidelines there.

Thanks, Mike
 
Code:
Question: Would it make more sense to use interrupts on the clock like to detect each clock transition OR stick to polling such as

Using interrupts would greatly simplify the timing requirements of your code. You only have to detect the falling edge of CLK. That is, the DATA is sampled/shifted out on this transistion depending on whether you are sending/receiving data to/from the mouse.

Secondly, unlike the PS2 keyboard which will work without having to send data to it, a P2 mouse requires initializing by sending a series of commands to it.
 
Yes, I've noticed the commands and have coded up some very different code (similar yet not really). I'm still sticking to the 'polling' idea of things and should have some results later today.

The problem is that between the Tx and Rx routines I had some output sent to the LCD....of course this is going to take LONGER than 30us!!

So the mouse transmits the reset command, talks to the LCD by which time anther 30us+++ has passed so it seems like the Tx isn't working at first...
 
Well it *seems* like it works a little now. I'm getting the error code 0x6A back from the mouse after sending it the reset command 0xFF where it *should* reply 0xAA if all goes well...

Righty now I have PORTD,0 with a red led to light up if the stop bit in Rx is received and this doesn't happen (ARGH!). However the green led on PORTD,1 lights up to indicate that it has gotten an ACK from the mouse...

So why....oh why...don't I get a stop bit?

The file is attached if anyone would like to see if they can figure out the problem...
 

Attachments

  • ps2mouse.h
    6 KB · Views: 235
  • delay.h
    2.4 KB · Views: 237
First problem on Tx, you're not sending data on the first falling edge. The effect is that you are sending two start bits.

Second problem on Tx, you're sending only 7 data bits. You should be sending 8 data bits.

On receive, you're only recording 7 data bits. You should be receiving 8 data bits. The reply code you're getting back looks wrong. The value sent by the mouse is probably 0xEA.
 
In the for loops I've made it i<8 for both, so it reads 8 bits and when i =8, it leaves the loop. Also disabled the following in Tx:

while(CLK); //startbit wait for falling edge
DelayUs(15);
while(!CLK);

and I now get 0xFE any ideas?

Thanks Mike :)
 
That's an error code. Its name is RESEND. So there's been a transmission error.

If you can fit 11 or 12 bits on the scope, pull the clock line low to prevent the mouse from sending back data. Then you can see if the RESET command was sent properly. In fact, you can pull the clock line low at any time to stop data transmission in either direction.
 
I was checking your code and comparing it to mine and I see there is some difference.

In the ps2_transmit routine:

Code:
      setdataclock(1,0);          //pull clock low 
        DelayMs_100(3);               //must be greater than 100ms 
        setdataclock(0,0);          //pull data low 
      DelayMs_100(3); 
        setdataclock(0,1);          //release clock - device will start generating clock 
/* 
 ***   You don't need this because the start bit has already been generated by pulling clk and then data low ***

      while(!CLK);             //wait for rising edge 
        while(CLK);               //wait for falling edge 
      setdataclock(0,1); 
*/

I commented out the section that is different.
 
motion, thanks for that. I have already fixed that according to tkbits:

"First problem on Tx, you're not sending data on the first falling edge. The effect is that you are sending two start bits."

tkbits: How can 0x7E be trusted as valid data (I'm not performing a parity check just yet) if no stop bit is received? The red led on RD0 does not light up... :?

and also updated i<8 (or you could use i<=7) so as to send/read 8 bits, and I'm now getting the code for 'RESEND'. Yes, today I intend to pull clock low right after the Tx and see what happens. I've also removed lots of the 'DelayUs(15);' from the code and want to see if that'll have an effect.

Will keep you posted, Thanks, Mike.
 
For the Tx routine I am, on the Rx routine I am not...and the reason I am not is cause no stop bit is received...I.e. how could the data be valid if I don't receive a full set of data?

If the parity on the Tx were wrong I wouldn't get an ACK....but my parity function seems to work...

parity == total # of 1s in byte of data. 1's complement of parity (i.e. parity = parity^0xFF) and transmit bit 0...

--Mike
 
parity == total # of 1s in byte of data. 1's complement of parity (i.e. parity = parity^0xFF) and transmit bit 0...

I compared it to my code and mine is:

Code:
parity_chk = count of 1s in byte of data (excludes parity bit itself)

if (parity_chk&0x01)
   parity_bit = 0;
else
   parity_bit = 1;
 
Cool, I'll try that. I looked at my code and plonked it on and OSC scope after some fiddling I think I found a mistake and the OSC and LCD now say that I'm infact getting 0xFA from the mouse. I've confirmed this...and oh I'm getting a STOP BIT NOW!

/me dances around...

It's the delays of 15us that propagated thru causing it to read as FE..

Now I'm about to go find out what FA is..

--Mike

OMG!! FA means it got it all ok..I just need to sort the code now thanks guys will report back tomorrow :D

--Mike
 
Well further to the good news I was able to initialise the mouse today but ran into a problem... The first problem was that I needed a way to display the value of the distance travelled on the LCD so what I did was:

b3 = distance /100;
b2 = (distance % 100) /10;
b1 = ((distance % 100) %10;

and they are sent to the lcd starting from b3 to b1...this works.

However I'm having trouble with the movement counters as they are 9-bit 2's complement where the 9th bit is in byte 1.

If I were to ignore the sign for the y-axis, and did:

distance = distance + packet3[2];

snd_char(distance);

I end up getting random characters from the character map...

So simply put... how does one convert from 9-bit 2's complement to an int (16bit)?...

-------
>> IDEA:

data = (movementdata - 1) ^0xFF
if (y-axis_sign == negative)
bitset(data,7);

Make sense? If an int, make bitset(data,15);
-------

I don't mind it being first a short int (8bit) and then later 16.

P.S. I also tried adding + 48 before sending to the LCD, still no luck...

Thanks :D

--Mike
 
bsodmike said:
So simply put... how does one convert from 9-bit 2's complement to an int (16bit)?...

--Mike

Test the high byte. If it's non zero (should be 0 or 1) then set it to 0xFF. Or to put it another way whatever the 9th bit is then set all higher bits the same. This will give you a 16bit signed integer.

Mike.
 
I'm not sure if I've confused you. The movement packet is only 8bits, so there is *no* high byte to test. And my int is an empty variable to which I want to add the movement data too i.e.

distance = distance + (signed) movement

If you go backwards, the distance will reduce... If you don't mind give me an example but I think you were telling me to do this:

int = movement packet for y-axis

00000000 00000001 // let's suppose it is 1
11111111 00000001 // ignore sign and make the high byte 0xFF?

I think that the movement packet needs to first under go and XOR with 0xFF so as to invert all the bits and then append the sign bit at the last spot so...

Let's suppose I'm being sent 111011101(-35 as a 9-bit 2's comp)

Now and invert it to give: 00100010 (35) and append the sign bit as:

X0000000 00100010, right now X=0 so it is +35.

Take this, add 48 and when sent to the LCD it should appear as '35'. I could then test the 15th bit and if true, send a '-' to the LCD first.

--Mike
 
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top