PID控制c程序

转一个PID控制电机的小程序, 被PID困扰好多天了, 知道它的原理但是一直不明白如何将它运用到电机调速中间去, 看了这个程序之后感觉茅塞顿开。原来也并不难^-^

转载地址:呃,刚刚不小心把网页关掉了(大写的尴尬)。。。。



  
  
  1. #include<reg52.h>
  2. #include<stdio.h>
  3. #define uchar unsigned char 
  4. #define uint unsigned int
  5. #define THC0 0xf8
  6. #define TLC0 0xcc   //2ms
  7. unsigned char code Duan[]={ 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; //共阴极数码管,0-9段码表
  8. unsigned char Data_Buffer[ 8]={ 0, 0, 0, 0, 0, 0, 0, 0};
  9. unsigned char Data[ 4]={ 0, 0, 0, 0};
  10. unsigned char Arry[ 4]={ 0, 0, 0, 0};
  11. bit flag1= 0;
  12. bit flag0= 0;
  13. uchar i= 0;
  14. sbit AddSpeed=P1^ 1;
  15. sbit SubSpeed=P1^ 2;
  16. sbit PWM_FC=P1^ 0;
  17. int e= 0,e1= 0,e2= 0; //pid 偏差
  18. float uk= 0,uk1= 0.0,duk= 0.0; //pid输出值
  19. float Kp= 5,Ki= 1.5,Kd= 0.9; //pid控制系数
  20. //float Kp=10;
  21. int out= 0;
  22. uint SpeedSet= 1000;
  23. uint cnt= 0;
  24. uint Inpluse= 0,num= 0; //脉冲计数
  25. uint PWMTime= 0; //脉冲宽度
  26. unsigned char arry[];
  27. void SendString(uint ch);
  28. void PIDControl();
  29. void SystemInit();
  30. void delay(uchar x);
  31. void PWMOUT();
  32. void SetSpeed();
  33. void SegRefre();
  34. /**************主函数************/
  35. void main()
  36. {
  37. SystemInit(); //系统初始化
  38. while( 1)
  39. {
  40. SetSpeed(); //设置速度
  41. SegRefre(); //更新数码管显示
  42. PWMOUT();  //PWM输出
  43. if(flag0== 1)
  44. {
  45. flag0= 0;
  46. ES= 0; //关串口中断,避免TI引起串口中断
  47. TI= 1; //printf前TI置1
  48. printf( "%f",( float)(num>> 8)); //脉冲计数
  49. printf( "%f",( float)num); //脉冲计数
  50. while(!TI);
  51. TI= 0;     //TI软件清除
  52. ES= 1;     //允许串口中断
  53. }
  54. }
  55. }
  56. //PID偏差控制计算
  57. void PIDControl()        //pid偏差计算
  58. {
  59. e=SpeedSet-num; //设置速度-实际速度,两者的差值 
  60. //对应于增量式PID的公式Δuk=uk-u(k-1)
  61. // duk=(Kp*(e-e1))/100;//只调节P
  62. duk=(Kp*(e-e1)+Ki*e)/ 100; //只调节PI
  63. // duk=(Kp*(e-e1)+Ki*e+Kd*(e-2*e1+e2))/100;//调节PID
  64. uk=uk1+duk; //uk=u(k-1)+Δuk
  65. out=( int)uk; //取整后输出
  66. if( out> 250) //设置最大限制
  67. out= 250;
  68. else if( out< 0) //设置最小限制
  69. out= 0;
  70. uk1=uk;   //为下一次增量做准备
  71. e2=e1;
  72. e1=e;
  73. PWMTime= out//out对应于PWM高电平的时间
  74. }
  75. //较短的延时。注意delay的值不要超过255
  76. void delay(uchar x)
  77. {
  78. uchar i,j;
  79. for(i=x;i> 0;i--)
  80. for(j= 50;j> 0;j--); //接近500us
  81. }
  82. //PWM输出
  83. void PWMOUT()
  84. {
  85. if(cnt<PWMTime) //若小于PWM的设定时间,则输出高电平
  86. PWM_FC= 1;
  87. else     //否则输出低电平
  88. PWM_FC= 0;
  89. if(cnt> 250)    //超过限制清零
  90. cnt= 0;
  91. }
  92. //系统初始化
  93. void SystemInit()
  94. {
  95. TMOD= 0X21;    //T1用于串口的波特率发生器 T0用于定时
  96. TH0=THC0;   //2ms定时,晶振频率是11.0592MHz,事先算好装入THC0中
  97. TL0=TLC0;   //2ms定时,晶振频率是11.0592MHz,实现算好装入TLCO中
  98. ET0= 1;   //允许定时器0中断
  99. TR0= 1;   //启动定时器0
  100. EX0= 1;   //允许外部中断
  101. IT0= 1;   //中断方式设置为下降沿
  102. //用于串口调试时用
  103. PCON= 0x00; //SMOD不加倍
  104. SCON= 0x50; //串口工作方式1,允许接收
  105. TH1= 0xfd; //波特率9600
  106. TL1= 0xfd; //波特率9600
  107. TR1= 1; //启动定时器1
  108. ES= 1; //开串口中断
  109. EA= 1; //开总中断
  110. e = 0; //PID的差值初值均为0
  111. e1= 0;  
  112. e2= 0;
  113. }
  114. //设置转速
  115. void SetSpeed()
  116. {
  117. if(AddSpeed== 0) //按键
  118. {
  119. delay( 200); //消抖
  120. if(AddSpeed== 0) //再次判断按键
  121. {
  122. SpeedSet+= 100; //速度增加
  123. if(SpeedSet> 9999) //超限
  124. SpeedSet= 9999; //设置为最大9999
  125. }
  126. }
  127. if(SubSpeed== 0) //按键
  128. {
  129. delay( 200); //消抖
  130. if(SubSpeed== 0) //按键
  131. {
  132. SpeedSet-= 100; //速度减
  133. if(SpeedSet< 0) //低于速度的最小值 
  134. SpeedSet= 0; //速度置零
  135. }
  136. }
  137. }
  138. //数码管显示更新
  139. void SegRefre()   //显示刷新
  140. {
  141. Data_Buffer[ 0]=SpeedSet/ 1000;
  142. Data_Buffer[ 1]=SpeedSet% 1000/ 100;
  143. Data_Buffer[ 2]=SpeedSet% 100/ 10;
  144. Data_Buffer[ 3]=SpeedSet% 10;
  145. Data_Buffer[ 4]=num/ 1000;
  146. Data_Buffer[ 5]=num% 1000/ 100;
  147. Data_Buffer[ 6]=num% 100/ 10;
  148. Data_Buffer[ 7]=num% 10;
  149. }
  150. //外部中断,统计脉冲数目,实际上是统计电机的转速
  151. //在实际中,电机没有一根线引出来可以表示他的转速,只能通过传感器如霍尔传感器的方式测量转速
  152. //脉冲一次下降沿对应于一次自增
  153. void int0() interrupt 0
  154. {
  155. Inpluse++;
  156. }
  157. //定时器T0操作
  158. void t0() interrupt 1
  159. {
  160. static unsigned char Bit= 0; //静态变量,退出程序值保留
  161. static unsigned int time= 0;
  162. static unsigned int aa= 0;
  163. TH0=THC0; //重新赋初值
  164. TL0=TLC0;
  165. aa++;
  166. if(aa== 50) //每100ms
  167. {
  168. aa= 0;
  169. flag0= 1;
  170. }
  171. cnt++; //pid 周期
  172. Bit++;
  173. time++;  //转速测量周期
  174. if(Bit> 8) Bit= 0; //数码管总共只有8位,超过8位则在程序上进行清零
  175. //数码管显示部分
  176. P0= 0xff;
  177. P2=Duan[Data_Buffer[Bit]];
  178. switch(Bit)
  179. {
  180. case 0:P0= 0X7F; break;
  181. case 1:P0= 0XBF; break;
  182. case 2:P0= 0XDF; break;
  183. case 3:P0= 0XEF; break;
  184. case 4:P0= 0XF7; break;
  185. case 5:P0= 0XFB; break;
  186. case 6:P0= 0XFD; break;
  187. case 7:P0= 0XFE; break;
  188. }
  189. //数码管显示部分
  190. if(time> 500) //每1s处理一次脉冲
  191. {
  192. time= 0;
  193. num=Inpluse* 15; //实际转速,*15是由电机决定的,电机的一个脉冲对应着电机转过了15转
  194. Inpluse= 0;     //清零,为下一次计数做准备
  195. PIDControl();  //调用PID控制程序
  196. }
  197. }
  198. //串口中断,用于对串口的数据进行处理
  199. void u_int( void) interrupt 4
  200. {
  201. ES= 0; //关串口中断,避免在数据处理的过程中造成影响
  202. if(RI) //若有RI==1,由RI产生中断
  203. {
  204. RI= 0; //RI标志位必须通过软件进行清零
  205. arry[i]=SBUF; //将字符赋到arry中
  206. i++; //下一个数
  207. if(i> 3) //接收完了指令,进行数据处理,一共有arry[0],arry[1],arry[2],arry[3]四个数据
  208. {
  209. flag1= 1; //将标志置1
  210. i= 0; //i清零,重新计数
  211. }
  212. if(flag1) //若flag1==1
  213. {
  214. flag1= 0; //flag1清零
  215. SpeedSet=(arry[ 0]- '0')+(arry[ 1]- '0')* 10+(arry[ 2]- '0')* 100+(arry[ 3]- '0')* 1000; //获得的速度值
  216. //由于串口发送的是字符,所以要减去'0'
  217. }
  218. }
  219. else //对应的由TI产生中断
  220. TI= 0; //TI由软件进行清零
  221. ES= 1; //处理完后再开中断
  222. }


#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;//处理完后再开中断
}



发布了11 篇原创文章 · 获赞 62 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_42264284/article/details/82845490