冷却ファンの回転数を検出し、PID制御する回路を作ります。
前回までに、ファンの制御に必要なフィードバック回路ができあがりました。
今回はいよいよ、ファンの回転数を PID制御してみようと思います。
毎度まいどですが、PID制御についてはググってください m(_ _;)m 過去記事もご覧いただければ。
PID制御のスケッチ
前回までに書いたスケッチに、PID制御を追加しました。
その他、細かい修正もしています。だいたいがまぁ、気分です (;´Д`)
- // FAN Speed Control v.1 2022.12.24 meyon230
- //#define DEBUG_ON
- class TM1630 {
- private:
- const byte DISPLAY_MODE = 0x00;
- const byte WRITE_REG_AUTO_ADDR = 0x40;
- const byte WRITE_REG_FIXED_ADDR = 0x44;
- const byte READ_KEY_SCAN = 0x42;
- const byte SET_DISPLAY_ADDR = 0xC0;
- const byte DISPLAY_OFF = 0x80;
- const byte DISPLAY_ON = 0x88 | 0x84;
- const byte STB_ENABLE = LOW;
- const byte STB_DISABLE = HIGH;
- const int DOT_POSITION = -1;
- const byte digit[12] = {
- 0b01111110, // 0
- 0b00001100, // 1
- 0b10110110, // 2
- 0b10011110, // 3
- 0b11001100, // 4
- 0b11011010, // 5
- 0b11111010, // 6
- 0b01001110, // 7
- 0b11111110, // 8
- 0b11011110, // 9
- 0b00000000, // 10:blank
- 0b00100000 // 11:dot
- };
- const byte dioPin = 10;
- const byte clkPin = 11;
- const byte stbPin = 12;
- const byte numberOfDigits = DISPLAY_MODE & 1 ? 5 : 4;
- const byte blank = 10;
- const byte dot = 11;
- byte gridData[];
- public:
- TM1630() {
- pinMode(dioPin, OUTPUT);
- pinMode(clkPin, OUTPUT);
- pinMode(stbPin, OUTPUT);
- gridData[numberOfDigits] = {0};
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, DISPLAY_MODE);
- digitalWrite(stbPin, STB_DISABLE);
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, WRITE_REG_AUTO_ADDR);
- digitalWrite(stbPin, STB_DISABLE);
- }
- void displayNumbers(int n) {
- for (byte i = 0; i < numberOfDigits; i++) {
- int exponentialInDecimal = pow(10, i) + 0.5;
- bool zeroSuppression = (0 != i) && (DOT_POSITION < i) && (exponentialInDecimal > n);
- gridData[i] = zeroSuppression ? blank : n / exponentialInDecimal % 10;
- }
- digitalWrite(stbPin,STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, SET_DISPLAY_ADDR | 0x00);
- for (byte i = 0; i < numberOfDigits; i++) {
- shiftOut(dioPin, clkPin, LSBFIRST, digit[gridData[i]]);
- shiftOut(dioPin, clkPin, LSBFIRST, DOT_POSITION == i ? digit[dot] : 0);
- }
- digitalWrite(stbPin, STB_DISABLE);
- digitalWrite(stbPin, STB_ENABLE);
- shiftOut(dioPin, clkPin, LSBFIRST, DISPLAY_ON);
- digitalWrite(stbPin, STB_DISABLE);
- }
- };
7セグメントLED ドライバ TM1630 の制御をおこなうクラスです。
基本的に、前回までと変わりないですが、一部、動作モードの転送を、コンストラクタからメンバ関数へ移動したりしてます。どっちでもいいんですケド。
- class PulseDetector {
- private:
- const byte sensorPin = 7;
- long pulsePeriod;
- public:
- PulseDetector () {
- pinMode(sensorPin, INPUT);
- pulsePeriod = 0;
- }
- long detectPulses() {
- pulsePeriod = pulseIn(sensorPin, HIGH);
- pulsePeriod += pulseIn(sensorPin, LOW);
- return pulsePeriod;
- }
- };
回転数パルスの検出を行なうクラス。
これも変更ないです。あ、変数の型をちょっと変えたかな。
- class PIDcontrol {
- private:
- const float Kp = 2.5; // Proportional
- const float Ki = 0.15; // Integral
- const float Kd = 1.3; // Differential
- const long minimumPeriod = 13800; // 2170rpm
- const long maximumPeriod = 25900; // 1160rpm
- const byte potentiometerPin = 0;
- const byte manipulateOutputPin = 5;
- long en;
- long en1;
- long en2;
- long MVn;
- long MVn1;
- long dMVn;
追加した PID制御のクラス宣言とメンバ変数。
PID制御は、速度型制御アルゴリズムを使いました。
Kp、Ki、Kd は、それぞれ比例制御、積分制御、微分制御の制御係数です。値に関しては、まぁ、こんな感じかなぁと。動作のようすについては、あらためて書きます。
en、en1、en2 はそれぞれ、今回、前回、前々回の偏差。MVn、MVn1 は今回、前回の操作量。dMVn は操作量の増分です。
- public:
- PIDcontrol() {
- pinMode(potentiometerPin, INPUT);
- pinMode(manipulateOutputPin, OUTPUT);
- en = 0;
- en1 = 0;
- en2 = 0;
- MVn = 0;
- MVn1 = 0;
- dMVn = 0;
- }
コンストラクタ。
ピンモードの設定をしてますけど、INPUT と PWM なので、必須ではないです。
- void manipulate(long pulsePeriod) {
- int potentiometerVoltage = analogRead(potentiometerPin);
- long SV = map(potentiometerVoltage, 0, 1023, maximumPeriod, minimumPeriod);
- long PV = pulsePeriod;
- en2 = en1;
- en1 = en;
- en = SV - PV;
- MVn1 = MVn;
- dMVn = Kp*(en-en1) + Ki*en + Kd*((en-en1)-(en1-en2));
- MVn = MVn1 + dMVn;
- int manipulatedVariable = map(MVn, maximumPeriod, minimumPeriod, 0, 255);
- int manipulatedOutput = constrain(manipulatedVariable, 0, 255);
- analogWrite(manipulateOutputPin, manipulatedOutput);
PID制御をおこなうメンバ関数。
回転数設定ボリュームからの入力を目標値 SV、回転数パルスの周期を計測値 PV として、今回操作量 MVn を算出します。その値を PWM デューティ比として出力しています。
- #ifdef DEBUG_ON
- Serial.print("SV:");
- Serial.print(SV);
- Serial.print(", PV:");
- Serial.print(PV);
- Serial.print(", MVn:");
- Serial.print(MVn);
- Serial.print("\n");
- #endif
- }
- };
3行目のコメントアウトを解除すると、動作のようすがシリアルプロッタにグラフ化されます。ただ、なんかちょっと動作が変わるような気もします。どこかの処理に影響してるんじゃないかなぁ。
- TM1630 ledDisplay;
- PulseDetector pulseDetector;
- PIDcontrol pidControl;
- void setup() {
- #ifdef DEBUG_ON
- Serial.begin(9600);
- #endif
- }
- void loop() {
- static unsigned long interval = 100;
- static unsigned long previousMillis = 0;
- static long pulsePeriod = 0;
- unsigned long currentMillis = millis();
- if(interval <= currentMillis - previousMillis) {
- pulsePeriod = pulseDetector.detectPulses();
- pidControl.manipulate(pulsePeriod);
- long numberToDisplay = 30000000 / pulsePeriod;
- ledDisplay.displayNumbers(numberToDisplay);
- previousMillis = currentMillis;
- }
- }
オブジェクトの生成とループ関数です。
回転数を表示するだけのときはサンプリングを 1秒間にしていましたが、制御を行なうので 100ms に変更しています。その分、表示も慌ただしくなっています。そのあたりは、いろいろやり方があると思いますので、お好みで。
PID制御のようすは、次回、報告します
あらためて書こうと思いますが、PID制御の動作のようすをちょっとだけ。
図1 は、ファンを加速したときのグラフです。
縦軸は、回転数パルスの周期ですので、下へいくほど高速回転、上へいくと低速回転です。
目標値 SV (青) を段階的に変化させたときの、計測値 PV (赤)、操作量 MVn (緑) を示しています。
PID制御している状態では、PV が SV に追従し、SV にしっかり張り付いています。
制御しない状態では、図2 のようになります。PV は SV から大きく外れて、自由奔放に回転しています。
PID制御の効果が、しっかりでていると思います。
後記
ずっと前に、冷却ファンの PWM駆動制御を Arduino でやったことがあります。
25KHz の PWM信号を、Arduino から直接出力させるものでした。レジスタをさわる必要がありますが、こんな方法もあります。
7セグメントLED の制御も、Arduino でできますね。ググれば、参考になるサイトがたくさんみつけられます。全部 Arduino でやるのはちょっとしんどいので、デコーダやドライバを使うのが楽ちんです。
少し前には、ディスクリートでダイナミック点灯をやってみました。
やり方、考え方はいろいろありますけど、Arduino に大きな負担をかけないのが、俺は好きです。
では次回は、PID制御の動作のようすを簡単にまとめて、冷却ファンの PID制御は終わりにしましょう。