SSブログ

ds-dccデコーダーをVVVF音っぽい音が出るPWMコントローラにする [ds-DCCデコーダ]

 題名だが、今回、本当にやりたかったことは、「ds-DCCデコーダをVVVF音が出るデコーダにする」だ。しかし、ちょっとゴールが遠かった。だから、まだ道半ば。
 DCC(デジタル・コマンド・コントロール)館 様の
DCCからでも、VVVF音 をds-DCCデコーダでもやりたかったが、一日では無理だった。
 ステップとしては、
(1)モーターから音をだせる事を確認する。
(2)モーターから音階を出せるようにする。
(3)VVVF音チック名PWMコントローラを作る。
(4)(3)のソースをds-DCCデコーダに乗せれば、VVVF音を奏でるモーター車が完成という流れだ。
 で、今回は(3)まで出来たつもり。
 ことの発端は、「ds-dccデコーダでVVVF音出そうと思っております」とYaasan様に連絡したら、
------------------
VVVF風ですが、TIMER1のモードをいじって、カウンタMAX値を調整すると、でき
ると思いますが今のところ試しておりません。
低速時はキャリア周波数を低めに、加速しながら速度に応じて上げていって高速
時は高くすると実物に近くなります。
------------------
 とのことで、できそうだと思ったこと。

<(1)モーターから音をだせる事を確認する。> 
 音を出すだけなら、一番簡単なのはAnalogWriteを使うことである。PWM周期は約 490Hzとのことで、Dutyだけ変化させると、音階は変わらないのに、モーターのスピードが変わることを確認した。
具体的には、以下のスケッチを使っている。
---------------------------
#define MOTOR_PWM_A 9
#define MOTOR_PWM_B 10
#define LED 13

void OutputStop();
 
void setup ()
{
  Serial.begin(115200);
  pinMode (MOTOR_PWM_A, OUTPUT);
  pinMode (MOTOR_PWM_B, OUTPUT);
  Serial.println("vvvf test");
}
 
void loop () {
  if(Serial.available() > 0)
  {
    int ch = Serial.read();
    //Serial.write(ch);
    switch (char(ch))
    {
      case '[':
      {
        Serial.println("A1,B0");
        OutputStop();
        digitalWrite(MOTOR_PWM_B, 0);
	//digitalWrite(MOTOR_PWM_A, 1);
        analogWrite(MOTOR_PWM_A, 25);
        delay(300);
        OutputStop();
        break;
      }
      case ']':
      {
        Serial.println("A0,B1");
        OutputStop();
        //digitalWrite(MOTOR_PWM_B, 1);
        analogWrite(MOTOR_PWM_B, 25);
	digitalWrite(MOTOR_PWM_A, 0);
        delay(300);
        OutputStop();
        break;
      }
      default:
        break;
    }
  }    
}

void OutputStop()
{
	digitalWrite(MOTOR_PWM_A, LOW);
	digitalWrite(MOTOR_PWM_B, LOW);
}

---------------------------
 接続は、ds-dccデコーダの入力にcmdr-arduino記事同様に12VのAcアダプタ出力をいれ、出力側に適当なモーターを接続(今回はマブチモーター3V用を使用。12Vだし危ないと思います。)。
 ピーピーとモーターが鳴るのはわかったので、OK。なお上記のスケッチはアクセサリーデコーダのものをなんとなく流用している。
 操作は、ツール→シリアルモニタから行い
"]":0.3秒間正転方向で音が出る
"[":0.3秒間逆転方向で音が出る
ソースの"analogWrite(MOTOR_PWM_B, 25);"などの25をもっと大きな数にすればDutyが大きくなるため、モーターは回りやすくなる。ただし255(上限)にすると、直流でHighなので今回的には(PWMにしたいわけだし)意味が無い。

<(2)モーターから音階を出せるようにする。>
 ここで、Timer1が必要になる。Timer1はArduinoからusオーダーでPWM波形を出せるライブラリとのことで、パルス幅、Dutyを調整できるため、周波数を変えることで音の高さを変えられるし、Dutyを変えることでモーターの速度を変えられる。そして、DS-DCCデコーダのドライバ出力ポート(D9,D10)に出力できる。
 で、実際に音の高さ(PWMの周波数)、モーターのスピード(Duty)を変えて、モーター車がどのように動くかを実験するために、スケッチを書いた。(以下)
-----------------------
#include <TimerOne.h>

#define MOTOR_PWM_A 9
#define MOTOR_PWM_B 10
#define LED 13

void OutputStop();

int pwm_duty = 32;
double pwm_freq = 440;
int pwm_a = 0;//pwm aは出力するか?
int pwm_b = 0;//pwm bは出力するか?

void setup ()
{
  //9,10は出力
  pinMode (MOTOR_PWM_A, OUTPUT);
  pinMode (MOTOR_PWM_B, OUTPUT);
  //Serial使用
  Serial.begin(115200);
  Serial.println("vvvf test K1");
  //Timer1使用
  Timer1.initialize();
  //初期設定
  int pwm_period = (double)1000000 / pwm_freq;
  Timer1.setPeriod(pwm_period);
  Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
  Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
  //PWMの開始
  OutputStop();
}

void loop () {
  if(Serial.available() > 0)
  {
    int ch = Serial.read();
    //Serial.write(ch);
    switch (char(ch))
    {
      case '2':
      {
        pwm_freq *= 1.05;//1.05倍で半音高くなる。
        Serial.print("Freq+ :");
        Serial.print(pwm_freq,DEC);
        Serial.print(" period :");
        int pwm_period = (double)1000000 / pwm_freq;
        Serial.println(pwm_period,DEC);
        Timer1.setPeriod(pwm_period);
        break;
      }
      case '1':
      {
        pwm_freq /= 1.05;//1.05割って半音低くなる。
        Serial.print("Freq- :");
        Serial.print(pwm_freq,DEC);
        Serial.print(" period :");
        int pwm_period = (double)1000000 / pwm_freq;
        Serial.println(pwm_period,DEC);
        Timer1.setPeriod(pwm_period);
        break;
      }
      case '4':
      {
        pwm_duty += 8;
        Serial.print("Duty(0-1023)+ :");
        Serial.println(pwm_duty,DEC);
        Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
        Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
        break;
      }
      case '3':
      {
        pwm_duty -= 8;
        Serial.print("Duty(0-1023)- :");
        Serial.println(pwm_duty,DEC);
        Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
        Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
        break;
      }
      case '0':
      {
        Serial.println("Stop");
        OutputStop();
        break;
      }
      case '[':
      {
        Serial.println("A1,B0");
        pwm_a = 1;
        pwm_b = 0;
        Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
        Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
        //delay(300);
        //OutputStop();
        break;
      }
      case ']':
      {
        Serial.println("A0,B1");
        pwm_a = 0;
        pwm_b = 1;
        Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
        Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
        //delay(300);
        //OutputStop();
        break;
      }
      default:
        break;
    }
  }    
}

void OutputStop()
{
//	digitalWrite(MOTOR_PWM_A, LOW);
//	digitalWrite(MOTOR_PWM_B, LOW);
  pwm_a = 0;
  pwm_b = 0;
  Timer1.pwm(MOTOR_PWM_A,0,0);
  Timer1.pwm(MOTOR_PWM_B,0,0);
}

-----------------------

 なんか、ソースが長いが、
 まず、TimerOneのライブラリを落としてきて、登録しておく必要がある。いくつかバージョンがあるようだが、わたしはTimer_One_r11を使用した。(違いは知らない)
 接続はcmdr_arduinoとまったく同じで、入力側に12VACアダプタを接続し、出力側をフィーダーにして線路につないでおく。線路には壊れても惜しくない車両(わたしだと鉄コレ15m級のモーター車。動きがとっても悪いから)。下は写真。ただしこの写真ではKato113系のモーター車だが。
システム.jpg
 操作は、ツール→シリアルモニタから行い
"]":モーター正転
"[":モーター逆転
"0":モーター非常停止(というか停止)
"2":PWMの周波数を半音上げる。
"1":PWMの周波数を半音下げる。
"4":PWMのDutyを+8する。(上限1023)
"3":PWMのDutyを-8する。(上限1023)
 音の周波数については、ここを参考にした。表から、半音上げるためには周波数を1.05倍すればよいらしい。
これで動かしてわかったことは、モーターから音はするけど、回転しないとかがよくあり、パラメータが難しいかもと思ったことである。
 適当な周波数に固定し、Dutyだけ変えるとE231系みたいな走り出し音になる。とかいっぱい遊んでしまって、時間が経ってしまった・・・。
 音自体は、電車のモーター音のページ 様のところでいっぱい聞いてみたが、ちょっと奥が深くて、おなかがいっぱいになってしまった。何かに忠実なものにするのはやめようと心に誓った。
 で、ここで、NゲージでVVVF音を出す動画を探そうと検索したら、しろくま 様の記事があり、動画をみて、「あ、こういうことしたい!」と横道にそれてしまうことにした。

<(3)VVVF音風なPWMコントローラを作る。>
 で、ここまで作った後に、VVVFってそういえばどう音階が切り替わるんですか?と思い検索したら、使えそうなページを二つ見つけられた。
 「1.車両用パワーデバイスのGTO、IGBT 3レベル回路とは 今後の素子は?」で、「3. IGBTと非同期PWM制御」のように周波数を変更してけば、本物っぽくなることがわかる。
 で、具体的には、DCC(デジタル・コマンド・コントロール)館 様の記事の、周波数テーブルのようなものを作る必要があることがわかった。
 そして、試行錯誤しながら、最終的に以下のスケッチを作った。
-------------------------------
#include <TimerOne.h>

#define MOTOR_PWM_A 9
#define MOTOR_PWM_B 10
#define LED 13


//速度領域始め、終わり、周波数始め、終わり
//速度は0-1023とする
const int kasoku[5][4] =
{
   {  0, 30,300,500},
   { 31, 60,300,500},
   { 61, 120,300,450},
   { 121, 200,300,350},
   { 201,300,300,320}
};
const int loco_speed_max = 200;

void OutputStop();

int duty_offset = 100;
int pwm_duty = 32;
double pwm_freq = 440;
int pwm_a = 0;//pwm aは出力するか?
int pwm_b = 0;//pwm bは出力するか?

int loco_speed = 0;//現在速度
int loco_direction = 1;//方向
int loco_accel = 0;//加速係数

void setup ()
{
  //9,10は出力
  pinMode (MOTOR_PWM_A, OUTPUT);
  pinMode (MOTOR_PWM_B, OUTPUT);
  //Serial使用
  Serial.begin(115200);
  Serial.println("vvvf test K2");
  //Timer1使用
  Timer1.initialize();
  //初期設定
  int pwm_period = (double)1000000 / pwm_freq;
  Timer1.setPeriod(pwm_period);
  Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
  Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
  //PWMの開始
  OutputStop();
}

void loop ()
{
  if(Serial.available() > 0)
  {
    int ch = Serial.read();
    //Serial.write(ch);
    switch (char(ch))
    {
      case '2':
      {
        //DutyOffset+
        duty_offset += 10;
        Serial.print("duty_offset+:");
        Serial.println(duty_offset,DEC);
        break;
      }
      case '1':
      {
        //DutyOffset+
        duty_offset -= 10;
        Serial.print("duty_offset-:");
        Serial.println(duty_offset,DEC);
        break;
      }
      case '7':
      {
        Serial.println("Brake");
        //loco_speed
        //loco_direction
        loco_accel = -1;
        break;
      }
      case '8':
      {
        Serial.println("ConstSpeed");
        //loco_speed
        //loco_direction
        loco_accel = 0;
        break;
      }
      case '9':
      {
        Serial.println("accel");
        //loco_speed
        //loco_direction
        loco_accel = 1;
        break;
      }
      case '0':
      {
        Serial.println("Emergency Stop");
        OutputStop();
        loco_speed = 0;
        //loco_direction = 1;
        loco_accel = 0;
        break;
      }
      case '[':
      {
        Serial.println("-direction");
        //loco_speed = 0;
        loco_direction = -1;
        //loco_accel = 1;
        break;
      }
      case ']':
      {
        Serial.println("+direction");
        //loco_speed = 0;
        loco_direction = 1;
        //loco_accel = 1;
        break;
      }
      default:
        break;
    }
  }//PWM制御関連
  {
    loco_speed += loco_accel;
    //上限制限
    if(loco_speed > loco_speed_max)
    {
      loco_speed = loco_speed_max;
    }
    if(loco_speed < 0)
    {
      loco_speed = 0;
    }
    Serial.print("loco_speed :");
    Serial.println(loco_speed,DEC);
    if(loco_accel != 0)
    {
      for(int i = 0;i < 5;i++)
      {
        if ( (kasoku[i][0] <= loco_speed ) and ( loco_speed <= kasoku [i][1]))
        {
          //音色側
          pwm_freq = (double)kasoku[i][2]
                   + (double)(kasoku[i][3] - kasoku[i][2]) / (double)(kasoku [i][1] - kasoku [i][0])
                   * (double)(loco_speed - kasoku [i][0]);
          int pwm_period = (double)1000000 / pwm_freq;
          Timer1.setPeriod(pwm_period);
          Serial.print("Freq- :");
          Serial.print(pwm_freq,DEC);
          Serial.print(" period :");
          Serial.println(pwm_period,DEC);
          //電力側
          pwm_duty = loco_speed + duty_offset;
          if (pwm_duty >1023)
          {
            pwm_duty = 1023;
          }
          if(loco_direction == 1)
          {
            pwm_a = 0;
            pwm_b = 1;
          }
          else
          {
            pwm_a = 1;
            pwm_b = 0;
          }
          //duty_offsetの設定によってはspeed0でも止まらないため
          if(loco_speed !=0)
          {
            Timer1.setPwmDuty(MOTOR_PWM_A,pwm_duty * pwm_a);
            Timer1.setPwmDuty(MOTOR_PWM_B,pwm_duty * pwm_b);
          }
          else
          {
             OutputStop();
          }          
          Serial.print("Duty(0-1023)- :");
          Serial.println(pwm_duty,DEC);
        }
      }
    }
  }  
  delay(80);
}



void OutputStop()
{
//	digitalWrite(MOTOR_PWM_A, LOW);
//	digitalWrite(MOTOR_PWM_B, LOW);
  pwm_a = 0;
  pwm_b = 0;
  Timer1.pwm(MOTOR_PWM_A,0,0);
  Timer1.pwm(MOTOR_PWM_B,0,0);
}

-------------------------------
システムは(2)と同じでcmdr-arduinoをds-dccデコーダーでやったときと配線は同じ。
操作は(2)同様 ツール→シリアルモニタで
"]":モーター車 正方向
"[":モーター車 逆方向
"0":モーター非常停止(というか停止)
"2":PWM Dutyのオフセットを+10する。(走り出しの調整)
"1":PWM Dutyのオフセットをー10する。(走り出しの調整)
"7":減速する。
"8":その速度で走る。
"9":加速する。

ソースだが、kasoku[5][4]が、音階を変えるLUTのようなもの。
5段持っており、例えば一段めは、”{ 0, 30,300,500},”なので、スピードが0~30のときに音階を300Hz~500Hzにかえるという意味。
loco_speed_max は車両の最高速リミット(本当は1023まで取れる)
スピードが上がるとモーター音が大きくなり、VVVF風の音はほとんど聞こえなくなる。
loopの最後にある”delay(80)”がLoopの周期をほぼ決めており、ここを大きくすれば加減速が遅くなる。小さくするともちろん加減速が速くなる。
以下が動画。音を大きくしないと聞こえないかも。

コメント(2)  トラックバック(0) 

コメント 2

Yaasan

PWMのキャリア周期を変更する理由について補足しておきます。

電車は見たとおりとても重たいので、始動には大電流を食います。しかし、IGBTやMOS-FETといった、電力調整半導体素子は、ON/OFFの周期が短い(キャリア周期が高い周波数である)場合、熱をたくさん出します。いわゆる、スイッチング損失と中の人達は呼びます。

なので、始動時・低速時は、ジージーなどとうるさいですが熱などの問題があるので仕方なく、キャリア周期の周波数を低くしてます。速度が高くなると、慣性で動けるので電流はそれほど食わないので、キャリア周期を高くしてうるさい音を聞こえない周波数(10kHzくらいになると人間の耳には聞こえない)にしてます。

メーカーによって、このキャリア周期と速度のバランスを変える味付けが違っていて、わざと周期をフラフラさせて分散させたり、速度との比例係数が違ったり、非同期から同期に切り替わるタイミングが違ったりします。

なので、ふじがやさんも、味付けをいろいろ工夫してみてください。
by Yaasan (2015-05-17 14:53) 

fujigaya2

コメントありがとうございます。

よく乗る電車はE231系なので、今度試行錯誤してみたいと思います。
和音は単純なPWMではないのでちょっと難しそうですが。
by fujigaya2 (2015-05-18 00:20) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

※ブログオーナーが承認したコメントのみ表示されます。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。