基于C语言及51单片机的PID控制电机调速详解(附详细代码及Protsus仿真)

        本文章将利用最简单的软件和硬件--51单片机和Proteus仿真软件,由简及深地介绍PID控制电机调速的相关技术。

1.认识PWM脉宽调制技术

      在介绍PWM脉宽调制技术之前,我们先在proteus上搭建如下模型,观察小灯的亮灭变化。

         在这个模型中,我省去了单片机的复位电路和灯的上拉电阻。这不影响我们观察仿真结果,但实际使用时要加上。接着用keil软件,用以下程序生成hex文件,添加到上面的模型中。

#include<reg51.h>

#define MAX 0x50
#define MIN 0x00
#define TIMELINE 11
#define TRUE 1
#define FALSE 0

unsigned int TimeCounter;
int ArrowFlg=0;
unsigned char upCounter,downCounter;

sbit LED=P2^0;//PWM输出口


void T0Deal() interrupt 1 using 0//控制PWM的高低电平,upCount代表高电平的时间单位量,downCount代表低电平时间单位量
{

	TH0=0xf1;
	TL0=0xf1;
	TR0=1;
	TimeCounter++;
	if(TimeCounter==TIMELINE)
	{
		if((upCounter==MAX)&&(downCounter==MIN))
		{
			ArrowFlg=FALSE;
		}
		if((upCounter==MIN)&&(downCounter==MAX))
		{
			ArrowFlg=TRUE;
		}
		if(ArrowFlg==TRUE)
		{
			 upCounter++;
			 downCounter--;
		}
		else
		{
			 upCounter--;
			 downCounter++;
		}
		TimeCounter=0; 
	}
}

void Delay(unsigned int i)//延时函数
{
	unsigned int j;
	while(i--)
	{
		for(j=0;j<32;j++);
	}
}

void main()
{
	upCounter=MIN;
	downCounter=MAX;
	TMOD=0x01;
	TH0=0xF0;
	TL0=0xf0;
	EA=1;
	ET0=1;
	TR0=1;
	while(1)
	{
		LED=0;
		Delay(downCounter);//延时产生高电平
		LED=1;
		Delay(upCounter);//延时产生低电平
	}
}

        添加hex文件并运行仿真以后,我们可以发现,灯的亮度变化是从灭慢慢变亮,然后再从亮慢慢熄灭。灯的亮度是在不停变化的。实际上,我们想要使灯的亮度发生变化,本质上是要改变灯的电压,但是在这里,我们是通过PWM来控制灯的亮度的。接下来我们来介绍具体是如何实现的。

        假如我们驱动一种小灯,让它保持最高亮度需要10V。如果想让它保持最高亮度,我们只要在整个PWM周期里都保持10V即可,即让整个PWM周期都是高电平。如下图所示。

       现在,我不想让灯那么亮,我想让它暗一点,使输入其的电压变成2V。理论上我要加更大的电阻才能实现,现在利用PWM技术,我只要让其占空比变成原来的1/5就可以实现。

        上图就是占空比为原来1/5时的样子,它的作用效果和将输入小灯泡的电压调成2V的作用效果是一样的。

       回到上面我给的程序中,我们是通过改变upCount和downCount的值,借助这两个值代入延时函数进而产生PWM脉冲的。我们观察到灯的亮度一直在变化,这是因为upCount和downCount的值一直在改变。upCount就是高电平维持的时间长度,downCount就是低电平维持的时间长度。两个加起来就是一个PWM周期。upCount大一点,灯的亮度就大一点,upCount小一点灯就暗一点。

       实际上,PWM控制灯的亮度和黑白激光打印机打印不同的灰色是一样的。黑白激光打印机只能打出黑色,而我们看到它打出灰色是因为在灰色部分它打出的黑色像素点没那么密集。在这个模型中,灯的亮度没那么亮其本质原因是因为在这一段PWM的占空比没那么大,灯亮的时间比较短而已。

2.认识直流电机和电机双H桥结构

       直流电机和我们上面所说的LED灯特性上基本上是一样的。比如只要让其两端有电压差,它就会转,就像LED灯两端有电压差就会亮一样。如果给直流电机的电压比较小,那么它的转速也越慢(这是在负载不变的情况下),就像上面所介绍的LED灯在电压不足的情况下变暗一样。唯一不同的是如果改变直流电机的电压差方向,只会改变电机的转动方向,而如果改变LED灯的正负极方向,LED灯会直接烧毁。

       接下来我们介绍电机的双H桥结构,其电路结构如下图所示。

        双H桥结构是指上图中的1、2、3、4号三极管组成的外H桥结构,和5、6、7、8号三极管组成的内H桥结构。其中外H桥的四个三极管作用是放大电流以驱动电机。而内H桥的作用是利用三极管的饱和截止特性作为开关控制电流流经电机的方向进而控制电机的转向,因此在一些场合中会用四个同样具有开关特性的晶体管来代替这四个三极管。我倾向于把这种结构称作内外双H桥结构。

3.位置式PID与增量式PID

       首先来谈谈为什么控制电机调速要用到PID控制。假设我们想把 电机的转速从此时的V1提到V2,在这过程中,我们不能直接输出一个速度V2的PWM信号,这样电机是无法正常运行的。而是应该缓慢地提高PWM的占空比,进而使电机的速度缓慢地提升上去。在这过程中就会出现一个问题,就是电机的速度不可能马上很好的停留在V2,而是会在V2附近波动震荡一段时间。如果我们能调试好合适的PID参数,就会使这个过程变得更短,震荡次数越少,震荡幅度越小,电机运行更平稳。

位置式PID

       首先我们先给出位置式PID控制的表达式

式中,e(k)为设定值与当前值的差,即误差;e(i)为从开始到当前时刻误差的累加(在数学里所谓的积分也就是每一时刻值的累加);e(k-1)表示上一时刻的误差。Kp、Ki、Kd就是我们需要根据实际情况调节的比例、积分、微分系数。在程序中如何实现在下面我们给出的PID控制电机转速实例中会有涉及。

        接下来我们通过一个小例子来看看位置式PID存在的一些问题。

        如上图所示,0处是电机所要拉动的一个负载,我们想让电机把这个负载拉倒5处。

        在这个系统中假设使用位置式PID进行电机调速,如果在一开始的时候负载的重量特别大,以至于电机拉不动,那么此时电机会输出力矩但不会输出转数。负载依旧在原位置0处,和我们想要的理想位置5处一直有大小为5的误差,如果这个负载依旧很大,那么在位置式PID的积分环节中,这个误差就会不断累加。

        这个时候我们适当减小负载的重量,使电机可以拉得动。当电机把负载拽到5附近的时候,由于之前的积分环节中积累的误差太多了,电机会一直输出转矩而不会停下来。哪怕电机拉着负载使它的位置超过了5,这时候误差变成了负值,也需要在5附近震荡一段时间才会停下来。在这过程中系统会有极大的超调和震荡。其负载位置与时间的关系大致可如下图所示。

       在上述情况中,系统有很大的超调和震荡显然是不理想的。因此在一些场合中,我们可以在位置式PID控制中把积分环节去掉,使系统性能更好。

增量式PID

      首先给出增量式PID的核心表达式

式中,e(k)同样是误差;e(k-1)是上一时刻的误差;e(k-2)表示上上时刻的误差。

        从表达式中我们可以看出,增量式PID仅与此时刻的误差,上一时刻的误差,上上时刻的误差这三个误差有关,输出的△u(k)是这三次误差的增量,顾名思义叫做增量式PID。

4.PID控制电机调速实例

      首先先展示实例模型电路图。

      在右侧的电机部分,我们可以很清晰地看到内外双H桥结构。左侧的四个按键分别调节比例系数P的大小及转速。通过判断P2.3的电平状态来判断应该正转还是反转。两个与非门的作用分别控制正转和反转时PWM对电机的控制,比如反转时,26、27角分别输出高电平和低电平,由于27角输出的是低电平,不管PWM信号如何变化,下面那个与非门输出的一定是高电平,而上面那个与非门由于是26脚的高电平同PWM信号与非产生结果,因此事实上上面那个与非门输出的信号就是对PWM信号进行取反。P3.4引脚用来读取电机的实时转速。下面通过代码来进一步阐述这个实例。

#include<reg52.h>
#include"lcd1602.h"

sfr T2MOD = 0x0c9;
#define uchar unsigned char
#define uint unsigned int

sbit Q0 = P2^4;
sbit Q1 = P2^5;
sbit Q2 = P2^6;
sbit Q3 = P2^7;

sbit PWM	 	= P1^7;
sbit UP		 	= P1^0;
sbit DOWM	 	= P1^1;
sbit GORB		= P2^3; //换相
sbit ADDSPEED 	= P1^2;
sbit SUBSPEED	= P1^3;

uint tuint = 65535;
uint tpwm = 1;	//pwm周期为10000us tpwm变量表示pwm高电平时间,也相当于占空比 (仿真时,频率高时,电机反应慢。在实物上要加大频率)
uchar t1_flag = 0;

uint pulse = 0;
uint t0_flag = 0;
uchar t2_flag = 0;
bit t2_over = 0;
bit Just_Get = 1;


#define 	ZZ 		{ Q0 = 0;Q1 = 0;Q2 = 1;Q3 = 1;}	//正转
#define 	FZ 		{ Q0 = 1;Q1 = 1;Q2 = 0;Q3 = 0;}	//反转
#define 	STOP	{ Q0 = 1;Q1 = 0;Q2 = 1;Q3 = 0;}	//停止
//禁止出现 Q0 = 0;Q1 = 1;Q2 = 0;Q3 = 1; 不然会烧掉mos管

//************************ PID *************************************
float now = 0,bef = 0,bbef = 0; 	//本次采样值,上次采样值,上上次采样值
float err_now,err_bef,err_bbef;		//当前偏差,上次偏差,上上次偏差
float error_add = 0;				//所有偏差之和
float set = 25;						//设定值

float kp = 25;
float ki = 25;
float kd = 0;

//*****************************************************************

void delayms(uint ms)//延时?个 ms
{
    uchar a,b,c;
	while(ms--)
	{ 
	  for(c=1;c>0;c--)
        for(b=142;b>0;b--)
            for(a=2;a>0;a--);
	}
}

void timer_init()
{
	EA = 1;
	ET0 = 1;
	ET1 = 1;
	ET2 = 1;
	
	TMOD = 0x15; //定时器0 计数模式 定时器1模式1
	T2MOD = 0x01;
	
	TH0 = TL0 = 255;
	TH2 = 0x3C;
	TL2 = 0xB0;		//50MS
	
}
void timer1() interrupt 3
{
	if(t1_flag == 0)
	{
		t1_flag = 1;
		PWM = 1;
		TH1 = (tuint - tpwm + 1)/256;
		TL1 = (tuint - tpwm + 1)%256;
		
	}
	else
	{
		t1_flag = 0;
		PWM = 0;
		TH1 = (tuint - 10000 + tpwm + 1)/256;
		TL1 = (tuint - 10000 + tpwm + 1)%256;
	}
}

void timer0() interrupt 1
{
	TH0 = TL0 = 255;
	t0_flag++;
}
void timer2() interrupt 5
{
	TF2 = 0;
	TH2 = 0x3C;
	TL2 = 0xB0;		//50MS
	
	t2_flag++;
	
	if(t2_flag == 2)
	{
		TR0 = 0;
		TR2 = 0;
		t2_flag = 0;
		t2_over = 1;	//表示100ms时间到
	}
}
void GetPulse()
{
	t0_flag = 0;
	t2_flag = 0;
	
	TH0 = TL0 = 255;
	TH2 = 0x3C;
	TL2 = 0xB0;		//50MS
	
	TR0 = 1;
	TR2 = 1;
}

int PID()	//增量式PID
{
	int change;

	err_now = set - now;
	err_bef = set - bef;
	err_bbef = set - bbef;
	
	change = kp*(err_now - err_bef) + ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
	
/*	
	if(set >= now)
	{	
		if(set - now > 1)
			change = kp*(err_now - err_bef) + ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
		else
			change = 0.2*kp*(err_now - err_bef) + 0.5*ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
	}
	else if(now > set)
	{
		if(now - set > 1)
			change = kp*(err_now - err_bef) + ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
		else
			change = 0.2*kp*(err_now - err_bef) + 0.5*ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
			
	}
*/
	
	//change = (kp + ki + kd)*(set - now) + (-kp - 2*kd)*(set - bef) + kd*(set - bbef);
	//change = kp*(set - now) + ki*(set - bef) + kd*(set - bbef);
	if(change > 0)
	{
		printchar(1,10,'+');	
		printuint(1,11,4,change);
		
	}
	else if(change < 0)
	{	
		printchar(1,10,'-');
		printuint(1,11,4,-change);
	}
	else if(change == 0)
	{	
		printchar(1,10,' ');
		printword(1,11," 0  ");

	}
	
	return(change);
}

int PID2()		//位置式PID
{
	
	int num = 0;
	static num_bef = 0;
	
	err_now = set - now;
	err_bef = set - bef;
	
	error_add = error_add + err_now;  //误差累加。一旦误差为0则error_add的值不变,PID输出值不变

	num = kp*err_now + ki*error_add + kd*(err_now - err_bef);
	
/*	
	if(set - now >= 0)
	{	
		if(set - now > 1)
			num = kp*err_now + ki*error_add + kd*(err_now - err_bef);
		else
			num = 0.1*kp*err_now + ki*error_add + kd*(err_now - err_bef);
	}
	else
	{
		if(now - set > 1)
			num = kp*err_now + ki*error_add + kd*(err_now - err_bef);
		else
			num = 0.1*kp*err_now + ki*error_add + kd*(err_now - err_bef);
			
	}
	*/
	
	if(num > num_bef)
	{
		printchar(1,10,'+');	
		printuint(1,11,4,num - num_bef);
	}
	else if(num < num_bef)
	{
		printchar(1,10,'-');	
		printuint(1,11,4,num_bef - num);
	}
	else
	{	
		printchar(1,10,' ');
		printuint(1,11,4,0);
	}
	
	num_bef = num;
	
	return((uint)num);
}

void main()
{	
	
	lcd_init();
	timer_init();
	TH1 = TL1 = 255;
	
	printword(0,0,"P:");		//比例系数
	printword(0,5,"S:");		//设定值 
	printword(1,0,"TPWM:");		//当前占空比
	printword(0,10,"PS:");		//当前电机反馈的每秒脉冲数
	
	while(1)
	{
		if(GORB == 1)
		{	ZZ;		}
		else
		{	FZ;		}
		
		if(ADDSPEED == 0)
			set++;
		if(SUBSPEED == 0)
			set--;
		
		if(Just_Get == 1)
		{	
			Just_Get = 0;
			GetPulse();
		}
		else if(t2_over == 1)
		{	
			t2_over = 0;
			Just_Get = 1;
			pulse = t0_flag;
			bbef = bef;
			bef = now;
			now = t0_flag;
			
			if(set != 0)
			{
				TR1 = 1;
			}
			else
			{
				TR1 = 0;
				PWM = 0;
			}
			
		//	tpwm = tpwm + PID();		//增量式PID
			tpwm = PID2();				//位置式PID
					
		}
		
		if(UP == 0)
			kp = kp + 1;
		if(DOWM == 0)
			kp = kp - 1;
		
		printuint(0,2,3,kp);
		printuint(0,7,3,set);
		printuint(1,5,4,tpwm);
		printuint(0,13,5,pulse);

	}
	
}

        PWM脉冲实质上是通过定时器1来负责产生的。函数GetPulse()借助定时器的延时定时采集电机的脉冲信号。PID和PID2分别是增量式PID和位置式PID的程序代码。希望读者能通过注释读懂程序的工作流程,也可以自行注释掉相关代码来看看不同PID控制方式下的效果。

版权声明:本文为CSDN博主「alltimehigh」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/octopassy/article/details/117827268

        本文章将利用最简单的软件和硬件--51单片机和Proteus仿真软件,由简及深地介绍PID控制电机调速的相关技术。

1.认识PWM脉宽调制技术

      在介绍PWM脉宽调制技术之前,我们先在proteus上搭建如下模型,观察小灯的亮灭变化。

         在这个模型中,我省去了单片机的复位电路和灯的上拉电阻。这不影响我们观察仿真结果,但实际使用时要加上。接着用keil软件,用以下程序生成hex文件,添加到上面的模型中。

#include<reg51.h>

#define MAX 0x50
#define MIN 0x00
#define TIMELINE 11
#define TRUE 1
#define FALSE 0

unsigned int TimeCounter;
int ArrowFlg=0;
unsigned char upCounter,downCounter;

sbit LED=P2^0;//PWM输出口


void T0Deal() interrupt 1 using 0//控制PWM的高低电平,upCount代表高电平的时间单位量,downCount代表低电平时间单位量
{

	TH0=0xf1;
	TL0=0xf1;
	TR0=1;
	TimeCounter++;
	if(TimeCounter==TIMELINE)
	{
		if((upCounter==MAX)&&(downCounter==MIN))
		{
			ArrowFlg=FALSE;
		}
		if((upCounter==MIN)&&(downCounter==MAX))
		{
			ArrowFlg=TRUE;
		}
		if(ArrowFlg==TRUE)
		{
			 upCounter++;
			 downCounter--;
		}
		else
		{
			 upCounter--;
			 downCounter++;
		}
		TimeCounter=0; 
	}
}

void Delay(unsigned int i)//延时函数
{
	unsigned int j;
	while(i--)
	{
		for(j=0;j<32;j++);
	}
}

void main()
{
	upCounter=MIN;
	downCounter=MAX;
	TMOD=0x01;
	TH0=0xF0;
	TL0=0xf0;
	EA=1;
	ET0=1;
	TR0=1;
	while(1)
	{
		LED=0;
		Delay(downCounter);//延时产生高电平
		LED=1;
		Delay(upCounter);//延时产生低电平
	}
}

        添加hex文件并运行仿真以后,我们可以发现,灯的亮度变化是从灭慢慢变亮,然后再从亮慢慢熄灭。灯的亮度是在不停变化的。实际上,我们想要使灯的亮度发生变化,本质上是要改变灯的电压,但是在这里,我们是通过PWM来控制灯的亮度的。接下来我们来介绍具体是如何实现的。

        假如我们驱动一种小灯,让它保持最高亮度需要10V。如果想让它保持最高亮度,我们只要在整个PWM周期里都保持10V即可,即让整个PWM周期都是高电平。如下图所示。

       现在,我不想让灯那么亮,我想让它暗一点,使输入其的电压变成2V。理论上我要加更大的电阻才能实现,现在利用PWM技术,我只要让其占空比变成原来的1/5就可以实现。

        上图就是占空比为原来1/5时的样子,它的作用效果和将输入小灯泡的电压调成2V的作用效果是一样的。

       回到上面我给的程序中,我们是通过改变upCount和downCount的值,借助这两个值代入延时函数进而产生PWM脉冲的。我们观察到灯的亮度一直在变化,这是因为upCount和downCount的值一直在改变。upCount就是高电平维持的时间长度,downCount就是低电平维持的时间长度。两个加起来就是一个PWM周期。upCount大一点,灯的亮度就大一点,upCount小一点灯就暗一点。

       实际上,PWM控制灯的亮度和黑白激光打印机打印不同的灰色是一样的。黑白激光打印机只能打出黑色,而我们看到它打出灰色是因为在灰色部分它打出的黑色像素点没那么密集。在这个模型中,灯的亮度没那么亮其本质原因是因为在这一段PWM的占空比没那么大,灯亮的时间比较短而已。

2.认识直流电机和电机双H桥结构

       直流电机和我们上面所说的LED灯特性上基本上是一样的。比如只要让其两端有电压差,它就会转,就像LED灯两端有电压差就会亮一样。如果给直流电机的电压比较小,那么它的转速也越慢(这是在负载不变的情况下),就像上面所介绍的LED灯在电压不足的情况下变暗一样。唯一不同的是如果改变直流电机的电压差方向,只会改变电机的转动方向,而如果改变LED灯的正负极方向,LED灯会直接烧毁。

       接下来我们介绍电机的双H桥结构,其电路结构如下图所示。

        双H桥结构是指上图中的1、2、3、4号三极管组成的外H桥结构,和5、6、7、8号三极管组成的内H桥结构。其中外H桥的四个三极管作用是放大电流以驱动电机。而内H桥的作用是利用三极管的饱和截止特性作为开关控制电流流经电机的方向进而控制电机的转向,因此在一些场合中会用四个同样具有开关特性的晶体管来代替这四个三极管。我倾向于把这种结构称作内外双H桥结构。

3.位置式PID与增量式PID

       首先来谈谈为什么控制电机调速要用到PID控制。假设我们想把 电机的转速从此时的V1提到V2,在这过程中,我们不能直接输出一个速度V2的PWM信号,这样电机是无法正常运行的。而是应该缓慢地提高PWM的占空比,进而使电机的速度缓慢地提升上去。在这过程中就会出现一个问题,就是电机的速度不可能马上很好的停留在V2,而是会在V2附近波动震荡一段时间。如果我们能调试好合适的PID参数,就会使这个过程变得更短,震荡次数越少,震荡幅度越小,电机运行更平稳。

位置式PID

       首先我们先给出位置式PID控制的表达式

式中,e(k)为设定值与当前值的差,即误差;e(i)为从开始到当前时刻误差的累加(在数学里所谓的积分也就是每一时刻值的累加);e(k-1)表示上一时刻的误差。Kp、Ki、Kd就是我们需要根据实际情况调节的比例、积分、微分系数。在程序中如何实现在下面我们给出的PID控制电机转速实例中会有涉及。

        接下来我们通过一个小例子来看看位置式PID存在的一些问题。

        如上图所示,0处是电机所要拉动的一个负载,我们想让电机把这个负载拉倒5处。

        在这个系统中假设使用位置式PID进行电机调速,如果在一开始的时候负载的重量特别大,以至于电机拉不动,那么此时电机会输出力矩但不会输出转数。负载依旧在原位置0处,和我们想要的理想位置5处一直有大小为5的误差,如果这个负载依旧很大,那么在位置式PID的积分环节中,这个误差就会不断累加。

        这个时候我们适当减小负载的重量,使电机可以拉得动。当电机把负载拽到5附近的时候,由于之前的积分环节中积累的误差太多了,电机会一直输出转矩而不会停下来。哪怕电机拉着负载使它的位置超过了5,这时候误差变成了负值,也需要在5附近震荡一段时间才会停下来。在这过程中系统会有极大的超调和震荡。其负载位置与时间的关系大致可如下图所示。

       在上述情况中,系统有很大的超调和震荡显然是不理想的。因此在一些场合中,我们可以在位置式PID控制中把积分环节去掉,使系统性能更好。

增量式PID

      首先给出增量式PID的核心表达式

式中,e(k)同样是误差;e(k-1)是上一时刻的误差;e(k-2)表示上上时刻的误差。

        从表达式中我们可以看出,增量式PID仅与此时刻的误差,上一时刻的误差,上上时刻的误差这三个误差有关,输出的△u(k)是这三次误差的增量,顾名思义叫做增量式PID。

4.PID控制电机调速实例

      首先先展示实例模型电路图。

      在右侧的电机部分,我们可以很清晰地看到内外双H桥结构。左侧的四个按键分别调节比例系数P的大小及转速。通过判断P2.3的电平状态来判断应该正转还是反转。两个与非门的作用分别控制正转和反转时PWM对电机的控制,比如反转时,26、27角分别输出高电平和低电平,由于27角输出的是低电平,不管PWM信号如何变化,下面那个与非门输出的一定是高电平,而上面那个与非门由于是26脚的高电平同PWM信号与非产生结果,因此事实上上面那个与非门输出的信号就是对PWM信号进行取反。P3.4引脚用来读取电机的实时转速。下面通过代码来进一步阐述这个实例。

#include<reg52.h>
#include"lcd1602.h"

sfr T2MOD = 0x0c9;
#define uchar unsigned char
#define uint unsigned int

sbit Q0 = P2^4;
sbit Q1 = P2^5;
sbit Q2 = P2^6;
sbit Q3 = P2^7;

sbit PWM	 	= P1^7;
sbit UP		 	= P1^0;
sbit DOWM	 	= P1^1;
sbit GORB		= P2^3; //换相
sbit ADDSPEED 	= P1^2;
sbit SUBSPEED	= P1^3;

uint tuint = 65535;
uint tpwm = 1;	//pwm周期为10000us tpwm变量表示pwm高电平时间,也相当于占空比 (仿真时,频率高时,电机反应慢。在实物上要加大频率)
uchar t1_flag = 0;

uint pulse = 0;
uint t0_flag = 0;
uchar t2_flag = 0;
bit t2_over = 0;
bit Just_Get = 1;


#define 	ZZ 		{ Q0 = 0;Q1 = 0;Q2 = 1;Q3 = 1;}	//正转
#define 	FZ 		{ Q0 = 1;Q1 = 1;Q2 = 0;Q3 = 0;}	//反转
#define 	STOP	{ Q0 = 1;Q1 = 0;Q2 = 1;Q3 = 0;}	//停止
//禁止出现 Q0 = 0;Q1 = 1;Q2 = 0;Q3 = 1; 不然会烧掉mos管

//************************ PID *************************************
float now = 0,bef = 0,bbef = 0; 	//本次采样值,上次采样值,上上次采样值
float err_now,err_bef,err_bbef;		//当前偏差,上次偏差,上上次偏差
float error_add = 0;				//所有偏差之和
float set = 25;						//设定值

float kp = 25;
float ki = 25;
float kd = 0;

//*****************************************************************

void delayms(uint ms)//延时?个 ms
{
    uchar a,b,c;
	while(ms--)
	{ 
	  for(c=1;c>0;c--)
        for(b=142;b>0;b--)
            for(a=2;a>0;a--);
	}
}

void timer_init()
{
	EA = 1;
	ET0 = 1;
	ET1 = 1;
	ET2 = 1;
	
	TMOD = 0x15; //定时器0 计数模式 定时器1模式1
	T2MOD = 0x01;
	
	TH0 = TL0 = 255;
	TH2 = 0x3C;
	TL2 = 0xB0;		//50MS
	
}
void timer1() interrupt 3
{
	if(t1_flag == 0)
	{
		t1_flag = 1;
		PWM = 1;
		TH1 = (tuint - tpwm + 1)/256;
		TL1 = (tuint - tpwm + 1)%256;
		
	}
	else
	{
		t1_flag = 0;
		PWM = 0;
		TH1 = (tuint - 10000 + tpwm + 1)/256;
		TL1 = (tuint - 10000 + tpwm + 1)%256;
	}
}

void timer0() interrupt 1
{
	TH0 = TL0 = 255;
	t0_flag++;
}
void timer2() interrupt 5
{
	TF2 = 0;
	TH2 = 0x3C;
	TL2 = 0xB0;		//50MS
	
	t2_flag++;
	
	if(t2_flag == 2)
	{
		TR0 = 0;
		TR2 = 0;
		t2_flag = 0;
		t2_over = 1;	//表示100ms时间到
	}
}
void GetPulse()
{
	t0_flag = 0;
	t2_flag = 0;
	
	TH0 = TL0 = 255;
	TH2 = 0x3C;
	TL2 = 0xB0;		//50MS
	
	TR0 = 1;
	TR2 = 1;
}

int PID()	//增量式PID
{
	int change;

	err_now = set - now;
	err_bef = set - bef;
	err_bbef = set - bbef;
	
	change = kp*(err_now - err_bef) + ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
	
/*	
	if(set >= now)
	{	
		if(set - now > 1)
			change = kp*(err_now - err_bef) + ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
		else
			change = 0.2*kp*(err_now - err_bef) + 0.5*ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
	}
	else if(now > set)
	{
		if(now - set > 1)
			change = kp*(err_now - err_bef) + ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
		else
			change = 0.2*kp*(err_now - err_bef) + 0.5*ki*err_now + kd*(err_now - 2*err_bef + err_bbef);
			
	}
*/
	
	//change = (kp + ki + kd)*(set - now) + (-kp - 2*kd)*(set - bef) + kd*(set - bbef);
	//change = kp*(set - now) + ki*(set - bef) + kd*(set - bbef);
	if(change > 0)
	{
		printchar(1,10,'+');	
		printuint(1,11,4,change);
		
	}
	else if(change < 0)
	{	
		printchar(1,10,'-');
		printuint(1,11,4,-change);
	}
	else if(change == 0)
	{	
		printchar(1,10,' ');
		printword(1,11," 0  ");

	}
	
	return(change);
}

int PID2()		//位置式PID
{
	
	int num = 0;
	static num_bef = 0;
	
	err_now = set - now;
	err_bef = set - bef;
	
	error_add = error_add + err_now;  //误差累加。一旦误差为0则error_add的值不变,PID输出值不变

	num = kp*err_now + ki*error_add + kd*(err_now - err_bef);
	
/*	
	if(set - now >= 0)
	{	
		if(set - now > 1)
			num = kp*err_now + ki*error_add + kd*(err_now - err_bef);
		else
			num = 0.1*kp*err_now + ki*error_add + kd*(err_now - err_bef);
	}
	else
	{
		if(now - set > 1)
			num = kp*err_now + ki*error_add + kd*(err_now - err_bef);
		else
			num = 0.1*kp*err_now + ki*error_add + kd*(err_now - err_bef);
			
	}
	*/
	
	if(num > num_bef)
	{
		printchar(1,10,'+');	
		printuint(1,11,4,num - num_bef);
	}
	else if(num < num_bef)
	{
		printchar(1,10,'-');	
		printuint(1,11,4,num_bef - num);
	}
	else
	{	
		printchar(1,10,' ');
		printuint(1,11,4,0);
	}
	
	num_bef = num;
	
	return((uint)num);
}

void main()
{	
	
	lcd_init();
	timer_init();
	TH1 = TL1 = 255;
	
	printword(0,0,"P:");		//比例系数
	printword(0,5,"S:");		//设定值 
	printword(1,0,"TPWM:");		//当前占空比
	printword(0,10,"PS:");		//当前电机反馈的每秒脉冲数
	
	while(1)
	{
		if(GORB == 1)
		{	ZZ;		}
		else
		{	FZ;		}
		
		if(ADDSPEED == 0)
			set++;
		if(SUBSPEED == 0)
			set--;
		
		if(Just_Get == 1)
		{	
			Just_Get = 0;
			GetPulse();
		}
		else if(t2_over == 1)
		{	
			t2_over = 0;
			Just_Get = 1;
			pulse = t0_flag;
			bbef = bef;
			bef = now;
			now = t0_flag;
			
			if(set != 0)
			{
				TR1 = 1;
			}
			else
			{
				TR1 = 0;
				PWM = 0;
			}
			
		//	tpwm = tpwm + PID();		//增量式PID
			tpwm = PID2();				//位置式PID
					
		}
		
		if(UP == 0)
			kp = kp + 1;
		if(DOWM == 0)
			kp = kp - 1;
		
		printuint(0,2,3,kp);
		printuint(0,7,3,set);
		printuint(1,5,4,tpwm);
		printuint(0,13,5,pulse);

	}
	
}

        PWM脉冲实质上是通过定时器1来负责产生的。函数GetPulse()借助定时器的延时定时采集电机的脉冲信号。PID和PID2分别是增量式PID和位置式PID的程序代码。希望读者能通过注释读懂程序的工作流程,也可以自行注释掉相关代码来看看不同PID控制方式下的效果。

版权声明:本文为CSDN博主「alltimehigh」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/octopassy/article/details/117827268

生成海报
点赞 0

alltimehigh

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐