上位に戻る PID制御 温度管理 初版2025/ 1/ 9

机用こたつのファンヒータをトライアックで手動で調整していたのですがちょっと面倒になってきたので自動制御してみること
にしました。サーモスタットユニットだと、安価なんですが ファンヒータの元電源をしょっちゅう入切は、ヒータの温度が下がって
いない状態でファンを停止してしまうと良くありません。で、余っている部品を使ってPID制御してみました。
余っている部品の活用なのでちょっと理想的な構成じゃないけど

  1. 構成
    構成は単純で、マイコンで、こたつ内に温度センサーを付けて計測し、PID制御計算して、サーボモータにつなげたトライアックの
    ボリュームを制御します 目標温度設定用にボリュームを付けます また、計測温度の表示と、目標温度の表示で7セグを付けました
    新規に部品を購入するんだったら フォトトライアックを使ってサーボを使わないで直接制御します。同期信号検出にトランスと、フォト
    カプラ(ヒータのみの制御だったら1分周期の長いPWMで同期しないで制御されている例もありました)必要ですね
    3桁7セグをダイナミック表示させていますが、シリアル通信できるものが安価に売られているのでそれを使ったほうがいいです
    7セグ制御配線はめんどうだし、プログラムもうざくなるス
  2. 回路構成
    もう配線は、ほぼ7セグ用ですね シリアル通信の7セグだったら3本線ですよね・・・。

  3. 使った構成品

    買ったときすでにあまり使われなく
    なっていて価格が高い!
    でも送ってきたもの1個がダイオードが
    取れており、申告したら、コネクタ違い
    のもを送ってきた 申告したら同じ2個
    セットものを送ってきた
    返品不要とのことで、ダイオード取れた
    ものはくっつけて使い、結局3個余って
    しまったもの
    CH340が模造っぽく古いドライバV3.5
    以降でないと使えない
    バドミントンの自動ノックマシンで2個
    使って余っている
    ELEGOOの Arduino用UNO R3
    スターターキット
     配線めんどうなの
    で使っていなかった7セグと
    シフトレジスタ
    5V電源 フィルター用インダクタが
    入ってないのでスイッチノイズでまくり
    なかなか減らん・・・。
    猫の熱中症対策で使った水温計




  4. どんな感じ

    トライアックと、サーボモータ取付部
    スマホが入っていた紙の空き箱
    に組付けたところ
    丸穴は、放熱排気用
    正面から見たところス
    スモークのエンビ板を付けてます

  5. プログラムのソース
    素人プログラムでかっこ悪いので、過去ソースは掲載していないのですが、PID制御 ムズかったので載せておきます
    7セグのダイナミック表示ですが、目の残像効果ですが、昔のテレビが垂直周波数が、60HZだったので
    1桁当たりの表示時間ですが (1/60)/3桁×1000=5msecで設定しています
    7セグの表示をタイマー割り込みで動作させたのですが、PID制御処理中の割り込み動作で、サーボモータがなんか
    ガガガガみたいな変な動きになるので、止めました loopで回しています そのためPID制御処理処理中は、7セグの表示
    が消えてしまいます
    手が冷たいので、こたつに両手突っ込んでいたら暖かい空気が漏れていつまでも目標値に到達しないことがありました
    この場合、積分値がどんどん巨大化してしまい その後目標値に到達しても設定値がもとに戻るのに時間がかかり、
    異常制御で大幅にオーバシュートします この為、積分値に制限を掛けました


    #include <Servo.h>
    #include <MsTimer2.h>  //7セグ表示をタイマー割込で試験動作 未使用
    #include <OneWire.h>
    #include <DallasTemperature.h>

    // Data wire is plugged into port 2 on the Arduino
    #define ONE_WIRE_BUS 2  //温度センサー
    // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
    OneWire oneWire(ONE_WIRE_BUS);
    // Pass our oneWire reference to Dallas Temperature.
    DallasTemperature sensors(&oneWire);

    Servo myservo;  // create servo object to control a servo
    int latch=9;  //74HC595  pin 9 STCP
    int clock=10; //74HC595  pin 10 SHCP
    int data=8;   //74HC595  pin 8 DS
    int P_DIG1=4; //7セグ4桁目
    int P_DIG2=5; //7セグ3桁目
    int P_DIG3=7; //7セグ2桁目
    int P_DIG4=6; //7セグ1桁目
    int sww=3;    //タッチスイッチ 切り替え用
    int potpin = 0; //ボリュームピン アナログ
    int val;    //ボリューム変数
    int val1;   //サーボ出力値
    int sondo=250; //温度変数
    int mondo=250; //目標温度
    int countt=1; //桁カウンター
    int xx;
    int ondoco=100;
    float sensatai;
    // PID定数設定 ===============
    int KP = 20;  // Pゲイン
    int KD = 5;   // Dゲイン
    float KI = 0.05;   // Iゲイン
    // PID変数 ===============
    int e_pre = 0; // 微分の近似計算のための初期値
    float ie = 0;    // 誤差の積分
    float e_ie; //誤差積分今回値
    float de; //誤差の微分
    int e; //誤差変数
    int e_per; //誤差前回値
    float out; //制御出力値
    int TT=5; //周期5秒


    unsigned char table[]=
    {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c
    ,0x39,0x5e,0x79,0x71,0x00,0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0xf7,0xfc,0xb9,0xde,0xf9,0xf1,0x80};

    void setup() {
      pinMode(latch,OUTPUT);
      pinMode(clock,OUTPUT);
      pinMode(data,OUTPUT);
      pinMode(P_DIG1,OUTPUT);
      pinMode(P_DIG2,OUTPUT);
      pinMode(P_DIG3,OUTPUT);
      pinMode(P_DIG4,OUTPUT);
      pinMode(sww,INPUT);
      digitalWrite(P_DIG1,HIGH);
      digitalWrite(P_DIG2,HIGH);
      digitalWrite(P_DIG3,HIGH);
      digitalWrite(P_DIG4,HIGH);
      myservo.attach(11);  // attaches the servo on pin 9 to the servo object
      sensors.begin();
      val = analogRead(potpin);            // 設定温度読み出し
      mondo = map(val, 0, 1024, 250, 450);
      Serial.begin(9600);
      //MsTimer2::set(5,ssegu); // 5mS INT タイマー割り込みでテスト用
      //MsTimer2::start();
    }

    //7セグ出力
    void Display(unsigned char num)
    {

      digitalWrite(latch,LOW);
      shiftOut(data,clock,MSBFIRST,table[num]);
      digitalWrite(latch,HIGH);
     
    }

    //7セグ表示
    void ssegu(){
      digitalWrite(P_DIG1,HIGH);
      digitalWrite(P_DIG2,HIGH);
      digitalWrite(P_DIG3,HIGH);
      digitalWrite(P_DIG4,HIGH);

      switch(countt){
        case 1:
         if(digitalRead(sww)==HIGH)
          {
          val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
          sondo = map(val, 0, 1024, 250, 450);
          mondo=sondo;  //設定温度を代入
          }else{
          //sensatai=sensatai*10;
          sondo=(int)sensatai;  //計測温度を代入
          }
          xx=sondo%10;  //1桁目を取り出し
          Display(xx);
          digitalWrite(P_DIG1,LOW);
          countt=countt+1;
          break;

        case 2:
          val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
          xx=sondo/10%10;  //2桁目を取り出し
          xx=xx+17;  //小数点ありの場合
          Display(xx);
          digitalWrite(P_DIG2,LOW);
          countt=countt+1;
          break;

        case 3:
          val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
          xx=sondo/100%10;  //3桁目を取り出し
          Display(xx);
          digitalWrite(P_DIG3,LOW);
          countt=1;

      }
    }



    void loop() {
     ssegu();  //1桁表示
     delay(5);  //1桁表示時間
    ondoco=ondoco-1;  //カウンタデクリメント
    if(ondoco==0){
      sensors.requestTemperatures(); // Send the command to get temperatures
      sensatai=sensors.getTempCByIndex(0);
      sensatai=sensatai*10;
      ondoco=1000;  //カウンタセット約5秒 1000×5msec
    //PID計算開始
    e=mondo-sensatai; //誤差計算
    de=(e-e_per)/TT; //誤差の微分
    e_ie=ie;
    ie = ie + (e + e_pre)*TT/2; // 誤差の積分を計算
    if((KI*ie)<0){ie=e_ie;Serial.println("低");}  //積分値が、制御範囲を超えた場合加算しない
    if((KI*ie)>2000){ie=e_ie;Serial.println(KI*ie);}  //積分値が、制御範囲を超えた場合加算しない
    out  = KP*e + KI*ie + KD*de; // PID制御の式にそれぞれを代入
    e_pre = e;

    //動作確認用
    Serial.print("誤差:");
    Serial.print(e);
    Serial.print("   誤差微分:");
    Serial.print(de);
    Serial.print("   誤差積分:");
    Serial.print(ie);
    Serial.print("  出力:");
    Serial.println(out);



    if(out<0){out=0;}  //出力値設定範囲を超えた場合制限
    if(out>2000){out=2000;}  //出力値設定範囲を超えた場合制限
    val1 = map(out, 0, 2000, 180, 30);

    myservo.write(val1);

    }


    }



    以上間違い等ありましたら、以下ご意見箱にお願いします

   

    ご意見箱