PID实例学习一【温度控制】-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 41772|回复: 8

PID实例学习一【温度控制】

[复制链接]
发表于 2014-1-23 23:00 | 显示全部楼层 |阅读模式
本帖最后由 syl312 于 2014-1-24 12:36 编辑

     这是一个根据Rancilio Silvia咖啡机的基础上加控制器部分改装成具有PID温度控制功能的咖啡机。
     BBCC(咖啡控制器)主要是为了给浓缩煮咖啡机器提供PID温度控制而设计的,但是并不局限于此。它是以PID控制为核心。PID控制算法不包含反馈环节, 在煮咖啡的时候,它监视煮水装置的水温并且通过控制加热线圈达到控制温度的目的。以下提供的代码可以作为制作复杂系统的PID控制器的基础学习。控制接口可以选择Arduino的串口,通过任何串口可实现设置,监控和PID调试过程。
于此同时可以和Processing应用程序配套使用,虽然不是一定要求使用这个软件,但是可以做出实时温度变化曲线,在PID调试过程中提供很好的帮助。
    硬件部分:数字引脚6接到与加热线圈相接的固态继电器上,模拟引脚输入1连接到AD595热电偶放大器。当然也可以用其他温度传感器替换。需要注意的是AD595K型热电偶放大器,而AD594J型热电偶放大器。任何其中一个都可以工作的很好。
    原理图部分:
     1.AD595部分:
                                                    图片3.png
      AD595K型热电偶放大器,输出热电偶数据为:10mV/degC. 在这个控制器中标准连接是连接到模拟引脚1.
     2.固态继电器部分:
                              图片2.png
   3.LCD部分
                      图片1.png
   程序部分:
[mw_shl_code=c,true]Main

// BBCC Main
// Tim Hirzel
// February 2008
//
// Main file for the Bare Bones Coffee Controller PID
// setup for Arduino.
// This project is set up such that each tab acts as a
// "module" or "library" that incporporates some more
// functionality.  Each tab correlates
// to a particular device (Nunchuck),  protocol (ie. SPI),
// or Algorithm (ie. PID).

// The general rule for any of these tabs/sections is that
// if they include a setup* or update* function, those should be added
// into the main setup and main loop functions. Also, in main loop, and in
// extra code, delays should probably be avoided.
// Instead, use millis() and check for a certain interval to have passed.
//
// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0


// These are addresses into EEPROM memory.  The values to be stores are floats which
// need 4 bytes each.  Thus 0,4,8,12,...
#define PGAIN_ADR 0
#define IGAIN_ADR 4
#define DGAIN_ADR 8

#define ESPRESSO_TEMP_ADDRESS 12
//#define STEAM_TEMP_ADDRESS 12  // steam temp currently not used with bare bones setup

#define PID_UPDATE_INTERVAL 200 // milliseconds


float targetTemp;  //current temperature goal
float heatPower; // 0 - 1000  milliseconds on per second

unsigned long lastPIDTime;  // most recent PID update time in ms

void setup()
{

  setupPID(PGAIN_ADR, IGAIN_ADR, DGAIN_ADR ); // Send addresses to the PID module
  targetTemp = readFloat(ESPRESSO_TEMP_ADDRESS); // from EEPROM. load the saved value
  lastPIDTime = millis();
  // module setup calls
  setupHeater();
  setupSerialInterface();
  setupTempSensor();
}

void setTargetTemp(float t) {
  targetTemp = t;
  writeFloat(t, ESPRESSO_TEMP_ADDRESS);
}

float getTargetTemp() {
  return targetTemp;
}


void loop()
{  
  // this call interprets characters from the serial port
  // its a very basic control to allow adjustment of gain values, and set temp
  updateSerialInterface();
  updateTempSensor();

  // every second, udpate the current heat control, and print out current status

  // This checks for rollover with millis()
  if (millis() < lastPIDTime) {
    lastPIDTime = 0;
  }
  if ((millis() - lastPIDTime) > PID_UPDATE_INTERVAL) {
    lastPIDTime +=  PID_UPDATE_INTERVAL;
    heatPower = updatePID(targetTemp, getFreshTemp());
    setHeatPowerPercentage(heatPower);

  }  
  updateHeater();
}[/mw_shl_code]

[mw_shl_code=c,true]// PID control code
// Tim Hirzel
// December 2007

// This is a module that implements a PID control loop
// initialize it with 3 values: p,i,d
// and then tune the feedback loop with the setP etc funcs
//
// this was written based on a great PID by Tim Wescott:
// http://www.embedded.com/2000/0010/0010feat3.htm
//
//
// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0



#define WINDUP_GUARD_GAIN 100.0

float iState = 0;
float lastTemp = 0;

float pgain;
float igain;
float dgain;

float pTerm, iTerm, dTerm;

int pgainAddress, igainAddress, dgainAddress;

void setupPID(unsigned int padd, int iadd, int dadd) {
  // with this setup, you pass the addresses for the PID algorithm to use to
  // for storing the gain settings.  This way wastes 6 bytes to store the addresses,
  // but its nice because you can keep all the EEPROM address allocaton in once place.

  pgainAddress = padd;
  igainAddress = iadd;
  dgainAddress = dadd;

  pgain = readFloat(pgainAddress);
  igain = readFloat(igainAddress);
  dgain = readFloat(dgainAddress);
}

float getP() {
  // get the P gain
  return pgain;
}
float getI() {
  // get the I gain
  return igain;
}
float getD() {
  // get the D gain
  return dgain;
}


void setP(float p) {
  // set the P gain and store it to eeprom
  pgain = p;
  writeFloat(p, pgainAddress);
}

void setI(float i) {
  // set the I gain and store it to eeprom
  igain = i;
  writeFloat(i, igainAddress);
}

void setD(float d) {
  // set the D gain and store it to eeprom
  dgain = d;
  writeFloat(d, dgainAddress);
}

float updatePID(float targetTemp, float curTemp)
{
  // these local variables can be factored out if memory is an issue,
  // but they make it more readable
  double result;
  float error;
  float windupGaurd;

  // determine how badly we are doing
  error = targetTemp - curTemp;

  // the pTerm is the view from now, the pgain judges
  // how much we care about error we are this instant.
  pTerm = pgain * error;

  // iState keeps changing over time; it's
  // overall "performance" over time, or accumulated error
  iState += error;

  // to prevent the iTerm getting huge despite lots of
  //  error, we use a "windup guard"
  // (this happens when the machine is first turned on and
  // it cant help be cold despite its best efforts)

  // not necessary, but this makes windup guard values
  // relative to the current iGain
  windupGaurd = WINDUP_GUARD_GAIN / igain;  

  if (iState > windupGaurd)
    iState = windupGaurd;
  else if (iState < -windupGaurd)
    iState = -windupGaurd;
  iTerm = igain * iState;

  // the dTerm, the difference between the temperature now
  //  and our last reading, indicated the "speed,"
  // how quickly the temp is changing. (aka. Differential)
  dTerm = (dgain* (curTemp - lastTemp));

  // now that we've use lastTemp, put the current temp in
  // our pocket until for the next round
  lastTemp = curTemp;

  // the magic feedback bit
  return  pTerm + iTerm - dTerm;
}

void printPIDDebugString() {
  // A  helper function to keep track of the PID algorithm
  Serial.print("PID formula (P + I - D): ");

  printFloat(pTerm, 2);
  Serial.print(" + ");
  printFloat(iTerm, 2);
  Serial.print(" - ");
  printFloat(dTerm, 2);
  Serial.print(" POWER: ");
  printFloat(getHeatCycles(), 0);
  Serial.print(" ");

}

// END PID[/mw_shl_code]


[mw_shl_code=c,true]// HeaterControl
// Tim Hirzel
// Dec 2007
//
// This file is for controlling a heater via a solid state zero crossing relay

// since these are zero-crossing relays, it makes sense to just match my local
// AC frequency, 60hz
//
// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0

#define HEAT_RELAY_PIN 6

float heatcycles; // the number of millis out of 1000 for the current heat amount (percent * 10)

boolean heaterState = 0;

unsigned long heatCurrentTime, heatLastTime;

void setupHeater() {
  pinMode(HEAT_RELAY_PIN , OUTPUT);
}


void updateHeater() {
  boolean h;
  heatCurrentTime = millis();
  if(heatCurrentTime - heatLastTime >= 1000 or heatLastTime > heatCurrentTime) { //second statement prevents overflow errors
    // begin cycle
    _turnHeatElementOnOff(1);  //
    heatLastTime = heatCurrentTime;   
  }
  if (heatCurrentTime - heatLastTime >= heatcycles) {
    _turnHeatElementOnOff(0);
  }
}

void setHeatPowerPercentage(float power) {
  if (power <= 0.0) {
    power = 0.0;
  }        
  if (power >= 1000.0) {
    power = 1000.0;
  }
  heatcycles = power;
}

float getHeatCycles() {
  return heatcycles;
}

void _turnHeatElementOnOff(boolean on) {
  digitalWrite(HEAT_RELAY_PIN, on);        //turn pin high
  heaterState = on;
}

// End Heater Control[/mw_shl_code]


[mw_shl_code=c,true]// Simple extension to the EEPROM library
// Tim Hirzel
// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0

#include <avr/EEPROM.h>

float readFloat(int address) {
  float out;
  eeprom_read_block((void *) &out, (unsigned char *) address ,4 );
  return out;
}

void writeFloat(float value, int address) {
  eeprom_write_block((void *) &value, (unsigned char *) address ,4);
}

// END EEPROM Float[/mw_shl_code]


[mw_shl_code=c,true]// With the AD 595, this process is just a matter of doing some math on an
// analog input
//
// Thanks to Karl Gruenewald for the conversion formula
// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0

// This current version is based on sensing temperature with
// an AD595 and thermocouple through an A/D pin.  Any other
// sensor could be used by replacing this one function.
// feel free to use degrees C as well, it will just give a different
// PID tuning than those from F.
//


#define TEMP_SENSOR_PIN 1

float tcSum = 0.0;
float latestReading = 0.0;
int readCount = 0;
float multiplier;
void setupTempSensor() {
  multiplier = 1.0/(1023.0) * 500.0 * 9.0 / 5.0;
}  

void updateTempSensor() {
    tcSum += analogRead(TEMP_SENSOR_PIN); //output from AD595 to analog pin

1
    readCount +=1;
}

float getFreshTemp() {
      latestReading = tcSum* multiplier/readCount+32.0;
      readCount = 0;
      tcSum = 0.0;
  return latestReading;

}

float getLastTemp() {
  return latestReading;

}

// END Temperature Sensor[/mw_shl_code]


[mw_shl_code=c,true]//serialInterface
// Tim Hirzel February 2008
// This is a very basic serial interface for controlling the PID loop.
// thanks to the Serial exampe code  

// All code released under
// Creative Commons Attribution-Noncommercial-Share Alike 3.0

#define AUTO_PRINT_INTERVAL 200  // milliseconds
#define MAX_DELTA  100
#define MIN_DELTA  0.01
#define PRINT_PLACES_AFTER_DECIMAL 2  // set to match MIN_DELTA


int incomingByte = 0;
float delta = 1.0;
boolean autoupdate;
boolean printmode = 0;

unsigned long lastUpdateTime = 0;
void setupSerialInterface()  {
  Serial.begin(115200);
  Serial.println("\nWelcome to the BBCC, the Bare Bones Coffee Controller

for Arduino");
  Serial.println("Send back one or more characters to setup the

controller.");
  Serial.println("If this is your initial run, please enter 'R' to Reset the

EEPROM.");
  Serial.println("Enter '?' for help.  Here's to a great cup!");
}

void printHelp() {
  Serial.println("Send these characters for control:");
  Serial.println("<space> : print status now");
  Serial.println("u : toggle periodic status update");
  Serial.println("g : toggle update style between human and graphing mode");
  Serial.println("R : reset/initialize PID gain values");
  Serial.println("b : print PID debug values");
  Serial.println("? : print help");  
  Serial.println("+/- : adjust delta by a factor of ten");
  Serial.println("P/p : up/down adjust p gain by delta");
  Serial.println("I/i : up/down adjust i gain by delta");
  Serial.println("D/d : up/down adjust d gain by delta");
  Serial.println("T/t : up/down adjust set temp by delta");


}

void updateSerialInterface() {
  while(Serial.available()){

    incomingByte = Serial.read();
    if (incomingByte == 'R') {
      setP(30.0); // make sure to keep the decimal point on these values
      setI(0.0);  // make sure to keep the decimal point on these values
      setD(0.0);  // make sure to keep the decimal point on these values
      setTargetTemp(200.0); // here too
    }
    if (incomingByte == 'P') {
      setP(getP() + delta);
    }
    if (incomingByte == 'p') {
      setP(getP() - delta);
    }
    if (incomingByte == 'I') {
      setI(getI() + delta);
    }
    if (incomingByte == 'i') {
      setI(getI() - delta);
    }
    if (incomingByte == 'D') {
      setD(getD() + delta);
    }
    if (incomingByte == 'd' ){
      setD(getD() - delta);
    }
    if (incomingByte == 'T') {
      setTargetTemp(getTargetTemp() + delta);
    }
    if (incomingByte == 't') {
      setTargetTemp(getTargetTemp() - delta);
    }
    if (incomingByte == '+') {
      delta *= 10.0;
      if (delta > MAX_DELTA)
        delta = MAX_DELTA;
    }
    if (incomingByte == '-') {
      delta /= 10.0;
      if (delta < MIN_DELTA)
        delta = MIN_DELTA;

    }
    if (incomingByte == 'u') {
      // toggle updating

      autoupdate = not autoupdate;
    }
    if (incomingByte == 'g') {
      // toggle updating

      printmode = not printmode;
    }
    if (incomingByte == ' ') {
      // toggle updating

      printStatus();
    }
    if (incomingByte == '?') {
      printHelp();
    }
    if (incomingByte == 'b') {
      printPIDDebugString();
      Serial.println();
    }
  }

  if (millis() < lastUpdateTime) {
    lastUpdateTime = 0;
  }
  if ((millis() - lastUpdateTime) > AUTO_PRINT_INTERVAL) {
    // this is triggers every slightly more than a second from the delay

between these two millis() calls
    lastUpdateTime += AUTO_PRINT_INTERVAL;
    if (autoupdate) {
      if (printmode) {
        printStatusForGraph();
      }
      else {
        printStatus();
      }
    }
  }
}

void printStatus() {
  // A means for getting feedback on the current system status and

controllable parameters
  Serial.print(" SET TEMP:");
  printFloat(getTargetTemp(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", CUR TEMP:");
  printFloat(getLastTemp(),PRINT_PLACES_AFTER_DECIMAL);

  Serial.print(", GAINS p:");
  printFloat(getP(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(" i:");
  printFloat(getI(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(" d:");
  printFloat(getD(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", Delta: ");
  printFloat(delta,PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", Power: ");
  printFloat((float)getHeatCycles(), 0);

  Serial.print(" \n");
}

void printStatusForGraph() {
  printFloat(getTargetTemp(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", ");
  printFloat(getLastTemp(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", ");
  printFloat(getP(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", ");
  printFloat(getI(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", ");
  printFloat(getD(),PRINT_PLACES_AFTER_DECIMAL);
  Serial.print(", ");
  printFloat((float)getHeatCycles(), 0);
  Serial.println();
}

// printFloat prints out the float 'value' rounded to 'places' places after

the decimal point
void printFloat(float value, int places) {
  // this is used to cast digits
  int digit;
  float tens = 0.1;
  int tenscount = 0;
  int i;
  float tempfloat = value;

  // make sure we round properly. this could use pow from <math.h>, but

doesn't seem worth the import
  // if this rounding step isn't here, the value  54.321 prints as 54.3209

  // calculate rounding term d:   0.5/pow(10,places)  
  float d = 0.5;
  if (value < 0)
    d *= -1.0;
  // divide by ten for each decimal place
  for (i = 0; i < places; i++)
    d/= 10.0;   
  // this small addition, combined with truncation will round our values

properly
  tempfloat +=  d;

  // first get value tens to be the large power of ten less than value
  // tenscount isn't necessary but it would be useful if you wanted to know

after this how many chars the number will take

  if (value < 0)
    tempfloat *= -1.0;
  while ((tens * 10.0) <= tempfloat) {
    tens *= 10.0;
    tenscount += 1;
  }


  // write out the negative if needed
  if (value < 0)
    Serial.print('-');

  if (tenscount == 0)
    Serial.print(0, DEC);

  for (i=0; i< tenscount; i++) {
    digit = (int) (tempfloat/tens);
    Serial.print(digit, DEC);
    tempfloat = tempfloat - ((float)digit * tens);
    tens /= 10.0;
  }

  // if no places after decimal, stop now and return
  if (places <= 0)
    return;

  // otherwise, write the point and continue on
  Serial.print('.');  

  // now write out each decimal place by shifting digits one by one into the

ones place and writing the truncated value
  for (i = 0; i < places; i++) {
    tempfloat *= 10.0;
    digit = (int) tempfloat;
    Serial.print(digit,DEC);  
    // once written, subtract off that digit
    tempfloat = tempfloat - (float) digit;
  }
}

// END Serial Interface[/mw_shl_code]



AD594_595.pdf

136.68 KB, 下载次数: 64

售价: 2 金币  [记录]

AD595手册

发表于 2014-1-31 18:47 | 显示全部楼层
你这用哪个代码啊?
 楼主| 发表于 2014-1-31 23:32 来自手机 | 显示全部楼层
for 发表于 2014-1-31 18:47
你这用哪个代码啊?

都是。
发表于 2014-2-3 20:50 | 显示全部楼层
任何一个都可以调用吗?
发表于 2015-4-26 10:17 | 显示全部楼层
好东西啊 不过 看不懂啊 哈哈
发表于 2016-4-5 10:13 | 显示全部楼层
怎么这么多代码阿?
发表于 2019-12-28 15:23 | 显示全部楼层
老哥,有没有库文件分享一下
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 09:27 , Processed in 0.085290 second(s), 20 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表