STM32與PS2的無線通訊和相關函式介紹
阿新 • • 發佈:2022-12-11
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部落格