2017-11-25

大搖改造-Twin Stick EX追加類比輸出


一般大搖的搖桿結構就像上圖一樣,只有四個開關,不像類比動多少算多少
有些遊戲因此不適合用大搖來玩,甚至有人在訂製大搖上加裝香菇頭以增加遊戲相容性
但我覺得這樣看起來很蠢,上面不是已經有搖桿了嗎,為什麼不好好利用呢?
這篇要講的是如何將利用磁力幫搖桿增加真正的類比訊號

我推薦的作法是完全不更動大搖本身的電路,也不動搖桿的結構
新增一塊Arduino控制板來處理類比信號輸出,再跟本來的電路板作訊號整合
電腦端可使用x360ce程式整合,遊戲機則使用GIMX轉接器

首先在搖桿的桿底部貼一顆圓片狀的磁鐵,尺寸跟桿直徑差不多,方便固定就好
一般格鬥用大搖(例如三和搖桿)的桿底部會有換桿頭用的刻痕,把磁鐵吸在這裡即可


我曾經試過大顆的強力磁鐵,尺寸跟底部的E型環大小相同
磁鐵吸上去就定位在正中間,不用擔心激烈操作後偏移,非常方便但結果不能用



可能是磁力太強或形狀的關係,大磁鐵位移的數值變化非常詭異,無法正確判讀位置
最後還是使用直徑7mm/厚1mm的小磁鐵,並點上白膠幫助固定


然後用透明膠帶幫助核對位置,把磁感模組貼在搖桿正下方的底板
我的搖桿底部到底板距離約14mm,如果框體太高的話可以把磁感模組墊高到適當距離


我使用的是長江智動科技的CJMCU-93模組,IC為MLX90333 3D霍爾感測器 $28 CNY
它可以偵測附近磁鐵的位置,模組已有標示X軸與Y軸並拉線到輸出接腳,使用方便


當操作搖桿時,桿底的磁鐵同時會在磁感模組上方晃動,提供非接觸式的類比信號輸出
使用Arduino類比腳位讀取訊號並平滑化,轉換數值為模擬搖桿輸出就完成了


電路板推薦使用Arduino Leonardo或SparkFun Pro Micro並安裝模擬手把程式庫
模組擺位確定之後先用Serial.print觀察X/Y軸的數值範圍,設定為類比搖桿數值範圍
數值平滑化可以參考內建範例Analog>Smoothing,不然搖桿沒動時訊號也會飄來飄去

固定模組與控制板的位置,並拉線到框體外,因為是Twin Stick所以裝兩片模組


(我知道那個洞挖得有點難看 XD)

改裝後結果
由Arduino模擬為4軸雙類比手把,與原本大搖的按鍵整合就很適合玩一些3D動作遊戲
例如移動方向的操作就會精確得多,我自己試過Sonic與Bayonetta,效果很好
如果想要讓此功能與現有的電路板直接整合後再輸出,應該也能利用Arduino轉介訊號
框體外再加裝一個開關控制Arduino啟動與否,或用來關閉微動訊號也很方便
不過我目前不須要作這麼高度的整合,哪天有需要用的時候會再來研究

底下是影片中使用的程式 by Knuckleslee
#include <Joystick.h> //模擬0鍵遊戲手把,4個類比軸
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD, 0, 0,
true, true, false, true, true, false, false, false, false, false, false);

//數值平滑化
const int axisReadings = 10; //陣列取樣數
int Xreadings[axisReadings]; // 從腳位讀取數值
int XreadIndex = 0; // 目前讀值
int Xtotal = 0; // the running total
int Xaverage = 0; // 平均值

int Yreadings[axisReadings]; // 從腳位讀取數值
int YreadIndex = 0; // 目前讀值
int Ytotal = 0; // the running total
int Yaverage = 0; // 平均值

int Rxreadings[axisReadings]; // 從腳位讀取數值
int RxreadIndex = 0; // 目前讀值
int Rxtotal = 0; // the running total
int Rxaverage = 0; // 平均值

int Ryreadings[axisReadings]; // 從腳位讀取數值
int RyreadIndex = 0; // 目前讀值
int Rytotal = 0; // the running total
int Ryaverage = 0; // 平均值

void setup() {
Serial.begin(9600);
// initialize all the readings to 0:
for (int thisXReading = 0; thisXReading < axisReadings ; thisXReading++) {
Xreadings[thisXReading] = 0; }
for (int thisYReading = 0; thisYReading < axisReadings ; thisYReading ++) {
Yreadings[thisYReading ] = 0; }
for (int thisRxReading = 0; thisRxReading < axisReadings ; thisRxReading ++) {
Rxreadings[thisRxReading ] = 0; }
for (int thisRyReading = 0; thisRyReading < axisReadings ; thisRyReading ++) {
Ryreadings[thisRyReading ] = 0; }

pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(A2, INPUT);
pinMode(A3, INPUT);

Joystick.begin();
Joystick.setXAxisRange(400,600);
Joystick.setYAxisRange(580,390);
Joystick.setRxAxisRange(650,400);
Joystick.setRyAxisRange(600,370);
}

void loop() {

//
Xtotal = Xtotal - Xreadings[XreadIndex]; // subtract the last reading:
Xreadings[XreadIndex] = analogRead(A0); // read from the sensor:
Xtotal = Xtotal + Xreadings[XreadIndex]; // add the reading to the Xtotal:
XreadIndex = XreadIndex + 1; // advance to the next position in the array:
if(XreadIndex >= axisReadings ) // if we're at the end of the array...
{
XreadIndex = 0; // ...wrap around to the beginning:
}
Xaverage = Xtotal / axisReadings ; // calculate the Xaverage:
//
Ytotal = Ytotal - Yreadings[YreadIndex]; // subtract the last reading:
Yreadings[YreadIndex] = analogRead(A1); // read from the sensor:
Ytotal = Ytotal + Yreadings[YreadIndex]; // add the reading to the Ytotal:
YreadIndex = YreadIndex + 1; // advance to the next position in the array:
if(YreadIndex >= axisReadings ) // if we're at the end of the array...
{
YreadIndex = 0; // ...wrap around to the beginning:
}
Yaverage = Ytotal / axisReadings ; // calculate the Yaverage:
//
Rxtotal = Rxtotal - Rxreadings[RxreadIndex]; // subtract the last reading:
Rxreadings[RxreadIndex] = analogRead(A2); // read from the sensor:
Rxtotal = Rxtotal + Rxreadings[RxreadIndex]; // add the reading to the Rxtotal:
RxreadIndex = RxreadIndex + 1; // advance to the next position in the array:
if(RxreadIndex >= axisReadings ) // if we're at the end of the array...
{
RxreadIndex = 0; // ...wrap around to the beginning:
}
Rxaverage = Rxtotal / axisReadings ; // calculate the Rxaverage:
//
Rytotal = Rytotal - Ryreadings[RyreadIndex]; // subtract the last reading:
Ryreadings[RyreadIndex] = analogRead(A3); // read from the sensor:
Rytotal = Rytotal + Ryreadings[RyreadIndex]; // add the reading to the Rytotal:
RyreadIndex = RyreadIndex + 1; // advance to the next position in the array:
if(RyreadIndex >= axisReadings ) // if we're at the end of the array...
{
RyreadIndex = 0; // ...wrap around to the beginning:
}
Ryaverage = Rytotal / axisReadings ; // calculate the Ryaverage:
//

Joystick.setXAxis(Xaverage);
Joystick.setYAxis(Yaverage);
Joystick.setRxAxis(Rxaverage);
Joystick.setRyAxis(Ryaverage);

//讀值校正
Serial.print("A0=");
Serial.print(analogRead(A0));
Serial.print("\t");
Serial.print("Xaverage=");
Serial.print(Xaverage);
Serial.print("\t");
Serial.print("A1=");
Serial.print(analogRead(A1));
Serial.print("\t");
Serial.print("Yaverage=");
Serial.print(Yaverage);
Serial.print("\t");
Serial.print("A2=");
Serial.print(analogRead(A2));
Serial.print("\t");
Serial.print("Rxaverage=");
Serial.print(Rxaverage);
Serial.print("\t");
Serial.print("A3=");
Serial.print(analogRead(A3));
Serial.print("\t");
Serial.print("Ryaverage=");
Serial.print(Ryaverage);
Serial.print("\t");

Serial.println("\t");
}

沒有留言: