STM32與PS2的無線通訊和相關函數介紹

2022-12-16 12:00:35

PS2採用SPI通訊協定


接收器介面

  1. DI:手柄->主機,時鐘的下降沿傳送訊號,訊號的讀取在時鐘由髙到低的變化過程中完成
  2. DO:主機->手柄,同步傳送於時鐘的下降沿
  3. 空埠
  4. GND
  5. VDD:3~5V
  6. CS:低電平被選中
  7. CLK
  8. 空埠
  9. 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);
}