stm32平衡小车学习记录

目录

一。小车介绍

1.1小车硬件介绍

1.2pid理论分析

二。代码编写

2.1 定时器开启编码器功能(使用TIM2和TIM4)

2.2 TIM1开启PWM模式(使用TIM1)

2.3 电机初始化

2.4 mpu6050初始化

2.5PID控制代码

2.6 初始化蓝牙模块使用到的串口USATRT4

2.7 EXTI2_IRQHandler中断处理

 2.8 main函数代码

总结


一。小车介绍

1.1小车硬件介绍

该小车使用大鱼电子的整套硬件设备,其中包括主控stm32f103c8t6,蓝牙模块,mpu6050模块,TB6612FNG电机驱动芯片,两个JGA25-370减速电机,还有一些车子的配件。

硬件占用的 GPIO 的框架:

编码器1--PA0/PA1---TIM2

编码器2--PB6/PB7---TIM4

电机1--PB12/PB13

电机2--PB14/PB15

PWM1--PA8

PWM1--PA11

MPU6050中断引脚--PB5

MPU6050的IIC--PB3/PB4

1.2pid理论分析

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

各项作用:

比例项:提高响应速度,减小静差。

积分项: 消除稳态误差。

微分项:减小震荡以及超调。

速度环:Kp *Ek+Ki *Ek_S

直立环:PD控制器,Kp *Ek+Kd *Ek_D

转向环:Kd *Z轴角速度+系数 *遥控数据

二。代码编写

2.1 定时器开启编码器功能(使用TIM2和TIM4)

//TIM2编码器模式
void Encoder_TIM2_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
	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;
	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=65535;
	TIM_TimeBaseInitStruct.TIM_Prescaler=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
	
	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);
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//配置溢出更新中断标志位
	
	TIM_SetCounter(TIM2,0);//清零定时器计数值
	
	TIM_Cmd(TIM2,ENABLE);//开启定时器
}
//TIM4编码器模式
void Encoder_TIM4_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_ICInitTypeDef TIM_ICInitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6 |GPIO_Pin_7;
	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);
	
	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);
	
	TIM_ClearFlag(TIM4,TIM_FLAG_Update);
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);
	
	TIM_SetCounter(TIM4,0);

	TIM_Cmd(TIM4,ENABLE);
}
//读取编码器值
int Read_Speed(int TIMx)
{
	int value_1;
	switch(TIMx)
	{
		case 2:value_1=(short)TIM_GetCounter(TIM2);
				TIM_SetCounter(TIM2,0);
				break;
		//IF是定时器2,1.采集编码器的计数值并保存。2.将定时器的计数值清零。
		case 4:value_1=(short)TIM_GetCounter(TIM4);
				TIM_SetCounter(TIM4,0);
				break;
		default:value_1=0;
	}
	return value_1;
}

2.2 TIM1开启PWM模式(使用TIM1)

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_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;//ARR值
	TIM_TimeBaseInitStruct.TIM_Prescaler=Psc;//分频值
	TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct);
	
	TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;//初始化输出比较
	TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;//选择有效电平时为高电平
	TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OCInitStruct.TIM_Pulse=0;//CCR的值
	TIM_OC1Init(TIM1,&TIM_OCInitStruct);
	TIM_OC4Init(TIM1,&TIM_OCInitStruct);
	
	TIM_CtrlPWMOutputs(TIM1,ENABLE);//高级定时器专属--MOE主输出使能
	
	TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);//ENABLE//OC1预装载寄存器使能
	TIM_OC4PreloadConfig(TIM1,TIM_OCPreload_Enable);//OC4预装载寄存器使能
	TIM_ARRPreloadConfig(TIM1,ENABLE);//TIM1在ARR上预装载寄存器使能
	
	TIM_Cmd(TIM1,ENABLE);//开定时器。
}

2.3 电机初始化

//电机引脚初始化
void Motor_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启时钟
	
	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);	
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//开启时钟
	
}
//pwm限幅函数
void Limit(int *motoA,int *motoB)
{
	if(*motoA>PWM_MAX)*motoA=PWM_MAX;
	else if(*motoA<PWM_MIN)*motoA=PWM_MIN;
	
	if(*motoB>PWM_MAX)*motoB=PWM_MAX;
	else if(*motoB<PWM_MIN)*motoB=PWM_MIN;
}
//把带符号的数据转为绝对值的函数
int GFP_abs(int p)
{
	int q;
	q=p>0?p:(-p);
	return q;
}
//将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));
}
//电机停止函数,当电机实际角度偏离超过50度时,停止电机,防止小车摔倒
char PWM_Zero=0,stop=0;
void Stop(float *Med_Angel,float *Angle)
{
	if(GFP_abs(*Angle-*Med_Angel)>50)Load(PWM_Zero,PWM_Zero);
}

2.4 mpu6050初始化

mpu6050驱动是调用InvenSense官方的DMP运动驱动库,然后在借鉴正点原子的驱动代码把它移植到stm32上,然后直接使用就可以了。

平衡小车使用了MPU6050的中断引脚,由于MPU6050的采样率设置为100Hz,所以中断引脚的中断频率为10ms一次。

当进入中断后,对小车进行控制操作。

下面是中断引脚初始化:


void MPU6050_EXTI_Init(void)
{
	EXTI_InitTypeDef EXTI_InitStruct;
	GPIO_InitTypeDef GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD ,ENABLE);//开启时钟
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//中断来时,引脚电平被拉低
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_2;//PD2配置为上拉输入
	GPIO_Init(GPIOD,&GPIO_InitStruct);	
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOD,GPIO_PinSource2);
	
	EXTI_InitStruct.EXTI_Line=EXTI_Line2;
	EXTI_InitStruct.EXTI_LineCmd=ENABLE;
	EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿
	EXTI_Init(&EXTI_InitStruct);
    
     NVIC_InitTypeDef NVIC_InitStructure;									 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_3);  	
     NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;	  
     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	
     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
     NVIC_Init(&NVIC_InitStructure);
}

2.5PID控制代码

//转向环
int Turn(int gyro_Z,int RC)
{
	int PWM_out;
	//这不是一个严格的PD控制器,Kd针对的是转向的约束,但Kp针对的是遥控的转向。
	PWM_out=Turn_Kd*gyro_Z + Turn_Kp*RC;
	return PWM_out;
}
//直立环pd
int Vertical(float Med,float Angle,float gyro_Y)
{
	int PWM_out;
	PWM_out=Vertical_Kp*(Angle-Med)+Vertical_Kd*(gyro_Y-0);
	return PWM_out;
}
//速度环pi
int Velocity(int Target,int encoder_left,int encoder_right)
{
	static int Encoder_S,EnC_Err_Lowout_last,PWM_out,Encoder_Err,EnC_Err_Lowout;
	float a=0.7;
	//1.计算速度偏差
	Encoder_Err=((encoder_left+encoder_right)-Target);//舍去误差
	//2.对速度偏差进行低通滤波
	//low_out=(1-a)*Ek+a*low_out_last;
    //使得波形更加平滑,滤除高频干扰,防止速度突变。
	EnC_Err_Lowout=(1-a)*Encoder_Err+a*EnC_Err_Lowout_last;
	EnC_Err_Lowout_last=EnC_Err_Lowout;
	//3.对速度偏差积分,积分出位移
	Encoder_S+=EnC_Err_Lowout;
	//4.积分限幅
	Encoder_S=Encoder_S>10000?10000:(Encoder_S<(-10000)?(-10000):Encoder_S);
	//5.速度环控制输出计算
	PWM_out=Velocity_Kp*EnC_Err_Lowout+Velocity_Ki*Encoder_S;
	return PWM_out;
}

2.6 初始化蓝牙模块使用到的串口USATRT4

void USART4_Init(u32 bound)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4,ENABLE); 
	//UART4_TX   PC10
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	//UART4_RX	  PC11
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOC, &GPIO_InitStructure);  
	//USART 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//一般蓝牙模块是默认设置为9600;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(UART4, &USART_InitStructure);
	
	USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);//开启中断
	USART_Cmd(UART4, ENABLE);                    //使能串口 
}

/**************************************************************
0x00:刹车(中间)
0x01:前进
0x02: 左上角(其实右上角)
0x03:左转(对应是右转,这里电机方向不同 所以是左转)
0x04: 左下角(其实右下角,电机方向不同导致)
0x05:后退
0x06:右下角(其实是左下角)
0x07:右转(其实是左转)
0x08: 右上角(其实是左上角)
**************************************************************/
//串口4中断接收数据
void UART4_IRQHandler(void) 
{
	char  Bluetooth_data;
	if(USART_GetITStatus(UART4,USART_IT_RXNE)!=RESET)//接收中断标志位拉高
	{
		Bluetooth_data=USART_ReceiveData(UART4);//保存接收的数据
		if(Bluetooth_data==0x00)    	Fore=0,Back=0,Left=0,Right=0;//刹
		else if(Bluetooth_data==0x01)	Fore=1,Back=0,Left=0,Right=0;//前
		else if(Bluetooth_data==0x05)	Fore=0,Back=1,Left=0,Right=0;//后
		else if(Bluetooth_data==0x03)	Fore=0,Back=0,Left=1,Right=0;//左
		else if(Bluetooth_data==0x07)	Fore=0,Back=0,Left=0,Right=1;//右
		else if(Bluetooth_data==0x02)	Fore=1,Back=0,Left=1,Right=0;//左上角
		else if(Bluetooth_data==0x04)	Fore=0,Back=1,Left=1,Right=0;//左下角
		else if(Bluetooth_data==0x06)	Fore=0,Back=1,Left=0,Right=1;//右下角
		else if(Bluetooth_data==0x08)	Fore=1,Back=0,Left=0,Right=1;//右上角
	}
		
}

2.7 EXTI2_IRQHandler中断处理

//EXTI2_IRQHandler中断服务函数,对应MPU6050的中断输入引脚,10ms进入一次
void EXTI2_IRQHandler(void) 
{	
	int PWM_out;
	if(EXTI_GetITStatus(EXTI_Line2)!=0)
	{
		if(PDin(2)==0)
		{
			EXTI_ClearITPendingBit(EXTI_Line2);//清除中断标志位		
			//1、采集编码器数据&MPU6050角度信息。
            //电机是相对安装,刚好相差180度,为了编码器输出极性一致,就需要对其中一个取反。
			Encoder_Left=-Read_Speed(2);
			Encoder_Right=Read_Speed(4);
			//这三个函数是调用DMP运动驱动库的,移植后可以直接使用
			mpu_dmp_get_data(&Pitch,&Roll,&Yaw);			//角度
			MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);	//陀螺仪
			MPU_Get_Accelerometer(&aacx,&aacy,&aacz);	//加速度
            
			//前后移动
			if((Fore==0)&&(Back==0))Target_Speed=0;//未接受到前进后退指令->速度清零,稳在原地
			else if(Fore==1)Target_Speed--;//Target_Speed是目标速度,由于方向是反的,所以减才是前进
			else if(Back==1)Target_Speed++;//
			Target_Speed=Target_Speed>SPEED_Y?SPEED_Y:(Target_Speed<-SPEED_Y?(-SPEED_Y):Target_Speed);//限幅
            
			//左右移动
			if((Left==0)&&(Right==0))Turn_Speed=0;
			else if(Left==1)Turn_Speed+=30;	//左转
			else if(Right==1)Turn_Speed-=30;	//右转
			Turn_Speed=Turn_Speed>SPEED_Z?SPEED_Z:(Turn_Speed<-SPEED_Z?(-SPEED_Z):Turn_Speed);//限幅
			
			//转向约束
			if((Left==0)&&(Right==0))Turn_Kd=-0.8;//若无左右转向指令,则开启转向约束
			else if((Left==1)||(Right==1))Turn_Kd=0;//若左右转向指令接收到,则去掉转向约束
            
			//2、将数据压入闭环控制中,计算出控制输出量。
			Velocity_out=Velocity(Target_Speed,Encoder_Left,Encoder_Right);	//速度环
			Vertical_out=Vertical(Velocity_out+Med_Angle,Pitch,gyroy);//直立环
			Turn_out=Turn(gyroz,Turn_Speed);//转向环														
			PWM_out=Vertical_out;//最终输出
            
			//3、把控制输出量加载到电机上,完成最终的的控制。
			MOTO1=PWM_out-Turn_out;//左电机
			MOTO2=PWM_out+Turn_out;//右电机
			Limit(&MOTO1,&MOTO2);	 //PWM限幅			
			Load(MOTO1,MOTO2);		 //加载到电机上。
			
			Stop(&Med_Angle,&Pitch);//安全检测
		} 
	}
}

 2.8 main函数代码

int mian()
{
    int encoder4,encoder2;
	USART4_Init(); //串口4(蓝牙使用)
    Encoder_TIM4_Init();//编码器初始化
    Encoder_TIM2_Init();//
    MPU6050_EXTI_Init();//MPU6050中断引脚
    MPU_Init(); // MPU6050初始化
    mpu_dmp_init(); //MPU6050的DMP初始化
    PWM_Init_TIM1(0,7199);//pwm初始化
    Motor_Init();//电机初始化
    OLED_Init();//OLED初始化
    while(1)
    {
        encoder2=Read_Speed(2);
        encoder4=Read_Speed(4);
		sprintf((char*)oledtext,"enc2:%d",encoder2 );
		OLED_ShowString(1,1,(char*)oledtext);
        sprintf((char*)oledtext,"enc4:%d",encoder4 );
		OLED_ShowString(2,1,(char*)oledtext);
        OLED_ShowString(3,1,(char*)Bluetooth_data);
        
	}
}

总结

        PID调参需要根据硬件来选择参数,也需要一定的方法和耐心。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值