rampadc 29 Posted March 5, 2014 Share Posted March 5, 2014 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. 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); } bluehash and igor 2 Quote Link to post Share on other sites
bluehash 1,581 Posted March 7, 2014 Share Posted March 7, 2014 @@rampadc Thank you for sharing your code. Very much appreciated! Just to understand, you are connecting a USB mouse/keyboard to the MSP430? Quote Link to post Share on other sites
rampadc 29 Posted March 8, 2014 Author Share Posted March 8, 2014 Hi bluehash, no, I'm connecting a PS/2 mouse to the MSP430. It's up to the MSP430 to convert those PS/2 reports to USB reports. Sent from my GT-I9100 using Tapatalk Quote Link to post Share on other sites
igor 163 Posted March 8, 2014 Share Posted March 8, 2014 What about voltage? PS/2 uses 5v, but, if memory serves, MSP430 pins are mostly not 5v tolerant. Are you using converters? Will (some) PS/2 devices work at 3.3 v? Quote Link to post Share on other sites
rampadc 29 Posted March 9, 2014 Author Share Posted March 9, 2014 You can use n-MOSFET as voltage converter like this one: http://www.hobbytronics.co.uk/mosfet-voltage-level-converter. You can also use the 2N7002 if you want SMD, or BSS138 like Sparkfun does. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.