写在前面的话:
这是我收藏的一个典型的pid处理程序,包含了zui常用的pid算法的基本架构,没有包含输入输出处理部分。适合新手了解pid结构,入门学习用。
注意:
使用不同的mcu的时候,需要进行相应的简化和改写。而且由于单片机的处理速度和ram资源的限制,一般不采用浮点数运算。而将所有参数全部用整数,运算到zui后再除以一个2的n次方数据(相当于移位),作类似定点数运算。这样可大大提高运算速度,根据控制精度的不同要求,当精度要求很高时,注意保留移位引起的“余数”,做好余数补偿就好了。
废话不多说,下面上程序
#include<reg51.h>#include<intrins.h>#include<math.h>#include<string.h>struct pid { unsigned int setpoint; // 设定目标 desired value unsigned int proportion; // 比例常数 proportional const unsigned int integral; // 积分常数 integral const unsigned int derivative; // 微分常数 derivative const unsigned int lasterror; // error[-1] unsigned int preverror; // error[-2] unsigned int sumerror; // sums of errors };struct pid spid; // pid control structureunsigned int rout; // pid response (output)unsigned int rin; // pid feedback (input)sbit data1=p1^0;sbit clk=p1^1;sbit plus=p2^0;sbit subs=p2^1;sbit stop=p2^2;sbit output=p3^4;sbit dq=p3^3;unsigned char flag,flag_1=0;unsigned char high_time,low_time,count=0;//占空比调节参数unsigned char set_temper=35;unsigned char temper;unsigned char i;unsigned char j=0;unsigned int s; /*********************************************************** 延时子程序,延时时间以12m晶振为准,延时时间为30us×time ***********************************************************/void delay(unsigned char time) { unsigned char m,n; for(n=0;n<time;n++) for(m=0;m<2;m++){} } /*********************************************************** 写一位数据子程序 ***********************************************************/void write_bit(unsigned char bitval){ ea=0; dq=0; /*拉低dq以开始一个写时序*/ if(bitval==1) { _nop_(); dq=1; /*如要写1,则将总线置高*/ } delay(5); /*延时90us供da18b20采样*/ dq=1; /*释放dq总线*/ _nop_(); _nop_(); ea=1;} /*********************************************************** 写一字节数据子程序 ***********************************************************/void write_byte(unsigned char val){ unsigned char i; unsigned char temp; ea=0; tr0=0; for(i=0;i<8;i++) /*写一字节数据,一次写一位*/ { temp=val>>i; /*移位操作,将本次要写的位移到zui低位*/ temp=temp&1; write_bit(temp); /*向总线写该位*/ } delay(7); /*延时120us后*/ // tr0=1; ea=1;} /*********************************************************** 读一位数据子程序 ***********************************************************/unsigned char read_bit(){ unsigned char i,value_bit; ea=0; dq=0; /*拉低dq,开始读时序*/ _nop_(); _nop_(); dq=1; /*释放总线*/ for(i=0;i<2;i++){} value_bit=dq; ea=1; return(value_bit);} /*********************************************************** 读一字节数据子程序 ***********************************************************/unsigned char read_byte(){ unsigned char i,value=0; ea=0; for(i=0;i<8;i++) { if(read_bit()) /*读一字节数据,一个时序中读一次,并作移位处理*/ value|=0x01<<i; delay(4); /*延时80us以完成此次都时序,之后再读下一数据*/ } ea=1; return(value);} /*********************************************************** 复位子程序 ***********************************************************/unsigned char reset(){ unsigned char presence; ea=0; dq=0; /*拉低dq总线开始复位*/ delay(30); /*保持低电平480us*/ dq=1; /*释放总线*/ delay(3); presence=dq; /*获取应答信号*/ delay(28); /*延时以完成整个时序*/ ea=1; return(presence); /*返回应答信号,有芯片应答返回0,无芯片则返回1*/} /*********************************************************** 获取温度子程序 ***********************************************************/void get_temper(){ unsigned char i,j; do { i=reset(); /*复位*/ }while(i!=0); /*1为无反馈信号*/ i=0xcc; /*发送设备定位命令*/ write_byte(i); i=0x44; /*发送开始转换命令*/ write_byte(i); delay(180); /*延时*/ do { i=reset(); /*复位*/ }while(i!=0); i=0xcc; /*设备定位*/ write_byte(i); i=0xbe; /*读出缓冲区内容*/ write_byte(i); j=read_byte(); i=read_byte(); i=(i<<4)&0x7f; s=(unsigned int)(j&0x0f); //得到小数部分 s=(s*100)/16; j=j>>4; temper=i|j; /*获取的温度放在temper中*/ } /*==================================================================================================== initialize pid structure =====================================================================================================*/void pidinit (struct pid *pp){ memset ( pp,0,sizeof(struct pid)); //全部初始化为0} /*==================================================================================================== pid计算部分 =====================================================================================================*/unsigned int pidcalc( struct pid *pp, unsigned int nextpoint ){ unsigned int derror,error; error = pp->setpoint - nextpoint; // 偏差 pp->sumerror += error; // 积分 derror = pp->lasterror - pp->preverror; // 当前微分 pp->preverror = pp->lasterror; pp->lasterror = error; return (pp->proportion * error // 比例项 + pp->integral * pp->sumerror // 积分项 + pp->derivative * derror); // 微分项} /*********************************************************** 温度比较处理子程序 ***********************************************************/void compare_temper(){ unsigned char i; if(set_temper>temper) //是否设置的温度大于实际温度 { if(set_temper-temper>1) //设置的温度比实际的温度是否是大于1度 { high_time=100; //如果是,则全速加热 low_time=0; } else //如果是在1度范围内,则运行pid计算 { for(i=0;i<10;i++) { get_temper(); //获取温度 rin = s; // read input rout = pidcalc ( &spid,rin ); // perform pid interation } if (high_time<=100) high_time=(unsigned char)(rout/800); else high_time=100; low_time= (100-high_time); } } else if(set_temper<=temper) { if(temper-set_temper>0) { high_time=0; low_time=100; } else { for(i=0;i<10;i++) { get_temper(); rin = s; // read input rout = pidcalc ( &spid,rin ); // perform pid interation } if (high_time<100) high_time=(unsigned char)(rout/10000); else high_time=0; low_time= (100-high_time); } } // else // {}} /***************************************************** t0中断服务子程序,用于控制电平的翻转 ,40us*100=4ms周期 ******************************************************/void serve_t0() interrupt 1 using 1{ if(++count<=(high_time)) output=1; else if(count<=100) { output=0; } else count=0; th0=0x2f; tl0=0xe0;} /***************************************************** 串行口中断服务程序,用于上位机通讯 ******************************************************/void serve_sio() interrupt 4 using 2{ /* ea=0; ri=0; i=sbuf; if(i==2) { while(ri==0){} ri=0; set_temper=sbuf; sbuf=0x02; while(ti==0){} ti=0; } else if(i==3) { ti=0; sbuf=temper; while(ti==0){} ti=0; } ea=1; */}void disp_1(unsigned char disp_num1[6]){ unsigned char n,a,m; for(n=0;n<6;n++) { // k=disp_num1[n]; for(a=0;a<8;a++) { clk=0; m=(disp_num1[n]&1); disp_num1[n]=disp_num1[n]>>1; if(m==1) data1=1; else data1=0; _nop_(); clk=1; _nop_(); } }} /***************************************************** 显示子程序 功能:将占空比温度转化为单个字符,显示占空比和测得到的温度 ******************************************************/void display(){ unsigned char code number[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6}; unsigned char disp_num[6]; unsigned int k,k1; k=high_time; k=k%1000; k1=k/100; if(k1==0) disp_num[0]=0; else disp_num[0]=0x60; k=k%100; disp_num[1]=number[k/10]; disp_num[2]=number[k%10]; k=temper; k=k%100; disp_num[3]=number[k/10]; disp_num[4]=number[k%10]+1; disp_num[5]=number[s/10]; disp_1(disp_num);} /*********************************************************** 主程序 ***********************************************************/void main(){ unsigned char z; unsigned char a,b,flag_2=1,count1=0; unsigned char phil[]={2,0xce,0x6e,0x60,0x1c,2}; tmod=0x21; th0=0x2f; tl0=0x40; scon=0x50; pcon=0x00; th1=0xfd; tl1=0xfd; ps=1; ea=1; ex1=0; et0=1; es=1; tr0=1; tr1=1; high_time=50; low_time=50; pidinit ( &spid ); // initialize structure spid.proportion = 10; // set pid coefficients比例常数 proportional const spid.integral = 8; //积分常数 integral const spid.derivative =6; //微分常数 derivative const spid.setpoint = 100; // set pid setpoint 设定目标 desired value while(1){ if(plus==0){ ea=0; for(a=0;a<5;a++) for(b=0;b<102;b++){} if(plus==0){ set_temper++; flag=0;}} else if(subs==0){ for(a=0;a<5;a++) for(b=0;a<102;b++){} if(subs==0) { set_temper--; flag=0; }} else if(stop==0) { for(a=0;a<5;a++) for(b=0;b<102;b++){} if(stop==0) { flag=0; break; } ea=1; } get_temper(); b=temper; if(flag_2==1) a=b; if((abs(a-b))>5) temper=a; else temper=b; a=temper; flag_2=0; if(++count1>30) { display(); count1=0; } compare_temper(); } tr0=0; z=1; while(1) { ea=0; if(stop==0) { for(a=0;a<5;a++) for(b=0;b<102;b++){} if(stop==0) disp_1(phil); // break; } ea=1;}}复制代码