PS2采用SPI通信協議
- 原始碼和參考檔案獲取:https://github.com/Sound-Sleep/PS2_Based_On_STM32
接收器介面
- DI:手柄->主機,時鐘的下降沿傳送信號,信號的讀取在時鐘由髙到低的變化程序中完成
- DO:主機->手柄,同步傳送于時鐘的下降沿
- 空埠
- GND
- VDD:3~5V
- CS:低電平被選中
- CLK
- 空埠
- ACK:一般不用
時鐘頻率
250Khz ~ 4us
資料不穩定可以適當增加頻率
通信流程
- 拉低 CS 線電平,并發出一個命令“0x01”
- 手柄會回復它的 ID “0x41=綠燈模式, 0x73=紅燈模式”
- 手柄發送 ID 的同時,單片機將傳送0x42,請求資料
- 手柄發送出 0x5A, 告訴單片機“資料來了”
下面是資料意義對照表,其中idle表示空閑
順序3~8的決議
- 按鍵按下時為0,未按下為1
紅燈模式和綠燈模式
- 紅燈模式:左右搖桿發送模擬值, 0x00?OxFF 之間,且搖桿按下的鍵值 L3、 R3 有效
ID = 0x73 - 綠燈模式:左右搖桿模擬值為無效,推到極限時,對應發送 UP、 RIGHT、 DOWN、LEFT、△、 〇、 X、 □
按鍵 L3、 R3 無效
ID = 0x41
連接使用說明
- 接收器和單片機共用一個電源
- 自動配對
- 未配對的情況下,兩邊的燈都會不停的閃
- 燈常亮則配對成功
- 在一定時間內未搜索到接收器,手柄將進入待機模式
- 待機模式下手柄的燈將滅掉,可以通過“START” 鍵,喚醒手柄,
- 按鍵 “MODE” (“ANALOG”) , 可以選擇紅燈模式和綠燈模式
pstwo.c部分函式詳解
void PS2_Init(void)
初始化GPIO介面
- 介面配置
- DI->PB12
- DO->PB13
- CS->PB14
- CLK->PB15
void PS2_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//使能PORTB時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置 PB13 PB14 PB15 為 通用推挽輸出,速度為50mMhz
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
//配置 PB12 為 下拉輸入模式
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void PS2_Cmd(u8 CMD)
發送資料給PS2的同時接收PS2的資料
- 涉及到的頭檔案
#define DI PBin(12) //PB12 輸入
#define DO_H PBout(13)=1 //命令位高
#define DO_L PBout(13)=0 //命令位低
#define CLK_H PBout(15)=1 //時鐘拉高
#define CLK_L PBout(15)=0 //時鐘拉低
- 涉及到的全域變數
//資料存盤陣列
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
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_TIME;
CLK_L;
DELAY_TIME;
CLK_H;
//若接受到資料,則在對應資料位寫1
if(DI)
Data[1] = ref|Data[1];
}
//發送完八位資料之后延時一段時間
delay_us(16);
}
- ref由0x00000001(8bit)變成0x10000000(8bit),模擬從低位開始的串行通信
- 時鐘電平每次出現一次下降沿,DO_H、DO_L同時發送一bit資料
void PS2_ReadData(void)
讀取手柄資料
- 涉及到的頭檔案
#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 //時鐘拉低
- 涉及到的全域變數
//資料存盤陣列
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
//用于存盤兩個命令,分別是開始命令和請求資料命令
u8 Comd[2]={0x01,0x42};
void PS2_ReadData(void)
{
volatile u8 byte=0;
volatile u16 ref=0x01;
//片選線拉低電平以選中接收器
CS_L;
//發送請求命令和請求資料命令
PS2_Cmd(Comd[0]);
PS2_Cmd(Comd[1]);
//依次讀取陣列Data的后七個位置
for(byte=2;byte<9;byte++)
{
//將資料寫入Data的后七個位置
for(ref=0x01;ref<0x100;ref<<=1)
{
CLK_H;
DELAY_TIME;
CLK_L;
DELAY_TIME;
CLK_H;
if(DI)
Data[byte] = ref|Data[byte];
}
//每發送完八位資料之后延時一段時間
delay_us(16);
}
//拉高片選線電平結束通信
CS_H;
}
- Data[1]用于存盤每次執行PS2_Cmd函式時DI回傳的信號資料了
剩下的Data[2]~Data[9]共7個位置就用來存盤需要回傳單片機處理的有效資料了 - 如果沒有進行任何操作,則Data的后7個位置的每一個位都會被寫入1
u8 PS2_RedLight(void)
判斷是否為紅燈模式,return0則為紅燈模式
紅燈的ID為“0x73”,綠燈的ID為“0x41”
- 涉及到的頭檔案
#define CS_H PBout(14)=1 //CS拉高
#define CS_L PBout(14)=0 //CS拉低
- 涉及到的全域變數
//資料存盤陣列
u8 Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
//用于存盤兩個命令,分別是開始命令和請求資料命令
u8 Comd[2]={0x01,0x42};
u8 PS2_RedLight(void)
{
CS_L;
PS2_Cmd(Comd[0]);
PS2_Cmd(Comd[1]);
CS_H;
//判斷是否是紅燈模式的ID
if( Data[1] == 0X73) return 0 ;
else return 1;
}
- 在發送comd[2],也就是0x42的同時,DI會用8次回圈將ID的每一位回傳到Data[1]中
- Data[1] = 0x73,也就是等于紅燈模式的ID,則return0,否則return1
void PS2_ClearData()
重置Data陣列的所有位
void PS2_ClearData()
{
u8 a;
for(a=0;a<9;a++)
Data[a]=0x00;
}
u8 PS2_DataKey()
回傳按鍵的對應鍵值 ,鍵值用按鍵名的宏去定義
按鍵按下為0,未按下為1
- 涉及到的全域變數
//用于儲存按鍵值
u16 Handkey;
//資料存盤陣列
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
};
- 涉及到的頭檔案宣告
//PS2按鍵鍵值的宏定義
#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
#define PSB_TRIANGLE 13
#define PSB_CIRCLE 14
#define PSB_CROSS 15
#define PSB_SQUARE 16
u8 PS2_DataKey()
{
u8 index;
PS2_ClearData();
PS2_ReadData();
//將所有按鍵對應的位整合成一個16bit的資料
Handkey=(Data[4]<<8)|Data[3];
for(index=0;index<16;index++)
{
//遍歷這個16bit的資料,并回傳被按下按鍵的值,按鍵的值被宏定義
if((Handkey&(1<<(MASK[index]-1)))==0)
return index+1;
}
return 0;
}
- 遍歷Handkey,回傳按鍵對應的鍵值的邏輯如下:
- 首先我們知道按鍵被按下時會朝對應的資料位寫入0,沒被按下則寫入1
- 我們想要檢測被寫入0的位置
- 而任何數&=0都會被清0
- 所以可以用 1&按鍵名在Handkey中對應位 并判斷結果是否為0,從而判斷按鍵是否被按下
- 所以將1左移到與Handkey中的按鍵名的對應位 對齊,進行&操作
- 由于1左移后其他位都為0,所以&了以后其他位都是0,所以整個數字是否為0就取決于按鍵名在Handkey中的對應位是否為0
- 接下來就是設定好1左移的量為(Mask[index] - 1)
u8 PS2_AnologData(u8 button)
回傳搖桿的狀態數值
u8 PS2_AnologData(u8 button)
{
return Data[button];
}
-
不同的button的值所讀取的資料:
- 5:右邊搖桿的X方向
- 6:右邊搖桿的Y方向
- 7:左邊搖桿的X方向
- 8:左邊搖桿的Y方向
-
回傳的搖桿的模擬值在0~255之間
-
x方向最左邊為0,最右邊為255
-
y方向最上方為0,最右邊為255
void PS2_SetInit(void)
手柄配置初始化
void PS2_SetInit(void)
{
PS2_ShortPoll();
PS2_ShortPoll();
PS2_ShortPoll();
PS2_EnterConfing(); //進入配置模式
PS2_TurnOnAnalogMode(); //“紅綠燈”配置模式,并選擇是否保存
//PS2_VibrationMode(); //開啟震動模式
PS2_ExitConfing(); //完成并保存配置
}
- 主函式里要寫在PS_Init( )之后
void PS2_TurnOnAnalogMode(void)
設定發送模式
void PS2_TurnOnAnalogMode(void)
{
CS_L;
PS2_Cmd(0x01); //設定成0x01為紅燈模式,0x00為綠燈模式
PS2_Cmd(0x44);
PS2_Cmd(0X00);
PS2_Cmd(0x01);
PS2_Cmd(0x03); //Ox03鎖存設定,即不可通過按鍵“MODE”設定模式,
//0xEE不鎖存軟體設定,可通過按鍵“MODE”設定模式,
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
PS2_Cmd(0X00);
CS_H;
delay_us(16);
}
- 參考:
- ps2解碼通訊手冊V1.5.pdf
- 對PS2遙控手柄與stm32單片機通信的理解(結合平衡小車之家的說明和程式)_Catherine Pro的博客-CSDN博客
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/540085.html
標籤:嵌入式