マイコン入門 ライントレースをP制御(比例制御)で走ろう

マイコン
この記事は約12分で読めます。

こんにちは、ナナです。

前章においてライントレースはできたもののカクカクした走行が課題として残っていました。本章ではこの走行を滑らかなものにする制御を実施します。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • ライントレースで使われるPID制御って何?
  • P制御って具体的にどんな制御なの?
  • モーターをP制御で動かす時の考え方とは?

では、PID制御によるライントレースの仕組みを学んでいきましょう。

スポンサー

ライントレースで使われるPID制御

ライントレースという単語で検索すると必ず出てくるのがPID制御と呼ばれる制御工学用語です。このPID制御を利用すると驚くほど滑らかにライントレースができるようになります。

PID制御とは

PID制御とはP制御+I制御+D制御の3つを合わせた制御です。

  • P制御(比例制御)
  • I制御(積分制御)
  • D制御(微分制御)

何やら難しい用語が出てきました。微分、積分・・・高校生時代を思い出す方もいるでしょう。逃げ出したくなる気持ちが芽生えた方はちょっと待ってください!

これらの本格的な制御を実施するわけではありません。この中で最も考え方がシンプルなP制御と呼ばれる制御方法を実施してみましょう。

なぜカクカクした走行になってしまったのか?

本章のライントレースにおける目標はカクカクした動きを滑らかにすることです。

では、なぜカクカクして走行してしまうのでしょうか。それはON/OFF制御と呼ばれる回す/止めるの2択によるモーター制御をしていたためです。

このように片方のモーターを目いっぱい回してもう片方を停止する、そのような制御ではカクカクして当然なのです。車を運転する際に全力アクセルと急ブレーキしかできない車だったらカクカクするのがイメージできると思います。

ここでモーター制御についてよく考えてみましょう。モーターの回転速度は皆さん自由に制御できますよね。モーターのデューティ比を調整することで自由に回転速度を調整することが可能です。

であれば、ON/OFFの2択の制御ではなく、もっと滑らかな回転制御を導入できれば解決できそうじゃないですか?そこで出てくるのがP制御です。

P制御(比例制御)とは

P制御となるとちょっとイメージしづらいですが、比例制御となれば少しイメージしやすい感じです。比例とはAが大きくなるとBも大きくなる、Aが小さくなるとBも小さくなるといった関係のことです。

皆さん、ご自宅にあるエアコンを夏場に使った時を思い出してください。真夏の部屋に帰ってきて部屋の中が35℃になっていました。暑すぎるのでエアコンの冷房設定で25℃に設定してスイッチONしました。

そうするとエアコンはすごい音と風と共に急速冷却を始めます。だんだん時間が経過して部屋の温度が28℃くらいになってくるとエアコンが少し穏やかな音になっていませんでしたか。これこそがP制御です。

目標とする数値に対して現状の数値を比較し、差が大きければ制御量を大きく、差が小さければ制御量を小さくする。これがP制御(比例制御)です。

ライントレースにおけるP制御の目標値とは何か?

ライントレースとは白と黒の境界線を走行することです。つまり、如何にスムーズに境界線の上をズレずに走ることができるのかということです。

白と黒の判別は光センサにて行うため、光センサの値を目標となる白黒境界に如何に近づけるかということですね。

P制御をするうえで光センサの特性を説明しておきましょう。

人の目にはくっきりと白と黒の境界線が見えていますが、光センサではグラデーションのように境界というものをしっかりと把握できません。しかし、このグラデーションの特性を利用することでP制御を使ったモーター制御ができるようになるのがわかるでしょうか。

例えば、およそ白黒中間地点の光センサ値:400を目標値とし、光センサで取得できた値をこの目標値に使づけるようにP制御で左右のモーター回転速度を変えてあげればスムーズにライン上を走行できそうです。

P制御の公式

P制御は次の公式で制御量を決定します。

制御量 = (目標センサ値 - 現在センサ値)× Kp

Kpとは比例ゲインと呼ばれる制御量を調整する定数パラメータです。今回モーターはデューティ比で回転速度を決定します。そのため制御量はデューティ比の単位に合わせるようにKpを決定しましょう。

Kp(比例ゲイン)の決定方法

まず、ライントレース中はビュートローバーを前進させるため、一定のデューティ比でモーターを前進させます。前進する中でビュートローバーを左前方や右前方に走行させるためには、左右のモーター回転速度に差をつけてあげれば移動できるのはイメージできると思います。

P制御の計算式にて算出した制御量を左右のモータのデューティ比にうまく変換して作用させてあげればうまくいきそうです。

それでは目標センサ値を決定しましょう。私が動かしている環境では次のセンサ値となりました。目標センサ値はこの中央値として定義します。皆さんの環境でもSCIモジュールを利用して調べてみるとよいでしょう。

  • 真っ白な場所:センサ値 約50
  • 真っ黒な場所:センサ値 約820

目標センサ値:(真っ黒な場所 + 真っ白な場所) ÷ 2 = 435

次はデューティ比として左右のモーターをどの程度の差をもって制御するかを検討します。まず前進する基準となるデューティ比を仮に10%として定義しましょう。

このデューティ比を元に左右のデューティ比調整幅を5%と定義したとします。つまり左右のモーターは5%~15%の間で常に動きながら走行することになります。このあたりの数字はざっくりです。走らせた結果、スムーズに走行しないようであれば調整を掛けていきます。

(目標値:435 ー センサ値)× Kp ⇒ 5% ~ -5%の制御範囲

この公式が成立するKpを決定したいのです。

センサ値は取得幅は約820~50程度であり目標値から約400前後の最大差分が取得できます。
400 × Kp = 5%が成立すればよいのですからKpは0.0125として定数定義をしましょう。

(435 ー センサ値)× 0.0125 ⇒ 5% ~ -5%

この算出された制御量のデューティ比を、基本回転速度10%に対して左右のモーターに±5%で制御してあげます。例えば3%の制御量だった場合、次のように計算します。

  • 右モーター:10% - 3% ⇒ 7%のモーター回転速度デューティ比
  • 左モーター:10% + 3% ⇒ 13%のモーター回転速度デューティ比

このように左右のモーター回転速度に差をつけることで目標値にどんどん近づいていくことになります。目標値にセンサ値が近くなると制御量が小さくなり、左右のモーター回転速度も差がなくなっていきスムーズにライン上を走っていくことになります。

スポンサー

P制御でDCモーター制御を行うMotorモジュールの変更

ON/OFF制御に引き続き、P制御用の走行処理を行うためのインターフェースをMotorモジュールに追加しましょう。

Motorモジュールの役割検討

  • ライントレース中は前進するのみとする
  • P制御に必要となる光センサの値は呼び出し側から入力する形とする
  • 光センサが白なら右へ、黒なら左へ向かうように走行する

Motorモジュールのインターフェース仕様書(追加部分)

3.インターフェース定義

3.5 P制御走行開始

スポンサー

課題:MotorモジュールをP制御で走ってみよう

課題1

課題内容

モーターモジュールに新規インターフェースを追加せよ。motor.cとmotor.hは前章のプログラムをベースとし、下記プログラムを追加して修正を加えよ。

//------------------------------------------------
//  概 要:P制御走行開始
//  引  数:brightness      光センサ値
//  戻り値:0   正常
//          -1  異常
//------------------------------------------------
int Motor_runPcon(unsigned short brightness)
{

}

motor.cに追加すべきこと

  • モーターは常に前進走行として回転方向を設定する。
  • 制御量のデューティ比は計算結果が-5%~5%の範囲外になった場合は、その範囲内に補正するように処理を行う。(小数点かつ負値を扱うため計算する型に注意すること)
  • モーターのデューティ比は10%を基準とし、制御量の-5%~5%を作用させて左右のデューティ比率を変化させる。

※motor.hには忘れずにプロトタイプ宣言を追加すること。

motor.h(変更部分のみ抜粋)

int Motor_runPcon(unsigned short brightness);
  • インターフェースのプロトタイプ宣言を追加する

motor.c(変更部部のみ抜粋)

#define D_MOTOR_KP                  (0.0125)    //  比例ゲイン
#define D_MOTOR_DUTY_BASE           (10)        //  基本走行デューティ比
#define D_MOTOR_DUTY_WIDTH          (5)         //  最大duty調整幅
#define D_MOTOR_TARGET_BRIGHTNESS   (435.0)     //  目標センサ値

//-----------------------------------------------------------------------------
//  概 要:P制御走行開始
//  引  数:brightness      光センサ値
//  戻り値:0   正常
//          -1  異常
//-----------------------------------------------------------------------------
int Motor_runPcon(unsigned short brightness)
{
    short dutyDiff;

    //  右モーター前進
    IO.PDR3.BIT.B0 = 1;
    IO.PDR3.BIT.B1 = 0;

    //  左モーター前進
    IO.PDR3.BIT.B2 = 1;
    IO.PDR3.BIT.B3 = 0;

    //  目標に対するduty制御量の算出
    dutyDiff = (D_MOTOR_TARGET_BRIGHTNESS - brightness) * D_MOTOR_KP;

    //  最大調整duty幅内での補正処理
    if (dutyDiff > D_MOTOR_DUTY_WIDTH)
    {
        dutyDiff = D_MOTOR_DUTY_WIDTH;
    }
    else if (dutyDiff < -D_MOTOR_DUTY_WIDTH)
    {
        dutyDiff = -D_MOTOR_DUTY_WIDTH;
    }

    //  GRAの周期レジスタをベースにデューティ比の割合を設定
    TZ0.GRB = TZ0.GRA * ((D_MOTOR_DUTY_BASE - dutyDiff) / 100.0);   //  右
    TZ0.GRC = TZ0.GRA * ((D_MOTOR_DUTY_BASE + dutyDiff) / 100.0);   //  左

    // タイマZのch0をカウント開始してPWM動作開始
    TZ.TSTR.BIT.STR0 = 1;

    return 0;
}
  • マクロ定義を使って今回の走行で使用するパラメータを定数定義を行う。目標センサ値を435.0と浮動小数点で定義することでデューティ比の算出を負値+浮動小数点計算ができるようにしていることに注意。
  • モーターの回転方向は前進方向で設定する。
  • 比例制御の公式を利用し、調整用のデューティ比制御量を計算する。
  • デューティ比が-5%~5%の範囲外だった場合はその範囲内に強制的に補正する。
  • 算出したデューティ比を10%を基準に左右のモーターへ逆の関係で作用させる。

課題2

課題内容

MAINモジュールからMotorモジュールに対しP制御で走行要求を行うようにし、次の処理を繰り返すようにせよ。

  • 光センサのセンサ1(左側)の値をMotor_runPcon関数に設定せよ

main.c

//------------------------------------------------
//  概  要:エントリーポイント
//------------------------------------------------
void main(void)
{
    //  システム初期化処理
    main_init();

    while(1)
    {

    }
}

課題が完成したらビルドを行いビュートローバー上で動作させ、期待動作通りに動くことを確認せよ。

※思うように走行しない場合はMotorモジュールにおいてSCIモジュールを呼び出すようにし、算出した値などをパソコン上で表示させて意図した動き化を確認してみるとよいでしょう。


期待動作

  • センサ1を黒い線の左側に合わせ走行させる。
  • 黒いラインに従ってP制御で滑らかに走行すること。

ライントレースのコースは黒ビニールテープなどで自作してみましょう。ホームセンターなどで購入するとよいでしょう。コースはどのような形でもよいです。

main.c

//------------------------------------------------
//  概  要:エントリーポイント
//------------------------------------------------
void main(void)
{
    //  システム初期化処理
    main_init();

    while(1)
    {
        unsigned short sen1;
        unsigned short sen2;

        Lsen_getSensor(&sen1, &sen2);

        Motor_runPcon(sen1);
    }
}
  • Lsenモジュールからセンサ値を取得しsen1をMotor_runPcon関数へ渡しています。
  • while文の中で常に光センサ値をMotorモジュールに提供してライントレースを行います。