AbudoriLab.

自律ロボットで誰でも遊べるよう試行錯誤するブログです。

モータドライバでモータを回してみよう④ モータの速さを変えてみよう

Abudoriです。前回に続き、モータを回していきます。 前回の記事では、モータドライバとマイコンを繋いで好きなタイミングで時計回り、反時計回りで回しました。

www.abudorilab.com

今回の記事では、モータの速さをプログラムから指示できるようにします。速度の指示をすることができるようになればロボットの車輪を自由に制御できるようになります。前回のプログラムを少しだけ変更して速度の指示を出せるようにします。

スポンサードリンク

PWMを使ってみよう

前回の記事で回したモータは全速力か停止のどちらかでした。モータドライバの入力にH/L入力をすると、モータに直接12Vの電圧が加えられます。 モータドライバはモータにかかる電圧の向きを変えてくれる(Hブリッジ回路、詳細はモータドライバを回してみよう①へ)だけで、電圧は変えてくれません。 モータの速度を変えるには電圧をマイコンで変えてあげる必要があります。

別の記事では、PWMをつかって仮想的に電圧を変える方法を紹介しました。PWM(Pulse Width Moduration)はマイコンで電源を高速にONOFFし、ON時間とOFF時間の比率を変えることで、仮想的に中間の電圧のような振る舞いをする操作です。

PWMについてはこちらの記事をご覧ください。 www.abudorilab.com

前回のモータを回す回路はそのままで、H/Lを指示したHの部分をPWMに変えてみます。

全速力で回す時はIN1をH、IN2をLにし続けました。これをIN1をH、IN2をL、少しの間だけIN1をL、IN2をLにして、またIN1をH、IN2をLにする、という動作に変えていきます。ずっとIN1がHのままより、すこしの間だけモータの停止時間がある状態になるわけです。これだと、全速力よりは遅くモータが回る気がしませんか?

この停止時間は人間にとってはあっという間すぎてわからないくらいの時間とします。なので、モータが動いて止まって…という動作にはなりません。単純に全速力よりは遅く見えるだけになるのです。

PWMの模式図

それでは、コードに置き換えてみます。 前回はIN1のPIN16をHにIN2のPIN17をLにするコードを書きました。

コード抜粋

void loop() {
  // 16ピンをHに、17ピンをLに設定
  digitalWrite(16, HIGH);
  digitalWrite(17, LOW);
  delay(3000);

ArduinoIDEでは、digitalWrite()を使うことでピンの出力をHにしたりLにしたりできましたね。 H/Lの2値を出力しているため、digitalWrite()関数という名前になっています。 次にH/Lの中間の値をPWMによって出力します。その名もanalogWrite()関数。0~3.3Vの電圧を256分割にした中間の値を出力することができます。

analogWriteが何をしているかはこのブログがわかりやすいです。 iot.keicode.com

analogWrite()を使って次のように書き換えます。

  digitalWrite(16, HIGH); // 第一引数にピン番号、第二引数にHまたはLの値を指示
  ↓
  analogWrite(16, 128); // 第一引数にピン番号、第二引数に0~255の値を指示

出力の値は0~255の範囲で入力します。0が0%、255が100%の出力です。0を含めて255までの数ですから、256分割ということになります。この例では、128/256=1/2から、50%の電圧、つまり3.3V / 2 = 1.65Vが出力されます。

前回のコードを次のように書き換えました。

void setup() {
  // 16, 17ピンをH,L出力できるようにします。
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
}

void loop() {
  // 16番ピンをHのまま、出力を弱めていく
  // 16番ピンを100%出力。H出力と同じ
  analogWrite(16, 255);
  digitalWrite(17, LOW);
  delay(3000);

  // 16番ピンを75%出力。192/256 = 3/4
  analogWrite(16, 192);
  digitalWrite(17, LOW);
  delay(3000);

  // 16番ピンを50%出力。128/256 = 1/2
  analogWrite(16, 128);
  digitalWrite(17, LOW);
  delay(3000);

  // 16ピンをLに、17ピンをLに設定 →停止
  digitalWrite(16, LOW);
  digitalWrite(17, LOW);
  delay(3000);
}

このコードで実際にモータを回すと以下のようになります。 最初に全速力で回転(前回記事と同じ速度)し、そこから75%、50%の出力で回転し、最後に停止するようになりました。

youtu.be

滑らかにモータの速さを変えてみよう

動画をみればわかる通り、ガツンと速度が変わっていて使い勝手が悪いように見えます。 今度はこれを滑らかに速度が変わるようなコードに変えていきます。

analogWrite()の値を0~255の値で指示することで速度を変えることができました。 前回のコードでは、255→192→128と値を大きく変えたのでガツンと速度が変わっていましたが、

255→254→253→252→251→250→…

と小さな値で小刻みに変えていくと滑らかな変化になります。

小刻みに変化させるために次のようにコードを変更しました。

void setup() {
  // 16, 17ピンをH,L出力できるようにします。
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
}

void loop() {
  // 3000ミリ秒かけてだんだんスピードアップ
  // 16番ピンを0(0%)から255(100%)にスピードアップする。
  // 30ミリ秒おきに少しずつ大きな値を代入するコマンドを100回実行する。
  for (int i = 0; i < 100; i++) {
    int analog_value = 2.56 * i; // 整数型なので小数点は切り捨て。気にしない

    // ただの画面表示。動画内で速度と数字が一致しているかみてみてね
    Serial.print("value: ");
    Serial.println(analog_value);

    analogWrite(16, analog_value);
    digitalWrite(17, LOW);
    delay(30); // 30ミリ秒の処理を100回繰り返す→3000ミリ秒
  }

  // 3000ミリ秒全速力
  analogWrite(16, 255);
  digitalWrite(17, LOW);
  delay(3000);

  // 3000ミリ秒かけてだんだんスピードダウン
  // 16番ピンを255(100%)から0(0%)にスピードダウンする。
  // 30ミリ秒おきに少しずつ小さな値を代入するコマンドを100回実行する。
  for (int i = 0; i < 100; i++) {
    // 3000ミリ秒で255になる数字を引き算することで最終的に0にします。
    int analog_value = 255 - 2.56 * i; 
    Serial.print("value: ");
    Serial.println(analog_value);

    analogWrite(16, analog_value);
    digitalWrite(17, LOW);
    delay(30);
  }

  // 3000ミリ秒全速力
  digitalWrite(16, LOW);
  digitalWrite(17, LOW);
  delay(3000);
}

for loopで100回同じことを繰り返します。この繰り返し内で少しずつ値を大きくしていき、analogWrite()に値を入力します。

for文の書き方についてはこちら tool-lab.com

loop内では、数値analog_valueを変化させて、analogWrite()に入力します。for文内のiは、

0→1→2→3→4→5→...

と変わりますから、analogValue = 2.56 * i の値は

0→2.56→5.12→7.68→10.24→12.80→...

と変わっていきます。整数型なので、小数は切り捨てられるので、

0→2→5→7→10→12→...

の値が入力されていきます。値が2か3ずつ増えていきますが目に見えるほど変わらないのでキニシナイ。

最終的には、2.56 x 99 = 253.44 → 253 になります。

このfor loopは100回繰り返されるので、30ミリ秒の待機時間を入れることで30 x 100 = 3000ミリ秒かけてだんだん速くなる処理として実行されるわけです。

コード抜粋

for (int i = 0; i < 100; i++) {
    int analog_value = 2.56 * i; // 整数型なので小数点は切り捨て。気にしない

    analogWrite(16, analog_value);
    digitalWrite(17, LOW);
    delay(30); // 30ミリ秒の処理を100回繰り返す→3000ミリ秒
  }

コード後半には、だんだん速くなるコードを少しだけ変えてだんだん遅くなるようにしています。

2.56 x i でだんだん値が大きくなっていきますが、これを最大値の255から引き算していくことで値が小さくなっていきます。

コード抜粋

  for (int i = 0; i < 100; i++) {
    // 3000ミリ秒で255になる数字を引き算することで最終的に0にします。
    int analog_value = 255 - 2.56 * i; 

    analogWrite(16, analog_value);
    digitalWrite(17, LOW);
    delay(30);
  }

以上の内容を実際に動かしてみました。

youtu.be

スポンサードリンク

モータを逆回転に回そう

動画のとおり、反時計回りで速度を変えていきました。回転方向を変えてどちらも滑らかに動作させてみましょう。

反時計回りではこのように書いていました。

  analogWrite(16, 255);
  digitalWrite(17, LOW);

時計回りでは、IN1をLに、IN2をanalogWrite()で値を指定します。

  digitalWrite(16, LOW);
  analogWrite(17, 255);

先ほどの滑らかに速度を変えるプログラムをコピーして回転方向を変えます。

void setup() {
  // 16, 17ピンをH,L出力できるようにします。
  pinMode(16, OUTPUT);
  pinMode(17, OUTPUT);
}

void loop() {
  // 3000ミリ秒かけてだんだんスピードアップ
  // 16番ピンを0(0%)から255(100%)にスピードアップする。
  // 30ミリ秒おきに少しずつ大きな値を代入するコマンドを100回実行する。
  for (int i = 0; i < 100; i++) {
    int analog_value = 2.56 * i; // 整数型なので小数点は切り捨て。気にしない

    // ただの画面表示。動画内で速度と数字が一致しているかみてみてね
    Serial.print("value: ");
    Serial.println(analog_value);

    analogWrite(16, analog_value);
    digitalWrite(17, LOW);
    delay(30); // 30ミリ秒の処理を100回繰り返す→3000ミリ秒
  }

  // 3000ミリ秒全速力
  analogWrite(16, 255);
  digitalWrite(17, LOW);
  delay(3000);

  // 3000ミリ秒かけてだんだんスピードダウン
  // 16番ピンを255(100%)から0(0%)にスピードダウンする。
  // 30ミリ秒おきに少しずつ小さな値を代入するコマンドを100回実行する。
  for (int i = 0; i < 100; i++) {
    // 3000ミリ秒で255になる数字を引き算することで最終的に0にします。
    int analog_value = 255 - 2.56 * i; 
    Serial.print("value: ");
    Serial.println(analog_value);

    analogWrite(16, analog_value);
    digitalWrite(17, LOW);
    delay(30);
  }


  // 3000ミリ秒かけてだんだんスピードアップ
  // 17番ピンを0(0%)から255(100%)にスピードアップする。
  // 30ミリ秒おきに少しずつ大きな値を代入するコマンドを100回実行する。
  for (int i = 0; i < 100; i++) {
    int analog_value = 2.56 * i; // 整数型なので小数点は切り捨て。気にしない

    // ただの画面表示。動画内で速度と数字が一致しているかみてみてね
    Serial.print("value: ");
    Serial.println(analog_value);

    digitalWrite(16, LOW);
    analogWrite(17, analog_value);
    delay(30); // 30ミリ秒の処理を100回繰り返す→3000ミリ秒
  }

  // 3000ミリ秒全速力
  digitalWrite(16, LOW);
  analogWrite(17, 255);
  delay(3000);

  // 3000ミリ秒かけてだんだんスピードダウン
  // 17番ピンを255(100%)から0(0%)にスピードダウンする。
  // 30ミリ秒おきに少しずつ小さな値を代入するコマンドを100回実行する。
  for (int i = 0; i < 100; i++) {
    // 3000ミリ秒で255になる数字を引き算することで最終的に0にします。
    int analog_value = 255 - 2.56 * i; 
    Serial.print("value: ");
    Serial.println(analog_value);

    digitalWrite(16, LOW);
    analogWrite(17, analog_value);
    delay(30);
  }
}

16ピンでanalogWrite()でやっていた操作をそのまま17ピンに変えただけです。

このコードをうごしたら以下の動画のようになります。 youtu.be

次回の記事では、より正確にモータを回します。この記事の回し方だと、モータ軸の抵抗や外乱の影響を考えていません。 そのため、回し始めは音はなるけど抵抗に負けて回り始めない状態になっています。

いよいよ簡単な制御を始めてみます。次回は回転数の計測とP制御を実施します。

お楽しみに!