millwood0 发表于 2011-5-18 02:19:37

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.

millwood0 发表于 2011-5-18 02:20:56

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.

millwood0 发表于 2011-5-18 02:22:07

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;
}

millwood0 发表于 2011-5-18 02:23:43

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.

millwood0 发表于 2011-5-18 03:32:59

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.

millwood0 发表于 2011-5-18 03:51:07

a quick note: a dead band is not implemented. it shouldn't be difficult if you want to.

millwood0 发表于 2011-5-18 06:35:08

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.

millwood0 发表于 2011-5-18 06:40:39

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.

rainbow 发表于 2011-5-18 08:16:26

mark.

sleet1986 发表于 2011-5-18 08:24:00

看起来不错,tks

Jigsaw 发表于 2011-5-18 08:26:57

English to the tail.


Seems the temperature is not high.
May I use it in soldering control?

electricit 发表于 2011-5-18 08:43:42

http://cache.amobbs.com/bbs_upload782111/files_39/ourdev_640591OU0E5N.jpg
(原文件名:PID.jpg)

正打算写一个这样的模拟器,带串口输入/输出,给定值与反馈量,这样就可以实际测试设备的控制效果勒

点击此处下载 ourdev_640592VWE6U0.zip(文件大小:12K) (原文件名:pid模拟器.zip)

dahai168 发表于 2011-5-18 08:52:47

good

cqfeiyu 发表于 2011-5-18 09:27:53

11楼的源码在这里下载:
<br>点击此处下载&nbsp;pid_loop.zip(文件大小:8K)&nbsp;(原文件名:pid_loop模拟器.zip)&nbsp;

song1km 发表于 2011-5-18 09:33:19

好东西。

xtx8962 发表于 2011-5-18 09:35:57

好东西

electricit 发表于 2011-5-18 09:54:36

回复【13楼】cqfeiyu
-----------------------------------------------------------------------

谢谢13楼

MZMMZMMZM 发表于 2011-5-18 11:06:54

MARK

bsz84 发表于 2011-5-18 11:44:50

mark

alan8918 发表于 2011-5-18 12:26:50

模拟器有点意思

823032003 发表于 2011-5-18 12:37:17

mark

jerico 发表于 2011-5-24 09:26:39

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

这几步看得不是很明白,能解释下是什么意思吗?

plc_avr 发表于 2011-5-24 13:24:58

mark。。。。。。。。

QQ373466062 发表于 2011-5-27 07:50:22

很好谢谢

chushichongyu 发表于 2011-5-28 15:43:28

pid真正用的有哪个

zjn8888 发表于 2011-6-26 23:37:15

来帮偶像顶下,支持你

mengzhihu2 发表于 2011-6-27 00:25:16

mark...

abner 发表于 2011-7-24 20:29:31

mark

416446891 发表于 2011-7-24 22:21:22

MARK

pengxin213 发表于 2011-8-5 19:32:38

看下

canberry00 发表于 2011-8-10 09:03:58

mark

ggyyll8683 发表于 2011-8-10 10:20:02

先mark

defineme 发表于 2011-8-16 13:54:14

不知道这辈子能用上不!

hexenzhou 发表于 2011-8-16 14:25:59

满屏的浮点数,会搞死单片机的。

usingavr 发表于 2011-9-2 09:24:07

mark pid

chenchen1103 发表于 2011-9-16 15:10:54

mark

mique 发表于 2013-8-19 14:55:49

mark pid~~~~~
页: [1]
查看完整版本: pid control