個人名片: 對人間的熱愛與歌頌,可抵歲月冗長:sun_with_face: Github👨🏻💻:念舒_C.ying CSDN主頁✏️:念舒_C.ying 個人博客:earth_asia: :念舒_C.ying 1 配置環境 掛載系統ISO,並配置好本地dnf源。(前面的文章講過,這裡就略過. ...
PS2採用SPI通信協議
接收器介面
- 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博客