본문 바로가기
프로세싱+아두이노+안드로이드

PID 온도제어

by YJHTPII 2022. 7. 25.
반응형

전기밥솥을 해킹해서 만드는 수비드머신의 제작  

PID콘트롤을 적용하다... #1

 

 

http://makewith.co/page/project/806/story/2292/

 

PID콘트롤을 적용하다... #1

애초의 계획은 On Off콘트롤로만 프로젝트를 끝내려고 했는데...히터의 관성이 커서 설정온도보다  10도이상 올라갔다가 온도가 너무 ...

www.makewith.co

 

 

 

PID콘트롤을 적용하다... #1

by 이희철 | 2017-01-16 15:37

애초의 계획은 On Off콘트롤로만 프로젝트를 끝내려고 했는데...

 

히터의 관성이 커서 설정온도보다  10도이상 올라갔다가 온도가 너무 천천히 내려오는 것을 보고 PID콘트롤을 추가하는 것을 검토하기로 했다...

 

비례-적분-미분 제어기 (Proportional–Integral–Derivative controller)의 앞의 첫글자를 따서 PID제어라고 하며  오차값, 오차값의 적분(integral), 오차값의 미분(derivative)의 합으로 시스템이 구성.

 

 

 

 

  • 비례항 : 현재 상태에서의 오차값의 크기에 비례한 제어작용을 한다.
  • 적분항 : 정상상태(steady-state) 오차를 없애는 작용을 한다.
  • 미분항 : 출력값의 급격한 변화에 제동을 걸어 오버슛(overshoot)을 줄이고 안정성(stability)을 향상시킨다.

 

기본적인 PID시스템에 관한 이론을 공부를 했는데...

 

이 역시 궁금한 사항을 물어볼 사람이 없어 많은 시간을 보냈다...

 

어쨋든...

 

 

첨부된 프로그램에서 Kp Ki Kd값을 여러번 측정해서 온도특성이 가장 잘 나오는 계수를 선택해서 상수값을 고정시킨다.

 

주의할 점은 상수값이 1000이상 큰 값으로 설정해야 PID시스템의 제어가 가능했다. (주의: 라이브러리에서 기본으로 제공했던 값(Kp=2 Ki=5 Kd=1)은 숫자가 너무 작아서 제대로 동작하지 않았슴.

프로그램의 오류가 생긴줄 알고 디버깅한다고 많은 시간을 허비했는데 결국 프로그램문제가 아니고 상수값 문제였다.)

 

 

프로그램 PLX-DAQ를 사용해서 엑셀로 전기밥솥의 온도를 수집해서 그래프로 나타내 보았다...

 

아래 그래프는 Ki와 Kd를 0으로 세팅한 후...

 

Kp값만 1000, 2000,3000,4000 4가지로 바꾸어 온도 특성 그래프를  나타내 보았는데...

 

Kp가 4000일 때(노란색선) 가장 좋은 온도특성을 나타내었으므로 Kp를 4000으로 설정했다...

 

 

 

Kp는 4000으로 설정했고 Kd값을 각각 1000, 2000,3000,4000으로 변화를 주어 온도특성을 살펴본 결과 Kd는 3000이 가장 적당한 값이라고 판단 3000으로 세팅했다...

 

 

 

 

 

마지막으로 Ki값을 설정할 차례...

 

Ki를 4000으로 설정해 보았더니 온도 제어를 못하고 100도까지 급상승했다...

 

너무 큰 값이라는 생각...

 

다시 Ki값을  1000부터 낮추기 시작해서 500, 250, -100까지 낮추어 보았더니...

 

-100일 때가 가장 세팅온도에 상당히 근접 한 값을 얻을 수 있었다...

 

Ki값은 -100으로 결졍...

 

PID시스템의 상수값은 실험을 많이 하면 할 수록 더 좋은 온도특성을 얻을 수 있지만...

 

이번 프로젝트에서는 이정도에서 만족하기로 한다...

 

 

 

위의 그래프에서 보듯이 On Off제어만 할 때는 세팅온도보다 10도이상 올라갔었는데...

 

PID콘트롤을 사용함으로서 온도가 너무 많이 올라가는 것을 억제하는 모습을 볼 수 있다...

 

 

https://github.com/br3ttb/Arduino-PID-Library/blob/master/PID_v1.h

 

GitHub - br3ttb/Arduino-PID-Library

Contribute to br3ttb/Arduino-PID-Library development by creating an account on GitHub.

github.com

 

#include <PID_v1.h>

/********************************************************
 * PID RelayOutput Example
 * Same as basic example, except that this time, the output
 * is going to a digital pin which (we presume) is controlling
 * a relay.  the pid is designed to Output an analog value,
 * but the relay can only be On/Off.
 *
 *   to connect them together we use "time proportioning
 * control"  it's essentially a really slow version of PWM.
 * first we decide on a window size (5000mS say.) we then
 * set the pid to adjust its output between 0 and that window
 * size.  lastly, we add some logic that translates the PID
 * output into "Relay On Time" with the remainder of the
 * window being "Relay Off Time"
 ********************************************************/

#define PIN_INPUT 0
#define SSR 11


//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
//double Kp=2, Ki=5, Kd=1;
double Kp=4000, Ki=-1000, Kd=3000;


PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE);

int WindowSize = 5000;
unsigned long windowStartTime;
unsigned long check_input_time;

// global variable
int tempTable[21] =   {45,70,90,118,147,
                      182,217,260,295,333,
                      382,417,466,508,555,
                      594,634,677,713,747,
                      797};

 int count=0;
 
 int duty = 0;
 int duty_high = 0;
 int duty_low = 0;

 int average_temp [10];  // 노이즈 감소용 배열  10개의 데이터를 평균치로 만듬

 int cookerTemp; // 전기밥솥에 평균화한 센서 측정온도

   double last_time = millis();

void setup()
{
  Serial.begin(9600);
/*
'WindowSize' is how many milliseconds per PWM cycle.
'windowStartTime' is the time the current PWM cycle started.
'now' is the current time (in milliseconds).
'now - WindowStartTime' is how far into the current PWM cycle we are.
The Output value is limited to the range 0 to WindowSize and sets the point in each PWM cycle where the output changes from HIGH to LOW:
*/



  windowStartTime = millis();
  check_input_time = millis();

  //initialize the variables we're linked to


 // Input = 0;  // initial value

 pinMode (11,OUTPUT);  // SSR OUTPUT MODE


  myPID.SetTunings(Kp, Ki, Kd); // Parameter setting
  
  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);

  Serial.println("CLEARDATA");
  Serial.println("LABEL,Current time,Cooker Temp");
}

/*
 5도 단위의 21개의 온도 테이블 값에서 해당하는 온도 구간을 알아낸 후
 델타값에 비례하는 온도값을 계산 한다. 
 */
int getTemp(int mADC)
{
  int i;
  for (i=0; i<21; i++)
  {
    if(mADC < tempTable[i])
      break;
  }
  if(i == 0)
    return 0;  // 0도 부터 5도 사이이면 0도를 리턴
  if(i == 21)
    return 100;  // 95도 이상이면 100도를 리턴
  int delta_ba = mADC - tempTable[i-1];
  int delta_ca = tempTable[i] - tempTable[i-1];
  return (i - 1) * 5 + 5 * delta_ba / delta_ca; // 섭씨 온도 값 리턴
}

int noise_reduction_temperature(){

 int total_temp = 0;
 int reduce_noise_temp_ADC = 0;
 int cookerTempADC;
    
  for (int i= 0; i<10;i++) {
    cookerTempADC = analogRead(A8);
 //   Serial.print ("cookerTempADC : "); Serial.println (cookerTempADC);
    average_temp [i] = cookerTempADC;
    total_temp += average_temp[i];
  }

 reduce_noise_temp_ADC = total_temp / 10;

//Serial.print ("reduce_noise_temp_ADC: ");
//Serial.println (reduce_noise_temp_ADC);
  
  cookerTemp = getTemp(reduce_noise_temp_ADC); 
  

// Serial.print ("cookerTemp: ");
// Serial.println (cookerTemp);

return cookerTemp;
}

void loop()
{
  Setpoint = 60;

 //Input =50;



/*
  if (millis() - check_input_time > 5000) {
      Input = temp_data[count++];
      if (count > 69) count=69;
      check_input_time = millis();
  }
*/
  myPID.Compute();

 if (millis() - last_time > 1000) {
  Input = (double) noise_reduction_temperature();
  Serial.print("DATA,TIME,");
  Serial.println(Input);
  last_time = millis();
 }
 
  /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if (millis() - windowStartTime > WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }

  
  if (Output < millis() - windowStartTime)
  { 
    digitalWrite(SSR, HIGH); 
    digitalWrite(13, HIGH);  
 //   Serial.print("Input: "); Serial.print (Input);  Serial.print("c       ");
 //   Serial.print("Output: "); Serial.println (Output); 


 /*
    duty_high++; 
    if (duty_high + duty_low == 100) { 
      Serial.print("Input: "); Serial.print (Input);
      Serial.print("     Duty: "); Serial.print(duty);  Serial.println("%");
      duty = duty_high; duty_high=0;duty_low=0; 
     }
 */  
 //     Serial.println ("RELAY ON");  
    }
  else 
  {
    digitalWrite(SSR, LOW);
    digitalWrite(13, LOW);
//    Serial.println ("RELAY OFF");

 //   Serial.print("Input: "); Serial.print (Input);  Serial.print("  c");
 //   Serial.print("output: "); Serial.println (Output); 
  // duty_low++;
  }

} // end of loop


반응형

댓글