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
占楼 你这用哪个代码啊? for 发表于 2014-1-31 18:47
你这用哪个代码啊?
都是。 任何一个都可以调用吗?
好东西啊 不过 看不懂啊 哈哈 怎么这么多代码阿? 老哥,有没有库文件分享一下 好东东,:)
页:
[1]