2022年4月9日 星期六

{04}兩輪智能小車-ESP32 PS3/PS4 遊戲手把控制遙控車

網頁最後修改時間:2022/04/09

相較於用手機螢幕中的虛擬搖桿操作小車,直接用遊戲手把來控制小車,操作感受上有所不同,因此本篇要來介紹怎麼用 PS3/PS4 遊戲手把控制兩輪智能小車。

但要與 PS3/PS4 遊戲手把通訊,除了小車主控晶片要支援藍牙之外,還要特別撰寫與 PS3/PS4 遊戲手把藍牙通訊相關的程式。幸運的是,ESP32 不但支援藍牙通訊且已有封裝好這部分的函式庫可以用,剩下要做的就是,取得 PS3/PS4 遊戲手把的 MAC 地址,撰寫取得需要的按鈕和搖桿的程式後,就可以用來控制小車。

PS3/PS4 遊戲手把上,除了有多顆按鈕之外還有兩個搖桿。有的僅支援數位輸出,也有的不但支援數位輸出也支援類比輸出,而且內建單軸陀螺儀和三軸加速度計能支援體感操作,還能知道電池狀態等...,只要好好利用這些數據資料,就能搭配出不同的控制需求。

** PS3/PS4 遊戲手把除了使用的函式庫不同之外(PS4 功能比較多),本篇所說的步驟幾乎都是相同的,所以就不另外贅述!

本篇內容有:


/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

有購買商品的使用者,網頁中所需相關資料已放置於雲端硬碟,請自行下載使用!

其餘的使用者,請自行依照提供之連結下載相關資料,程式碼複製貼上使用!

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*


*********************************************************************************

更多機器人(多足、智能小車)相關商品,請至分類賣場。

*********************************************************************************



【(01)取得 PS3/PS4 遊戲手把的 MAC Address】

ESP32 要與 PS3/PS4 遊戲手把做藍牙通訊,首先要先知道 PS3/PS4 遊戲手把的 MAC 地址。雖然有很多方法可以用,但是並不是所有的方法、軟體、APP 所取出的 MAC 地址就是有效且可用的。

經過一些的測試之後發現,Sixaxis Pair Tool 所取得的是正確且有效的 MAC 地址。

下載 Sixaxis Pair Tool


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (01-01)軟體操作步驟與使用說明

在沒有插上 PS3/PS4 遊戲手把到電腦之前,安裝好 Sixaxis Pair Tool 之後打開它,會看到 Current Master: 出現 No device found... 的訊息。

插上 PS3/PS4 遊戲手把到電腦之後,若之前沒有安裝過驅動程式,則  Sixaxis Pair Tool 會自動安裝(安裝驅動程式需要一些時間,要耐心等等)。

若沒有驅動程式需要安裝的問題,那麼原本 Current Master: 地方就會出現 Searching... 的訊息,表示現在偵測到有東西插入到電腦的 USB 埠,軟體就會開始進行 PS3/PS4 遊戲手把的搜尋。

如果軟體有抓到 PS3/PS4 遊戲手把,則原本的 Current Master: 就會出現類似下圖 aa:bb:cc:dd:ee:ff 的 MAC 地址,而這就是下面 ESP32 Arduino 程式連線需要的 MAC 地址,要複製下來。

如果是原廠的 SONY PS3/PS4 遊戲手把,那麼若是有多支遊戲手把想要一起用的時候,作為相互之間辨識的最好方法就是修改 MAC 地址。Sixaxis Pair Tool 為此提供了修改 MAC 地址的功能,只要將要修改的 MAC 地址(例如,11:22:33:44:55:66)填入到 Change Master: 的欄位中,按下 "Update" 即可完成變更。

變更完成後,軟體畫面就會類似下圖所示,上下欄位值都會是一樣的(若是重開新的軟體視窗,至少 Current Master: 要是修改後的值 11:22:33:44:55:66)。

確認是否真的變更成功?建議直接拔掉 USB 線再重新插回電腦做確認。這時 Current Master: 會先變 Searching... 再變成 No device found...,插上 USB 線後就應該要出現類似與上圖同樣的 11:22:33:44:55:66 的欄位值,這樣才能保證被修改完成,也才能使用新修改的 MAC 地址,否則就不能用。

** 下面的程式碼使用修改後的 MAC 地址 11:22:33:44:55:66


【(02)PS3/PS4 遊戲手把 ESP32 Arduino 函式庫】

PS4 遊戲手把的函式庫是以 PS3 遊戲手把的函式庫為基礎下去修改的,因此兩個函式庫使用的方法類似且差異不大,詳細可自行比較兩者的範例程式。


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (02-01)函式庫下載

如果有購買小車 ESP32 分類套件,函式庫已預先存放至雲端硬碟中,下載解壓縮即可使用。另外,若想自行下載,下載連結如下。

下載 ESP32 Arduino PS3 遊戲手把函式庫

下載 ESP32 Arduino PS4 遊戲手把函式庫


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (02-02)遊戲手把的數據輸出範圍

了解 PS3/PS4 遊戲手把的輸出數據範圍,對於小車動作的操作至關重要!

除了 "SELECT""START"、中間 "PS"、搖桿 "L3""R3" 這五顆按鈕沒有類比輸出數據之外,對於控制器其他的按鈕來說,輸出不但有數位(0 或 1)的數據,而且也有類比(0 至 255)的數據。

對於遊戲手把的兩個搖桿來說,輸出只有類比數據(-128 ~ 127),但要特別留意不同方向所代表的正負值和範圍。

另外,隱藏於遊戲手把內部的東西:陀螺儀 Z 軸和加速度計三軸的體感應用數據,還有就是鋰電池容量狀態等,都在這支遊戲手把裡。


/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* (02-03)函式庫範例程式測試

打開函式庫的範例程式 Ps3Demo,修改下圖中黃色背景的 MAC 地址。

編譯後上傳到 ESP32 開發板。

打開 "Serial Monitor",重啟 ESP32,最後按下遊戲手把上的 "PS" 按鈕,過幾秒後,遊戲手把就會和 ESP32 完成連線。這時遊戲手把上的四顆 LED 燈,就會開始由 player 1 至 10 輪流亮起。

按下和操作遊戲手把上的按鈕和搖桿,就能看到與下圖類似的訊息和數據的輸出。

關於小車 ESP32 UNO 開發板的 Arduino IDE 安裝版本和編譯選項的設定,請直接看雲端硬碟裡面的圖片照著安裝和設定即可。如果是使用其他不同的 ESP32 開發板,請自行修改相對應的編譯選項,這裡不再做贅述!

如果不清楚 Arduino IDE 如何安裝 ESP32 開發板套件,請看下面連結網頁中的說明。

** Arduino IDE 安裝 ESP32 開發板套件的說明


【(03)遊戲手把的小車動作組合鍵】

根據上一節 Ps3Demo 程式對遊戲手把的測試輸出訊息和數據,我對小車做了下面幾個動作的規劃:

  • 左轉彎(左輪停止,右輪向前/向後)
    按下 "L1" + 左搖桿上下控制小車右輪向前/向後的速度。
  • 右轉彎(右輪停止,左輪向前/向後)
    按下 "R1" + 左搖桿上下控制小車左輪向前/向後的速度。
  • 逆時針(CCW)旋轉(左輪向後,右輪向前)
    按下 "L2" + 左搖桿同時控制小車兩個輪子的速度。
  • 順時針(CW)旋轉(右輪向後,左輪向前)
    按下 "R2" + 左搖桿同時控制小車兩個輪子的速度。
  • 兩個搖桿操作小車的速度和轉向
    左搖桿上下控制小車向前/向後的速度,右搖桿左右控制小車的轉向,兩者相互搭配可控制小車行進的動作。


【(04)遙控車程式框架說明】

有了小車需要的動作組合鍵規劃之後,將這些動作結合 Ps3Demo 範例程式所測試出來的結果,程式可以這樣改寫。

Ps3HotKeyDetect.ino, line: 13 ~ 24, 122 ~ 145

#include <Ps3Controller.h>

//** VARIABLES **//
// Built-in LED
const uint8_t LED_BUILTIN = 2;
#define BUILTIN_LED  LED_BUILTIN
// Offical PS3 Controller
const char* SONY_PS3_CONTROLLER_BT_MAC_ADDRESS = "11:22:33:44:55:66";
// Joystick Deadzone
const int8_t JOYSTICK_DEAD_ZONE = 10;
// others
bool _deviceConnected = false;

void setup() {
    Serial.begin (115200);

    //** 內建 LED 初始化 */
    pinMode (BUILTIN_LED, OUTPUT);      // power led is blue color
    digitalWrite (BUILTIN_LED, LOW);    // turn off red color led

    //** 馬達驅動板初始化
    // code here...
    // 

    //** PS3 Controller 初始化 */
    // MAC address comes from SixacixPairTool
    Ps3.begin (SONY_PS3_CONTROLLER_BT_MAC_ADDRESS);
    Ps3.attachOnConnect (onConnect);
    Serial.print (F("The ESP32's Bluetooth MAC address is: "));
    Serial.println (Ps3.getAddress());
    Serial.println (F("Waiting for PS3 Controller Connection"));
}

void loop() {
    checkHotKey();
    checkBattery();
}

首先,在程式開頭宣告幾個常數和變數。其中,SONY_PS3_CONTROLLER_BT_MAC_ADDRESS 是從 Sixaxis Pair Tool 取得的 MAC 地址,要根據實際的數據填寫;JOYSTICK_DEAD_ZONE 是雙搖桿動作啟動的判斷值,只有在左搖桿上下值加上右搖桿左右值的加總超過此值,雙搖桿控制的動作才會被觸發和被執行。

setup() 就是硬體的初始化。其中,小車馬達驅動的方式,要依照你實際使用的馬達驅動板,將初始化的程式碼加入到這裡面來;另外,加入 onConnect() 事件,監控 PS3 遊戲手把與 ESP32 UNO 開發板是否已連線?

loop() 函式裡,無限循環監視組合鍵(checkHotKey())和電池(checkBattery())的狀態,一但條件達到設定值就會執行相對應的動作。


Ps3HotKeyDetect.ino, line: 26 ~ 40

void onConnect() {
    Serial.println (F("PS3 Controll Connected"));

    // 震動左右馬達
    // Turn rumble on full intensity for 1 second
    Ps3.setRumble (100.0, 1000);
    delay(1000);
    // Turn off rumble
    Ps3.setRumble(0.0);

    _deviceConnected = true;

    // led
    digitalWrite (BUILTIN_LED, HIGH);     // turn on red color led
}

onConnect() 事件一但被觸發,如果遊戲手把有馬達的話,那麼會震動一秒鐘後停止;另外,原本在 ESP32 UNO 閃爍的紅色 LED 也會變成恆亮。



Ps3HotKeyDetect.ino, line: 91 ~ 120

void checkBattery() {
    static uint16_t loopTime        = 850;
    static uint8_t loopCount        = 10;
    static unsigned long lastMillis = 0;
    static int battery              = 0;

    if (millis () - lastMillis > loopTime) {
        if (!_deviceConnected) {
            digitalWrite (BUILTIN_LED, !digitalRead(BUILTIN_LED));
        } else {
            if (loopCount++ >= 10 && battery != Ps3.data.status.battery) {
                battery = Ps3.data.status.battery;
                loopTime = 1;
                Serial.print("The controller battery is ");
                if( battery == ps3_status_battery_charging )        Serial.println("charging");
                else if( battery == ps3_status_battery_full )       Serial.println("FULL");
                else if( battery == ps3_status_battery_high )       Serial.println("HIGH");
                else if( battery == ps3_status_battery_low)         Serial.println("LOW");
                else if( battery == ps3_status_battery_dying )      Serial.println("DYING");
                else if( battery == ps3_status_battery_shutdown )   Serial.println("SHUTDOWN");
                else Serial.println("UNDEFINED");
            }
        }
        lastMillis = millis();
    }

}   // checkBattery()

checkBattery() 函式,每隔 loopTime 檢查一次是否已連線,若沒有,改變 BUILTIN_LED 的開/關狀態,且每隔 10 次 loopTime 檢查一次遊戲手把的電池狀態並輸出。



Ps3HotKeyDetect.ino, line: 42 ~ 92

void checkHotKey() {
    if (!Ps3.isConnected ()) return;
    
    // 左轉彎
    if (Ps3.data.button.l1 && abs(Ps3.data.analog.stick.ly)) {        
        //** 左轉彎程式碼
        // code here...
        //         
        Serial.print (F("Turn Left: ")); Serial.print (Ps3.data.analog.stick.ly, DEC);
        Serial.println();
    }
    // 右轉彎
    else if (Ps3.data.button.r1 && abs(Ps3.data.analog.stick.ly)) {
        //** 右轉彎程式碼
        // code here...
        //     
        Serial.print (F("Turn Right: ")); Serial.print (Ps3.data.analog.stick.ly, DEC);
        Serial.println();
    }
    // 逆時針旋轉
    else if (Ps3.data.button.l2 && abs(Ps3.data.analog.stick.ly)) {
        //** 逆時針旋轉程式碼
        // code here...
        //     
        Serial.print (F("Rotate CCW: ")); Serial.print (Ps3.data.analog.stick.ly, DEC);
        Serial.println();
    }
    // 順時針旋轉
    else if (Ps3.data.button.r2 && abs(Ps3.data.analog.stick.ly)) {
        //** 順時針旋轉程式碼
        // code here...
        // 
        Serial.print (F("Rotate CW: ")); Serial.print (Ps3.data.analog.stick.ly, DEC);
        Serial.println();
    }
    // 左右搖桿一起操作
    else if (abs(Ps3.data.analog.stick.ly) + abs(Ps3.data.analog.stick.rx) > JOYSTICK_DEAD_ZONE) {
        //** 左右搖桿一起控制程式碼
        // code here...
        // 
        Serial.print(" y="); Serial.print(Ps3.data.analog.stick.ly, DEC);
        Serial.print(" x="); Serial.print(Ps3.data.analog.stick.rx, DEC);
        Serial.println();
    } 
    // 其他動作 
    else {
        //** 小車停止程式碼
        // code here...
        // 
    }
}   // checkHotKey()

checkHotKey() 函式裡列出了幾個要偵測的按鍵組合,每個按鍵有它的判斷條件,成立時觸發相對應的小車動作。例如,左轉彎被觸發時,小車左輪停止,小車右輪則根據左搖桿上下決定轉向和轉速:正負值決定轉向向前轉或向後轉,數值則乘以二並限制在 -255 ~ 255 之間, 作為 PWM 值輸出給小車右輪,如下範例程式碼所示。

void motor_turn_left (int8_t speedR){    
    leftDCMotor->setSpeed (0);
    leftDCMotor->run (RELEASE);
    
    if (speedR < 0) {
        rightDCMotor->setSpeed (-map2 (speedR));
        rightDCMotor->run (FORWARD);
    }
    else {
        rightDCMotor->setSpeed (map2 (speedR));
        rightDCMotor->run (BACKWARD);
    }
}

motor_turn_left() 是小車左轉彎的實際程式碼,其中,map2() 的功用是將 speedR 乘以二,並限制範圍在 -255 ~ 255 之間;其他的函式,望文生義不難了解和實現。



全部程式碼如下所示:



【(05)結論】

文中說明了 PS3/PS4 遊戲手把怎麼與 ESP32 進行藍牙通訊,並且取得遊戲手把按鍵的組合。利用這些按鍵的組合,可定義出小車不同的動作。

馬達驅動板有多種形式,但只要掌握轉向和轉速的控制方式,要實現文中沒有特別列出的小車馬達驅動部分程式碼,就很容易!

若有購買 ESP32 系列的小車分類套件,展示影片中的程式碼在雲端硬碟中,請自行下載參照。


.

.


<<部落格相關文章>>


.

.

3 則留言:

  1. Thank you for giving me so much information that I can use. I can't wait for your next blog post. Proteus Design Suite

    回覆刪除
  2. 你好:有個兩輪農用自走車的專案, 想請你合作開發 。如何跟你取得聯繫?

    回覆刪除
    回覆
    1. 您好:可以使用 GMail 電子郵件,或是露天賣場使用露露通直接溝通。

      刪除

留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !