User Tools

Site Tools


en:pid_regulator

PID regulator

PID regulator is combination of temperature and humidity sensor DHT11 and triac regulation. Primary it is dedicated to regulation of fan in dependency on actual temperature or humidity. It can be set maximum or minimum power on it. It can operate in normal(with increasing temperature increases power) or reverse(with decreasing temperature increases power) mode. Power is set by PID regulator (see Typy regulace). Constants of P,I i D regulator can be change in menu.

Schema

PCB overlay


Code

PIDregulator.ino
#include <avr/io.h>
#include <avr/interrupt.h>
#include <LiquidCrystal.h>
 
#include <DHT.h>
#include <EEPROM.h>
#include "EEPROMAnything.h"
 
#include <PID_v1.h>
#include <math.h>
 
 
#define TriakRegul 5
#define DHTInput A0
#define Buttons A1
#define ZeroPointDetect 2
#define MenuHideButton 3
 
#define BOUNCE_DURATION 200
 
#define PULSE 10   //trigger pulse width (counts)
#define DHTTYPE DHT11
 
#define KeyUp    30
#define KeyDown  40
#define KeySel   50
#define KeyInv   60 
 
#define MainMenuA 0
#define MainMenuB 1
#define MainMenuE 5
#define DirectMenuA 1
#define DirectMenuB 6
#define DirectMenuE 7
#define KeyMenuA 2
#define KeyMenuB 8
#define KeyMenuE 9
#define ModeMenuA 3
#define ModeMenuB 10
#define ModeMenuE 11
#define ConstMenuA 4
#define ConstMenuB 12
#define ConstMenuE 14
#define PConstMenuA 12
#define IConstMenuA 13
#define DConstMenuA 14
#define LimitMenuA 5
#define LimitMenuB 15
#define LimitMenuE 17
#define SubLimitMenuOff 17
#define SubLimitMenuOffB 18
#define SubLimitMenuOffE 19
#define SubLimitMenuMin 15
#define SubLimitMenuMax 16
#define SuccessfulyDone 20
 
 
volatile unsigned long bounceTime=0;
//int i=483;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7  );
 
 
DHT dht(DHTInput, DHTTYPE);
 
double value;
 
boolean showStatus;
 
char tmp[8];
 
boolean detached;
 
prog_char string_0[] PROGMEM =   "MENU";  //0
prog_char string_1[] PROGMEM =   "Trend";  //1
prog_char string_2[] PROGMEM =   "Set pt";  //2
prog_char string_3[] PROGMEM =   "Mode";  //3
prog_char string_4[] PROGMEM =   "Const";  //4
prog_char string_5[] PROGMEM =   "Limit";  //5
prog_char string_6[] PROGMEM =   "DIRECT";  //6
prog_char string_7[] PROGMEM =   "REVERSE";  //7
prog_char string_8[] PROGMEM =  "TEMP"; //8
prog_char string_9[] PROGMEM =  "HUM"; //9
prog_char string_10[] PROGMEM =   "MANUAL";  //10
prog_char string_11[] PROGMEM =  "AUTO"; //11
prog_char string_12[] PROGMEM =  "P"; //12
prog_char string_13[] PROGMEM =   "I";  //13
prog_char string_14[] PROGMEM =  "D"; //14
prog_char string_15[] PROGMEM =  "MIN"; //15
prog_char string_16[] PROGMEM =  "MAX"; //16
prog_char string_17[] PROGMEM =  "OFFING"; //17
prog_char string_18[] PROGMEM =  "ON"; //17
prog_char string_19[] PROGMEM =  "OFF"; //17
prog_char string_20[] PROGMEM =  "reading..."; //17
 
PROGMEM const char *StringTable[] = {
string_0, //0
string_1, //1
string_2, //2
string_3, //3         
string_4, //4
string_5, //5
string_6, //6
string_7, //7
string_8, //8
string_9,
string_10,
string_11,
string_12,
string_13,
string_14,
string_15,
string_16,
string_17,
string_18,
string_19,
string_20//17 
};
 
byte whichkey,offset;
 
 
int direct;
double p,i,d;
double Setpoint, Output;
boolean mode=1; //AUTOMATIC
boolean keyVal,offing;
double maxOut,minOut;
PID myPID(&value, &Output, &Setpoint,p,i,d,REVERSE);
char* menuList[]={"P", "I", "D"};
 
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup()                
{
  pinMode(TriakRegul,OUTPUT); 
  pinMode(DHTInput,INPUT);  
  pinMode(ZeroPointDetect,INPUT);
  pinMode(MenuHideButton,INPUT);
 digitalWrite(ZeroPointDetect, HIGH);
 
lcd.begin(8, 2);
 
  OCR1A = 100;      //initialize the comparator
  TIMSK1 = 0x03;    //enable comparator A and overflow interrupts
  TCCR1A = 0x00;    //timer control registers set for
  TCCR1B = 0x00;
 
attachInterrupt(0,zeroCrossingInterrupt, RISING);
attachInterrupt(1,show, FALLING);
dht.begin();
 
EEPROM_readAnything(10, Setpoint);
EEPROM_readAnything(20, keyVal);
EEPROM_readAnything(30, direct);
EEPROM_readAnything(40, p);
EEPROM_readAnything(50, i);
EEPROM_readAnything(60, d);
EEPROM_readAnything(70, minOut);
EEPROM_readAnything(80, maxOut);
EEPROM_readAnything(90, offing);
 
myPID.SetControllerDirection(direct);
myPID.SetTunings(p, i, d); 
myPID.SetMode(AUTOMATIC);
myPID.SetOutputLimits(minOut, maxOut);
 
delay(2000);
}
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void zeroCrossingInterrupt(){ //zero cross detect   
  TCCR1B=0x04; //start timer with divide by 256 input
  TCNT1 = 0;   //reset timer - count from zero
}
 
ISR(TIMER1_COMPA_vect){ //comparator match
  digitalWrite(TriakRegul,HIGH);  //set triac gate to high
  TCNT1 = 65536-PULSE;      //trigger pulse width
}
 
ISR(TIMER1_OVF_vect){ //timer1 overflow
  digitalWrite(TriakRegul,LOW); //turn off triac gate
  TCCR1B = 0x00;          //disable timer stopd unintended triggers
}
 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void show(){
if(abs(millis() - bounceTime) > BOUNCE_DURATION)   
  {
      showStatus=!showStatus;
      bounceTime = millis();  
 }
}
 
void done(){
lcd.clear(); 
PrintLCDAt_P(SuccessfulyDone,0,0);
delay(500);
mainMenu();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop()                    
{
if(showStatus)mainMenu();
delay(100);
if(!keyVal) value=dht.readTemperature();
else value=dht.readHumidity(); 
 
if(mode){
myPID.Compute();
 
if (Output==myPID.GetMinOutput() && offing){
  LCDAt("F",7,0);
  detachInterrupt(0);
  detached=true; 
} else if(detached){
attachInterrupt(0,zeroCrossingInterrupt, RISING);
detached=false; 
} 
 
 
OCR1A=PowerToIMCA(Output);
}else{switch(KeyScan()){
case KeyDown:if(OCR1A<440)OCR1A+=10;break;
case KeyUp:if(OCR1A>81)OCR1A-=10;break;
break;
}
}
 
if(!keyVal) {
dtostrf(value,1,1,tmp);
LCDAt(tmp,0,0);
lcd.print((char)178);
lcd.print("C");
}else{
dtostrf(value,0,0,tmp);
LCDAt(tmp,0,0);
lcd.print("%");
}
 
dtostrf(Setpoint,0,0,tmp);
LCDAt(tmp,0,1);
 
dtostrf(IMCAToPower(OCR1A),0,0,tmp);
LCDAt(tmp,5,1);
lcd.print("%");
delay(10);
}
 
///////////////////////////////////////MAIN MENU///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void mainMenu() {
 delay(50);
  lcd.clear();
  showStatus=true;
  offset=MainMenuB;
  delay(50);
  do {
    PrintLCDAt_P(MainMenuA,0,0);
    lcd.setCursor(0, 1);
    lcd.print(offset-MainMenuB+1);
    PrintLCDAt_P(offset,2,1);
    whichkey = PollKey();// PollKey();
    switch(whichkey) {
       case KeyDown:
         if (offset < MainMenuB+1) offset=MainMenuE;
          else offset--;
          break;
       case KeyUp:
          if (offset > MainMenuE-1) offset=MainMenuB;
          else offset++;
          break;
       case KeySel:
          switch (offset) {
             case 1:DirectMenu();break;
             case 2:KeyMenu();break;
             case 3:ModeMenu();break;
             case 4:ConstMenu();break;
             case 5:LimitMenu();break;
             }
          break;
    }
   lcd.clear();
  } while (showStatus);
 
}
 
/////////////////////////////////////////Direct MENU///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void DirectMenu() {
  lcd.clear();
  offset=DirectMenuB;
   do {
    PrintLCDAt_P(DirectMenuA,0,0);
    if(myPID.GetDirection()==(offset-DirectMenuB))LCDAt("X",7,1); //(char)196 
    PrintLCDAt_P(offset,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (offset < DirectMenuB+1) offset=DirectMenuE;
          else offset--;
          break;
       case KeyUp:
          if (offset > DirectMenuE-1) offset=DirectMenuB;
          else offset++;
          break;
       case KeySel:
          myPID.SetControllerDirection(offset-DirectMenuB);
          EEPROM_writeAnything(30,offset-DirectMenuB);
          done();
          break;
    }
   lcd.clear();
  } while (showStatus);
}
 
/////////////////////////////////////////KEY MENU///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void KeyMenu() {
  lcd.clear();
  offset=KeyMenuB;
  do {
    PrintLCDAt_P(KeyMenuA,0,0);
    PrintLCDAt_P(offset,0,1);
    if(keyVal==(offset-KeyMenuB))LCDAt(Setpoint,6,1); 
    else LCDAt("X",6,1); 
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (offset < KeyMenuB+1) offset=KeyMenuE;
          else offset--;
          break;
       case KeyUp:
          if (offset > KeyMenuE-1) offset=KeyMenuB;
          else offset++;
          break;
       case KeySel:
          keyVal=(offset-KeyMenuB);
          EEPROM_writeAnything(20,keyVal);
          SubKeyMenu(offset);
          break;
    }
   lcd.clear();
  } while (showStatus);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SubKeyMenu(int what) {
  lcd.clear();
  offset=Setpoint;
  do {
    if(what==KeyMenuB)PrintLCDAt_P(KeyMenuB,0,0);
    else PrintLCDAt_P(KeyMenuE,0,0);
    LCDAt(offset,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (offset < -21) offset=-20;
          else offset--;
          break;
       case KeyUp:
          if (offset > 99) offset=100;
          else offset++;
          break;
       case KeySel:
          Setpoint=offset;
          EEPROM_writeAnything(10,Setpoint);
          done();
          break;
    }
   lcd.clear();
  } while (showStatus);
}
 
 
/////////////////////////////////////////Mode MENU///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void ModeMenu() {
  lcd.clear();
  offset=ModeMenuB;
  do {
    PrintLCDAt_P(ModeMenuA,0,0);
    if(myPID.GetMode()==(offset-ModeMenuB))LCDAt("X",7,1); 
    PrintLCDAt_P(offset,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
          if (offset < ModeMenuB+1) offset=ModeMenuE;
          else offset--;
          break;
       case KeyUp:
          if (offset > ModeMenuE-1) offset=ModeMenuB;
          else offset++;
          break;
       case KeySel:
          mode=(offset-ModeMenuB);
          myPID.SetMode(mode);
          done();
          break;
    }
   lcd.clear();
  } while (showStatus);
}
/////////////////////////////////////////CONST MENU///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void ConstMenu() {
  lcd.clear();
  offset=ConstMenuB;
    do {
  PrintLCDAt_P(ConstMenuA,0,0);
  switch (offset){
 case ConstMenuB:dtostrf(myPID.GetKp(),1,1,tmp);LCDAt(tmp,4,1);break;
 case ConstMenuB+1:dtostrf(myPID.GetKi(),1,1,tmp);LCDAt(tmp,4,1);break;
 case ConstMenuB+2:dtostrf(myPID.GetKd(),1,1,tmp);LCDAt(tmp,4,1);break;
  }
    PrintLCDAt_P(offset,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
          if (offset < ConstMenuB+1) offset=ConstMenuE;
          else offset--;
          break;
       case KeyUp:
          if (offset > ConstMenuE-1) offset=ConstMenuB;
          else offset++;
          break;
       case KeySel:
          SubConstMenu(offset);
          break;
    }
   lcd.clear();
  } while (showStatus);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SubConstMenu(int which) {
  lcd.clear();
  showStatus=true;
  int name;
  double number;
  switch(which){
  case ConstMenuB:name=PConstMenuA; number=myPID.GetKp();break;
  case ConstMenuB+1:name=IConstMenuA; number=myPID.GetKi();break;
  case ConstMenuB+2:name=DConstMenuA; number=myPID.GetKd();break;
  }
 
    do {
    PrintLCDAt_P(name,0,0);
    LCDAt(number,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (number < 1) number=0;
          else number-=0.1;
          break;
       case KeyUp:
          number+=0.1;
          break;
       case KeySel:
          switch(which){
          case ConstMenuB:myPID.SetTunings(number, myPID.GetKi(),myPID.GetKd());EEPROM_writeAnything(40,number);break; 
          case ConstMenuB+1:myPID.SetTunings(myPID.GetKp(),number,myPID.GetKd());EEPROM_writeAnything(50,number);break;
          case ConstMenuB+2:myPID.SetTunings(myPID.GetKp(),myPID.GetKi(),number);EEPROM_writeAnything(60,number);break;
          }
          done();
          break;
    }
   lcd.clear();
  } while (showStatus);
}
 
/////////////////////////////////////////LIMIT MENU///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void LimitMenu() {
  lcd.clear();
  offset=LimitMenuB;
  do {
    PrintLCDAt_P(LimitMenuA,0,0);
    PrintLCDAt_P(offset,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (offset < LimitMenuB+1) offset=LimitMenuE;
          else offset--;
          break;
       case KeyUp:
          if (offset > LimitMenuE-1) offset=LimitMenuB;
          else offset++;
          break;
       case KeySel:
          SubLimitMenu(offset);
          break;
    }
   lcd.clear();
  } while (showStatus);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void SubLimitMenu(int which) {
  lcd.clear();
  int name;
  double number;
  switch(which){
  case LimitMenuB:name=SubLimitMenuMin;number=myPID.GetMinOutput();break;
  case LimitMenuB+1:name=SubLimitMenuMax;number=myPID.GetMaxOutput();break;
  case LimitMenuB+2:PrintLCDAt_P(SubLimitMenuOff,0,0);offset=SubLimitMenuOffB;break;
  }
  if(which!=LimitMenuB+2){  
   do {
     lcd.clear();
    PrintLCDAt_P(name,0,0);
    LCDAt(number,0,1);
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (number < 21) number=20;
          else number--;
          break;
       case KeyUp:
          if (number > 99) number=100;
          else number++;
          break;
       case KeySel:  
          switch(which){
          case LimitMenuB:if(number<myPID.GetMaxOutput()) myPID.SetOutputLimits(number,myPID.GetMaxOutput());EEPROM_writeAnything(70,number);break;
          case LimitMenuB+1:if(number>myPID.GetMinOutput()) myPID.SetOutputLimits(myPID.GetMinOutput(),number);EEPROM_writeAnything(80,number);break;
          }
          done();
          break;
    }
   } while (showStatus);
   }else{
      do {
    lcd.clear();
    PrintLCDAt_P(name,0,0);
    PrintLCDAt_P(offset,0,1);
    if(offing!=(offset-SubLimitMenuOffB))LCDAt("X",7,1); 
    whichkey = PollKey();
    switch(whichkey) {
       case KeyDown:
         if (offset < SubLimitMenuOffB+1) offset=SubLimitMenuOffE;
          else offset--;
          break;
       case KeyUp:
          if (offset > SubLimitMenuOffE-1) offset=SubLimitMenuOffB;
          else offset++;
          break;
       case KeySel:
          if(offset==SubLimitMenuOffB)offing=true;
          else offing=false;
          if(detached){
          attachInterrupt(0,zeroCrossingInterrupt, RISING);
          detached=false; 
          }
          EEPROM_writeAnything(90,offing);
          done();
          break;
    }
   }while (showStatus);
     }
   lcd.clear();
 
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void PrintLCDAt(char *inStr, char x, char y) {
     lcd.setCursor( x,y ); 
     delay(20);
     lcd.print(inStr);
     delay(40);
}
 
void PrintLCDAt_P(int which, char x, char y) {
   lcd.setCursor( x,y );  
   delay(20);
   PrintLCD_P(which);  
}
 
void PrintLCD_P(int which) {
   char buffer[21];
   strcpy_P(buffer, (char*)pgm_read_word(&(StringTable[which])));
   lcd.print(buffer);
   delay(40);
}
 
void LCDAt(String what, int x, int y){
lcd.setCursor(x,y);
lcd.print(what);
}
 
void LCDAt(double what, int x, int y){
lcd.setCursor(x,y);
lcd.print(what);
}
 
char KeyScan() {
   int which, which2, diff,retVal;
   which = analogRead(Buttons);
   delay(10);
   which2 = analogRead(Buttons);
   retVal = KeyInv;
   diff = abs(which - which2);
   if (diff < 12) {
 
      if (which > 900 && which < 1024) retVal =  KeySel;
      if (which > 300  && which < 360) retVal =  KeyDown;
 
      if (which > 480 && which < 520) retVal =  KeyUp;
   }
return retVal;
}
 
char PollKey() {
  char Whichkey;
    do {
     Whichkey = KeyScan();
     delay(60);
  } while ((Whichkey==KeyInv) && showStatus);
  delay(80);
  return Whichkey;
}
 
double IMCAToPower(double orca){
return -(exp((orca+37.887)/108.08)-103);
}
 
double PowerToIMCA(double power){
return 108.08*log(-power+103)-37.887;
}

When starting PID regulator reads the calibration constants from EEPROM. Before the first run and calibration there is need of write some numbers to EEPROM so code do not panic. For this purpose serves code below:

PIDBareLoader.ino
#include <EEPROM.h>
#include "EEPROMAnything.h"
 
int direct=1; //REVERSE
double p=3;
double i=5;
double d=1;
double Setpoint=25;
boolean keyVal=0; //Temperature
double minOut=20;
double maxOut=100;
boolean offing=false;
 
 
void setup() {
EEPROM_writeAnything(10, Setpoint);
EEPROM_writeAnything(20, keyVal);
EEPROM_writeAnything(30, direct);
EEPROM_writeAnything(40, p);
EEPROM_writeAnything(50, i);
EEPROM_writeAnything(60, d);
EEPROM_writeAnything(70, minOut);
EEPROM_writeAnything(80, maxOut);
EEPROM_writeAnything(90, offing);
 
Serial.begin(9600);
Serial.println("Writing successfuly done!");
}
 
 
void loop() {
}

There is more complex menu in this box.

Main Menu
Direct Menu - there the direction can be set to NORMAL or REVERSE
Key Menu -key value is set there (temperature or humidity)
SubKey Menu -required value is set there

Mode Menu -Automatic mode (control by PID algorithm) or manual (power is set manualy)
Const Menu -PID constants can be change there
Limit Menu -setting of maximimum and minimum power. Eventually “offing” can be set, this means that if power let down to set minimum it power off instead of run in minimum power.


Box

Becasue there is need of line voltage to connect to circuit, it is possible to hide the adapter of arduino right into the box.

en/pid_regulator.txt · Last modified: 2018/01/29 10:12 (external edit)