X-Plane コクピットの自作に挑戦!
個別のプログラミング例
まず、このB777 Wordlinerプロジェクトでは5つの種類の部品が使われています。
  • トグルスイッチ
  • タクトスイッチ(大)
  • タクトスイッチ(小)
  • ロータリーエンコーダ
  • LED
ドグルスイッチ
このスイッチはON/OFFとスイッチを移動するたびに切り替わります。
FD_ToggleSW.ino

//トグルスイッチでON/OFF
 
#include <Bounce.h>

const int FDSwPin = 0;
Bounce FDir_toggle_switch = Bounce(FDSwPin, 5);      // ピン0のトグルスイッチ、5msデバウンス
 
FlightSimInteger FlightDirector;
 
elapsedMillis inactivityTimeout;// 無活動タイムアウト
 
 
// setupはTeensyが起動するときに一度だけ実行されます。
//
void setup() {
  // すべてのハードウェアを初期化する
  pinMode(FDSwPin, INPUT_PULLUP);  // 入力がプルアップ・モードの場合これを使う。
  FlightDirector = XPlaneRef("anim/34/switch");  //datarefのパスをここに入れる。 
}
 

void loop() {
  // 通常、loop()の最初のステップは、X-Planeを更新する必要があります。
  FlightSim.update();
 
  // スイッチを押したボタンの状態を読んで、X-Planeコマンドを送る作業をここで行う。
  FDir_toggle_switch.update();
  //スイッチのON/OFFのコントロール
  if (FDir_toggle_switch.fallingEdge()) { //押し下げエッヂを読む
    FlightDirector = 1;          //
    inactivityTimeout = 0;        //無活動タイムアウトをリセット(スイッチのチャタリングを防止するため)
  }
  if (FDir_toggle_switch.risingEdge()) { //押し上げエッヂを読む
    FlightDirector = 0;
  }
}

トグルスイッチは全て同じスクリプトになります。datarefを変更すればそのまま使えます。但し、他のスイッチと同じプラグラム内で使用する場合はTeensyのピン番号、変数名を変更する必要があります。
  • A/T ARM トグルスイッチdataref:anim/33/switch
タクトスイッチ(大:12mm)
このタクトスイッチと言われているプッシュスイッチは押すとON、離すとOFFになり、ONを維持することができません。
コマンドを使用する場合に便利なスイッチになります。PCのキーと同じような原理のスイッチです。
A_P_SW.ino
//タクトスイッチ
#include <Bounce.h>

  elapsedMillis inactivityTimeout;// 無活動タイムアウト
  
  const int APSwPin = 2;
  Bounce APSwitch = Bounce(APSwPin, 5);
  FlightSimInteger AutoPilotSW;

void setup() {
  pinMode(APSwPin, INPUT_PULLUP);
  AutoPilotSW = XPlaneRef("anim/13/button");
}

void loop() {
  FlightSim.update();
 
  APSwitch.update();
  if (APSwitch.fallingEdge()) {
    AutoPilotSW = 2; 
    inactivityTimeout = 0;
  }
  if (APSwitch.risingEdge()) {
    AutoPilotSW = 0;
  }
}

以下のA/Tスイッチは上と違って押し上げたときもONの状態を維持する必要があります。datarefの動きがそうなっています。トグル状態です。
チョット複雑になって以下のようなプログラムになります。
A_T_SW.ino
//タクトスイッチ
//このスイッチは押上げした時点でONを保持する必要があります。普通はOFFになるのでONを維持するために一工夫が必要になります。
//Serial.println(AT_SwPin_state)でシリアルモニタを使うと「1」が表示されるので理解できると思う。

  #include <Bounce.h>

  elapsedMillis inactivityTimeout;// 無活動タイムアウト

  const int AT_SwPin = 4;
  Bounce AT_Switch = Bounce(AT_SwPin, 5); 
  
  int AT_SwPin_val = 0; 
  int AT_SwPin_old_val = 0;   //古い
  int AT_SwPin_state = 0;
  
  FlightSimInteger AT_SW;
  
void setup() {   
  Serial.begin(9600); 
  
  pinMode(AT_SwPin, INPUT_PULLUP); //BUTTONは入力に設定 
  AT_SW = XPlaneRef("anim/15/button");
}

void loop() {
  FlightSim.update();
  
  //スイッチの保持設定スクリプト。
  AT_SwPin_val = digitalRead(AT_SwPin);         //ピンの状態、HIGHかLOWを読む
  if((AT_SwPin_val == LOW) && (AT_SwPin_old_val == HIGH)) {  //最初のピン状態がLOWで古いピン状態がHIGHなら(これはタクトスイッチの押下、押上を実行した状態)
    AT_SwPin_state = 1 - AT_SwPin_state;        // 1-0=1
    //delay(10);     //チャタリングが起きるとき使用。
     Serial.println(AT_SwPin_state) ;
  } 
  
  AT_SwPin_old_val = AT_SwPin_val;  //AT_SwPin_valは0、AT_SwPin_old_valは1になる。
 
  if(AT_SwPin_state == 1){   //もしAT_SwPin_stateが1なら
    AT_SW = 1;            //datarefの値を1にする
  } else { 
    AT_SW = 0;            //datarefの値を0にする
  }
}
タクトイッチは単純な単一のdatarefだけで動作していて、ほとんどはこのタイプのプログラムが使われています。但し、他のスイッチと同じプラグラム内で使用する場合はTeensyのピン番号、変数名を変更する必要があります。
  • LNAV タクトイッチdataref:anim/16/button
  • VNAV タクトイッチdataref:anim/17/button
  • FLCH タクトイッチdataref:anim/18/button
  • IAS/MACH チェンジ タクトイッチdataref:anim/19/button
  • TOGA タクトイッチdataref:anim/149/button
  • DISC タクトイッチdataref:anim/176/button
  • HDG BANK LIMIT 切り替えタクトイッチdataref:anim/176/button
  • HDG HOLD タクトイッチdataref:anim/22/button
  • HDG/TRK 切り替え タクトイッチdataref:anim/68/switch
  • V/S FPA タクトイッチdataref:anim/24/button
  • V/S FPA 切り替え タクトイッチdataref:anim/23/button
  • ALT HOLD タクトイッチdataref:anim/25/button
  • LOC タクトイッチdataref:anim/26/button
  • APP タクトイッチdataref:anim/27/button
  • AUTO 1000 セレクト タクトイッチdataref:anim/69/switch

この中でdatarefをどうしても見つけることができないスイッチがあります。

  • CLB CON タクトイッチdataref:不明(どのdatarefが動作しているのかがわからない。)使い方もよくわかりません。ほとんどの場合、このスイッチは使用しないと書いてあったので無視しています。
  • LNAVの右横にある小さなスイッチ、これは名前すら書いてないがスイッチを押すと動作はしています。
ロータリーエンコーダ(プッシュスイッチ付き)
Speed_RotaryEncoder.ino

  //プッシュ付きロータリーエンコーダ、B777Wordliner。

  #include <Encoder.h>
  #include <Bounce.h>
  
  elapsedMillis inactivityTimeout;// 無活動タイムアウト
 
  //===============================================================================================
  /////////// AirSeed エンコーダ //////////////////
  //===============================================================================================
  const int AirSpEncR_pin = 9;      //エンコーダ teensy5ピンを使用
  const int AirSpEncL_Pin = 10;     //エンコーダ teensy6ピンを使用
  Encoder AirSpEnc(AirSpEncR_pin, AirSpEncL_Pin);  
  short AirSpEncPrev = 0;  //エンコーダの逆回転
  FlightSimInteger AirSpeed;        
  FlightSimInteger AirSpeedknob;    //AirSpeedのノブを回転させるためだけの専用
  //===============================================================================================
  /////////// AirSeed エンコーダ プッシュスイッチ //////////////////
  //===============================================================================================
  const int AirSpeed_EncPush_SwPin = 11;
  int AirSpeed_EncPush_SwPin_val = 0; 
  int AirSpeed_EncPush_SwPin_old_val = 0; 
  int AirSpeed_EncPush_SwPin_state = 0; 
  FlightSimInteger AirSpeed_EncPush_SW;

void setup() {
  //===============================================================================================
  /////////// setup内 エアースピード エンコーダ  //////////////////
  //===============================================================================================
  pinMode(AirSpEncR_pin, INPUT_PULLUP);  //エンコーダ
  pinMode(AirSpEncL_Pin, INPUT_PULLUP);  //エンコーダ
    AirSpeed = XPlaneRef("T7Avionics/ap/spd_act");  //encoder
    AirSpeedknob = XPlaneRef("anim/1/spacial");  //ノブを回転させるだけのために
  //===============================================================================================
  /////////// setup内 エアースピード エンコーダ プッシュスイッチ  //////////////////
  //===============================================================================================
  pinMode(AirSpeed_EncPush_SwPin, INPUT_PULLUP); //BUTTONは入力に設定 
  AirSpeed_EncPush_SW = XPlaneRef("anim/186/button");
}

void loop() {
  FlightSim.update();
  //==============================================================================================
  /////////// loop内 AirSpeed エンコーダ //////////////////
  //==============================================================================================
  // 1クリックで4つ変化するので4で割って1つずつカウントするようにする
  short AirSpEncDiff = (AirSpEnc.read() - AirSpEncPrev) / 4;
  
  if (AirSpEncDiff) {
    // only update prev when we've reached a detent!
    //戻り止めに達した場合にのみPREVを更新!
    AirSpEncPrev = 0;
    AirSpEnc.write(0);
  
    // datarefを一時的な値にコピー
    float airSp = AirSpeed;
    float airSpkn = AirSpeedknob;
  
    // 一時的な値に変更を適用
    airSp += AirSpEncDiff;
    airSpkn += AirSpEncDiff;
  
    // 範囲チェックを行う
    while (airSp < 100) airSp += 1;  //エアスピードが 100 より小さい場合は 1 をプラスする(つまり100で止まる)
    while (airSp >= 400) airSp -= 1;  //エアスピードが 400 より大きい場合は 1 をマイナスする(つまり399で止まる)
  
    // datarefへ検証された新しいエアースピードの数値を書く
    AirSpeed = airSp;
    AirSpeedknob = airSpkn;
  }
  //==============================================================================================
  /////////// loop内 Part-A AirSpeed エンコーダ プッシュスイッチ //////////////////
  //==============================================================================================
  //スイッチの保持設定スクリプト。
  AirSpeed_EncPush_SwPin_val = digitalRead(AirSpeed_EncPush_SwPin); 
  if((AirSpeed_EncPush_SwPin_val == LOW) && (AirSpeed_EncPush_SwPin_old_val == HIGH)) { 
    AirSpeed_EncPush_SwPin_state = 1 - AirSpeed_EncPush_SwPin_state;  
    //delay(10); //この位がチャタリングが起きない(500位が安定している)
  } 
  AirSpeed_EncPush_SwPin_old_val = AirSpeed_EncPush_SwPin_val; //valはもう古くなったので保持しておく 
  if(AirSpeed_EncPush_SwPin_state ==1){ 
    AirSpeed_EncPush_SW = 1;
  } else { 
    AirSpeed_EncPush_SW = 0;
  } 
}
LED
LED点灯のスクリプトで、このA/Pランプは一つのなdatarefだけを参照することで実現します。
他のランプはこんなに単純ではないので、別の方法を考えなけらばなりません。
そして、プログラム内の共通スクリプト部分は74HC595(LED表示)シフトレジスタを動かすためのもので、これを1つ入れておけばLEDのON/OFFは全て使えます。
A_P_LED.ino

  //75HC595を使ったLED用 

  #include <LedControl.h>
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  /////////// 74HC595(LED表示)シフトレジスタ 共通スクリプト ///////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  int SER_dataPin = 43;   //pin 14(DS-data) on the 75HC595
  int RCLK_latchPin = 44;  //pin 12(ST_CP-latch) on the 75HC595
  int SRCLK_clockPin = 45; //pin 11(SH_CP-clock) on the 75HC595

  #define number_of_74hc595s 3    //シフトレジスタ74HC595の数
  #define numOfRegisterPins number_of_74hc595s * 8    //ここは変更しないこと
  boolean registers[numOfRegisterPins];  //上のレジスタの数を反映させる共通スクリプト
  
  void clearRegisters(){
    for(int i = numOfRegisterPins - 1; i >=  0; i--){
       registers[i] = LOW;  //最初にLEDを全消灯するため(LOWは全点灯)、
                           //確認の為にスタート時にLEDをON,完全にX-Planeが起動したらOFFになる。
    }
  }

  void writeRegisters(){
    digitalWrite(RCLK_latchPin, LOW);
    for(int i = numOfRegisterPins - 1; i >=  0; i--){
      digitalWrite(SRCLK_clockPin, LOW);
      int val = registers[i];
      digitalWrite(SER_dataPin, val);
      digitalWrite(SRCLK_clockPin, HIGH);
    }
    digitalWrite(RCLK_latchPin, HIGH);
  }
  // 個々のピンをHIGかLOWに設定する
  void setRegisterPin(int index, int value){  //indexでピン位置、valueでLOWかHIGH
    registers[index] = value;
  }
  //////// 共通ここまで ///////////////////////////////
  

  //==============================================================================================
  /////////// Part-A LED表示(74HC595)シフトレジスタ A/P ランプ//////////////////
  //==============================================================================================
  const int AP_74HC595_Pin = 0;
  FlightSimInteger AutoPilotSwLED; 

void setup() {
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  // setup内 シフトレジスタ  共通スクリプト //
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  pinMode(SER_dataPin, OUTPUT);
  pinMode(RCLK_latchPin, OUTPUT);
  pinMode(SRCLK_clockPin, OUTPUT);
  clearRegisters();  //すべてのレジスタピンをリセット
  writeRegisters();
  //////// 共通スクリプト ここまで ///////////////////////
 
  AutoPilotSwLED = XPlaneRef("T7Avionics/ap/ap_mode");
    AutoPilotSwLED.onChange(updateAutoPilotSwLED);
}


void loop() {
  FlightSim.update();
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  // loop内 74HC595 シフトレジスタ 共通 //
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  writeRegisters();  //変更内容を表示するために呼び出さなければなりません
                     //必要な値が設定された後に一度だけ呼び出します。
  //////// loop内 シフトレジスタ 共通 END ////////////////
 
}

  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  /////////// 関数 LEDピン 74HC595(出力)シフトレジスタ経由 ///////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////
  void updateAutoPilotSwLED(long value) {
    if (value == 1)  {      //T7Avionics/ap/ap_modeを見ている★
      setRegisterPin(AP_74HC595_Pin, HIGH);    //点灯
    } 
    else if (value == 3)  { 
      setRegisterPin(AP_74HC595_Pin, HIGH);    //点灯 
    }
    else {
      setRegisterPin(AP_74HC595_Pin, LOW);     //消灯
    }
  }