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; }
コメント