PS2遊戲手柄——基於STC15W4K32S4

PS2遊戲手柄

1 PS2介紹

在這裏插入圖片描述
PS2手柄是日本SONY公司的PlayStation2 遊戲機的遙控手柄。索尼的 PSX系列遊戲主機在全球都很暢銷。不知什麼時候便有人打起 PS2手柄的主意,破解了通訊協議,使得手柄可以接在其他器件上遙控使用,比如遙控我們熟悉的機器人。突出的特點是這款手柄性價比極高,按鍵豐富,方便擴展到其它應用中。

2 PS2通訊協議介紹

PS2採用的是SPI通信協議,SPI是串行外設接口(Serial Peripheral Interface)的縮寫,是一種高速的,全雙工,同步的通信總線,並且在芯片的管腳上只佔用四根線(DI、DO、CS、CLK),節約了芯片的管腳,同時爲PCB的佈局上節省空間。
在這裏插入圖片描述
在這裏插入圖片描述
PS2接收器上一共有九根引腳,按上圖從左往右,依次爲:

1.DI/DAT:信號流向,從手柄到主機,此信號是一個8bit 的串行數據,同步傳送於時鐘的下降沿。信號的讀取在時鐘由高到低的變化過程中完成。

2.DO/CMD:信號流向,從主機到手柄,此信號和 DI相對,信號是一個 8bit 的串行數據, 同步傳送於時鐘的下降沿。

3.NC:空端口。

4.GND:電源地。

5.VCC:接收器工作電源,電源範圍 3~5V。

6.CS/SEL:用於提供手柄觸發信號。在通訊期間,處於低電平。

7.CLK:時鐘信號,由主機發出,用於保持數據同步。

8.NC:空端口。

9.ACK:從手柄到主機的應答信號。此信號在每個8bits數據發送的最後一個週期變低並且CS一直保持低電平,如果CS信號不變低,約60微秒PS主機會試另一個外設。在編程時未使用ACK端口。(可以忽略)
在這裏插入圖片描述
時鐘頻率 250KHz(4us),如果接收數據不穩定,可以適當的增加頻率。 在通訊過中,
一串數據通訊完成後 CS 纔會由低轉高,不是 1 個字節通訊完成後就由低轉高,在通訊期
間,一直處於低電平。

在時鐘下降沿時,完成數據(lbit)的發送與接收,發送和接收是同時完成的。當單片
機想讀手柄數據或向手柄發送命令時,將會拉低 CS 線電平,併發出一個命令“0x01”;手
柄會回覆它的 ID “0x41=綠燈模式,0x73=紅燈模式”;在手柄發送 ID 的同時,單片機將
傳 送 0x42,請求數據;隨後手柄發送出 0x5A,告訴單片機“數據來了”。
idle:數據線空閒,該數據線無數據傳送。

一個通訊週期有 9 個字節(8 位),這些數據是依次按位傳送。

注意的是:

  1. CS線在通訊期間拉低,通信過程中CS信號線在一串數據(9個字節,每個字節爲8位)發送完畢後纔會拉高,而不是每個字節發送完拉高。

  2. DO、DI在在CLK時鐘的下降沿完成數據的發送和讀取。
    下降沿:數字電平從高電平(數字“1”)變爲低電平(數字“0”)的那一瞬間叫作下降沿。

  3. CLK的每個週期爲12us。若在某個時刻,CLK處於下降沿,若此時DO爲高電平則取“1”,低電平則取“0”。連續讀8次則得到一個字節byte的數據,連續讀9個字節就能得到一次傳輸週期所需要的數據。DI也是一樣的,發送和傳輸同時進行。

具體的通訊過程如下:
在這裏插入圖片描述
以STC15爲例:

1、首先STC15拉低CS片選信號線,然後在每個CLK的下降沿讀一個bit,每讀八個bit(即一個byte)CLK拉高一小段時間,一共讀九組bit。

2、第一個byte是STC15發給接收器命令“0X01” 。

3、PS2手柄會在第二個byte回覆它的ID(0x41=綠燈模式,0x73=紅燈模式),同時第二個byte時STC15發給PS2一個0x42請求數據。

紅燈模式時:左右搖桿發送模擬值,0x00~0xFF 之間,且搖桿按下的 鍵值 L3 、 R3 有效;
綠燈模式時:左右搖桿模擬值爲無效,推到極限時,對應發送 UP、RIGHT、DOWN、 LEFT、△、○、╳、□,按鍵 L3 、 R3 無效。

4、第三個byte PS2 會給主機發送 “0x5A” 告訴STC15數據來了。

5、從第四個byte開始全是接收器給主機發送數據,每個byte定義如上圖,當有按鍵按下,對應位爲“0 ”,例如當鍵“SELECT”被按下時, Data[3]=11111110。

對於整個通訊過程,你理解成下面的一段對話:


拉低CS,表示開始數據通信


byte 0 :
STC15(DO) : 0x01 ------------------------- [現在開始通信]
PS2手柄(DI) : 空 ---------------------------- [空]


byte 1 :
STC15(DO) : 0x42 -------------------------- [請求發送數據]
PS2手柄(DI) : 紅燈0x73
            綠燈0X41---------------------[現在的ID]


byte 2:
STC15(DO) : 空 ------------------------------ [空]
PS2手柄(DI) : 0X5A ------------------------- [數據來了]


byte 3:
STC15(DO) : 0X00~0XFF ------------------ [右側小震動電機是否開啓]
PS2手柄(DI) : 00000000~11111111 ------- [SELECT、 L3 、 R3、 START 、 UP、 RIGHT、 DOWN、 LEFT 是否被按下,若被按下對應位爲0]


byte 4:
STC15(DO) : 0X00~0XFF ------------------ [左側大震動電機振動幅度]
PS2手柄(DI) : 00000000~11111111 ------- [L2 、 R2、L1 、R1、△、○、╳、□ 是否被按下,若被按下對應位爲0]


byte 5:
STC15(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [左側X軸搖桿模擬量]


byte 6:
STC15(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [左側Y軸搖桿模擬量]


byte 7:
STC15(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [右側X軸搖桿模擬量]


byte 8:
STC15(DO) : 空 -------------------------------- [空]
PS2手柄(DI) : 0X00~0XFF ------------------ [右側Y軸搖桿模擬量]

注意:模擬量只對紅燈模式下有效,綠燈模式下搖桿推至極限分別對應 UP、RIGHT、DOWN、 LEFT、△、○、╳、□ 。L3、R3只對紅燈模式下有效,在綠燈模式下無效。
在這裏插入圖片描述
在手柄通信前還需要一系列的初始化(是否啓動振動電機、是否進行鎖存等),詳情可以參考下面代碼。當然,不進行初始化也是可以的,手柄會默認之前的配置。
——————————————————————————————

3 代碼

主機收到的數據在out[]數組中,在其他文件用到PS2時,只要對out[]數組進行處理和分析即可。

h文件

#ifndef __PS2_H__
#define __PS2_H__
#include "STC15Fxxxx.h"


#define Up_L 0xEF
#define Down_L 0xBF
#define Left_L 0x7F
#define Right_L 0xDF

#define Up_R 0xEF
#define Down_R 0xBF
#define Left_R 0x7F
#define Right_R 0xDF

#define L1 0xFB
#define L2 0xFE
#define L3 0xFD
#define R1 0xF7
#define R2 0xFD
#define R3 0xFB


#define UP_L 0x7F
#define UP_L 0x7F
#define UP_L 0x7F
#define UP_L 0x7F
#define UP_L 0x7F
#define UP_L 0x7F
extern u8 out[9];  

void PS2_Init(void);
void PS2_ShortPoll(void);
void psin(u8 command);//手柄發送子程序
u8 PS2_Cmd(u8 command);
void Read_PS2(void);
u8 PS2_RedLight(void);
void PS2_EnterConfing(void);
void PS2_TurnOnAnalogMode(void);
void PS2_VibrationMode(void);
void PS2_ExitConfing(void);
void PS2_ClearData();
void PS2_Vibration(u8 motor1,u8 motor2);
u8 PS2_AnologData(u8 button);
#endif

c文件

#include "PS2.h"
#include "delay.h"
/*****************************PS2遙控器說明
out[3]==0xEF//左4個按鍵中上
out[3]==0xBF//左4個按鍵中下
out[3]==0x7F//左4個按鍵中左
out[3]==0xDF//左4個按鍵中右

out[4]==0xEF//右4個按鍵中上
out[4]==0xBF//右4個按鍵中下
out[4]==0x7F//右4個按鍵中左
out[4]==0xDF//右4個按鍵中右

out[4]==0xFB//左1,2個按鍵中1
out[4]==0xFE//左1,2個按鍵中2
out[4]==0xF7//右1,2個按鍵中1
out[4]==0xFD//右1,2個按鍵中2

當按下MODE鍵手柄MODE LED燈亮起時
out[7] 00——80——FF 左搖桿從左到右
out[8] 00——7F——FF 左搖桿從上到下
out[5] 00——80——FF 右搖桿從左到右
out[6] 00——7F——FF 右搖桿從上到下
當手柄MODE LED燈不亮時,手柄功能同左四右四按鍵
*******************************/
//******定義接口*********
sbit  DATA=P3^0;	//手柄接口
sbit  CMND=P3^1;
sbit  CS=P3^2;
sbit  CLK=P3^3;

/********手柄定義變量*********/
u8 code Comd[9]={0x01,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
u8 out[9];  

void PS2_Init(void)
{
	DATA=1;
	PS2_ShortPoll();
	PS2_ShortPoll();
	PS2_ShortPoll(); 
	PS2_EnterConfing(); // 進入配置模式 
	PS2_TurnOnAnalogMode(); // “紅綠燈”配置模式,並選擇是否保存 
	PS2_VibrationMode(); // 開啓震動模式 
	PS2_ExitConfing(); // 完成並保存配置
}
//手柄配置初始化: 
void PS2_ShortPoll(void) 
{
  CS=0; 
  delay_us(16); 
  PS2_Cmd(0x01); 
  PS2_Cmd(0x42); 
  PS2_Cmd(0X00); 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x00); 
  CS=1; 
  delay_us(16);
}
void delay(u16 n)  //delay(x)=(2.5+x)us; 
{
	u16 i;
	for(i=0;i<n;i++) _nop_();       
//		_nop_();//每個_nop_();大概0.1微秒	
}
void psin(u8 command)//手柄發送子程序
{
    u8 i;
    for(i=0;i<=7;i++)     //逐位接收     
    {
	 if(command&0x01)	 //此if下5行語句用時1us
        CMND=1;
     else
        CMND=0;
     command=command>>1;
	 	_nop_();	
		_nop_();
     CLK=0;
     delay(10); 
     CLK=1;
	 delay(3);
    }
	CMND=1;
}

u8 PS2_Cmd(u8 command)
{
    u8 i,j=1;
	u8 res=0; 
    for(i=0;i<=7;i++)     //逐位接收     
    {
	 if(command&0x01)
        CMND=1;
     else
        CMND=0;
     command=command>>1;
	 	_nop_();	
		_nop_();
     CLK=0;
     delay(10);
	 if(DATA) res=res+j;
     j=j<<1; 
	 CLK=1;
	 delay(3);		 
    }
    CMND=1;
    return res;	
}

void Read_PS2(void)//手柄讀取程序
{
	 u8 i;
     CS=0;
	 for(i=0;i<9;i++)	//掃描按鍵
	 {
		out[i]=PS2_Cmd(Comd[i]);	
	 } 
     CS=1;   	 
} 

// 判斷是否爲紅燈模式,0x41=模擬綠燈,0x73=模擬紅燈 
// 返回值;0,紅燈模式 
// 其他,其他模式
u8 PS2_RedLight(void) 
{
  CS=0;
  PS2_Cmd(Comd[0]); // 開始命令
  PS2_Cmd(Comd[1]); // 請求數據
  CS=1;
  if( out[1]== 0x73) return 0;
  else return 1;
}
//進入配置
void PS2_EnterConfing(void) 
{
	CS=0; 
	delay_us(16); 
	PS2_Cmd(0x01); 
	PS2_Cmd(0x43); 
	PS2_Cmd(0x00); 
	PS2_Cmd(0x01); 
	PS2_Cmd(0x00); 
	PS2_Cmd(0x00);
	PS2_Cmd(0x00); 
	PS2_Cmd(0x00); 
	PS2_Cmd(0x00); 
	CS=1; 
	delay_us(16);
}
// 發送模式設置 
void PS2_TurnOnAnalogMode(void) 
{
  CS=0; 
  PS2_Cmd(0x01); 
  PS2_Cmd(0x44); 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x01);//analog=0x01;digital=0x00 軟件設置發送模式 
  PS2_Cmd(0xEE);//Ox03 鎖存設置,即不可通過按鍵“MODE ”設置模式。        //0xEE 不鎖存軟件設置,可通過按鍵“MODE ”設置模式。 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x00); 
  CS=1; 
  delay_us(16);
}

// 振動設置
void PS2_VibrationMode(void) 
{
  CS=0; 
  delay_us(16); 
  PS2_Cmd(0x01); 
  PS2_Cmd(0x4D); 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x00); 
  PS2_Cmd(0x01); 
  CS=1;
  delay_us(16);
}
// 完成並保存配置
void PS2_ExitConfing(void)
{
  CS=0;
  delay_us(16);
  PS2_Cmd(0x01);
  PS2_Cmd(0x43); 
  PS2_Cmd(0x00);
  PS2_Cmd(0x00); 
  PS2_Cmd(0x5A); 
  PS2_Cmd(0x5A); 
  PS2_Cmd(0x5A);
  PS2_Cmd(0x5A); 
  PS2_Cmd(0x5A); 
  CS=1; 
  delay_us(16);
}
// 清除數據緩衝區
void PS2_ClearData() 
{
  u8 a; 
  for(a=0;a<9;a++) 
    {out[a]=0x00;}
}
//手柄震動函數
//motor1:右側小震動電機 0x00關,其他開
//motor2:左側大震動電機 0x40~0xFF,電機開,值越大,震動越大
//只有在初始化函數 void PS2_Init(void)中,對震動電機進行了初始化
//(PS2_VibrationMode();//開啓震動模式),這個函數命令纔會被執行。
void PS2_Vibration(u8 motor1,u8 motor2)
{
   CS=0; 
   delay_us(16); 
   PS2_Cmd(0x01); // 開始命令
   PS2_Cmd(0x42);// 請求數據
   PS2_Cmd(0x00);
   PS2_Cmd(motor1);
   PS2_Cmd(motor2); 
   PS2_Cmd(0x00); 
   PS2_Cmd(0x00); 
   PS2_Cmd(0x00); 
   PS2_Cmd(0x00); 
   CS=1; 
   delay_us(16);
} 
// 得到一個搖桿的模擬量 範圍 0~256 
u8 PS2_AnologData(u8 button) 
{
  return out[button];
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章