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

syl312 发表于 2014-1-23 23:00

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

本帖最后由 syl312 于 2014-1-24 12:36 编辑

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

// 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
returnpTerm + iTerm - dTerm;
}

void printPIDDebugString() {
// Ahelper 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


// 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


// 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


// 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


//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_DELTA100
#define MIN_DELTA0.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 value54.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



syl312 发表于 2014-1-23 23:09

占楼

for 发表于 2014-1-31 18:47

你这用哪个代码啊?

syl312 发表于 2014-1-31 23:32

for 发表于 2014-1-31 18:47
你这用哪个代码啊?

都是。

for 发表于 2014-2-3 20:50

任何一个都可以调用吗?

binggdudu 发表于 2015-4-26 10:17

好东西啊 不过 看不懂啊 哈哈

chendonghai 发表于 2016-4-5 10:13

怎么这么多代码阿?

lijiachen00977 发表于 2019-12-28 15:23

老哥,有没有库文件分享一下

lss123 发表于 2020-8-17 18:02

好东东,:)
页: [1]
查看完整版本: PID实例学习一【温度控制】