Arduinoで簡単なPID制御のプログラムを作ってみる

Pocket

arduino

 

 Arduinoで簡単なPID制御のサンプルスケッチ(ソース)を作ってみました。

 

 制御するものですが、Arduino単体で手軽に試せるように、PWMのDuty比を「操作」して、発生する電圧を「制御」するというものです。Duty比をそのまま制御しても良かったのですが、面白味がなかったのであえて電圧にしてみました。(ほとんど変わりませんが・・・。)

 

 PWM出力用の3番pinとアナログ入力の0番pin(AN0)をブレッドボード用のワイヤー等で直差ししておくだけで準備はO.Kです。後はUSBでPCと繋いでおくだけ。

 

 3番ピンから出力されたPWM信号をAN0番ピンで読み取って電圧を測定。目標となる電圧へ制御するものです。内容は、ArduinoのPIDライブラリにあるデモ内容とほぼ同様です。今回ライブラリは使用しません。PIDライブラリは非常に簡単で便利なのですが、痒い所に手が届かないというか・・・。ですので今回は使用しません。

 

 以下のソースや検証結果はArduino101で行ったものですが、UNOでも動くはずです。

 

 UNOで使用する場合は基準電圧や動作周波数の関係で、目標値への収束の仕方が変わってきます。また基準電圧の違いで若干ソースを変更する必要があります。(そのままでも動きますが・・。)

 

Arduinoソース

 記事最下段にソース全体を掲載してます。

 

 切り取って要所のみの説明。

#define Kp      30
#define Ki      300
#define Kd      0.1
#define target  2.5
float duty = 0;

 まずは定数の定義、PID各項のゲインと目標となる電圧(ここでは2.5V)を定義しておきます。初期のPWM出力は「0」としてます。0Vからスタートです。

 

 

  Serial.begin(115200); // initialize Serial communication
  while (!Serial) {
    digitalWrite(13, LED);
    delay(200);
    LED = 1 - LED;
  }
  digitalWrite(13, LED);

 シリアル通信が開始されるまで処理をループさせてます。ループ中はオンボードのLED(13番pin)を点滅。シリアル通信が開始されたら、ループを抜け出しLEDを点灯してます。101用の記述です。UNOでもこのままで動きますがシリアル通信開始直後のLEDの点き方が少しおかしいです。

<スポンサーリンク>

 

 

  for (int i = 0; i < 1000; i++) {
    vol += analogRead(0);
  }
  vol = 3.3 * (vol / 1000) / 1023;

 アナログpin(AN0)から電圧を読み取ってます。普通に読み取ってしまうと、PWMのON/OFFをそのまま読み取るだけとなります。ちょっと強引ですが、多めに1000回程サンプリングしてから電圧へ変換計算してます。UNOの場合は基準電圧が5Vなので「3.3」のところを「5.0」とします。

 

 

  dt = (micros() - preTime) / 1000000;
  preTime = micros();

 今回はポーリングで制御を行いますのでループ時間を取得しておきます。この[dt]を積分項、微分項の計算に使用します。

 

 

  P  = target - vol;
  I += P * dt;
  D  = (P - preP) / dt;
  preP = P;

  duty += Kp * P + Ki * I + Kd * D;

 PIDの各項を計算します。積分項は台形近似のほうが正確ですが、今回は単純に積算をしてます。最後に操作量を計算します。微分項はノイズに弱いのでマスクしても良いかも知れません。

 

 実際にArduinoに書き込んでシリアルモニタで電圧変化のログを取ってみました。

 

▼電圧のPID制御▼

電圧 PID制御

 それらしくなるようにわざとオーバーシュートするようなゲイン設定にしてます。電圧変化が、ほぼ理論値と同様の推移で、目標の2.5Vへ収束してます。実測では多少ノイズ?のようなものが入ってしまってます。

 

 いろいろゲインを弄って傾向をみるのも楽しいかと思います。ゲインを弄る場合は制御量が、PWMの0~255を超えないような値にしましょう。(本来はソース側で加味すべきことかもしれませんが・・。)

 

 

Arduinoスケッチ全体

#define Kp      30
#define Ki      300
#define Kd      0.1
#define target  2.5

bool LED;
float duty = 0;
float dt, preTime;
float vol;
float P, I, D, preP;

void setup() {
  pinMode(13, OUTPUT);

  Serial.begin(115200); // initialize Serial communication
  while (!Serial) {
    digitalWrite(13, LED);
    delay(200);
    LED = 1 - LED;
  }
  digitalWrite(13, LED);

  preTime = micros();
}

void loop() {
  analogWrite(3, duty);
  for (int i = 0; i < 1000; i++) {
    vol += analogRead(0);
  }
  vol = 3.3 * (vol / 1000) / 1023;

  PID();

  //if (Serial.available() > 0) {
  //int val = Serial.read();
  //if (val == 's') {
  serialMonitor();
  //}
  //}
}

inline void PID() {
  dt = (micros() - preTime) / 1000000;
  preTime = micros();
  P  = target - vol;
  I += P * dt;
  D  = (P - preP) / dt;
  preP = P;

  duty += Kp * P + Ki * I + Kd * D;
}

inline void serialMonitor() {
  Serial.print(dt , 3); Serial.print(",");
  Serial.print(duty, 3); Serial.print(",");
  Serial.print(P, 3); Serial.print(",");
  Serial.print(I, 3); Serial.print(",");
  Serial.print(D, 3); Serial.print(",");
  Serial.println(vol, 3);
}
Pocket

<スポンサーリンク>


この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL