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

 Raspberry Pi Pico(ラズパイPico) 単体で試せる簡単なPID制御のサンプルソースを作ってみます。過去にArduinoでも同様のことをしているのですが、今回はそのRaspberry Pi 版です。

 Picoの開発環境はArduinoIDEを使用しているので、PicoでもArduinoでも動く内容となってます。内容(ソース)は以前実施したArduinoのときとほぼ同じです。また若干ですが当時よりも少しだけ融通が利くようなソースに改造してます。

スポンサーリンク

概要

 PID制御するものですが、Pico単体で簡単に試せるように、PWM信号のパルス幅(デューティー比)を「操作」して、発生する電圧を目標値へ「制御」するというものです。

 事前準備としてはPWM出力するピン(GP3)と、アナログ入力のGP26ピン(ADC0)をワイヤー等で直差ししておくだけでO.K。あとはUSBでパソコンと接続しておくだけ。

 GP3ピンから出力されたPWM信号をADC0(GP26)ピンで読み取って電圧を測定。PWM信号を操作して目標となる電圧へ制御するものです。内容は以前行ったArduinoとほぼ同様の内容となります。この手の物はライブラリとかがあるのですが今回は使用せずにPID制御を行ってみます。

ソース解説

 記事最下段にソース全体は掲載してます。ここでは切り取って要所を解説。

#define Kp      30
#define Ki      150
#define Kd      2
#define dt      0.2   //0.2s
#define target  2.5   //2.5v

 まずは定数の準備から、PID各項のゲイン、制御周期、目標となる電圧を定義してます。

  while (!Serial) {
    digitalWrite(25, digitalRead(25) == LOW ? 1 : 0);
    delay(200);
  }
  digitalWrite(25, HIGH);

 setup()内の記述です。シリアル通信が開始されるまでLED点滅をしながら待機(無限ループ)させてます。シリアル通信が開始されたらループを脱出してLEDを点灯します。

▼ここからは loop()内の記述になります。

  while (currentTime - loopTimer <= dt * 1000000)currentTime = micros();
  loopTimer = currentTime;

 時間を監視して0.2s(dt=0.2)毎にプログラムがループするように待機させます(0.2s経過するまで強制待機)。このループ時間がそのままPID制御の周期となります。当然、1ループの処理を0.2s以内で完了させる必要があります。今回のソースでは処理時間が0.007s程度なのでかなり余裕はあります。

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

 GP26ピンの電圧を読み取ってます。 普通に読み取ってしまうと、PWMのON/OFFをそのまま読み取るだけとなります。ちょっと強引ですが、多めに1000回程サンプリングしてから電圧へ変換計算してます。

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

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

  PID制御の部分です。各項(比例、積分、微分)を計算して、最後に操作量を計算してます。積分項は単純な積算で数値積分してます。

  if (U > 255)U = 255;
  if (U < 0)  U = 0;
  duty += (U - duty) * 0.2;

 操作量がanalogWrite()の引数0~255値(PWMデューティ比)を外れないように調整してます。 操作量をそのままデューティ比にしてもよかったのですが、少しだけ反応が遅れるようにわざとこのような処理を加えてます。ここの3行目は無くてかまいません(無い方がいいです)。

  analogWrite(3, duty);

 最後に計算された操作量でPWM出力して完了です。

スポンサーリンク

実測結果

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

▼結果▼

 それらしくなるようにわざとオーバーシュートさせてます。理論値とほぼ一緒ですね。(時間軸の単位は[s]秒です)

ソース全体

 ソース全体です。このままでRaspberry Pi Pico、Arduinoのどちらにも使えます。

// 2021/09/19 imo lab.
//https://garchiving.com/

#define Kp      30
#define Ki      150
#define Kd      2
#define dt      0.2   //0.2s
#define target  2.5   //2.5v

bool LED;
float duty = 0;
float vol;
float P, I, D, U, preP;
uint32_t currentTime, loopTimer;

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

  Serial.begin(115200);
  while (!Serial) {
    digitalWrite(25, digitalRead(25) == LOW ? 1 : 0);
    delay(200);
  }
  digitalWrite(25, HIGH);

  currentTime = micros();
}

void loop() {
  while (currentTime - loopTimer <= dt * 1000000)currentTime = micros();
  loopTimer = currentTime;

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

  PID();
  analogWrite(3, duty);

  //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);
}

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

  U = Kp * P + Ki * I + Kd * D;
  if (U > 255)U = 255;
  if (U < 0)  U = 0;
  duty += (U - duty) * 0.2;
}

コメント