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:
- Change CLOCK and DATA ports definitions in PS2.h. I have attempted to use pointers but it was too messy.
- Comment PS2_setupClockLine() if your clock line does not support interrupt.
- Use an accurate clock. PS/2 timing is strict.
- Change the delay multiplier accordingly to your clock frequency. I am still finding a way so the compiler automatically substitute in the correct value.
-
#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.
For remote mode, this code doesn't play well with USB either. I am still not sure why. Testing with MSP430F5510.
Relevant portion of USB test code.
case ST_ENUM_ACTIVE:
printf("Enumerated\r\n");
while(1) {
printf("Hard at work!\r\n"); //uncommenting this line and the mouse will not work. Why?
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);
USBHID_sendReport((void *)&data, HID1_INTFNUM); //TrackPoint is at HID1_INTFNUM
}
Optimizations are very much welcomed. This code is very much brute force. Any help with the synchronization problem with USB and the problem with the above code is very much appreciated.