搜索
bottom↓
回复: 36

pid control

[复制链接]

出0入0汤圆

发表于 2011-5-18 02:19:37 | 显示全部楼层 |阅读模式
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.

出0入0汤圆

 楼主| 发表于 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.

出0入0汤圆

 楼主| 发表于 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;
}

出0入0汤圆

 楼主| 发表于 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.

出0入0汤圆

 楼主| 发表于 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.

出0入0汤圆

 楼主| 发表于 2011-5-18 03:51:07 | 显示全部楼层
a quick note: a dead band is not implemented. it shouldn't be difficult if you want to.

出0入0汤圆

 楼主| 发表于 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 target  to 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 target  to 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.

出0入0汤圆

 楼主| 发表于 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.


(原文件名: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.

出10入210汤圆

发表于 2011-5-18 08:16:26 | 显示全部楼层
mark.

出0入0汤圆

发表于 2011-5-18 08:24:00 | 显示全部楼层
看起来不错,tks

出0入0汤圆

发表于 2011-5-18 08:26:57 | 显示全部楼层
English to the tail.


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

出0入4汤圆

发表于 2011-5-18 08:43:42 | 显示全部楼层

(原文件名:PID.jpg)

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

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

出0入0汤圆

发表于 2011-5-18 08:52:47 | 显示全部楼层
good

出0入0汤圆

发表于 2011-5-18 09:27:53 | 显示全部楼层
11楼的源码在这里下载:
<br>点击此处下载&nbsp;pid_loop.zip(文件大小:8K)&nbsp;(原文件名:pid_loop模拟器.zip)&nbsp;

出0入0汤圆

发表于 2011-5-18 09:33:19 | 显示全部楼层
好东西。

出0入0汤圆

发表于 2011-5-18 09:35:57 | 显示全部楼层
好东西

出0入4汤圆

发表于 2011-5-18 09:54:36 | 显示全部楼层
回复【13楼】cqfeiyu
-----------------------------------------------------------------------

谢谢13楼

出0入4汤圆

发表于 2011-5-18 11:06:54 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-5-18 11:44:50 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-5-18 12:26:50 | 显示全部楼层
模拟器有点意思

出0入0汤圆

发表于 2011-5-18 12:37:17 | 显示全部楼层
mark

出0入0汤圆

发表于 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);

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

出0入0汤圆

发表于 2011-5-24 13:24:58 | 显示全部楼层
mark。。。。。。。。

出0入0汤圆

发表于 2011-5-27 07:50:22 | 显示全部楼层
很好  谢谢

出0入0汤圆

发表于 2011-5-28 15:43:28 | 显示全部楼层
pid真正用的有哪个

出0入0汤圆

发表于 2011-6-26 23:37:15 | 显示全部楼层
来帮偶像顶下,支持你

出0入0汤圆

发表于 2011-6-27 00:25:16 | 显示全部楼层
mark...

出0入0汤圆

发表于 2011-7-24 20:29:31 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-7-24 22:21:22 | 显示全部楼层
MARK

出0入0汤圆

发表于 2011-8-5 19:32:38 | 显示全部楼层
看下

出0入0汤圆

发表于 2011-8-10 09:03:58 | 显示全部楼层
mark

出0入0汤圆

发表于 2011-8-10 10:20:02 | 显示全部楼层
先mark

出0入0汤圆

发表于 2011-8-16 13:54:14 | 显示全部楼层
不知道这辈子能用上不!

出0入0汤圆

发表于 2011-8-16 14:25:59 | 显示全部楼层
满屏的浮点数,会搞死单片机的。

出0入0汤圆

发表于 2011-9-2 09:24:07 | 显示全部楼层
mark pid

出0入0汤圆

发表于 2011-9-16 15:10:54 | 显示全部楼层
mark

出0入0汤圆

发表于 2013-8-19 14:55:49 | 显示全部楼层
mark pid~~~~~
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-4 18:48

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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