C语言实现PID之应用

从代码中认识PID的应用 

电机转速PID控制。两份网上的工程代码。

原理:PWM波控制电机转速,控制PWM波的高电平时间,即控制占空比来控制转速。

都是增量式PID,需要三次误差值,这次测的误差,error,上次测的误差,pre_error,上上次测的误差,pre_pre_error

转速是执行器的输出。

输入你想要的转速(设定的转速)Setspeed,测量得到的转速,Now_speed。

得到误差error=Setspeed-Now_speed

设定你的PID系数Kp,Ki,Kd

经过PID函数

Now_pwm = Per_pwm+Kp*(error-pre_error)+Ki*error+Kd*(pre_pre_error*pre_error+pre_pre_error)

得到输入执行器的PWM值。

void SpeedAdjust()
{
    long d_error,dd_error,error;                //声明变量
    error = (int)(speed_ept - now_speed);       //计算本次误差(期待速度-当前速度)
    d_error = error - pre_error;                //本次误差与上次误差之差
    dd_error = pre_error - pre_pre_error;       //上次误差与上上次误差之差
    pre_error = error;                          //将本次误差赋值给上次误差(下次计算用)
    pre_pre_error = pre_error;                  //将上次误差赋值给上上次误差(下次计算用)
    pwm_tmp = pwm_tmp + PID_P*d_error + PID_I*error + PID_D*dd_error;       
                                                //计算pwm宽度调整量


    if(now_speed>22)                            //如果当前速度大于22
    {
        Set_DCMotor(0,0);                       //不调整电机
    }

    else        //否则
    {
        if(pwm_tmp>=0)                      //如果pwm宽度调整为正(增加宽度)
        {
            Set_DCMotor(pwm_tmp,0);         //增加直流电机转速
        }
        else        //否则
        {
            Set_DCMotor(pwm_tmp,1);         //降低直流电机转速
        }
    }
    before_speed = now_speed;               //讲本次速度赋值给上次速度(下次使用)
}
#include<reg52.h>
#include<stdio.h>

#define uchar unsigned char 
#define uint unsigned int
#define THC0 0xf8
#define TLC0 0xcc   //2ms

unsigned char code Duan[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};//共阴极数码管,0-9段码表

unsigned char Data_Buffer[8]={0,0,0,0,0,0,0,0};
unsigned char Data[4]={0,0,0,0};
unsigned char Arry[4]={0,0,0,0};

bit flag1=0;
bit flag0=0;
uchar i=0;

sbit AddSpeed=P1^1;
sbit SubSpeed=P1^2;
sbit PWM_FC=P1^0;

int e=0,e1=0,e2=0;			//pid 偏差
float uk=0,uk1=0.0,duk=0.0;	//pid输出值
float Kp=5,Ki=1.5,Kd=0.9;	//pid控制系数	
//float Kp=10;

int out=0;
uint SpeedSet=1000;
uint cnt=0;
uint Inpluse=0,num=0;		//脉冲计数
uint PWMTime=0;				//脉冲宽度

unsigned char arry[];

void SendString(uint ch);
void PIDControl();
void SystemInit();
void delay(uchar x);
void PWMOUT();
void SetSpeed();
void SegRefre();


/**************主函数************/
void main()
{
	SystemInit();//系统初始化
	while(1)
	{
		SetSpeed();//设置速度
		SegRefre();//更新数码管显示
		PWMOUT();  //PWM输出
		if(flag0==1)
		{
			flag0=0;
			ES=0;//关串口中断,避免TI引起串口中断
			TI=1;//printf前TI置1
			printf("%f",(float)(num>>8));//脉冲计数
			printf("%f",(float)num);	 //脉冲计数
			while(!TI);
			TI=0;	   //TI软件清除
			ES=1;	   //允许串口中断
		}
	}
}
 
 
//PID偏差控制计算
void PIDControl()        //pid偏差计算
{
	e=SpeedSet-num;//设置速度-实际速度,两者的差值 
	//对应于增量式PID的公式Δuk=uk-u(k-1)

//	duk=(Kp*(e-e1))/100;//只调节P
	duk=(Kp*(e-e1)+Ki*e)/100;//只调节PI
//	duk=(Kp*(e-e1)+Ki*e+Kd*(e-2*e1+e2))/100;//调节PID

	uk=uk1+duk;//uk=u(k-1)+Δuk

	out=(int)uk;//取整后输出

	if(out>250)	//设置最大限制
		out=250;
	else if(out<0)//设置最小限制
		out=0;

	uk1=uk;		  //为下一次增量做准备
	e2=e1;
	e1=e;

	PWMTime=out;  //out对应于PWM高电平的时间

}
 
 
//较短的延时。注意delay的值不要超过255
void delay(uchar x)
{
	uchar i,j;
	for(i=x;i>0;i--)
		for(j=50;j>0;j--);//接近500us
}
 
 
//PWM输出
void PWMOUT()
{
	if(cnt<PWMTime)//若小于PWM的设定时间,则输出高电平
		PWM_FC=1;
	else		   //否则输出低电平
		PWM_FC=0;
	if(cnt>250)    //超过限制清零
		cnt=0;
}
 
 
//系统初始化
void SystemInit()
{
	TMOD=0X21;    //T1用于串口的波特率发生器 T0用于定时
	TH0=THC0;	  //2ms定时,晶振频率是11.0592MHz,事先算好装入THC0中
	TL0=TLC0;	  //2ms定时,晶振频率是11.0592MHz,实现算好装入TLCO中
	ET0=1;		  //允许定时器0中断
	TR0=1;		  //启动定时器0
	EX0=1;		  //允许外部中断
	IT0=1;		  //中断方式设置为下降沿
	//用于串口调试时用
	PCON=0x00;	 //SMOD不加倍
	SCON=0x50;	 //串口工作方式1,允许接收
	TH1=0xfd;	 //波特率9600
	TL1=0xfd;	 //波特率9600
	TR1=1;		 //启动定时器1
	ES=1;		 //开串口中断
	EA=1;		 //开总中断
	e =0;		 //PID的差值初值均为0
	e1=0;		 
	e2=0;
}
 
 
//设置转速
void SetSpeed()
{
	if(AddSpeed==0)//按键
	{
		delay(200);//消抖
		if(AddSpeed==0)//再次判断按键
		{
			SpeedSet+=100;//速度增加
			if(SpeedSet>9999)//超限
				SpeedSet=9999;//设置为最大9999
		}
	}
	if(SubSpeed==0)//按键
	{
		delay(200);//消抖
		if(SubSpeed==0)//按键
		{
			SpeedSet-=100;//速度减
			if(SpeedSet<0)//低于速度的最小值 
				SpeedSet=0;//速度置零
		}
	}
}
 
 
//数码管显示更新
void SegRefre()		  //显示刷新
{
	Data_Buffer[0]=SpeedSet/1000;
	Data_Buffer[1]=SpeedSet%1000/100;
	Data_Buffer[2]=SpeedSet%100/10;
	Data_Buffer[3]=SpeedSet%10;
	Data_Buffer[4]=num/1000;
	Data_Buffer[5]=num%1000/100;
	Data_Buffer[6]=num%100/10;
	Data_Buffer[7]=num%10;
}
 
 
//外部中断,统计脉冲数目,实际上是统计电机的转速
//在实际中,电机没有一根线引出来可以表示他的转速,只能通过传感器如霍尔传感器的方式测量转速
//脉冲一次下降沿对应于一次自增
void int0() interrupt 0
{
	Inpluse++;
}
 
 
//定时器T0操作
void t0() interrupt 1
{
	static unsigned char Bit=0;//静态变量,退出程序值保留
	static unsigned int time=0;
	static unsigned int aa=0;
	TH0=THC0;//重新赋初值
	TL0=TLC0;
	aa++;
	if(aa==50)//每100ms
	{
		aa=0;
		flag0=1;
	}
	cnt++;	//pid 周期
	Bit++;
	time++;  //转速测量周期
	if(Bit>8) Bit=0;//数码管总共只有8位,超过8位则在程序上进行清零
	//数码管显示部分
	P0=0xff;
	P2=Duan[Data_Buffer[Bit]];
	switch(Bit)
	{
		case 0:P0=0X7F;break;
		case 1:P0=0XBF;break;
		case 2:P0=0XDF;break;
		case 3:P0=0XEF;break;
		case 4:P0=0XF7;break;
		case 5:P0=0XFB;break;
		case 6:P0=0XFD;break;
		case 7:P0=0XFE;break;
	}
	//数码管显示部分
	if(time>500)//每1s处理一次脉冲
	{
		time=0;
		num=Inpluse*15;//实际转速,*15是由电机决定的,电机的一个脉冲对应着电机转过了15转
		Inpluse=0;	   //清零,为下一次计数做准备
		PIDControl();  //调用PID控制程序
	}
}
 
 
//串口中断,用于对串口的数据进行处理
void u_int(void) interrupt 4
{
	ES=0;//关串口中断,避免在数据处理的过程中造成影响
	if(RI)//若有RI==1,由RI产生中断
	{
		RI=0;//RI标志位必须通过软件进行清零
		arry[i]=SBUF;//将字符赋到arry中
		i++;		 //下一个数
		if(i>3)		 //接收完了指令,进行数据处理,一共有arry[0],arry[1],arry[2],arry[3]四个数据
		{
			flag1=1;//将标志置1
			i=0;	//i清零,重新计数
		}
		if(flag1)	//若flag1==1
		{
			flag1=0;//flag1清零
			SpeedSet=(arry[0]-'0')+(arry[1]-'0')*10+(arry[2]-'0')*100+(arry[3]-'0')*1000;//获得的速度值
			//由于串口发送的是字符,所以要减去'0'
		}
	}
	else//对应的由TI产生中断
		TI=0;//TI由软件进行清零	
	ES=1;//处理完后再开中断
}
 
 

猜你喜欢

转载自blog.csdn.net/fzf1996/article/details/88551240
今日推荐