Jump to content
43oh

PS/2 Mouse for any MSP430


Recommended Posts

Strangely enough, there aren't a lot of PS/2 mouse codes available for MSP430. The following code allows you to communicate with your PS/2 mouse by remote mode and stream mode (require interrupts). TrackPoint is a mouse module used in ThinkPad keyboards. It uses the PS/2 protocol with some proprietary commands. Keyboards are pretty much the same as mouse, only their reports are simpler. 

 

PS2.h

/*
 * PS2.h
 *
 *  Created on: 14/11/2013
 *      Author: CONG (rampADC)
 */

#ifndef PS2_H_
#define PS2_H_

#include "msp430.h"
#include "stdint.h"

//4MHz XT2
#define delay_us(x) __delay_cycles(x * 4)

#define	CLOCK			0xCC
#define	CLOCK_DIR		P4DIR
#define	CLOCK_OUT		P4OUT
#define CLOCK_IN		P4IN
#define CLOCK_REN		P4REN
#define CLOCK_IFG		0
#define CLOCK_IE		0	
#define CLOCK_IES		0
#define	CLOCK_PIN		BIT7

#define	DATA			0xDA
#define	DATA_DIR		P4DIR
#define	DATA_OUT		P4OUT
#define DATA_IN			P4IN
#define DATA_REN		P4REN
#define DATA_PIN		BIT6

#define	RESET			0xEE
#define	RESET_DIR		P4DIR
#define RESET_OUT		P4OUT
#define RESET_PIN		BIT5

#define	REMOTE_MODE		0xF0
#define	STREAM_MODE		0xEA
#define	RESET_MODE		0xFF

#define	READ_DATA		0xEB
#define	DISABLE_REPORT	0xF5
#define	SET_DEFAULTS	0xF6
#define	ENABLE_REPORT	0xF4
#define	SET_SAMPLE_RATE	0xF3
#define GET_ID			0xF2
#define STATUS_REQUEST	0xE9
#define	SET_RESOLUTION	0xE8
#define	SET_SCALING_2	0xE7
#define SET_SCALING_1	0xE6

#define	TIMING_ERROR	50000

#define	LOW				0
#define	HIGH			1

typedef struct {
	uint8_t state;
	int8_t x;
	int8_t y;
} PS2Data_t;

typedef struct {
	uint8_t state;
	int8_t x;
	int8_t y;
	int8_t z;
} USB_MOUSE_REPORT_t;

void PS2_initialize(void);
void PS2_transmit(uint8_t data);
uint8_t PS2_receive(void);
uint8_t PS2_getBit();
uint8_t PS2_setMode(uint8_t mode);
PS2Data_t PS2_getData(void);

void PS2_setupClockLine(void);
int PS2_dataAvailable(void);
PS2Data_t PS2_streamGetData(void);

USB_MOUSE_REPORT_t PS2_getUSBReport(void);

//trackpoint specific
void Trackpoint_writeToRAM(uint8_t location, uint8_t data);
void Trackpoint_setSensitivity(uint8_t sensitivityFactor);

#endif /* PS2_H_ */

PS2.c

/*
 * PS2.c
 *
 *  Created on: 14/11/2013
 *      Author: CONG (rampADC)
 */

#include "PS2.h"
#include "string.h"
#include "..///printf.h"

void setLine(uint8_t, uint8_t);
uint8_t readLine(uint8_t);

static int dataAvailable = 0;
static int counter = 0;
static PS2Data_t ps2MouseData;
static USB_MOUSE_REPORT_t usbMouseData;

static volatile uint8_t bitcount = 0;
static volatile uint8_t incoming = 0;
static volatile uint8_t incomingParity = 0;
static volatile uint8_t onesCounter = 0;
static volatile uint8_t calculatedParity = 0;
static volatile uint8_t parityOk = 0;
static volatile uint8_t parityChecks[3] = {0,0,0};

uint8_t n, val;

USB_MOUSE_REPORT_t PS2_getUSBReport(void) {
	ps2MouseData = PS2_getData();
	
	usbMouseData.x = ps2MouseData.x;
	usbMouseData.y = -ps2MouseData.y; //by default, ps/2 mouse y-value is opposite to direction moving to
	usbMouseData.state = ps2MouseData.state & 0x07; //only least significant three bits are transferred
	
	//use middle button to scroll
	if(ps2MouseData.state & BIT2) {
		usbMouseData.z = ps2MouseData.y;
	}
	
	return usbMouseData;
}

int Trackpoint_isConnected(void) {
	PS2_transmit(0xE1); //read secondary data
	if(PS2_receive() != 0x01) return 0; //first byte needs to always be 0x01
	uint8_t secondByte = PS2_receive();
	//assuming there will be newer revisions, let's second byte may not be important
	return 1;
}

void Trackpoint_writeToRAM(uint8_t location, uint8_t data) {
	//refer to "Trackpoint System Version 4.0 Engineering Specification" pg. 20
	PS2_transmit(0xE2); 
	PS2_receive(); //ACK
	PS2_transmit(0x81);
	PS2_receive(); //ACK
	PS2_transmit(location);
	PS2_receive(); //ACK
	PS2_transmit(data);
	PS2_receive(); //ACK
}

void Trackpoint_setSensitivity(uint8_t sensitivityFactor) {
	Trackpoint_writeToRAM(0x4A, sensitivityFactor);
}

int PS2_dataAvailable(void) {
	if(dataAvailable) return 1;
	else return 0;
}

PS2Data_t PS2_streamGetData(void) {
	return ps2MouseData;
}

void PS2_initialize(void) {
	//printf("Initializing...\r\n");
	setLine(CLOCK, HIGH);
	setLine(DATA, HIGH);
	//printf("Clock, data idle\r\n");
}

void PS2_transmit(uint8_t data) {
	//printf("Transmitting: %x\r\n",data);
	uint8_t parity = 1;
//	uint8_t data_bak = data;
	uint8_t i;

	setLine(DATA, HIGH);
	setLine(CLOCK, HIGH);
	delay_us(300);
	setLine(CLOCK, LOW);
	delay_us(300);
	setLine(DATA, LOW);
	delay_us(10);
	setLine(CLOCK, HIGH);

	while(readLine(CLOCK) == HIGH);

	for(i = 0; i < 8; i++) {
		if(data & BIT0) {
			setLine(DATA, HIGH);
		} else {
			setLine(DATA, LOW);
		}

		while(readLine(CLOCK) == LOW);
		while(readLine(CLOCK) == HIGH);
		parity ^= (data & BIT0);
		data >>= 1;
	}

	if(parity) {
		setLine(DATA, HIGH);
	} else {
		setLine(DATA, LOW);
	}

	while(readLine(CLOCK) == LOW);
	while(readLine(CLOCK) == HIGH);
	setLine(DATA, HIGH);
	delay_us(50);
	while(readLine(CLOCK) == HIGH);
	while(readLine(CLOCK) == LOW || readLine(DATA) == LOW);

	setLine(CLOCK, LOW);
	//printf("Host: %x\r\n",data_bak);
}

//Get incoming data bit during interrupt caused by CLOCK change
uint8_t PS2_getBit(void) {
	parityOk = 0;
	dataAvailable = 0;

	val = ((DATA_IN & DATA_PIN) ? 1 : 0);
	n = bitcount-1;
	if(n <= 7) {
		incoming |= (val << n);
		if(val) onesCounter++;
	} else if(n == 8) {
		incomingParity = val;
	}
	bitcount++;

	if(bitcount == 11) {
		if(onesCounter % 2) {
			calculatedParity = 0; //not divisible by 2 thus ODD, keep it odd (odd parity)
		} else {
			calculatedParity = 1;
		}

		if(calculatedParity == incomingParity) {
			parityOk = 1;
		}

		switch(counter) {
		case 0:
			parityChecks[0] = parityOk;
			ps2MouseData.state = incoming;
			counter++;
			break;
		case 1: 
			parityChecks[1] = parityOk;
			ps2MouseData.x = incoming;
			counter++;
			break;
		case 2:
			parityChecks[2] = parityOk;
			ps2MouseData.y = -incoming;
			counter = 0;

			if(parityChecks[0] && parityChecks[1] && parityChecks[2]) dataAvailable = 1;
			//reset parities
			memset(&parityChecks, 0, 3);

			break;
		}
		bitcount = 0;
		incoming = 0;
		onesCounter = 0;
		calculatedParity = 0;
		incomingParity = 0;
		parityOk = 0;
	}
	return val;
}

uint8_t PS2_receive(void) {
	//printf("Receiving...\r\n");
	uint8_t data = 0;
	uint8_t i;
	uint8_t bit = 0x01;

	setLine(CLOCK, HIGH);
	setLine(DATA, HIGH);
	delay_us(50);
	while(readLine(CLOCK) == HIGH);

	delay_us(5);
	while(readLine(CLOCK) == LOW);

	for(i = 0; i < 8; i++) {
		while(readLine(CLOCK) == HIGH);
		if(DATA_IN & DATA_PIN) {
			data |= bit;
		}
		while(readLine(CLOCK) == LOW);
		bit <<= 1;
	}

	while(readLine(CLOCK) == HIGH);
	while(readLine(CLOCK) == LOW);
	while(readLine(CLOCK) == HIGH);
	while(readLine(CLOCK) == LOW);

	setLine(CLOCK, LOW);

	//printf("Mouse: %x\r\n",data);
	return data;
}
/*
 * Comment out when clock line does support interrupts
 */

void PS2_setupClockLine(void) {
	//setup interrupts
	CLOCK_DIR &= ~CLOCK_PIN;	//set direction to input
	CLOCK_REN |= CLOCK_PIN;		//enable pull-up
	CLOCK_OUT |= CLOCK_PIN;		//enable pull-up

	CLOCK_IES |= CLOCK_PIN;		//falling
	CLOCK_IFG &= ~CLOCK_PIN;	//interrupt flag cleared
	CLOCK_IE |= CLOCK_PIN;		//interrupt enable for clock
}
 
uint8_t PS2_setMode(uint8_t mode) {
	uint8_t success = 0;

	switch(mode) {
	case REMOTE_MODE:
		PS2_transmit(REMOTE_MODE);
		PS2_receive();				
		break;
	case STREAM_MODE:	
		//setup mouse
		PS2_transmit(STREAM_MODE);
		PS2_receive();
		PS2_transmit(ENABLE_REPORT); 
		PS2_receive();
		break;
	case RESET_MODE:
		//TrackPoint has a reset pin, need to use it.
		RESET_OUT |= RESET_PIN;
		RESET_DIR |= RESET_PIN;
		delay_us(2000000);
		RESET_OUT &= ~RESET_PIN; 
		//For normal PS/2 devices, this should be enough
		PS2_transmit(RESET_MODE);
		PS2_receive();
		PS2_receive();
		PS2_receive();
		break;
	}

	return success;
}

PS2Data_t PS2_getData(void) {
	PS2_transmit(READ_DATA);
	PS2_receive();
	ps2MouseData.state = PS2_receive();
	ps2MouseData.x = PS2_receive();
	ps2MouseData.y = PS2_receive();

	return ps2MouseData;
}

//private functions
uint8_t readLine(uint8_t line) {
	if(line == CLOCK) {
		if(CLOCK_IN & CLOCK_PIN) {
			return 1;
		} else {
			return 0;
		}
	} else {
		if(DATA_IN & DATA_PIN) {
			return 1;
		} else {
			return 0;
		}
	}
}

void setLine(uint8_t line, uint8_t state) {
	if(line == CLOCK) {
		if(state == LOW) {
			CLOCK_DIR |= CLOCK_PIN;		//set direction to output
			CLOCK_OUT &= ~CLOCK_PIN;	//set output to low
		} else {
			CLOCK_DIR &= ~CLOCK_PIN;	//set direction to input
			CLOCK_REN |= CLOCK_PIN;		//enable pull-up
			CLOCK_OUT |= CLOCK_PIN;		//enable pull-up
		}
	} else {
		if(state == LOW) {
			DATA_DIR |= DATA_PIN;		//set direction to output
			DATA_OUT &= ~DATA_PIN;	//set output to low
		} else {
			DATA_DIR &= ~DATA_PIN;	//set direction to input
			DATA_OUT |= DATA_PIN;		//enable pull-up
			DATA_REN |= DATA_PIN;		//enable pull-up 
		}
	}
}

To use this, you need to:

  1. Change CLOCK and DATA ports definitions in PS2.h. I have attempted to use pointers but it was too messy. 
  2. Comment PS2_setupClockLine() if your clock line does not support interrupt.
  3. Use an accurate clock. PS/2 timing is strict.
  4. Change the delay multiplier accordingly to your clock frequency. I am still finding a way so the compiler automatically substitute in the correct value.
  5. #define delay_us(x) __delay_cycles(x * 4) //4MHz is used in PS2.h. Find this line, and change it the number '4' if needed.
    

Test code:

UART Remote mode - works great. Tested with oPossum's tiny printf code. In this example, I am using an external 4MHz crystal. Tested with MSP430F5510

#include <msp430.h> 

#include "PS2.h"

#include "Printf.h"

void initClocksXT2(void);
void initUART(void);

static USB_MOUSE_REPORT_t data;

int main(void) {
	WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer

	initClocksXT2();
	initUART();

	PS2_initialize();
	PS2_setMode(RESET_MODE);
	PS2_setMode(REMOTE_MODE);
	
	while(1) {
		data = PS2_getUSBReport();
		printf("x: %i, y: %i, z: %i, LMR: %i%i%i\r\n",data.x,data.y,data.z,data.state & BIT0,data.state & BIT2,data.state & BIT1);
	}
}

void initClocksXT2(void) {
	///XT2 as MCLK and SMCLK  
	P5SEL |= BIT2+BIT3;                       // Port select XT2

	UCSCTL6 &= ~XT2OFF;                       // Enable XT2 
	UCSCTL3 |= SELREF_2;                      // FLLref = REFO
	// Since LFXT1 is not used,
	// sourcing FLL with LFXT1 can cause
	// XT1OFFG flag to set
	UCSCTL4 |= SELA_2;                        // ACLK=REFO,SMCLK=DCO,MCLK=DCO

	// Loop until XT1,XT2 & DCO stabilizes - in this case loop until XT2 settles
	do
	{
		UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
		// Clear XT2,XT1,DCO fault flags
		SFRIFG1 &= ~OFIFG;                      // Clear fault flags
	}while (SFRIFG1&OFIFG);                   // Test oscillator fault flag

	UCSCTL6 &= ~XT2DRIVE0;                    // Decrease XT2 Drive according to
	// expected frequency
	UCSCTL4 |= SELS_5 + SELM_5;               // SMCLK=MCLK=XT2
}

void initUART() {
	//4MHz 9600
	P4SEL = BIT4;
	UCA1CTL1 |= UCSWRST;					 // **Put state machine in reset**
	UCA1CTL1 |= UCSSEL_2;					 // SMCLK
	UCA1BR0 = 0xA0;							 
	UCA1BR1 = 0x01;							 
	UCA1MCTL = UCBRS_5 + UCBRF_0; 
	// over sampling
	UCA1CTL1 &= ~UCSWRST;					 // **Initialize USCI state machine**
}

UART - Stream mode. Tested on the MSP430F5529LP. Using crystal as clock source.

#include <msp430.h> 
#include "Printf.h"
#include "PS2.h"

PS2Data_t mouseData;

uint8_t left = 0;
uint8_t mid = 0;
uint8_t right = 0;
int main(void) {

	WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer

	//Testing pins
	P1OUT &= ~BIT0;
	P1DIR |= BIT0;
	P6OUT &= BIT5;
	P6DIR |= BIT5;
	P3OUT &= ~BIT4;
	P3DIR |= BIT4;

	initClocks(); //code stored in another file. just XT2 at 4MHz
	initUART(); //code stored in another file. 4MHz at 9600 baud.

	PS2_initialize();
	PS2_setMode(RESET_MODE);
	PS2_setMode(STREAM_MODE);

	PS2_setupClockLine(); //set up interrupt on clock line

	printf("UART's working fine\r\n");
	while(1) {
		//_BIS_SR(LPM0_bits + GIE);

		if(PS2_dataAvailable()) {
			
			left = PS2_streamGetData().state & BIT0 == BIT0;
			right = PS2_streamGetData().state & BIT1 == BIT1;
			mid = PS2_streamGetData().state & BIT2 == BIT2;
			printf("LEFT: %i, MID: %i, RIGHT: %i ", left, mid, right);
			printf("(%i, %i)\r\n", PS2_streamGetData().x, PS2_streamGetData().y);
		}
	}
}

//CLOCK on P1.4
#pragma vector = PORT1_VECTOR
__interrupt void P1ISR(void) {
	if(CLOCK_IFG & CLOCK_PIN) {
		PS2_getBit(); 
		CLOCK_IFG &= ~CLOCK_PIN;
		//_BIC_SR_IRQ(LPM0_bits);
	}
}

This PS/2 stream code does not work well with USB. Synchronization errors appears. I am still trying to fix this. Use PS2_getUSBReport() to get the report needed for a USB HID mouse.

 

UPDATE 11/03/2014

To use remote mode with USB, put the mouse reading-report-converting block into a timer interrupt block and set the timer to interrupt I'd say at least 6ms, which is the time it takes for the PS/2 mouse to communicate with my 430 chip. My PS/2 mouse clock operates at 14kHz. Yours may range from 10kHz to 16kHz so increase the time interval if it doesn't work. Better yet, probe it with a logic analyzer to determine the time.

void initTimers() {
	//set TA0CCR0 to time 7ms, assuming 4MHz clock
	TA0CCR0 = 27999;
	//ENABLE CAPTURE/COMPARE INTERRUPT (FOR WHEN TA0CCR0 IS REACHED)
	TA0CCTL0 = CCIE;
	//SMCLK, UP, /1
	TA0CTL = TASSEL_2 + MC_0 + ID_0; //stop it first
}
case ST_ENUM_ACTIVE:
	PS2_setMode(RESET_MODE);
	PS2_setMode(REMOTE_MODE);
	Trackpoint_setSensitivity(0xC0);

	TA0CTL |= MC_1; //start timer
	
	while(1) {
		__bis_SR_register(LPM0_bits + GIE);
	}
	
	break;
#pragma vector = TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void) {
	ps2Data = PS2_getData();
	usbData.x = ps2Data.x;
	usbData.y = -ps2Data.y; //by default, ps/2 mouse y-value is opposite to direction moving to
	usbData.state = ps2Data.state & 0x07; //only least significant three bits are transferred

	//use middle button to scroll
	if(ps2Data.state & BIT2) {
		usbData.z = ps2Data.y;
	}

	USBHID_sendReport((void *) &usbData, HID_MOUSE);
}
Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...