pid control
I am helping a friend to build a microbrewer (for home brewed beer) so here is a simple pid controller I wrote.as of today, it is entirely on paper - we haven't put the heater / pot together yet.
the code is written entirely in C, and you can simulate it in your ide fairly easily.
it comes with three major parts:
1) pid_gain() to set the gains (p_factor, i_factor, and d_factor). this section can be replaced by the tuning routine pid-tuning_zn(); pid_tuning_zn() to use Ziegler-Nichols (open loop) method to tune the gain factors. you can implement your own tuning routines.
2) pid_limits() to set the upper and lower limits for the control variable;
3) pid_output() to obtain the value of the current control variable. here is the application code
==========main.c=============
#include "pid.h" //we use pid control
//simulate a heater / temperature relationship
float temp_read(float heater) {
unsigned int t_mass=200; //thermal mass
unsigned int t_decay=25; //thermal decay
//unsigned int t_energy=50; //energy input per sample period. it must be greater than t_decay
float temp=0; //current temperature
static float energy_sum=0; //cumulative energy input
temp = energy_sum / t_mass;
energy_sum += heater - temp * t_decay;
return temp;
}
int main(void) {
float temp;
float heater=100; //header opening
//pid_gain(&_pid, 6, 10, 2.34); //initialize the pid
pid_tuning_zn(&_pid, 10, 3.30/1000); //ziegler - nichols tuning: time constant = 100s, sloep = 33c per 1000s
pid_limits(&_pid, 5000, 0); //set the limits
pid_target(&_pid, 100); //set the target
while (1) {
temp=temp_read(heater); //read the temperature
heater = pid_output(&_pid, temp); //calculate heater power levels
}
return 0;
}
========================
read_temp() is a fictions piece of code used to simulate a pot being heated by a heater. in a real application, you will need a routine that reads the temperature. here are the pid routine / header file
===========pid.h=======================
//pid header file
//pid type definitions
typedef struct {
float p_factor, i_factor, d_factor; //pid factors
float target; //target for input
float output; //pid output
float output_max, output_min; //max and minimum for output
float error_last; //last error
float error_sum; //integer of errors
} PID_T;
extern PID_T _pid;
//initialize the pid's gains
void pid_gain(PID_T * pid_ptr, float p, float i, float d);
//initialize the output limits
void pid_limits(PID_T * pid_ptr, float max, float min);
//initialize the pid's target
void pid_target(PID_T * pid_ptr, float target);
//tuning pid factors with the Ziegler-Nichols (open loop) method
//tuning process:
//1) apply a shock to the system at t0, at output levels of 20%. the original temperature is T0
//2) wait for the temperature to rise more than 10%, at t0+t_deadtime.
//3) record time t1 when temperature is more than 30% higher than the original temperature (T1)
//4) record time t1+t_delta when temperature is more than 70% of the original temperature (T2)
//5) the slope a = 40% * T0 / (t_delta) * (1/20%) = 2 * T0 / t_delta
//
void pid_tuning_zn(PID_T * pid_ptr, float t_deadtime, float a);
float pid_output(PID_T * pid_ptr, float input);
==================================================
it outlines the process used to tune the pid using the ziegler - nichols approach
===================pid.c=========================
#include "pid.h" //we use pid
PID_T _pid; //pid global variable
//initialize the pid's gains
void pid_gain(PID_T * pid_ptr, float p, float i, float d) {
//pid_ptr->target = target;
pid_ptr->p_factor = p;
pid_ptr->i_factor = i;
pid_ptr->d_factor = d;
pid_ptr->error_last = 0;
pid_ptr->error_sum = 0;
}
//initialize the output limits
void pid_limits(PID_T * pid_ptr, float max, float min) {
pid_ptr->output_max = max;
pid_ptr->output_min = min;
}
//initialize the pid's target
void pid_target(PID_T * pid_ptr, float target) {
pid_ptr->target = target;
}
//tuning pid factors with the Ziegler-Nichols (open loop) method
//tuning process:
//1) apply a shock to the system at t0, at output levels of 20%. the original temperature is T0
//2) wait for the temperature to rise more than 10%, at t0+t_deadtime.
//3) record time t1 when temperature is more than 30% higher than the original temperature (T1)
//4) record time t1+t_delta when temperature is more than 70% of the original temperature (T2)
//5) the slope a = 40% * T0 / (t_delta) * (1/20%) = 2 * T0 / t_delta
//
void pid_tuning_zn(PID_T * pid_ptr, float t_deadtime, float a) {
pid_ptr->p_factor = 1.2 / (t_deadtime * a);
pid_ptr->i_factor = 2 * t_deadtime;
pid_ptr->d_factor = 0.5 * t_deadtime;
}
float pid_output(PID_T * pid_ptr, float input) {
float error=pid_ptr->target - input; //calculate the current error
pid_ptr->error_sum += error; //update the error sum
pid_ptr->output = pid_ptr->p_factor * error; //the p_factor
pid_ptr->output+= pid_ptr->i_factor * pid_ptr->error_sum; //the i factor
pid_ptr->output+= pid_ptr->d_factor * (error - pid_ptr->error_last); //the d factor
if (pid_ptr->output > pid_ptr->output_max) pid_ptr->output = pid_ptr->output_max;
if (pid_ptr->output < pid_ptr->output_min) pid_ptr->output = pid_ptr->output_min;
pid_ptr->error_last = error;
return pid_ptr->output;
} both routines are self-contained and once set up, they are pretty much on an autopilot.
on a 8051, they take about 2.4kb program space and 80 bytes of memory.
I am now using floating point math. you can greatly simplify it if you recode it to use fixed point math or integer math.
enjoy. once you have tuned the pid controller, it is very interesting to see, in your ide, how it corrects its own errors and tracks to the target. a quick note: a dead band is not implemented. it shouldn't be difficult if you want to. here is an example of using the pid controller on two distinct processes:
=============main.c================
#include <avr/io.h>
#include "gpio.h"
#include "pid.h" //we use pid control (multi-controller version)
//hardware configuration
#define OUT_PORT PORTD
#define OUT_DDR DDRD
#define OUTs 0xff
#define OUT(val) OUT_PORT = (val)
#define OUT1_PORT PORTE
#define OUT1_DDR DDRE
#define OUT1s 0xff
#define OUT1(val) OUT1_PORT = (val)
//end hardware configuration
//simulate a heater / temperature relationship 0
float temp_read(float heater) {
unsigned int t_mass=200; //thermal mass
unsigned int t_decay=25; //thermal decay
//unsigned int t_energy=50; //energy input per sample period. it must be greater than t_decay
float temp=0; //current temperature
static float energy_sum=0; //cumulative energy input
//simulate a pot beign heated
temp = energy_sum / t_mass;
energy_sum += heater - temp * t_decay;
return temp;
}
//simulate a heater / temperature relationship 0
float temp_read1(float heater) {
unsigned int t_mass=20*5; //thermal mass
unsigned int t_decay=10; //thermal decay
//unsigned int t_energy=50; //energy input per sample period. it must be greater than t_decay
float temp=0; //current temperature
static float energy_sum=0; //cumulative energy input
//simulate a pot beign heated
temp = energy_sum / t_mass;
energy_sum += heater - temp * t_decay;
return temp;
}
void mcu_init(void) {
IO_CLR(OUT_PORT, OUTs); //outs cleared
IO_OUT(OUT_DDR, OUTs); //outs as output
IO_CLR(OUT1_PORT, OUT1s); //outs cleared
IO_OUT(OUT1_DDR, OUT1s); //outs as output
}
int main(void) {
//for heater 0
float temp=0;
float heater=100; //header opening
PID_T pid; //pid struct - you can use multiple pid controllers on the same routine
//for heater 1
float temp1=0;
float heater1=10; //header opening
PID_T pid1; //pid struct - you can use multiple pid controllers on the same routine
mcu_init(); //reset the chip
//set up pid
pid_set(&pid); //set up the pid controller - not used for multi-controller version
//pid_gain(&pid, 6, 10, 2.34); //initialize the pid
pid_tuning_zn(&pid, 10, 3.30/1000); //ziegler - nichols tuning: time constant = 10s, sloep = 3.3c per 1000s
pid_limits(&pid, 5000, 10); //set the limits
pid_target(&pid, 25); //set the targetto 25c
//set up pid1
pid_set(&pid1); //set up the pid controller - not used for multi-controller version
//pid_gain(&pid, 6, 10, 2.34); //initialize the pid
pid_tuning_zn(&pid1, 10, 2.6/1000); //ziegler - nichols tuning: time constant = 10s, sloep = 2.6c per 1000s
pid_limits(&pid1, 500, 0); //set the limits
pid_target(&pid1, 25); //set the targetto 25c
while (1) {
temp=temp_read(heater); //read the temperature for heater
heater = pid_output(&pid, temp); //calculate heater power levels
OUT(temp);
temp1=temp_read1(heater1); //read the temperature for heater
heater1 = pid_output(&pid1, temp1); //calculate heater power levels
OUT1(temp1);
}
return 0;
}
=================================
read_temp() and read_temp1() are two simulated heaters, each with its own thermal mass and decay parameters.
we are outputing the temperature of read_temp() on PORTD, and temperature of read_temp1() on PORTE. In both cases, the temperature targets are set to 25c so we expect an output of 0x19 on portd and porte. here is the simulation.
we got exactly 0x19 on portd, and we got 0x18 - 0x19 on porte, indicating some instability issues - our simulated heater response isn't that great.
http://cache.amobbs.com/bbs_upload782111/files_39/ourdev_640582IQMR4J.PNG
(原文件名:45. pid x2.PNG)
you obviously can utilize the same routines on as many process variables as you want, without blowing up the code size. mark. 看起来不错,tks English to the tail.
Seems the temperature is not high.
May I use it in soldering control? http://cache.amobbs.com/bbs_upload782111/files_39/ourdev_640591OU0E5N.jpg
(原文件名:PID.jpg)
正打算写一个这样的模拟器,带串口输入/输出,给定值与反馈量,这样就可以实际测试设备的控制效果勒
点击此处下载 ourdev_640592VWE6U0.zip(文件大小:12K) (原文件名:pid模拟器.zip) good 11楼的源码在这里下载:
<br>点击此处下载 pid_loop.zip(文件大小:8K) (原文件名:pid_loop模拟器.zip) 好东西。 好东西 回复【13楼】cqfeiyu
-----------------------------------------------------------------------
谢谢13楼 MARK mark 模拟器有点意思 mark //tuning pid factors with the Ziegler-Nichols (open loop) method
//tuning process:
//1) apply a shock to the system at t0, at output levels of 20%. the original temperature is T0
//2) wait for the temperature to rise more than 10%, at t0+t_deadtime.
//3) record time t1 when temperature is more than 30% higher than the original temperature (T1)
//4) record time t1+t_delta when temperature is more than 70% of the original temperature (T2)
//5) the slope a = 40% * T0 / (t_delta) * (1/20%) = 2 * T0 / t_delta
//
void pid_tuning_zn(PID_T * pid_ptr, float t_deadtime, float a);
这几步看得不是很明白,能解释下是什么意思吗? mark。。。。。。。。 很好谢谢 pid真正用的有哪个 来帮偶像顶下,支持你 mark... mark MARK 看下 mark 先mark 不知道这辈子能用上不! 满屏的浮点数,会搞死单片机的。 mark pid mark mark pid~~~~~
页:
[1]