1. 程式人生 > >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;//處理完後再開中斷
}