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 位),這些數據是依次按位傳送。
注意的是:
-
CS線在通訊期間拉低,通信過程中CS信號線在一串數據(9個字節,每個字節爲8位)發送完畢後纔會拉高,而不是每個字節發送完拉高。
-
DO、DI在在CLK時鐘的下降沿完成數據的發送和讀取。
下降沿:數字電平從高電平(數字“1”)變爲低電平(數字“0”)的那一瞬間叫作下降沿。 -
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];
}