Control PID simple basado en el carro de cuatro ruedas stm32

Antes de leer, debe saber: el propio autor usa cuatro motores TT ordinarios más codificadores + PID incremental, que es adecuado para principiantes en PID, pero necesita tener cierta comprensión de PID y PID incremental, que no se detalla en este artículo. Introducir, principalmente la aplicación de código, no rociarlo.


* ¿Dónde cayeron las plumas en las nubes? Camina por las tres islas para encontrar el sonido *

Una, charla dura sobre PID

La EPI es muy común en la vida, por ejemplo. Por ejemplo, en la vida, un calentador necesita controlar la temperatura de un objeto, pero la temperatura es demasiado alta o demasiado baja por alguna razón. En este momento, el sensor devolverá los datos correspondientes y le indicará al controlador que debe realizar los ajustes correspondientes. , refrigeración o calefacción, esto completa un control de circuito cerrado PID simple. PID es la abreviatura de los tres coeficientes en la fórmula de corrección.
El control PID incremental marca la diferencia entre la cantidad de control actual y la cantidad de control anterior, y usa la diferencia como la nueva cantidad de control, que es un algoritmo recursivo.

Inserte la descripción de la imagen aquí

En segundo lugar, el equipo de hardware utilizado

1. stm32f103rct6
2. Un motor TT (motor amarillo pequeño) + codificador Hall
3. Dos módulos de accionamiento l2980
4. Cuatro baterías 18650 para fuente de alimentación

Tres, diseño de software

1. Cuatro motores utilizan puertos IO: PB8-PB9, PB10-PB11,
PB12-PB13, PB14-PB15.
2. PWM usa CH1-CH4 del temporizador avanzado TIM8, y los puertos IO usados: PC6 PC7 PC8 PC9.
3. Cuatro codificadores corresponden a cuatro temporizadores TIM2 TIM3 TIM4 TIM5. Para activar el modo codificador integrado, debe activar los correspondientes CH1 y CH2 al mismo tiempo. (Los pines correspondientes se pueden encontrar en la hoja de datos)
4. Utilice TIM1 para la temporización de interrupciones.
Nota: TIM2 debe reasignarse por completo, porque es igual que los pines CH1 y CH2 de TIM5 cuando no se reasigna. GPIO_PinRemapConfig (GPIO_Remap_SWJ_JTAGDisable, ENABLE); ¡No olvide deshabilitar esto!

Cuarto, el código clave

1. Temporizador TIM1:

void Timer_Init(void)
{
    
    

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
	
	TIM_TimeBaseStructure.TIM_Period = 9999;//自动重新装载寄存器周期的值澹ㄥ计数值澹)
	TIM_TimeBaseStructure.TIM_Prescaler = 719;//时钟分频系数
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//对外部时钟进行采样的时钟分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
	TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //高级定时器1是用定时器功能配置这个才可以是正常的计数频率一开始的72mhz 值得注意的地方
	TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure);//参数初始化

	TIM_ClearFlag(TIM1, TIM_FLAG_Update);
	TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE);

	TIM_Cmd(TIM1, ENABLE);//启动定时器 

}


2. Ejemplo de modo de codificador TIM2:

void Encoder_Init_TIM2(void)
{
    
    
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  
  TIM_ICInitTypeDef TIM_ICInitStructure;  
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器2的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); 
  
  GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);  
  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE); 
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;	//端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOA
 

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;	//端口配置
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOB, &GPIO_InitStructure);					      //根据设定参数初始化GPIOA
 

	
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 
  TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
  TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数  
  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
  TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
  TIM_ICStructInit(&TIM_ICInitStructure);
  TIM_ICInitStructure.TIM_ICFilter = 10;
  TIM_ICInit(TIM2, &TIM_ICInitStructure);
  TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
  TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  //Reset counter
  TIM_SetCounter(TIM2,0);
  TIM_Cmd(TIM2, ENABLE); 
}

3. Inicialización del motor:

void dj1_Init(void)
{
    
    
 
 GPIO_InitTypeDef  GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	 //使能PB端口时钟
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_11|GPIO_Pin_10|GPIO_Pin_9|GPIO_Pin_8;	
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB
	
}

4. Salida TIM8PWM:

void PWM_Init(u16 arr,u16 psc)

{
    
    		 		
GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);// 
     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC , ENABLE);  //使能GPIO外设时钟使能
                                                                             
//    GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);  //全映射   PC6-9

   //设置该引脚为复用输出功能,输出TIM8 CH1的PWM脉冲波形
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9; //TIM_CH1
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    
    TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频
    TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    
 
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
		
		
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset ; 

TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable; 

//TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;

    TIM_OC1Init(TIM8, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
    TIM_OC2Init(TIM8, &TIM_OCInitStructure);
    TIM_OC3Init(TIM8, &TIM_OCInitStructure);
	  TIM_OC4Init(TIM8, &TIM_OCInitStructure);
    TIM_CtrlPWMOutputs(TIM8,ENABLE);    //MOE 主输出使能    

    TIM_OC1PreloadConfig(TIM8, TIM_OCPreload_Enable);  //CH1预装载使能     
    TIM_OC2PreloadConfig(TIM8, TIM_OCPreload_Enable); 
		TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable); 
		TIM_OC4PreloadConfig(TIM8, TIM_OCPreload_Enable); 
    TIM_ARRPreloadConfig(TIM8, ENABLE); //使能TIMx在ARR上的预装载寄存器
    
    TIM_Cmd(TIM8, ENABLE);  //使能TIM8   
   
}
 

5.PID:

static double   Proportion=0.45;                               //比例常数 Proportional Const
static double   Integral=0.1;                                 //积分常数 Integral Const
static double   Derivative=0;                                 //b不采用微分

/********************增量式PID控制设计************************************/
//NowPoint当前输出值
//SetPoint设定值
int PID_Calc1(int NowPoint,int SetPoint) 
{
    
    
                             //微分常数 Derivative Const
	static int      LastError1;                                //Error[-1]
	static int      PrevError1;                                //Error[-2]
  int iError,Outpid;                                   //当前误差
	
  iError=SetPoint-NowPoint;                           //增量计算
  Outpid=(Proportion * iError)                   //E[k]项
              -(Integral * LastError1)      //E[k-1]项
              +(Derivative * PrevError1);   //E[k-2]项
              
  PrevError1=LastError1;                     //存储误差,用于下次计算
  LastError1=iError;
  return(Outpid);                                      //返回增量值
}

int PID_Calc2(int NowPoint,int SetPoint) 
{
    
    
                             //微分常数 Derivative Const
	static int      LastError2;                                //Error[-1]
	static int      PrevError2;                                //Error[-2]
  int iError,Outpid;                                   //当前误差
	
  iError=SetPoint-NowPoint;                           //增量计算
  Outpid=(Proportion * iError)                   //E[k]项
              -(Integral * LastError2)      //E[k-1]项
              +(Derivative * PrevError2);   //E[k-2]项
              
  PrevError2=LastError2;                     //存储误差,用于下次计算
  LastError2=iError;
  return(Outpid);                                      //返回增量值
}


int PID_Calc3(int NowPoint,int SetPoint) 
{
    
    
                             //微分常数 Derivative Const
	static int      LastError3;                                //Error[-1]
	static int      PrevError3;                                //Error[-2]
  int iError,Outpid;                                   //当前误差
	
  iError=SetPoint-NowPoint;                           //增量计算
  Outpid=(Proportion * iError)                   //E[k]项
              -(Integral * LastError3)      //E[k-1]项
              +(Derivative * PrevError3);   //E[k-2]项
              
  PrevError3=LastError3;                     //存储误差,用于下次计算
  LastError3=iError;
  return(Outpid);                                      //返回增量值
}


int PID_Calc4(int NowPoint,int SetPoint) 
{
    
    
                             //微分常数 Derivative Const
	static int      LastError4;                                //Error[-1]
	static int      PrevError4;                                //Error[-2]
  int iError,Outpid;                                   //当前误差
	
  iError=SetPoint-NowPoint;                           //增量计算
  Outpid=(Proportion * iError)                   //E[k]项
              -(Integral * LastError4)      //E[k-1]项
              +(Derivative * PrevError4);   //E[k-2]项
              
  PrevError4=LastError4;                     //存储误差,用于下次计算
  LastError4=iError;
  return(Outpid);                                      //返回增量值
}

6. Función de servicio de interrupción:

void dj1_Init(void)
{
    
    
 
int Encoder_Front_Left,Encoder_Front_Right,Encoder_Back_Right,Encoder_Back_Left;
int Left_t,Right_t,Encoder_R,Encoder_L;
int Moto_Front_Left,Moto_Front_Right,Moto_Back_Left,Moto_Back_Right; 
int para1,para2,para3,para4;//增量
int SetPoint1=30;//设置目标值单位RPM
int SetPoint2=30;
//使用减速比是1:120的减速箱
#define SetPoint_back  SetPoint1*6240/600//换算成编码器速度,因为最终pid控制的是编码器的脉冲数量
#define SetPoint_front SetPoint2*6240/600//换算成编码器速度,因为最终pid控制的是编码器的脉冲数量

//Time1定时器1中断服务函数
//200ms定时
void TIM1_UP_IRQHandler(void)
{
    
    
	if(TIM_GetFlagStatus(TIM1, TIM_IT_Update) != RESET)   //时间到了
	{
    
    
		TIM_ClearITPendingBit(TIM1, TIM_FLAG_Update);//清中断
			
		  Encoder_Front_Left=myabs(Read_Encoder(2));  //读取编码器 
		  Encoder_Front_Right=myabs(Read_Encoder(3));
		  Encoder_Back_Left=myabs(Read_Encoder(4));
		  Encoder_Back_Right=myabs(Read_Encoder(5));
			
			para1=PID_Calc1(Encoder_Front_Left,SetPoint_back);	//左电机,计数得到增量式PID的增量数值 
		  para2=PID_Calc2(Encoder_Front_Right,SetPoint_back);
		  para3=PID_Calc3(Encoder_Back_Left,SetPoint_front);
		  para4=PID_Calc4(Encoder_Back_Right,SetPoint_front);
		
		
		
		
			if((para1<-3)||(para1>3)) // 不做 PID 调整,避免误差较小时频繁调节引起震荡。
			{
    
    
				Moto_Front_Left +=para1;  
			}   
					if(Moto_Front_Left>3500) Moto_Front_Left=3500;//限幅
			    TIM8->CCR1=Moto_Front_Left;//更新pwm
			
			if((para2<-3)||(para2>3)) // 不做 PID 调整,避免误差较小时频繁调节引起震荡。
			{
    
    
				Moto_Front_Right +=para2;  
			}   
					if(Moto_Front_Right>3500) Moto_Front_Right=3500;//限幅
			    TIM8->CCR2=Moto_Front_Right;
			
	///		
			if((para3<-3)||(para3>3)) // 不做 PID 调整,避免误差较小时频繁调节引起震荡。
			{
    
    
				Moto_Back_Left +=para3;  
			}   
					if(Moto_Back_Left>3500) Moto_Back_Left=3500;//限幅
			    TIM8->CCR3=Moto_Back_Left;//更新pwm
			
			if((para4<-3)||(para4>3)) // 不做 PID 调整,避免误差较小时频繁调节引起震荡。
			{
    
    
				Moto_Back_Right +=para4;  
			}   
					if(Moto_Back_Right>3500) Moto_Back_Right=3500;//限幅
			    TIM8->CCR4=Moto_Back_Right;//更新pwm
			delay_ms(2);
			
	  	}
	
		
}

int myabs(int a)
{
    
     		   
	int temp;
	if(a<0)  
	  temp=-a;  
	else 
	  temp=a;
	return temp;
}

para resumir

Como soy un novato, el código puede parecer extenso, por lo que no escribiré sobre la configuración del cálculo de velocidad en este artículo. Puedes intentar comprenderlo junto con los artículos de otros grandes blogueros. Si necesitas el código, Puede dejar un mensaje en el área de comentarios + conexión triple con un clic (jejeje). Escribir no es fácil, ¡gracias por su apoyo y progresen juntos!

Supongo que te gusta

Origin blog.csdn.net/qq_51621579/article/details/115359755
Recomendado
Clasificación