一、硬體準備:戰艦開發板、PS2手柄接收器、PS2手柄、連接線

二、硬體連接:
PS2手柄接收器有六個引腳,和單片機連接IO口連接,如下圖:
| 接收器信號 | 單片機IO |
|---|---|
| GND | GND |
| VCC | 3.3V |
| DI/DAT | PB12 |
| DO/CMD | PB13 |
| CS | PB14 |
| CLK | PB15 |

三、PS2通信簡介
通訊時序如下,感覺和SPI很像,也是四線
DI與DO是一對同時傳輸的8 bit串行資料,傳輸的時候需要CS為低電平,CLK由高變低,
DO是單片機發送給接收器的信號,
DI是接收器發送給單片機的信號,

第一點:CS在資料輸出或者輸入的時候,都是低電平的,那么我們在資料傳輸的時候先把CS拉高再拉低,然后資料進行傳輸,傳輸完成之后再把CS拉高,
第二點:DI(Data Input)與DO(Data Output)是同時完成的,說明這是全雙工通信,串口是全雙工通信,IIC是半雙工通信,
第三點:在時鐘上降沿的時候,DI和DO的資料有交叉,也就是說資料進行交換(資料只有0和1),這個時候我們是不能夠讀和寫資料的,因為資料還不穩定,我們讀到的資料不準確,在時鐘為下降沿的時候,資料已經穩定了,我們在這個時候開始讀和寫資料,
第四點:由于是從0到7,可以知道有8位資料,并且是從低位到高位進行讀寫,我們可以把資料放到陣列中,一個時鐘進行一個資料位(也可以叫做位元位0或1)傳輸,

時鐘頻率250KHZ(4us),資料不穩定可適當增加頻率,
當單片機發送0x01時,接收器會回復它的ID“0X41表示綠燈模式”、“0x73表示紅燈模式”;在手柄發送ID的同時,單片機將發送0X42,手柄會發送0X5A,高速單片機資料來了,
上表中的idle表示資料線空閑,該資料線無資料傳送,
所以Data[0]、Data[1]、Data[2]不能用來存放PS2搖桿的按鍵
Data[3]、Data[4]用來存放按鍵的值
Data[5]、Data[6]、Data[7]、Data[8]用來存放搖桿的模擬量
當有按鍵下,對應位為0,其他位為1
譬如,當SELECT按下,Data[3]=1111 1110B
當L3按下,Data[3]=1111 1101B
當R3按下,Data[3]=1111 1011B
當START按下,Data[3]=1111 0111B
當UP按下,Data[3]=1110 1111B
當RIGHT按下,Data[3]=1101 1111B
當DOWN按下,Data[3]=1011 1111B
當LEFT按下,Data[3]=0111 1111B
手柄有兩個模式,紅燈模式(手柄亮紅燈+綠燈)和綠燈模式(手柄只亮綠燈),可以通過按下MODE按鍵進行切換,


紅燈模式:
1.按鍵L3/R3按下有效
2.推動左右搖桿,根據行程不一樣,可輸出0x00-0xff的模擬量
綠燈模式:
1.按鍵L3/R3按下有效
2.左右搖桿不輸出模擬量,推動到上下左右的極限值,左搖桿實作的效果和UP/DOWN/RIGHT/LEFT一樣,右搖桿實作的效果和和△/X/□/○一樣,
設定震動模式后:
WW用來控制右側的小電機,0x00表示關,其他值為開,
YY用來控制左側的大電機,0x40-0xff表示電機開,值越大,震感越強烈;其他值表示電機關,
四、代碼分析
1.配置IO口,將PB12設定為下拉輸入;PB13/14/15設定為推挽輸出
void PS2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//輸入 DI->PB12
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); //使能PORTB時鐘
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //設定成上拉、下拉、浮空輸入皆可
GPIO_Init(GPIOB, &GPIO_InitStructure);
//輸出 DO->PB13 CS->PB14 CLK->PB15
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //設定成推挽輸出
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
2.定義3個陣列
u8 Comd[2]={0x01,0x42}; //開始命令,請求資料
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //資料存盤陣列
//每個按鍵對應一個數值
u16 MASK[]={
PSB_SELECT,
PSB_L3,
PSB_R3 ,
PSB_START,
PSB_PAD_UP,
PSB_PAD_RIGHT,
PSB_PAD_DOWN,
PSB_PAD_LEFT,
PSB_L2,
PSB_R2,
PSB_L1,
PSB_R1 ,
PSB_GREEN,
PSB_RED,
PSB_BLUE,
PSB_PINK
};
3.給PB12/PB13/PB14/PB15這4個IO的狀態均進行宏定義
#define DI PBin(12) //PB12 輸入
#define DO_H PBout(13)=1 //命令位高
#define DO_L PBout(13)=0 //命令位低
#define CS_H PBout(14)=1 //CS拉高
#define CS_L PBout(14)=0 //CS拉低
#define CLK_H PBout(15)=1 //時鐘拉高
#define CLK_L PBout(15)=0 //時鐘拉低
4.單片機向手柄發送命令
//向手柄發送命令
void PS2_Cmd(u8 CMD)
{
volatile u16 ref=0x01;
Data[1] = 0;
for(ref=0x01;ref<0x0100;ref<<=1)
{
if(ref&CMD)
{
DO_H; //輸出以為控制位
}
else DO_L;
CLK_H; //產生時鐘
delay_us(50);
CLK_L;
delay_us(50);
CLK_H;
if(DI)
Data[1] = ref|Data[1];
}
}
//
假如單片機給接收器發送0x01=0000 0001B,
接收器接受到0x01后給單片機發送了0x41=0100 0001B,
ref=0x01=0000 0001B;
Data[1] = 0;
CMD=0000 0001B
DI=0100 0001B
/
從低位到高位執行8次回圈
第一次回圈:ref=0000 0001B
if(ref&CMD)為真,輸出PB13=1
if(DI)為真,輸出Data[1] = ref|Data[1]=0000 0001|0000 0000=0000 0001
第二次回圈:ref=0000 0010B
if(ref&CMD)為假,輸出PB13=0
if(DI)為假,不執行操作
第三次回圈:ref=0000 0100B
if(ref&CMD)為假,輸出PB13=0
if(DI)為假,不執行操作
第四次回圈:ref=0000 1000B
if(ref&CMD)為假,輸出PB13=0
if(DI)為假,不執行操作
第五次回圈:ref=0001 0000B
if(ref&CMD)為假,輸出PB13=0
if(DI)為假,不執行操作
第六次回圈:ref=0010 0000B
if(ref&CMD)為假,輸出PB13=0
if(DI)為假,不執行操作
第七次回圈:ref=0100 0000B
if(ref&CMD)為假,輸出PB13=0
if(DI)為真,輸出Data[1] = ref|Data[1]=0100 0000|0000 0001=0100 0001
第八次回圈:ref=1000 0000B
if(ref&CMD)為假,輸出PB13=0
if(DI)為假,不執行操作
/
所以最后的結果就是
單片機將0x01按位發送了出去
接識訓發送的資料0x41保存到了Data[1]里面
a…volatile修飾符可以保證ref每次開始都是0x01即0000 0001B
b.ref=0x01;ref<0x0100;ref<<=1理解這句首先需要將十六進制改為二進制,即ref=0000 0001B;ref<0000 0001 0000 000B;ref<<=1即將ref=0x01每次左移一位,回圈八次,
c.ref&CMD即可以通過與運算回圈八次,將CMD 這個八位二進制的數按位發送出去,
d.CLK電平進行高-低-高可以產生一個周期,同時產生一個下降沿,在這個程序中,DO將信號從單片機(發送)給接收器(接收),DI將信號從接收器(發送)給單片機(接收)
e.單片機接收到的資料被保存在了Data[1]里面

5.判斷手柄是紅燈模式還是綠燈模式,通過單片機給手柄發送0x01 0x42后,手柄回傳的值來判斷,如果回傳的是0X41表示"綠燈模式"、0x73表示"紅燈模式"
//判斷是否為紅燈模式
//回傳值;0,紅燈模式
//回傳值;1,綠燈模式
u8 PS2_RedLight(void)
{
CS_L;
PS2_Cmd(Comd[0]); //開始命令0x01
PS2_Cmd(Comd[1]); //請求資料0x42
CS_H;
if( Data[1] == 0X73) return 0 ;
else return 1;
}
5.單片機接收手柄資料
//讀取手柄資料
void PS2_ReadData(void)
{
volatile u8 byte=0;
volatile u16 ref=0x01;
CS_L;
PS2_Cmd(Comd[0]); //開始命令0x01
PS2_Cmd(Comd[1]); //請求資料0x42
for(byte=2;byte<9;byte++) //開始接受資料
{
for(ref=0x01;ref<0x100;ref<<=1)
{
CLK_H;
CLK_L;
delay_us(50);
CLK_H;
if(DI)
Data[byte] = ref|Data[byte];
}
delay_us(50);
}
CS_H;
}
a.資料傳輸必須在CS拉低期間進行,資料傳輸完成后,還要將CS拉回高電平,以便下一次的通訊,
b.單片機發送了0x01 0x42給手柄,此時手柄會回傳0x5A給單片機,意味著接收到了請求,即將回傳資料,所以Data[2]保存的就是手柄回傳的0x5A,后面的Data[3]-Data[8]回傳的都是按鍵和搖桿的狀態資訊,
6.檢測按鍵狀態
//對讀出來的PS2的資料進行處理 只處理了按鍵部分 默認資料是紅燈模式 只有一個按鍵按下時
//按下為0, 未按下為1
u8 PS2_DataKey()
{
u8 index;
PS2_ClearData();
PS2_ReadData();
Handkey=(Data[4]<<8)|Data[3]; //這是16個按鍵 按下為0, 未按下為1
for(index=0;index<16;index++)
{
if((Handkey&(1<<(MASK[index]-1)))==0)
return index+1;
}
return 0; //沒有任何按鍵按下
}
a.上圖有給手柄按鍵標號,一共16個按鍵,包括兩個搖桿,不包括MODE鍵,16個按鍵剛好是兩個八位二進制數,所以用Data[3]和Data[4]表示所有的按鍵的狀態,
b.u16 Handkey,通過這個變數定義可以看出來Handkey是一個16位的二進制數,Handkey=(Data[4]<<8)|Data[3]表示的是Handkey這個變數的高八位是Data[4],低八位是Data[3]

假如我SELECT按下了,那么Data[3]=1111 1110B
如果L2按下了,那么Data[4]=1111 1110B
所以Handkey表示的就是1111 1110 1111 1110B
c.for(index=0;index<16;index++)因為有16個按鍵,所以進行16次回圈,以此判斷是被按下的是哪個鍵,
if((Handkey&(1<<(MASK[index]-1)))==0)這個函式由內到外分析0<=index<=15,所以1<=MASK[index]<=16,所以0<=MASK[index]-1)<=15,然后將1換算成16位的二進制數為:0000 0000 0000 0001B,所以1<<(MASK[index]-1)就是每次左移一位,回圈15次,然后和Handkey進行與運算,如果為0,則說明按鍵被按下,最后index+1是因為index是從0開始算的,而按鍵是從1開始計算的,所以最后回傳的值需要+1,但這個函式只能判斷單個按鍵按下,如果有多個按鍵按下,只能檢測按鍵數較小的那個值,譬如方向上(5)和START(4)同時被按下,則回傳值就是4,
#define PSB_SELECT 1
#define PSB_L3 2
#define PSB_R3 3
#define PSB_START 4
#define PSB_PAD_UP 5
#define PSB_PAD_RIGHT 6
#define PSB_PAD_DOWN 7
#define PSB_PAD_LEFT 8
#define PSB_L2 9
#define PSB_R2 10
#define PSB_L1 11
#define PSB_R1 12
#define PSB_GREEN 13
#define PSB_RED 14
#define PSB_BLUE 15
#define PSB_PINK 16
u16 MASK[]={
PSB_SELECT,
PSB_L3,
PSB_R3 ,
PSB_START,
PSB_PAD_UP,
PSB_PAD_RIGHT,
PSB_PAD_DOWN,
PSB_PAD_LEFT,
PSB_L2,
PSB_R2,
PSB_L1,
PSB_R1 ,
PSB_GREEN,
PSB_RED,
PSB_BLUE,
PSB_PINK
};
7.檢測搖桿的狀態
#define PSS_RX 5
#define PSS_RY 6
#define PSS_LX 7
#define PSS_LY 8
//得到一個搖桿的模擬量 范圍0~256
u8 PS2_AnologData(u8 button)
{
return Data[button];
}
a.四個搖桿反饋的是模擬量,范圍在0x00~0xFF,轉換成十進制即為0-255,搖桿的數值存放在Data[5]、Data[6]、Data[7]、Data[8]中
b.注意只有紅燈模式下搖桿才反饋模擬量,綠燈模式下搖桿不反饋模擬量,
8.其他函式
8.1清除資料緩沖
//清除資料緩沖區
void PS2_ClearData()
{
u8 a;
for(a=0;a<9;a++)
Data[a]=0x00;
}
8.2手柄震動函式
/******************************************************
motor1:右側小震動電機 0x00關,其他開
motor2:左側大震動電機 0x40~0xFF 電機開,值越大 震動越大
******************************************************/
void PS2_Vibration(u8 motor1, u8 motor2)
{
CS_L;
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_H;
delay_us(16);
}
8.3發送模式設定
a.第八行PS2_Cmd(0x01)則為紅燈模式;PS2_Cmd(0x00)則為綠燈模式;
b.第九行PS2_Cmd(0x03)則只可以通過第八行的指令進行紅綠燈模式切換;PS2_Cmd(0xEE)則可以通過按MODE進行紅綠燈模式切換,
//發送模式設定
void PS2_TurnOnAnalogMode(void)
{
CS_L;
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_H;
delay_us(16);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/357205.html
標籤:其他
