Nゲージ電子化ステップ1(ソフトウェア編)

Nゲージ

マスコンハンドルとブレーキハンドルを使用したコントローラの自作

 Arduino Unoボードのソフトウェアの開発環境はArduino IDEを使用しており、以下のリンクからダウンロードできる。

https://www.arduino.cc/en/software

 PWM方式のモータ制御では、駆動開始時のうなり音が問題となる可能性があり、デフォルト設定ではPWM信号の周波数は約490Hzです(5番ポート及び6番ピンは約980Hzで出力)。人間の可聴周波数は20~20,000Hzと言われているので、本試作では62,528Hzに変更しています。

 ただし、実際の車両の様に動き出しのモータ音やVVVF的なうなり音が好きな人は低めのPWM周波数に設定するのもありかも知れません。

 マスコンとブレーキレバー位置をリアルタイムに読み取り、車両モータの駆動電圧を制御するArduino用ソフトウェアを以下に示します。

 マスコンとブレーキレバーによる制御は、あくまで個人的感覚に基づいたもので、実際とは異なるのでご了承願います。

// Nゲージ用コントローラ
// 電車でGo用コントローラ(セガサターン)を改造
// アクセル:アクセルハンドル用エンコーダ出力
// ブレーキ:ブレーキハンドル用ボリューム(電圧値のA/D変換)又はエンコーダ出力
// アクセルハンドルの位置情報に対応した加速定数 ACCEL_COEFF[0:5]
// ブレーキハンドルの位置情報に対応した減速定数 BRAKE_COEFF[0:14]
// 惰性運転(アクセル=ニュートラル & ブレーキ=ニュートラル)時の減速定数 INERTIA_DECE_COEFF
// アクセル及びブレーキの特性をリニア、常時加わる惰性運転時の減速定数を一定と仮定すると
// アクセルハンドル位置 i, ブレーキ位置 jの時の電圧値(0~255)V(k)は
// V(k) = V(k-1) +/- (ACCEL_COEFF[i] - BRAKE_COEFF[j] - INERTIA_DECE_COEFF)
// V(k)の最大値は255

//#define DC15V_MODE

// Accel input pin//
const int ACCEL_IN_0_PIN = 2;         // アクセル入力0ピンを2(デジタル入力)
const int ACCEL_IN_1_PIN = 3;         // アクセル入力1ピンを3(デジタル入力)
const int ACCEL_IN_2_PIN = 4;         // アクセル入力2ピンを4(デジタル入力)

// Brake input pin
const int BRAKE_IN_0_PIN = 8;         // ブレーキ入力0ピンを5(デジタル入力)
const int BRAKE_IN_1_PIN = 9;         // ブレーキ入力1ピンを6(デジタル入力)
const int BRAKE_IN_2_PIN = 10;        // ブレーキ入力2ピンを7(デジタル入力)
const int BRAKE_IN_3_PIN = 11;        // ブレーキ入力3ピンを8(デジタル入力)

// 正転、逆転選択ボタン入力
const int SELECT_IN_PIN = 12;         // SELECTボタン入力ピン(デジタル入力)
// LED output
const int LED_OUT_PIN = 13;           // LED出力ピン(デジタル出力)

// Hブリッジ(L298N) output
const int IN2_OUT_PIN = 7;            // Hブリッジ IN2出力ピン(デジタル出力)
const int IN1_OUT_PIN = 6;            // Hブリッジ IN1出力ピン(デジタル出力)
const int ENA_OUT_PIN = 5;            // Hブリッジ ENA出力ピン(デジタル出力)

// 加速、減速、慣性係数
#ifdef DC15V_MODE
// for 15V DC Power Unit
const float ACCEL_COEFF[] = {0.0, 0.48, 0.8, 1.12, 1.44, 1.68};
const float BRAKE_COEFF[] = {0.0, 0.48, 0.8, 1.12, 1.44, 1.68, 1.92, 2.16, 2.4, 2.4, 2.4, 2.4, 2.4, 2.4, 25.6};
const float INERTIA_DECE_COEFF = 0.16;
// 最大、最小リミットPWM数
const float VMIN = 0.0;
const float VMAX = 200.0;

// 各アクセル値に於ける最大値
const float ACC_1_MAX = 40.0;
const float ACC_2_MAX = 80.0;
const float ACC_3_MAX = 120.0;
const float ACC_4_MAX = 160.0;

// アクセル初期値
const float accel_init = 48.0;

#else
// for 12V DC Power Unit
const float ACCEL_COEFF[] = {0.0, 0.7, 1.0, 1.4, 1.8, 2.1};
const float BRAKE_COEFF[] = {0.0, 0.7, 1.0, 1.4, 1.8, 2.1, 2.4, 2.7, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 32.0};
const float INERTIA_DECE_COEFF = 0.2;
// 最大、最小リミットPWM数
const float VMIN = 0.0;
const float VMAX = 255.0;

// 各アクセル値に於ける最大値
const float ACC_1_MAX = 100.0;
const float ACC_2_MAX = 120.0;
const float ACC_3_MAX = 150.0;
const float ACC_4_MAX = 200.0;

// アクセル初期値
const float accel_init = 60.0;
#endif

// 変数定義
int accel;                            // アクセル値(3bitのデジタル入力を整数化 0~5)
int brake;                            // ブレーキ値(4bitのデジタル入力を整数化 0~8/9~14:非常)
int accel_prev;                       // 変化前のアクセル値
int brake_prev;                       // 変化前のビレーキ値
float accel_const;                    // アクセルの加速定数
float brake_const;                    // ブレーキの減速定数
float voltage_level;                  // 出力電圧レベルフロート計算値
int vlevel;                           // 出力電圧レベル
bool led_out;                         // 正転、逆転選択表示(逆転時に点灯)
bool reversal;                        // 正転、逆転選択値
int select_in;                        // 正転、逆転選択値
int select_in_prev;                   // 前回正転、逆転選択値
bool acc_dec;                         // アクセル方向(false:加速、true:減速)
 
void setup(){
  pinMode(ACCEL_IN_0_PIN, INPUT);     // 2番ピンを入力に設定
  pinMode(ACCEL_IN_1_PIN, INPUT);     // 3番ピンを入力に設定
  pinMode(ACCEL_IN_2_PIN, INPUT);     // 4番ピンを入力に設定
  
  pinMode(BRAKE_IN_0_PIN, INPUT);     // 8番ピンを出力に設定
  pinMode(BRAKE_IN_1_PIN, INPUT);     // 9番ピンを出力に設定
  pinMode(BRAKE_IN_2_PIN, INPUT);     // 10番ピンを出力に設定
  pinMode(BRAKE_IN_3_PIN, INPUT);     // 11番ピンを出力に設定

  pinMode(SELECT_IN_PIN, INPUT_PULLUP); // 12番ピンを入力に設定
  pinMode(LED_OUT_PIN, OUTPUT);       // 13番ピンを出力に設定

  pinMode(IN2_OUT_PIN, OUTPUT);       // 5番ピンを出力に設定
  pinMode(IN1_OUT_PIN, OUTPUT);       // 6番ピンを出力に設定
  pinMode(ENA_OUT_PIN, OUTPUT);       // 7番ピンを出力に設定

  // シリアルモニタボーレート定義
  Serial.begin(9600);

  // アクセル、ブレーキ値初期化
  accel = 0;
  brake = 0;
  // 過去アクセル、ブレーキ値初期化
  accel_prev = 0;
  brake_prev = 0;
  acc_dec = false;

  // 電圧レベル初期値
  voltage_level = 0.0;

  // 正転、逆転選択値(初期値:正転=false)
  select_in = LOW;
  select_in_prev = LOW;
  reversal = false;

 // PWM信号の周波数を62.528KHzに変更
  TCCR0B &= B11111000;
  TCCR0B |= B00000001;// r=1の場合 977 x 64 = 62.528KHz
}
 
void loop(){
  // エンコードアクセル値を読み取り
  int accel0 = digitalRead(ACCEL_IN_0_PIN);   // デジタル値の読み取り
  int accel1 = digitalRead(ACCEL_IN_1_PIN);   // デジタル値の読み取り
  int accel2 = digitalRead(ACCEL_IN_2_PIN);   // デジタル値の読み取り

  // アクセル値をデコード
  if ((accel2 == LOW) || (accel1 == LOW) || (accel0 == LOW)) {
    if      ((accel2 == LOW)  && (accel1 == LOW)  && (accel0 == LOW))  accel = 0;         // not use
    else if ((accel2 == LOW)  && (accel1 == LOW)  && (accel0 == HIGH)) accel = 0;         // Stop
    else if ((accel2 == LOW)  && (accel1 == HIGH) && (accel0 == LOW))  accel = 1;         // Accel 1
    else if ((accel2 == LOW)  && (accel1 == HIGH) && (accel0 == HIGH)) accel = 2;         // Accel 2
    else if ((accel2 == HIGH) && (accel1 == LOW)  && (accel0 == LOW))  accel = 3;         // Accel 3
    else if ((accel2 == HIGH) && (accel1 == LOW)  && (accel0 == HIGH)) accel = 4;         // Accel 4
    else if ((accel2 == HIGH) && (accel1 == HIGH) && (accel0 == LOW))  accel = 5;         // Accel 5
    
    //Serial.print("Accel: ");
    //Serial.println(accel);
  }

  // アクセルを切りから1に変更した時を検出
  if ((accel == 1) && (accel_prev == 0)) voltage_level = accel_init;

  // エンコードブレーキ値を読み取り
  int brake0 = digitalRead(BRAKE_IN_0_PIN);   // デジタル値の読み取り
  int brake1 = digitalRead(BRAKE_IN_1_PIN);   // デジタル値の読み取り
  int brake2 = digitalRead(BRAKE_IN_2_PIN);   // デジタル値の読み取り
  int brake3 = digitalRead(BRAKE_IN_3_PIN);   // デジタル値の読み取り

  // ブレーキ値をデコード
  if (!((brake3 == LOW) && (brake2 == LOW) && (brake1 == LOW) && (brake0 == LOW))) {
    if      ((brake3 == LOW)  && (brake2 == LOW)   && (brake1 == LOW)   && (brake0 == LOW))  brake = -1;       // nc
    else if ((brake3 == LOW)  && (brake2 == LOW)   && (brake1 == LOW)   && (brake0 == HIGH)) brake = 0;        // brake open
    else if ((brake3 == LOW)  && (brake2 == LOW)   && (brake1 == HIGH)  && (brake0 == LOW))  brake = 1;        // brake 1
    else if ((brake3 == LOW)  && (brake2 == LOW)   && (brake1 == HIGH)  && (brake0 == HIGH)) brake = 2;        // brake 2
    else if ((brake3 == LOW)  && (brake2 == HIGH)  && (brake1 == LOW)   && (brake0 == LOW))  brake = 3;        // brake 3
    else if ((brake3 == LOW)  && (brake2 == HIGH)  && (brake1 == LOW)   && (brake0 == HIGH)) brake = 4;        // brake 4
    else if ((brake3 == LOW)  && (brake2 == HIGH)  && (brake1 == HIGH)  && (brake0 == LOW))  brake = 5;        // brake 5
    else if ((brake3 == LOW)  && (brake2 == HIGH)  && (brake1 == HIGH)  && (brake0 == HIGH)) brake = 6;        // brake 6
    else if ((brake3 == HIGH) && (brake2 == LOW)   && (brake1 == LOW)   && (brake0 == LOW))  brake = 7;        // emergency
    else if ((brake3 == HIGH) && (brake2 == LOW)   && (brake1 == LOW)   && (brake0 == HIGH)) brake = 8;        // emergency
    else if ((brake3 == HIGH) && (brake2 == LOW)   && (brake1 == HIGH)  && (brake0 == LOW))  brake = 9;        // emergency
    else if ((brake3 == HIGH) && (brake2 == LOW)   && (brake1 == HIGH)  && (brake0 == HIGH)) brake = 10;       // emergency
    else if ((brake3 == HIGH) && (brake2 == HIGH)  && (brake1 == LOW)   && (brake0 == LOW))  brake = 11;       // emergency
    else if ((brake3 == HIGH) && (brake2 == HIGH)  && (brake1 == LOW)   && (brake0 == HIGH)) brake = 12;       // emergency
    else if ((brake3 == HIGH) && (brake2 == HIGH)  && (brake1 == HIGH)  && (brake0 == LOW))  brake = 13;       // emergency
    else if ((brake3 == HIGH) && (brake2 == HIGH)  && (brake1 == HIGH)  && (brake0 == HIGH)) brake = 14;       // emergency

    brake_prev = brake;
  }
  
  // アクセル加速定数を設定
  accel_const = ACCEL_COEFF[accel];

  // ブレーキ減速定数を設定
  brake_const = BRAKE_COEFF[brake];

  // 各アクセル値の上限を設定
  if      ((accel == 1) && (voltage_level > ACC_1_MAX)) acc_dec = true;
  else if ((accel == 2) && (voltage_level > ACC_2_MAX)) acc_dec = true;
  else if ((accel == 3) && (voltage_level > ACC_3_MAX)) acc_dec = true;
  else if ((accel == 4) && (voltage_level > ACC_4_MAX)) acc_dec = true;
  else                                                  acc_dec = false;

  // 電圧レベルの計算
  if (acc_dec)  voltage_level = voltage_level - accel_const - brake_const - INERTIA_DECE_COEFF;
  else          voltage_level = voltage_level + accel_const - brake_const - INERTIA_DECE_COEFF;

  // 電圧レベルの飽和処理
  if (voltage_level > VMAX)    voltage_level = VMAX;
  else if (voltage_level < VMIN) voltage_level = VMIN;
  
  // 電圧値を整数に
  vlevel = int(voltage_level);
  
  // モーター制御出力
  // 非常ブレーキ
  if (brake == 14) {
    // 停止設定
    digitalWrite(IN1_OUT_PIN, HIGH);       // IN1出力
    digitalWrite(IN2_OUT_PIN, HIGH);       // IN2出力
  }
  else {
    if (vlevel == 0) {
      // 停止設定(惰性運転)
      digitalWrite(IN1_OUT_PIN, LOW);      // IN1出力
      digitalWrite(IN2_OUT_PIN, LOW);      // IN2出力
    }
    else {
        if (!reversal) {
          // 正転設定
          digitalWrite(IN1_OUT_PIN, HIGH);  // IN1出力
          digitalWrite(IN2_OUT_PIN, LOW);   // IN2出力
        }
        else {
          // 逆転設定
          digitalWrite(IN1_OUT_PIN, LOW);   // IN1出力
          digitalWrite(IN2_OUT_PIN, HIGH);  // IN2出力
        }
    }
  }
  // PWM(ENA)へ出力
  analogWrite(ENA_OUT_PIN, vlevel);         // PWM出力

  // 正転、逆転選択入力(SELECTボタン入力)
  select_in = digitalRead(SELECT_IN_PIN);
  Serial.println(select_in);
  
  if ((select_in == LOW) && (select_in_prev == HIGH) && (vlevel == 0)){
    reversal = !reversal;
  }
  select_in_prev = select_in;

  // アクセル値を保存
  accel_prev = accel;
    
  Serial.print("reversal: ");
  Serial.print(reversal);
  Serial.print("  Voltage: ");
  //Serial.print(voltage_level);
  Serial.print(vlevel);
  Serial.print("  Accel: ");
  Serial.print(accel);
  Serial.print("  Brake: ");
  Serial.println(brake);

// 正転、逆転表示
  digitalWrite(LED_OUT_PIN, reversal);       // LED出力
  
  // 遅延 100msec
  delay(100);
 
}

以上のソフトウェアをArduino Unoボードに書き込んで実際動作させた動画も貼っておきます。因みに線路中央のコントローラは、最初に試作した可変電圧レギュレータを組み込んだコントローラです。

 線路のポイント切り替えに対応したソフトウェアの追加分を下記に示す。

.
.
// ポイント切り替え信号A
const int A_BUTTON_IN_PIN = A1;       // Aボタン入力ピン(デジタル入力)

// ポタン A LED
const int A_BUTTON_OUT_PIN = A0;      // Aボタン出力ピン(デジタル出力)

// Hブリッジ(L298N) output for point
const int IN4_OUT_PIN = A3;           // Hブリッジ IN4出力ピン(デジタル出力)
const int IN3_OUT_PIN = A2;           // Hブリッジ IN3出力ピン(デジタル出力)

int a_button_in;                      // ポイント切り替え用Aボタン
int a_button_in_prev;                 // 前ポイント切り替え用Aボタン値
bool point_a_select;                  // ポイントA切り替え信号
bool point_a_select_prev;             // 前ポイントA切り替え信号
.
.

void setup(){
.
.

  pinMode(A_BUTTON_IN_PIN, INPUT_PULLUP); // A0番ピンを入力に設定
  pinMode(A_BUTTON_OUT_PIN, OUTPUT);  // A1番ピンを出力に設定
  pinMode(IN4_OUT_PIN, OUTPUT);       // 1番ピンを出力に設定
  pinMode(IN3_OUT_PIN, OUTPUT);       // 0番ピンを出力に設定

  // ポイント切り替え値(初期値:ストレート)
  a_button_in = LOW;
  a_button_in_prev = LOW;

  digitalWrite(IN3_OUT_PIN, LOW);   // IN3出力
  digitalWrite(IN4_OUT_PIN, LOW);   // IN4出力
.
.
}

void loop(){
.
.
  // ポイント切り替えボタン制御
  a_button_in = digitalRead(A_BUTTON_IN_PIN);
  //Serial.println(a_button_in);
  
  if ((a_button_in == LOW) && (a_button_in_prev == HIGH)){
    point_a_select = !point_a_select;
  }
  a_button_in_prev = a_button_in;

  // ポイント切り替え ボタンA選択
  if (point_a_select && !point_a_select_prev) {
    // 曲線切り替え
    digitalWrite(IN3_OUT_PIN, HIGH);  // IN3出力
    digitalWrite(IN4_OUT_PIN, LOW);   // IN4出力
    // 遅延 800msec  ※500msでは短すぎる
    delay(800);
    digitalWrite(IN3_OUT_PIN, LOW);   // IN3出力
    digitalWrite(IN4_OUT_PIN, LOW);   // IN4出力
  }
  else if (!point_a_select && point_a_select_prev) {
    // 直線切り替え
    digitalWrite(IN3_OUT_PIN, LOW);   // IN3出力
    digitalWrite(IN4_OUT_PIN, HIGH);  // IN4出力
    // 遅延 1000msec
    delay(1000);
    digitalWrite(IN3_OUT_PIN, LOW);   // IN3出力
    digitalWrite(IN4_OUT_PIN, LOW);   // IN4出力
  }

  point_a_select_prev = point_a_select;
.
.
}

 +12V電源で2つのポイント切り替えを行うには、1000ms(500msでは短い)程度の期間正負電圧を印加する必要がある(常時印加すると、ポイント切り替え部が加熱され温度リミッター作動する)。

コメント

タイトルとURLをコピーしました