传统平衡小车学习

博主教学视频

4601016f751b4ca487b3544a22e626a0.png

 10b059f452424d7c87e462fce156f505.jpeg

 

代码部分

第一部分

--------------------------------------------------------------------------------------------------------------------------

1、编码器函数

Encoder.c

GPIO初始化,定时器初始化,设置编码器模式,清除标志位,中断配置,开定时器

#include "encoder.h"
//初始化GPIO口:A0/A1为TIM2;B6/B7为TIM4
void Encoder_TIM2_Init(void)
{
	//1、GPIO口开启
	GPIO_InitTypeDef GPIO_InitStruct;//定义GPIO口初始化结构体
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//定义定时器初始化结构体
	TIM_ICInitTypeDef TIM_ICInitStruct;//定义结构体
	//2、开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIO开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//开启定时器时钟
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;//初始化GPIO--PA0、PA1,浮空输入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 |GPIO_Pin_1;//TIM2的口
	//这里不用速度因为编码器式读取它的速度数据并不是向外输出
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	//TIM_InternalClockConfig(TIM2);	
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);//初始化定时器##
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//定时器的时钟分割(不分)
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//定时器的计数模式(向上计数)
	TIM_TimeBaseInitStruct.TIM_Period=65535;//定时器的重装载值
	TIM_TimeBaseInitStruct.TIM_Prescaler=0;//定时器的分频系数(不分)
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);	
	//4、输入捕获
	TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//配置编码器模式,(定时器,模式三,上升沿,上升沿)
	TIM_ICStructInit(&TIM_ICInitStruct);//初始化输入捕获##
	TIM_ICInitStruct.TIM_ICFilter=10;//滤波器
	TIM_ICInit(TIM2,&TIM_ICInitStruct);
	//5、其他函数,清除本身定时器的标志位
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);//(计数,计数溢出之后标志位会拉高,要把这个标志位清除一下)
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//配置溢出更新中断标志位
	//因为编码器从一开始就要捕获通道的计数值,刚开始一定要为零,如果不是零基础就会错了,所以要清零定时器计数值
	TIM_SetCounter(TIM2,0);//清零定时器计数值(设置为0,即清零)
	//6、开启计时器
	TIM_Cmd(TIM2,ENABLE);//开启定时器
}
void Encoder_TIM4_Init(void)
{
	//1、GPIO口开启
	GPIO_InitTypeDef GPIO_InitStruct;//定义GPIO口初始化结构体
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;//定义定时器初始化结构体
	TIM_ICInitTypeDef TIM_ICInitStruct;//定义结构体
	//2、开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//GPIO开启时钟
	//3、配置编码器
	//开启定时器(tim.c)
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);//开启定时器时钟
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;//初始化GPIO--PA0、PA1,浮空输入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 |GPIO_Pin_7;//TIM2的口
	//这里不用速度因为编码器式读取它的速度数据并不是向外输出
	GPIO_Init(GPIOB,&GPIO_InitStruct);
	
	
	
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);//初始化定时器##
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//定时器的时钟分割(不分)
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//定时器的计数模式(向上计数)
	TIM_TimeBaseInitStruct.TIM_Period=65535;//定时器的重装载值
	TIM_TimeBaseInitStruct.TIM_Prescaler=0;//定时器的分频系数(不分)
	TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct);
	
	//4、输入捕获
	TIM_EncoderInterfaceConfig(TIM4,TIM_EncoderMode_TI12,TIM_ICPolarity_Rising,TIM_ICPolarity_Rising);//配置编码器模式,(定时器,模式三,上升沿,上升沿)
	TIM_ICStructInit(&TIM_ICInitStruct);//初始化输入捕获##
	TIM_ICInitStruct.TIM_ICFilter=10;//滤波器
	TIM_ICInit(TIM4,&TIM_ICInitStruct);

	//5、其他函数,清除本身定时器的标志位
	TIM_ClearFlag(TIM4,TIM_FLAG_Update);//(计数,计数溢出之后标志位会拉高,要把这个标志位清除一下)
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);//配置溢出更新中断标志位
	//因为编码器从一开始就要捕获通道的计数值,刚开始一定要为零,如果不是零基础就会错了,所以要清零定时器计数值
	TIM_SetCounter(TIM4,0);//清零定时器计数值(设置为0,即清零)
	//6、开启计时器
	TIM_Cmd(TIM4,ENABLE);//开启定时器
}
//读取速度
//编码器读取函数入口参数为定时器
//编码器读的实际上是编码电机用的位移量,而不是速度,现在写的是读取速度

int Read_Speed(int TIMx)//传定时器,并且执行操作:1.采集编码器的计数值并保存;2.将定时器的计数值清零
{
	int value_1;//把读取的计数器的值给一个变量让他保存后,定时器清零
	switch(TIMx)
	{
		case 2:{value_1 = (short)TIM_GetCounter(TIM2); TIM_SetCounter(TIM2,0);};break;//记得转类型,TIM_GetCounter()是短整型int short
		case 4:{value_1 = (short)TIM_GetCounter(TIM4); TIM_SetCounter(TIM4,0);};break;
		default:value_1=0;
	}
	return value_1;
}

//中断函数,中断之后要找一个地址给他寻过去(寻址)
void TIM2_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=0)
	{
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}
	
void TIM4_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM4,TIM_IT_Update)!=0)//标志位不等于0,说明他已经进入了中断,这时候把它清零
	{
		TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
	}
}

对于编码器函数 

d041282728964b5c835fd8ab8f47a26d.png

参数为(定时器,模式,极性,极性)

这里讲极性,但是点进去定义会发现成了上升沿或者下降沿:

9f9f48970c8a440ca032ba51becd3388.png

原因:在stm32f10x_tim.c,(1294行),“/*set the TI1 and the TI2 Polarities*/”,

99f21f98eec94341acb18a855d5f4fd7.png

去中文手册找查找CCER,找到通用计时器

1b0d7de52b0440799e541cc584c709b8.png

找到CC1,编码器为输入

第二个参数:

d085e1f5cd424b81a03b159510e53500.png

TI1:在T1的所有边沿计数

TI2:在T2的所有边沿计数

TI12:在T1和T2的所有边沿计数

f69c6c833d06459f81d5d1c568c6d40d.png

31fefdf72bd446e99ee012ec6a072761.jpeg

Encoder.h

#ifndef  _ENCODER_H
#define  _ENCODER_H
#include "sys.h" 
void TIM2_IRQHandler(void);
void TIM4_IRQHandler(void);
int Read_Speed(int TIMx);
void Encoder_TIM2_Init(void);
void Encoder_TIM4_Init(void);
#endif


----------------------------------------------------------------------------------------------------------------------------

2、PWM控制

PWM.c

GPIO初始化,定时器初始化,输出比较模式初始化,MOE主输出使能,通道预装载使能,使能定时器在ARR寄存器上的预装载寄存器

#include "pwm.h"
//定时器1-PA8\PA11
void PWM_Init_TIM1(u16 Psc,u16 Per)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_OCInitTypeDef TIM_OCInitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1 | RCC_APB2Periph_AFIO,ENABLE);//开启时钟
	//GPIO空初始化
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;//初始化GPIO--PA8、PA11为复用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_8 |GPIO_Pin_11;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	//初始化定时器
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);//初始化定时器。
	TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitStruct.TIM_Period=Per;//设置变量
	TIM_TimeBaseInitStruct.TIM_Prescaler=Psc;//设置参数
	TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct);//高级定时器1
	
	//输入捕获比较
	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;//初始化输出比较模式:PWM1模式
	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;//输出比较极限:计数超过CCR之后,状态为高
	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;//输出比较状态
	TIM_OCInitStruct.TIM_Pulse=0;//初始脉冲设置为0即可
	TIM_OC1Init(TIM1,&TIM_OCInitStruct);//1通道
	TIM_OC4Init(TIM1,&TIM_OCInitStruct);//4通道
	
	TIM_CtrlPWMOutputs(TIM1,ENABLE);//高级定时器专属函数--MOE主输出使能(必然要开,不然输出不了PWM波形)
	
	TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);//ENABLE//OC1预装载寄存器使能
	TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);//ENABLE//OC4预装载寄存器使能
	TIM_ARRPreloadConfig(TIM1,ENABLE);//TIM1在ARR上预装载寄存器使能
	
	TIM_Cmd(TIM1,ENABLE);//开定时器。
}


PWM.h

#ifndef  _PWM_H
#define  _PWM_H

#include "sys.h" 



void PWM_Init_TIM1(u16 Psc,u16 Per);
#endif

------------------------------------------------------------------------------------------------------------------------------

3、MPU6050

Exit.c

GPIO初始化、EXTI外部中断初始化

#include "exti.h"


void MPU6050_EXTI_Init(void)
{
	EXTI_InitTypeDef EXTI_InitStruct;//stm32f10x_exit.h
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);//开启时钟,因为用的是第二功能,所以要开复用时钟AFIO
	//MPU中断引脚-PB5
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//GPIO_Mode_AF_PP//推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;//PB5配置为上拉输入
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);	
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource5);//外部中断和GPIO映射
	
	EXTI_InitStruct.EXTI_Line=EXTI_Line5;//中断线;5(EXTI_Init->assert_param()中的IS_EXTI_LINE->#define EXTI_Line5)
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;//使能
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;//模式:中断触发
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling;//边沿触发:下降沿触发
	EXTI_Init(&EXTI_InitStruct);
}



Exit.h

#ifndef  _EXTI_H
#define  _EXTI_H

#include "sys.h" 

void MPU6050_EXTI_Init(void);
#endif


4、motor控制

Motor.c

#include "motor.h"

//电机初始化函数
void Motor_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
	//电机1:PB12\PB13   电机2:PB14/PB15
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP;//初始化GPIO--PB12、PB13、PB14、PB15为推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_12 |GPIO_Pin_13 |GPIO_Pin_14 |GPIO_Pin_15;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);	
}
//电机是10kHz,如果分频是1,重装载值是7199,+1=7200,stm32f1就是70兆/7=100us,而100us对应的刚好是10kHZ,刚刚好对应电机频率
//
//int PWM_MAX=7200,PWM_MIN= -7200;//放主函数了main
//限幅函数
void Limit(int *motoA,int *motoB)
{
	if(*motoA>PWM_MAX)*motoA=PWM_MAX;//限制在最小和最大区间
	if(*motoA<PWM_MIN)*motoA=PWM_MIN;
	
	if(*motoB>PWM_MAX)*motoB=PWM_MAX;
	if(*motoB<PWM_MIN)*motoB=PWM_MIN;
}

//绝对值函数(返回正值)
int GFP_abs(int p)
{
	int q;
	q=p>0?p:(-p);//>0返回p,<0返回-p
	return q;
}

//赋值函数
//将PWM写进电机
//入口参数:PID运算完成后的最终PWM值
void Load(int moto1,int moto2)//假设moto1=-200:负号表示反转200个脉冲
{
	//1.研究正负号,对应正反转(极性,给逻辑电平)
	if(moto1>0)	
		Ain1=1,Ain2=0;//左电机正转
	else 		
		Ain1=0,Ain2=1;//左电机反转
	//2.研究PWM值(不需要符号,所以要设置一个绝对值函数来取正)
	TIM_SetCompare1(TIM1,GFP_abs(moto1));
	
	//右电机同理
	if(moto2>0)	
		Bin1=1,Bin2=0;
	else 		
		Bin1=0,Bin2=1;	
	TIM_SetCompare4(TIM1,GFP_abs(moto2));
}

//后期
char PWM_Zero=0,stop=0;
void Stop(float *Med_Jiaodu,float *Jiaodu)
{
	if(GFP_abs(*Jiaodu-*Med_Jiaodu)>60)
	{
		Load(PWM_Zero,PWM_Zero);
		stop=1;
	}
}

Motor.h

#ifndef  _MOTOR_H
#define  _MOTOR_H

#include "sys.h" 

//宏定义几个位带函数
#define Ain1  PBout(14)
#define Ain2  PBout(15)

#define Bin1  PBout(13)/*【4】*/
#define Bin2  PBout(12)



void Stop(float *Med_Jiaodu,float *Jiaodu);
void Motor_Init(void);/*【5】*/
void Limit(int *motoA,int *motoB);
int GFP_abs(int p);
void Load(int moto1,int moto2);
#endif

------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------------------------------------

第二部分

(链接可能会失效,在我的主页有这篇)

PID原理

(之前在知乎看到的一篇文章做了笔记)

工程中P必存在,在P的基础上又有PD、PI、PID控制器。

1、三项作用:

比例项P:提高响应速度,减少稳态误差(Kp太小了,Kp太大时会出现震荡) 

积分项I:消除稳态误差

微分项D:减少震荡以及超调

7705bf9accb5499cbf2348a091606dd9.png

稳态误差(Kp太小了,距离目标值有一定的距离)

39c2d22fc7f14dee8023551a34bea333.jpeg

震荡(Kp太大了,在目标值震荡)

b8bcc6880fcf4084b0279bfa06e5ac6e.jpeg02266d31d41841868f545648e471de8a.jpeg

2、各项作用解释:

比例项:提高响应速度->上式可见,I和D原本的式子就含有Kp,所以比例项稍微提高一点,这三项都会有一个提升,输出就会比较大的变化,大幅增大,提高了响应速度。稳态误差就是Kp太小导致的,增大Kp即可减少。

积分项:只要有偏差,就会积分,直至偏差变为0。消除稳态误差

微分项:减少震荡和超调,可以理解为"刹车"(单摆中的阻尼),给式子加了一个负数,负数越大,减少的震荡越明显。

3、PID函数(位置式PID)

位置闭环控制就是根据编码器的脉冲累加测量电机的位置信息,并与目标值比较,得到控制偏差,然后通过对偏差的比例、积分、微分进行控制,使偏差趋向于零的过程。 

d3bd3c19084b4cb9b74ff51a0ccbb452.png

e(k):本次偏差--------------------------------------

eq?%5Csume(n):e(k)以及以前的偏差的累积和,其中k=1,2,......,k------------------------

e(k-1):上一次误差---------------------------------

(PWM相当于u(k),代表输出) ------------Pwm

Bias偏差,Pwm输出,Integral_bias偏差积分,Last_Bias上一次偏差

​
int Position_PID(int Encoder,int Target)
{
    static float Bias,Pwm,Integral_bias,Last_Bias;
    Bias=Encoder-Target; //计算偏差
    Integral_bias+=Bias;//求出偏差的积分(和)
    //位置式PID控制器
    Pwm=Position_KP*Bias + Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias);
    Last_Bias=Bias;//保存上一次偏差
    return Pwm;//输出
}

​

串级控制系统(两个PID串起来)

 b8976b2bdea34e5184423d686ef8c2fc.png

1、直立环

(1)输入:1、给定角度(速度环输出),2、角度反馈

(2)输出:PWM(直接控制小车)

//直立PD控制器输出=Kp1*(真实角度-期望角度)+Kd*角度偏差的微分

//其中角度偏差=真实角度-期待角度

Vertical_out=Kp1*(real_Angle-expect_Angle)+Kd*D(real_Angle-expect_Angle)

2、速度环

(1)输入:1、给定速度,2、速度反馈

(2)输出:角度值(直立环的期望角度输入)

//速度PI控制器输出=Kp2*(反馈编码器值-期望编码器值)+Ki*编码器偏差的积分

//其中编码器偏差=反馈编码器值-期望编码器值

Velocity_out=Kp2*(Encoder_real-Encoder_expect)+Ki*S(Encoder_real-Encoder_expert) 

(备注:速度环输出=直立环的期待角度、Kp1:直立环Kp、Kp2:速度环Kp)

aecb13925629449db87972a1e6e22af7.png

显而易见,红色两项就为直立环式子,只不过角度偏差改成了真实角度,蓝色两项就为速度环式子,只不过多乘了Kp1。并不是真正的直立环和速度环。

编程就按照该式子编写即可。 

0591973806af48e79339c29a452c5968.png

直立环PD(内环)

目的让小车角度趋向于0

小车往哪边倒,车轮就往哪边开,保持车平衡(手持棒)

P是必有的,这里用PD比例微分,不用到积分I

大小与角度成正比、方向与角速度成正比的回复力--------P

大小与角速度成正比、方向与回复力成反比的阻尼力-----D

比例微分控制:PD(out)=Kp*Angle+Kd*(Angle-Angle_last)

a=b1*eq?%5Ctheta+b2*eq?%5Ctheta

d41ad3f682554b6788a5d1142ab05b37.jpege3d98c3a5302414286b5da5d400ca676.png

速度环PI(外环)

目的:让电机速度趋近于0

转向环(无,用了z轴的角速度的系数做了转向环的输出)

平衡小车的控制理论和控制过程的编程

第三部分

PID的调参

  • 23
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值