PID控制电机转速

本文分享了一个使用PID控制算法实现电机调速的小程序。该程序详细展示了如何利用PID算法来调整电机的速度,并通过按键和串口输入设置目标速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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



<think>好的,我现在需要帮助用户了解如何使用PID控制电机转速,包括实现方法和代码示例。首先,我需要回忆PID控制的基本原理,然后结合用户提供的引用内容,特别是引用[1]和[2],来构建回答结构。 用户提到了Arduino和STC15单片机,所以可能需要分别针对这两个平台给出示例。不过用户的问题可能更偏向于一般性的方法,所以需要先解释PID的组成部分,再给出具体的代码例子。 首先,PID的三个参数:比例、积分、微分。要解释每个项的作用,以及如何调整它们。然后,实现步骤可能包括硬件连接、编码实现PID算法、PWM输出控制电机转速。 接下来,代码示例部分需要参考用户提供的引用。引用[1]提到Arduino使用PWM控制BLDC电机,而引用[2]是STC15的C语言代码,包含PID计算的部分。所以可能需要分别给出这两个平台的示例。 需要注意的是,用户提供的引用中的代码片段可能不完整,需要合理补充,确保代码语法正确。例如,引用[2]中的公式可能需要检查是否正确,因为通常PID的增量式公式是Δu = Kp*(e(k)-e(k-1)) + Ki*e(k) + Kd*(e(k)-2e(k-1)+e(k-2)),但用户给出的代码中的计算式可能有误,需要确认是否正确,或者是否用户引用中的代码有特定实现方式。 此外,在编写代码示例时,需要遵循用户给出的格式要求,比如行内公式用$...$,独立公式用$$...$$,并且正确使用LaTeX语法。例如,PID的公式应该用独立公式格式。 可能还需要生成相关问题,比如如何调节PID参数,PID的应用场景,常见问题等。 现在需要组织内容,确保结构清晰,步骤明确,代码正确,并且符合用户提供的引用内容。同时注意引用标识的正确添加,比如在提到Arduino或STC15的代码时,加上对应的引用标记。</think>### PID控制电机转速实现方法 PID控制通过比例(P)、积分(I)、微分(D)三部分动态调整系统输出,实现对目标值的精准跟踪。其核心公式为: $$u(t) = K_p e(t) + K_i \int_0^t e(\tau)d\tau + K_d \frac{de(t)}{dt}$$ 其中$e(t)$为设定值与实际值的偏差[^2]。 #### 实现步骤 1. **硬件连接** - 电机驱动模块连接单片机PWM输出引脚 - 编码器或霍尔传感器反馈转速信号 - 单片机通过ADC读取转速测量值 2. **参数整定** - 比例系数$K_p$:快速响应但可能超调 - 积分系数$K_i$:消除稳态误差 - 微分系数$K_d$:抑制振荡 3. **控制流程** ```mermaid graph TD A[读取设定转速] --> B[获取实际转速] B --> C[计算偏差e(t)] C --> D[PID计算输出] D --> E[调整PWM占空比] E --> F[更新历史误差] ``` #### 代码示例(Arduino平台) ```cpp // PID参数 float Kp=0.8, Ki=0.2, Kd=0.05; float error=0, integral=0, derivative=0, lastError=0; void setup() { pinMode(9, OUTPUT); // PWM输出引脚 } void loop() { int setpoint = analogRead(A0); // 设定值输入 int feedback = getRPM(); // 获取实际转速 error = setpoint - feedback; integral += error * dt; // dt为采样周期 derivative = (error - lastError)/dt; float output = Kp*error + Ki*integral + Kd*derivative; analogWrite(9, constrain(output, 0, 255)); lastError = error; delay(10); // 10ms控制周期 } ``` *基于Arduino的PWM控制实现[^1]* #### 代码示例(STC15单片机) ```c typedef struct { float Kp, Ki, Kd; float err, err_last, err_next; } PID; void PID_Update(PID* pid, float setpoint, float actual) { pid->err = setpoint - actual; float increment = pid->Kp*(pid->err - pid->err_next) + pid->Ki*pid->err + pid->Kd*(pid->err - 2*pid->err_next + pid->err_last); PWM_SetDuty(increment); // 更新PWM输出 // 误差历史更新 pid->err_last = pid->err_next; pid->err_next = pid->err; } ``` *增量式PID实现代码[^2]*
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值