转一个PID控制电机的小程序, 被PID困扰好多天了, 知道它的原理但是一直不明白如何将它运用到电机调速中间去, 看了这个程序之后感觉茅塞顿开。原来也并不难^-^
转载地址:呃,刚刚不小心把网页关掉了(大写的尴尬)。。。。
-
#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;
//处理完后再开中断
-
}
-
-
#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;//处理完后再开中断
}