アフィリエイト広告
アフィリエイト広告

冷却ファンの PID制御 (4) / 回転数を PID制御する

冷却ファンの回転数を検出し、PID制御する回路を作ります。

前回までに、ファンの制御に必要なフィードバック回路ができあがりました。

冷却ファンの PID制御 (3) / Arduino から制御する回路
冷却ファンの回転数を検出し、PID制御する回路を作ります。 前回は、じっさいに CPU冷却ファンを回転させ、回転数センサからの出力を Arduino NANO で検出、7セグメントLED で回転数を表示できるようにしました。 今回は、ファン...

今回はいよいよ、ファンの回転数を PID制御してみようと思います。
毎度まいどですが、PID制御についてはググってください m(_ _;)m 過去記事もご覧いただければ。

PID制御のスケッチ

前回までに書いたスケッチに、PID制御を追加しました。
その他、細かい修正もしています。だいたいがまぁ、気分です (;´Д`)

  1. // FAN Speed Control v.1 2022.12.24 meyon230
  2. //#define DEBUG_ON
  3. class TM1630 {
  4. private:
  5.   const byte DISPLAY_MODE = 0x00;
  6.   const byte WRITE_REG_AUTO_ADDR = 0x40;
  7.   const byte WRITE_REG_FIXED_ADDR = 0x44;
  8.   const byte READ_KEY_SCAN = 0x42;
  9.   const byte SET_DISPLAY_ADDR = 0xC0;
  10.   const byte DISPLAY_OFF = 0x80;
  11.   const byte DISPLAY_ON = 0x88 | 0x84;
  12.   const byte STB_ENABLE = LOW;
  13.   const byte STB_DISABLE = HIGH;
  14.   const int DOT_POSITION = -1;
  15.   const byte digit[12] = {
  16.     0b01111110, // 0
  17.     0b00001100, // 1
  18.     0b10110110, // 2
  19.     0b10011110, // 3
  20.     0b11001100, // 4
  21.     0b11011010, // 5
  22.     0b11111010, // 6
  23.     0b01001110, // 7
  24.     0b11111110, // 8
  25.     0b11011110, // 9
  26.     0b00000000, // 10:blank
  27.     0b00100000 // 11:dot
  28.   };
  29.   const byte dioPin = 10;
  30.   const byte clkPin = 11;
  31.   const byte stbPin = 12;
  32.   const byte numberOfDigits = DISPLAY_MODE & 1 ? 5 : 4;
  33.   const byte blank = 10;
  34.   const byte dot = 11;
  35.   byte gridData[];
  36. public:
  37.   TM1630() {
  38.     pinMode(dioPin, OUTPUT);
  39.     pinMode(clkPin, OUTPUT);
  40.     pinMode(stbPin, OUTPUT);
  41.     gridData[numberOfDigits] = {0};
  42.     digitalWrite(stbPin, STB_ENABLE);
  43.     shiftOut(dioPin, clkPin, LSBFIRST, DISPLAY_MODE);
  44.     digitalWrite(stbPin, STB_DISABLE);
  45.     digitalWrite(stbPin, STB_ENABLE);
  46.     shiftOut(dioPin, clkPin, LSBFIRST, WRITE_REG_AUTO_ADDR);
  47.     digitalWrite(stbPin, STB_DISABLE);
  48.   }
  49.   void displayNumbers(int n) {
  50.     for (byte i = 0; i < numberOfDigits; i++) {
  51.       int exponentialInDecimal = pow(10, i) + 0.5;
  52.       bool zeroSuppression = (0 != i) && (DOT_POSITION < i) && (exponentialInDecimal > n);
  53.       gridData[i] = zeroSuppression ? blank : n / exponentialInDecimal % 10;
  54.     }
  55.     digitalWrite(stbPin,STB_ENABLE);
  56.     shiftOut(dioPin, clkPin, LSBFIRST, SET_DISPLAY_ADDR | 0x00);
  57.     for (byte i = 0; i < numberOfDigits; i++) {
  58.       shiftOut(dioPin, clkPin, LSBFIRST, digit[gridData[i]]);
  59.       shiftOut(dioPin, clkPin, LSBFIRST, DOT_POSITION == i ? digit[dot] : 0);
  60.     }
  61.     digitalWrite(stbPin, STB_DISABLE);
  62.     digitalWrite(stbPin, STB_ENABLE);
  63.     shiftOut(dioPin, clkPin, LSBFIRST, DISPLAY_ON);
  64.     digitalWrite(stbPin, STB_DISABLE);
  65.   }
  66. };

7セグメントLED ドライバ TM1630 の制御をおこなうクラスです。
基本的に、前回までと変わりないですが、一部、動作モードの転送を、コンストラクタからメンバ関数へ移動したりしてます。どっちでもいいんですケド。

  1. class PulseDetector {
  2. private:
  3.   const byte sensorPin = 7;
  4.   long pulsePeriod;
  5. public:
  6.   PulseDetector () {
  7.     pinMode(sensorPin, INPUT);
  8.     pulsePeriod = 0;
  9.   }
  10.   long detectPulses() {
  11.     pulsePeriod = pulseIn(sensorPin, HIGH);
  12.     pulsePeriod += pulseIn(sensorPin, LOW);
  13.     return pulsePeriod;
  14.   }
  15. };

回転数パルスの検出を行なうクラス。
これも変更ないです。あ、変数の型をちょっと変えたかな。

  1. class PIDcontrol {
  2. private:
  3.   const float Kp = 2.5; // Proportional
  4.   const float Ki = 0.15; // Integral
  5.   const float Kd = 1.3; // Differential
  6.   const long minimumPeriod = 13800; // 2170rpm
  7.   const long maximumPeriod = 25900; // 1160rpm
  8.   const byte potentiometerPin = 0;
  9.   const byte manipulateOutputPin = 5;
  10.   long en;
  11.   long en1;
  12.   long en2;
  13.   long MVn;
  14.   long MVn1;
  15.   long dMVn;

追加した PID制御のクラス宣言とメンバ変数。

PID制御は、速度型制御アルゴリズムを使いました。
Kp、Ki、Kd は、それぞれ比例制御、積分制御、微分制御の制御係数です。値に関しては、まぁ、こんな感じかなぁと。動作のようすについては、あらためて書きます。
en、en1、en2 はそれぞれ、今回、前回、前々回の偏差。MVn、MVn1 は今回、前回の操作量。dMVn は操作量の増分です。

  1. public:
  2.   PIDcontrol() {
  3.     pinMode(potentiometerPin, INPUT);
  4.     pinMode(manipulateOutputPin, OUTPUT);
  5.     en = 0;
  6.     en1 = 0;
  7.     en2 = 0;
  8.     MVn = 0;
  9.     MVn1 = 0;
  10.     dMVn = 0;
  11.   }

コンストラクタ。
ピンモードの設定をしてますけど、INPUT と PWM なので、必須ではないです。

  1.   void manipulate(long pulsePeriod) {
  2.     int potentiometerVoltage = analogRead(potentiometerPin);
  3.     long SV = map(potentiometerVoltage, 0, 1023, maximumPeriod, minimumPeriod);
  4.     long PV = pulsePeriod;
  5.     en2 = en1;
  6.     en1 = en;
  7.     en = SV - PV;
  8.     MVn1 = MVn;
  9.     dMVn = Kp*(en-en1) + Ki*en + Kd*((en-en1)-(en1-en2));
  10.     MVn = MVn1 + dMVn;
  11.     int manipulatedVariable = map(MVn, maximumPeriod, minimumPeriod, 0, 255);
  12.     int manipulatedOutput = constrain(manipulatedVariable, 0, 255);
  13.     analogWrite(manipulateOutputPin, manipulatedOutput);

PID制御をおこなうメンバ関数。
回転数設定ボリュームからの入力を目標値 SV、回転数パルスの周期を計測値 PV として、今回操作量 MVn を算出します。その値を PWM デューティ比として出力しています。

  1. #ifdef DEBUG_ON
  2.   Serial.print("SV:");
  3.   Serial.print(SV);
  4.   Serial.print(", PV:");
  5.   Serial.print(PV);
  6.   Serial.print(", MVn:");
  7.   Serial.print(MVn);
  8.   Serial.print("\n");
  9. #endif
  10.   }
  11. };

3行目のコメントアウトを解除すると、動作のようすがシリアルプロッタにグラフ化されます。ただ、なんかちょっと動作が変わるような気もします。どこかの処理に影響してるんじゃないかなぁ。

  1. TM1630 ledDisplay;
  2. PulseDetector pulseDetector;
  3. PIDcontrol pidControl;
  4. void setup() {
  5. #ifdef DEBUG_ON
  6.   Serial.begin(9600);
  7. #endif
  8. }
  9. void loop() {
  10.   static unsigned long interval = 100;
  11.   static unsigned long previousMillis = 0;
  12.   static long pulsePeriod = 0;
  13.   unsigned long currentMillis = millis();
  14.   if(interval <= currentMillis - previousMillis) {
  15.     pulsePeriod = pulseDetector.detectPulses();
  16.     pidControl.manipulate(pulsePeriod);
  17.     long numberToDisplay = 30000000 / pulsePeriod;
  18.     ledDisplay.displayNumbers(numberToDisplay);
  19.     previousMillis = currentMillis;
  20.   }
  21. }

オブジェクトの生成とループ関数です。
回転数を表示するだけのときはサンプリングを 1秒間にしていましたが、制御を行なうので 100ms に変更しています。その分、表示も慌ただしくなっています。そのあたりは、いろいろやり方があると思いますので、お好みで。

PID制御のようすは、次回、報告します

あらためて書こうと思いますが、PID制御の動作のようすをちょっとだけ。

図1. 加速時の動作 (PID制御)

図2. 制御なしの状態

図1 は、ファンを加速したときのグラフです。
縦軸は、回転数パルスの周期ですので、下へいくほど高速回転、上へいくと低速回転です。
目標値 SV (青) を段階的に変化させたときの、計測値 PV (赤)、操作量 MVn (緑) を示しています。

PID制御している状態では、PV が SV に追従し、SV にしっかり張り付いています。

制御しない状態では、図2 のようになります。PV は SV から大きく外れて、自由奔放に回転しています。

PID制御の効果が、しっかりでていると思います。

後記

ずっと前に、冷却ファンの PWM駆動制御を Arduino でやったことがあります。

Arduino CPUファン (PWM)を回してみた
体調があまり良くなくて、競技プログラミングのある問題のアルゴリズムを考えつつ、ぼんやりする日が続いてます。しかし、暑い。でもエアコンが寒い。そんな感じ。で、ファンを回してみようと思った。なんだかな〜 (^_^;) 使っていない CPU 冷却...

25KHz の PWM信号を、Arduino から直接出力させるものでした。レジスタをさわる必要がありますが、こんな方法もあります。

7セグメントLED の制御も、Arduino でできますね。ググれば、参考になるサイトがたくさんみつけられます。全部 Arduino でやるのはちょっとしんどいので、デコーダやドライバを使うのが楽ちんです。
少し前には、ディスクリートでダイナミック点灯をやってみました。

7セグメントLEDをダイナミック点灯させる (1) / デジット制御
先に製作したデジタル時計では、表示器として 7 セグメント LED を使用し、スタティック点灯させていました。それはそれでいいのですが、単純に技術的な興味から、ダイナミック点灯させるってのもやってみたいなと考えていました。もちろん、Ardu...

やり方、考え方はいろいろありますけど、Arduino に大きな負担をかけないのが、俺は好きです。

では次回は、PID制御の動作のようすを簡単にまとめて、冷却ファンの PID制御は終わりにしましょう。

タイトルとURLをコピーしました