2018-01-07

大搖改造-GAMECUBE格鬥搖桿

這次要動刀的是 HORI劍魂2 GAMECUBE版格鬥搖桿
除了改裝成PC用的USB介面之外,順便還換了按鍵跟桿球,加裝旋鈕與類比





拆開底板,本來的電線在框體內收好備用,搖桿底一樣貼上磁鐵


本來的按鍵手感就不太好,後來發現一拆就斷卡榫,只好整套換掉
按鍵換GamerFinger的Cherry軸靜音按鍵,把手邊的軸按過一輪之後決定用銀軸


新按鍵腳位不合,PCB找空位鑽洞,焊上插銷轉接線,整塊板子剛好稍微往下移動一點點,還要把搖桿的端子座改焊到背面,不然會卡到按鍵厚度


角落找個空位塞便宜貨旋鈕,DIY改造把旋轉的段落感移除,外層的旋鈕其實是法蘭聯軸器,家裡隨便找個尺寸剛好的就拿來用了


紫色的磁感應器直接固定在底部四角檔板,以後拆大搖底板不用擔心拉到線



找個角落固定Arduino電路板
拉出4組線分別接到 大搖電路板,旋鈕,磁類比,跟面板的觸控功能開關,USB線從內部用轉接線鎖到外殼

Arduino如果要讀取這隻GAMECUBE搖桿的訊號,只需要接一條線給訊號,另外加上接地,3.3V供電總共三條線,雖然有從GAMECUBE端接出5V電線,但到電路板上之後是空接的,並沒有使用

本來標準尺寸的Arduino有內建3.3V供電,不過我用的這款SparkFun Pro Micro並沒有,但我實測僅靠PWM腳位產生的訊號電流就能供搖桿電路運作 (DutyCycle設定為168)

另外這裡有現成的專案可以讀取GAMECUBE控制器訊號
Arduino Nintendo Library (目前版本1.2.1)
https://github.com/NicoHood/Nintendo

它除了可以讀取控制器訊號之外也能模擬訊號,也支援震動
以此為基礎再加上我之前用過的磁類比與編碼器程式,最後拼湊成這樣
(我覺得剪片其實比改電路還難吧)

轉接到PC之後,搖桿本來的功能齊全,面板有觸控鍵選擇左右類比/方向鍵開關
旋鈕同時輸出類比訊號與滑鼠游標移動,觸控選擇水平或垂直方向
不過可惜的是我一直沒弄清楚如何存取GAMECUBE的C鈕訊號

#include "HID-Project.h"
#include "Nintendo.h"
#include "Encoder.h"

// Define a Gamecube Controller
CGamecubeController GamecubeController1(7);
Encoder encR(0,1);
const int PW_1 = 6; // 電源輸出Pin
const int TOUCH[] = {0,5,4,3,2}; //觸控按鍵
const int AXIS_L[] = {0,A1,A2,310,790,310,810}; //{左類比,OUT1,OUT2,1下限,1上限,2下限,2上限}
const int J = 0x7FF0; // J = 類比範圍/2
int AXIS_R[] = {0,0,1,8,0,5, //{0類比,1A,2B,3按鈕,4新讀值,5滑鼠倍率 )
0,0,10}; //6舊讀值,7歸零計數,8歸零上限}

void setup()
{
//Serial.begin(9600);
Mouse.begin();
Gamepad.begin();
int Pins[] = {0,TOUCH[1],TOUCH[2],TOUCH[3],TOUCH[4],AXIS_L[1],AXIS_L[2],AXIS_R[1],AXIS_R[2],AXIS_R[3]};
for (int p = 1; p <= 9; p++)
{pinMode(Pins[p], INPUT_PULLUP); }
pinMode(PW_1, OUTPUT);
analogWrite(PW_1,168); // 168 for 3.3V Output
}

void loop()
{
if (GamecubeController1.read()) // Try to read the controller data
{ auto report = GamecubeController1.getReport();
sendGamecubeReport(report); } // Send controller data to the USB interface
else
{ Gamepad.end(); } // Center gamepad on disconnect

AXIS_R[4] = encR.read();
if (AXIS_R[4] > AXIS_R[6])
{AXIS_R[7] = 0;
if (AXIS_R[0] == J)
AXIS_R[0] = 0;
else
AXIS_R[0] = J;

if (digitalRead (TOUCH[3]) == LOW ) //右類比,預設關閉
{if (digitalRead (TOUCH[4]) == LOW ) //旋鈕右類比,預設X軸
Mouse.move(AXIS_R[5], 0);
else if (digitalRead (TOUCH[4]) == HIGH ) //旋鈕右類比,切換Y軸
Mouse.move(0, AXIS_R[5]);}
}

else if (AXIS_R[4] < AXIS_R[6])
{AXIS_R[7] = 0;
if (AXIS_R[0] == (J*-1))
AXIS_R[0] = 0;
else
AXIS_R[0] = (J*-1);

if (digitalRead (TOUCH[3]) == LOW ) //右類比,預設關閉
{if (digitalRead (TOUCH[4]) == LOW ) //旋鈕右類比,預設X軸
Mouse.move((AXIS_R[5]*-1), 0);
else if (digitalRead (TOUCH[4]) == HIGH ) //旋鈕右類比,切換Y軸
Mouse.move(0, (AXIS_R[5]*-1));}
}

else //類比歸零計數迴圈
{AXIS_R[7] ++;
if (AXIS_R[7] > AXIS_R[8])
{AXIS_R[7] = 0;
AXIS_R[0] = 0;}
}
AXIS_R[6] = AXIS_R[4]; //回存數值

Serial.print("report=");
Serial.print(encR.read());
Serial.print("\t");
Serial.println("\t");

/* 磁控類比範圍校正用程式
Serial.print("report=");
Serial.print(AXIS_L[3]);
Serial.print("\t");
Serial.print("report=");
Serial.print(AXIS_L[4]);
Serial.print("\t");
Serial.print("report=");
Serial.print(AXIS_L[5]);
Serial.print("\t");
Serial.print("report=");
Serial.print(AXIS_L[6]);
Serial.print("\t");
Serial.println("\t");
AXIS_L[7] = analogRead(AXIS_L[1]);
AXIS_L[8] = analogRead(AXIS_L[2]);
if (AXIS_L[7] < AXIS_L[3])
{AXIS_L[3] = AXIS_L[7];}
else if (AXIS_L[7] > AXIS_L[4])
{AXIS_L[4] = AXIS_L[7];}
if (AXIS_L[8] < AXIS_L[5])
{AXIS_L[5] = AXIS_L[8];}
else if (AXIS_L[8] > AXIS_L[6])
{AXIS_L[6] = AXIS_L[8];}*/

} // End loop

void sendGamecubeReport(Gamecube_Report_t &gc_report)
{
// 8 Gamecube buttons
Gamepad.buttons(0x00UL | (gc_report.buttons0 & 0x1F) | ((gc_report.buttons1 & 0x70) << 1));

if (digitalRead (TOUCH[1]) == LOW) //左類比,預設開啟
{Gamepad.xAxis(constrain((map(analogRead(AXIS_L[1]),AXIS_L[3],AXIS_L[4],(J*-1),J)),(J*-1),J));
Gamepad.yAxis(constrain((map(analogRead(AXIS_L[2]),AXIS_L[5],AXIS_L[6],(J*-1),J)),(J*-1),J));}
else
{Gamepad.xAxis(0);
Gamepad.yAxis(0);}

if (digitalRead (TOUCH[3]) == HIGH) //右類比,預設關閉
{Gamepad.rxAxis(constrain((map(analogRead(AXIS_L[1]),AXIS_L[3],AXIS_L[4],(J*-1),J)),(J*-1),J));
Gamepad.ryAxis(constrain((map(analogRead(AXIS_L[2]),AXIS_L[5],AXIS_L[6],(J*-1),J)),(J*-1),J));}
else
{if (digitalRead (TOUCH[4]) == LOW ) //旋鈕右類比,預設X軸
{Gamepad.rxAxis(AXIS_R[0]);
Gamepad.ryAxis(0); }
else if (digitalRead (TOUCH[0]) == HIGH ) //旋鈕右類比,切換Y軸
{Gamepad.rxAxis(0);
Gamepad.ryAxis(AXIS_R[0]); }
}

if (digitalRead (TOUCH[2]) == HIGH) //十字鍵,預設關閉
{if (gc_report.dup && gc_report.dright)
{Gamepad.dPad1(2); }
else if (gc_report.dup && gc_report.dleft)
{Gamepad.dPad1(8);}
else if (gc_report.ddown && gc_report.dright)
{Gamepad.dPad1(4);}
else if (gc_report.ddown && gc_report.dleft)
{Gamepad.dPad1(6);}
else if (gc_report.dup)
{Gamepad.dPad1(1);}
else if (gc_report.ddown)
{Gamepad.dPad1(5);}
else if (gc_report.dright)
{Gamepad.dPad1(3);}
else if (gc_report.dleft)
{Gamepad.dPad1(7);}
else
{Gamepad.dPad1(0);}}
else
{Gamepad.dPad1(0); }

if (digitalRead (TOUCH[2]) == HIGH && digitalRead (TOUCH[1]) == HIGH) //十字鍵轉按鍵,預設關閉
{if (gc_report.dup)
{Gamepad.press(11);}
if (gc_report.ddown)
{Gamepad.press(27);}
if (gc_report.dright)
{Gamepad.press(20);}
if (gc_report.dleft)
{Gamepad.press(18);}}

if (digitalRead (AXIS_R[3]) == LOW) //右類比旋鈕開關
{Gamepad.press(9); }
else
{Gamepad.release(9); }

Gamepad.write(); // Write the information to the PC
}


6 則留言:

hbj1941 提到...

那克大,请問可以提供你的ARDUINO多軸多按鈕的模擬搖桿的代碼嗎?,我也想DIY看看,磁角感應IC台灣賣的好貴,還是要去淘寶買嗎?

那克魯斯 提到...

code沒什麼整理,總之我先更新上去 XD

hbj1941 提到...

感謝您,我去淘寶淘零件回來試試

那克魯斯 提到...

其實我比較建議你參考這一篇,作用一樣而且code比較單純
https://knuckleslee.blogspot.tw/2017/11/twin-stick-ex.html

hbj1941 提到...

那克大,您有用過stm32板來當多軸搖桿碼,我剛才g了一下,發現stm32好像也可以作,而且比arduino leonardo 小很多,還是這兩個有差別?

那克魯斯 提到...

你可以發現我這兩款實作都用SparkFun Pro Micro,這款體積也很小,你可以參考看看