自製Switch Pro相容遊戲控制器(三):Joystick程式庫的類別方法說明

Joystick.h標頭檔宣告的Joystick_類別定義了下列公用方法,本文將解析它們的原始碼,並且透過一個簡易的任天堂Switch遊戲機的相容控制器範例,介紹它們的用法。

  • void begin(bool initAutoSendState = true):設定是否自動傳送按鍵狀態報告,預設為true(是)。
  • void end():結束;無實質作用
  • void pressButton(uint8_t button):按下某按鍵;把代表該按鍵的資料值設成1。
  • void releaseButton(uint8_t button):放開某按鍵;把代表該按鍵的資料值設成0。
  • void setButton(uint8_t button, uint8_t value):設定某按鍵的值,若輸入值為0,代表則執行releaseButton()方法,否則執行pressButton()方法。
  • void setHatSwitch(int16_t value):設定十字鍵的狀態值。
  • void setXAxis(int8_t value):設定左搖桿X軸值。
  • void setYAxis(int8_t value):設定左搖桿Y軸值。
  • void setZAxis(int8_t value):設定右搖桿X軸值。
  • void setZAxisRotation(int16_t value):設定右搖桿Y軸值。
  • void sendState():傳送遊戲控制器的HID報告。

透過bitSet()與bitClear()設置與清除位元

Joystick程式庫原始碼定義了一個uint32_t(無號32位元整數,也就是正整數)類型的buttons變數,用來儲存手把上的14個按鍵的狀態(其實14個按鍵只需要16位元空間儲存,所以改用uint16_t類型也行)。

儲存手把上的14個按鍵狀態的buttons變數

buttons變數的特定位元值,可透過Arduino程式語言內建的bitSet()和bitClear()兩個函式來設定或清除:

bitSet(資料變數, 編號):把「資料變數」的「編號」位置值設定成1,「資料變數」為整數類型。例如,底下敘述將把buttons變數的第2個位元(代表A鍵)設成1,其餘值不變:

bitSet(buttons, 2);

bitClear(資料變數, 編號):清除「資料變數」的「編號」位置,也就是將該值設定成0。例如,底下敘述將把buttons變數的第7個位元(代表ZR鍵)設為0,其餘值不變:

bitClear(buttons, 7);

底下是Joystick程式庫的pressButton(), releaseButton()和setButton()的原始碼:

void Joystick_::pressButton(uint8_t button){  // 按下按鍵
    bitSet(buttons, button);
    // 如果執行begin()時,沒有傳入false參數值,
    // 則底下的條件式將被執行,代表每次按下按鍵,
    // 就傳送此遊戲控制器的報告
    if (autoSendState)
        sendState();
}

void Joystick_::releaseButton(uint8_t button) {  // 放開按鍵
    bitClear(buttons, button);
    if (autoSendState)
        sendState();
}

void Joystick_::setButton(uint8_t button, uint8_t value) {
    if (value == 0) {
        releaseButton(button);  // 放開按鍵
    } else {
        pressButton(button);    // 按下按鍵
    }
}

設定十字鍵(HAT)的值

十字鍵的狀態值存在帶正負號16位元整數類型的hatSwitch變數中:

int16_t hatSwitch;

其預設值為-1,代表「未按下」,請參閱上一篇貼文說明。設定十字鍵狀態值的方法是setHatSwitch(),它將把收到的值存入hatSwitch變數:

void Joystick_::setHatSwitch(int16_t value) {
    hatSwitch = value;
    if (autoSendState)
        sendState();
}

設定兩個類比搖桿的值

兩個類比搖桿的X, Y方向值分別暫存在底下4個變數,類型是帶正負號的整數,可接受-127~127的輸入值:

int8_t xAxis;  // 左搖桿X
int8_t yAxis;  // 左搖桿Y
int8_t zAxis;  // 右搖桿X
int16_t zAxisRotation;  // Z軸旋轉;右搖桿Y。

設定這些軸的方法的原始碼如下,處理後的每個軸的資料值都是介於0~255(參閱下一節)。

void Joystick_::setXAxis(int8_t value) {  // 左搖桿X,接受正負整數值。
    xAxis = value;
    if (autoSendState)
        sendState();
}

void Joystick_::setYAxis(int8_t value) {  // 左搖桿Y
    yAxis = value;
    if (autoSendState)
        sendState();
}

void Joystick_::setZAxis(int8_t value) {  // 右搖桿X
    zAxis = value;
    if (autoSendState)
        sendState();
}

void Joystick_::setZAxisRotation(int16_t value) {  // 右搖桿Y
    zAxisRotation = value;
    if (autoSendState)
        sendState();
}

傳送遊戲控制器的HID報告

傳送報告的sendState()方法原始碼如下:

void Joystick_::sendState(){
    // JOYSTICK_STATE_SIZE(報告數據大小)的值為7
    uint8_t data[JOYSTICK_STATE_SIZE];
    uint32_t buttonTmp = buttons;   // 暫存14個按鍵的資料

    // 14個按鍵資料拆分成兩個位元組
    data[0] = buttonTmp & 0xFF;   // 低位元組存入data[0]
    buttonTmp >>= 8;            // 把高位元組右移到低位元
    data[1] = buttonTmp & 0xFF;   // 高位元組存入data[1]

    if (hatSwitch < 0)             // 若十字鍵的值為-1…
        hatSwitch = 8;           // 設成8

    // 不管十字鍵的高4位元的值為何,存入data[2]。
    data[2] = (B00001111 & hatSwitch);

    // 類比搖桿值+127,讓資料介於0~255。
    data[3] = xAxis + 127;
    data[4] = yAxis + 127;
    data[5] = zAxis + 127;
    data[6] = zAxisRotation + 127;

    HID().SendReport(data[0], data + 1, JOYSTICK_STATE_SIZE);
}

上面的程式中,原本兩個類比搖桿、四個軸的值介於-127~127(參閱下文「Switch Pro相容控制器的Arduino原始碼說明」),加上127之後變成介於0~254。最後一行透過Arduino IDE內建的HID程式庫物件的SendReport()方法送出HID報告,其語法格式如下:

HID物件. SendReport(報告編號, 報告資料陣列, 長度)

若要在Windows系統上使用此遊戲控制器,請將送出HID報告的敘述改成:

HID().SendReport(JOYSTICK_REPORT_ID, data,  JOYSTICK_STATE_SIZE);

要在任天堂Switch遊戲機上使用此控制器,送出HID報告的敘述的「報告編號」要改成第1筆報告數據,報告資料從第2筆開始:

HID().SendReport(data[0], data + 1, JOYSTICK_STATE_SIZE);

Switch Pro相容控制器的Arduino原始碼說明

Switch Pro相容控制器的Arduino原始碼引用了兩個程式庫,一個負責讀取Wii Classic Controller(經典手把)的狀態,一個將手把的資料轉成HID報告傳給Switch遊戲機。

Switch手把的程式庫

Wii經典手把的類比搖桿比較特別,兩個搖桿的數值範圍不一樣,而且中間值也不同。

Wii經典手把的類比搖桿

這個搖桿程式的原始碼透過底下的函式,將左類比搖桿的水平和垂直軸以15, 25, 35和45為分界,對應成這些值:-127, -64, 0, 64和127。

int leftStick(uint8_t input) {
   if  (input > 45)
      return 127;
   else if (input > 35)
      return 64;
   else if (input < 15)
      return -127;
   else if (input < 25)
      return -64;
   return 0;
}

右類比搖桿的水平和垂直軸以8, 12, 20和24為分界,對應成這些值:-127, -64, 0, 64和127。

int rightStick(uint8_t input) {
   if  (input > 24)
      return 127;
   else if (input > 20)
      return 64;
   else if (input < 8)
      return -127;
   else if (input < 12)
      return -64;
   return 0;
}

一般的類比搖桿模組(像下圖這一款),垂直軸往上感測值會遞減、往下遞增(假設類比數位轉換器的解析度是10位元,最高值為1023)。

類比搖桿模組(

經典手把的類比搖桿則相反:垂直軸往上感測值會遞增、往下遞減。所以loop()函式中,設置左右搖桿Y軸的值要乘上-1:

Joystick.setXAxis(leftStick(classic.leftJoyX()));
Joystick.setYAxis(leftStick(classic.leftJoyY())  * -1);  // 反相垂直軸的值 
Joystick.setZAxis(rightStick(classic.rightJoyX()));
Joystick.setZAxisRotation(rightStick(classic.rightJoyY())  * -1);  // 反相垂直軸的值

自製任天堂Switch遊戲機的相容控制器

我們可以用其他元件替換這個Switch Pro相容控制器的手把,像是自己組裝的類比搖桿和按鍵。Arduino也曾在2012年底發表遊戲控制器造型的開發板“Arduino Esplora”,當年也有一款同樣採用Atmega32u4微控器的”MaKey MaKey”開發板上市(參閱「寓教於樂的Arduino:MaKey MaKey, Arduino Leonardo以及Arduino Esplora控制板」),它們都可以透過Joystick程式庫連接任天堂Switch遊戲機。

Arduino Esplora控制板

底下是簡單的Switch遊戲機相容控制器實驗,連接兩個微觸開關當作A和B鍵,一個類比搖桿模組當作「左」搖桿。兩個微觸開關分別數位2和3腳,開關的另一腳接地。類比搖桿的X和Y分別接到A0和A1腳。

Switch遊戲機相容控制器實驗電路

這個實驗只需要引用Joystick程式庫,程式碼如下。由於開關接腳啟用內部上拉電阻,所以平時輸入值是高電位,按下開關時變成低電位。類比搖桿的原始值介於0~1023,透過map()函式轉換成0~255。

#include <Joystick.h>

void setup() {
  pinMode(2, INPUT_PULLUP);  // 啟用上拉電阻
  pinMode(3, INPUT_PULLUP);
  Joystick.begin(false);     // 不要自動送出HID報告
}

void loop() {
  int leftX = analogRead(A0);
  int leftY = analogRead(A1);

  Joystick.setXAxis(map(leftX, 0, 1023, 0, 255));  // 左搖桿X
  Joystick.setYAxis(map(leftY, 0, 1023, 0, 255));  // 左搖桿Y,不用乘上-1
  Joystick.setZAxis(127);          // 右搖桿X,保持在中間。
  Joystick.setZAxisRotation(127);  // 右搖桿Y,保持在中間。

  Joystick.setButton(2, !digitalRead(2));   // A鍵,取相反值
  Joystick.setButton(1, !digitalRead(3));   // B鍵,取相反值
  Joystick.setButton(0, 0);   // Y鍵,始終輸入0。

  for (byte i = 3; i < 11; i++) {  // 其餘11個按鍵,始終輸入0。
    Joystick.setButton(i, 0);
  }

  Joystick.setHatSwitch(-1);   // 十字鍵,保持「未按下」。
  Joystick.sendState();        // 送出手把的HID報告。
  delay(1);
}

上文提到,Joystick程式庫的類比搖桿輸入值介於-127~127,程式庫會將它加上127轉換成0~254。我覺得這個步驟有點多餘,所以把程式庫的sendState()方法的這段敘述:

data[3] = xAxis + 127;
data[4] = yAxis + 127;
data[5] = zAxis + 127;
data[6] = zAxisRotation + 127;

改成:

data[3] = xAxis;
data[4] = yAxis;
data[5] = zAxis;
data[6] = zAxisRotation;

Joystick.h標頭檔的這些儲存類比軸的私有屬性的類型,也從原本的int8_t改成正整數類型:

uint8_t xAxis;
uint8_t yAxis;
uint8_t zAxis;
uint8_t zAxisRotation;

Joystick.cpp程式中,使用到這些變數的程式碼也要調整,例如,設定Z軸旋轉的setZAxisRotation()方法,原本接收int16_t類型值,改成uint8_t:

void Joystick_::setZAxisRotation(uint8_t  value) {
   zAxisRotation  = value;
   if (autoSendState)
      sendState();
}

修改完畢後,編譯並上傳程式到Leonardo板,即可透過類比搖桿以及A和B鍵操控Switch遊戲機。

Posts created 483

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top