STM32两轮平衡小车原理详解(开源)

一、引言

关于STM32两轮平衡车的设计,我想在读者阅读本文之前应该已经有所了解,所以本文的重点是代码的分享和分析。至于具体的原理,我觉得读者不必阅读长篇大论的文章,只需按照本文分享的代码自己亲手制作一辆平衡车,其原理并不言而喻了。源完整代码工程在文章末尾百度网盘链接,请需要的读者自行下载即可。

另外,由于平衡车的精髓在于PID算法的运用,有需要了解PID算法的读者可以参考以下两篇文章:

PID算法详解(代码详解篇),位置式PID、增量式PID(通用)_pid 代码-CSDN博客

PID算法详解(精华知识汇总)_小小_扫地僧的博客-CSDN博客

二、所需材料

1、STM32F03C8T6

2、MPU6050

3、蓝牙模块

4、编码电机

5、TB6612

6、电源+稳压模块

7、OLED显示模块

三、接线强调

1、TB6612接线

2、蓝牙模块与单片机之间

单片机                蓝牙模块

 TX      ——>     RX  

 RX      ——>     TX  

3、MPU6050 

使用IIC通信,所以对照代码接SDA、SCL、GND、VCC、IN(中断触发线)

四、功能介绍

1、两轮平衡直立

2、蓝牙APP控制运动状态

3、遥控手柄控制

4、超声波避障

五、关键算法

PID算法对编码电机的控制

1.位置闭环控制

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

1.1理论分析

1.2控制原理图 

1.3C语言实现 

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

入口参数为编码器的位置测量值和位置控制的目标值,返回值为电机控制PWM(现在再看一下上面的控制原理图是不是更加容易明白了)。
第一行是相关内部变量的定义。
第二行是求出速度偏差,由测量值减去目标值。第三行通过累加求出偏差的积分。
第四行使用位置式PID控制器求出电机 PWM。第五行保存上一次偏差,便于下次调用。最后一行是返回。
然后,在定时中断服务函数里面调用该函数实现我们的控制目标:Moto=Position_PID(Encoder, Target_Position);
Set_Pwm(Moto) ;//===赋值给PWM寄存器

2、速度闭环控制

速度闭环控制就是根据单位时间获取的脉冲数(这里使用了M法测速)测量电机的速度信息,并与目标值进行比较,得到控制偏差,然后通过对偏差的比例、积分、微分进行控制,使偏差趋向于零的过程。
一些PID的要点在位置控制中已经有讲解,这里不再赘叙。
需要说明的是,这里速度控制20ms一次,一般建议10ms或者5ms,因为在这里电机是使用USB供电,速度比较慢,20ms可以延长获取速度的单位时间,提高编码器的采值。

 2.1理论分析

根据增量式离散PID公式 根据增量式离散PID公式
Pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
e(k):本次偏差
e(k-1):上一次的偏差e (k-2):上上次的偏差
Pwm 代表增量输出

在我们的速度控制闭环系统里面只使用PI控制,因此对PID控制器可简化为以下公式:
Pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)

2.2 控制原理图

2.3 C语言实现

增量式PI控制器具体通过C语言实现的代码如下:
 

int Incremental_PI (int Encoder,int Target)
{static float Bias, Pwm, Last_bias;Bias=Encoder-Target;//计算偏差Pwm+=Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias;//增量式PI控制器Last_bias=Bias;//保存上一次偏差return Pwm;//增量输出
}

入口参数为编码器的速度测量值和速度控制的目标值,返回值为电机控制PWM。
第一行是相关内部变量的定义。
第二行是求出速度偏差,由测量值减去目标值。第三行使用增量PI控制器求出电机PWM。
第四行保存上一次偏差,便于下次调用。最后一行是返回。
然后,在定时中断服务函数里面调用该函数实现我们的控制目标:

Moto=Incremental_PI(Encoder, Target_Velocity);Set_Pwm(Moto);//===赋值给对应MCU的PWM寄存器

六、关键代码分析

1、编码电机PID算法控制

#include "control.h"
#include "usart2.h"/**************************************************************************
函数功能:所有的控制代码都在这里面5ms定时中断由MPU6050的INT引脚触发严格保证采样和数据处理的时间同步	在MPU6050的采样频率设置中,设置成100HZ,即可保证6050的数据是10ms更新一次。读者可在imv_mpu.h文件第26行的宏定义进行修改(#define DEFAULT_MPU_HZ  (100))
**************************************************************************/
#define SPEED_Y 100 //俯仰(前后)最大设定速度
#define SPEED_Z 80//偏航(左右)最大设定速度 int Balance_Pwm,Velocity_Pwm,Turn_Pwm,Turn_Kp;float Mechanical_angle=8; 
float Target_Speed=0;	//期望速度(俯仰)。用于控制小车前进后退及其速度。
float Turn_Speed=0;		//期望速度(偏航)//针对不同车型参数,在sys.h内设置define的电机类型
float balance_UP_KP=BLC_KP; 	 // 小车直立环PD参数
float balance_UP_KD=BLC_KD;float velocity_KP=SPD_KP;     // 小车速度环PI参数
float velocity_KI=SPD_KI;float Turn_Kd=TURN_KD;//转向环KP、KD
float Turn_KP=TURN_KP;void EXTI9_5_IRQHandler(void) 
{static u8 Voltage_Counter=0;if(PBin(5)==0){EXTI->PR=1<<5;                                          //清除LINE5上的中断标志位   mpu_dmp_get_data(&pitch,&roll,&yaw);		            //得到欧拉角(姿态角)的数据MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);				//得到陀螺仪数据Encoder_Left=Read_Encoder(2);                           //读取编码器的值,保证输出极性一致Encoder_Right=-Read_Encoder(3);                         //读取编码器的值Led_Flash(100);Voltage_Counter++;if(Voltage_Counter==20)                                 //100ms读取一次电压{Voltage_Counter=0;Voltage=Get_battery_volt();		                    //读取电池电压}if(KEY_Press(100))										//长按按键切换模式并触发模式切换初始化{if(++CTRL_MODE>=101) CTRL_MODE=97;Mode_Change=1;}Get_RC();Target_Speed=Target_Speed>SPEED_Y?SPEED_Y:(Target_Speed<-SPEED_Y?(-SPEED_Y):Target_Speed);//限幅Turn_Speed=Turn_Speed>SPEED_Z?SPEED_Z:(Turn_Speed<-SPEED_Z?(-SPEED_Z):Turn_Speed);//限幅( (20*100) * 100)Balance_Pwm =balance_UP(pitch,Mechanical_angle,gyroy);   							//===直立环PID控制	Velocity_Pwm=velocity(Encoder_Left,Encoder_Right,Target_Speed);       //===速度环PID控制	 Turn_Pwm =Turn_UP(gyroz,Turn_Speed);        						  //===转向环PID控制Moto1=Balance_Pwm-Velocity_Pwm+Turn_Pwm;                              //===计算左轮电机最终PWMMoto2=Balance_Pwm-Velocity_Pwm-Turn_Pwm;                              //===计算右轮电机最终PWMXianfu_Pwm();  														  //===PWM限幅Turn_Off(pitch,12);													  //===检查角度以及电压是否正常Set_Pwm(Moto1,Moto2);                                                 //===赋值给PWM寄存器  }
}/**************************************************************************
函数功能:直立PD控制
入口参数:角度、机械平衡角度(机械中值)、角速度
返回  值:直立控制PWM
**************************************************************************/
int balance_UP(float Angle,float Mechanical_balance,float Gyro)
{  float Bias;int balance;Bias=Angle-Mechanical_balance;    							 //===求出平衡的角度中值和机械相关balance=balance_UP_KP*Bias+balance_UP_KD*Gyro;              //===计算平衡控制的电机PWM  PD控制   kp是P系数 kd是D系数 return balance;
}/**************************************************************************
函数功能:速度PI控制
入口参数:电机编码器的值
返回  值:速度控制PWM
**************************************************************************/
int velocity(int encoder_left,int encoder_right,int Target_Speed)
{  static float Velocity,Encoder_Least,Encoder;static float Encoder_Integral;//=============速度PI控制器=======================//	Encoder_Least =(Encoder_Left+Encoder_Right);//-target;              //===获取最新速度偏差==测量速度(左右编码器之和)-目标速度 Encoder *= 0.8;		                                                //===一阶低通滤波器       Encoder += Encoder_Least*0.2;	                                    //===一阶低通滤波器    Encoder_Integral +=Encoder;                                         //===积分出位移 积分时间:10msEncoder_Integral=Encoder_Integral - Target_Speed;                   //===接收遥控器数据,控制前进后退if(Encoder_Integral>10000)  	Encoder_Integral=10000;             //===积分限幅if(Encoder_Integral<-10000)		Encoder_Integral=-10000;            //===积分限幅	Velocity=Encoder*velocity_KP+Encoder_Integral*velocity_KI;          //===速度控制	if(pitch<-40||pitch>40) 			Encoder_Integral=0;     			//===电机关闭后清除积分return Velocity;
}
/**************************************************************************
函数功能:转向PD控制
入口参数:电机编码器的值、Z轴角速度
返回  值:转向控制PWM
**************************************************************************/int Turn_UP(int gyro_Z, int RC)
{int PWM_out;/*转向约束*/if(RC==0)Turn_Kd=TURN_KD;                                              //若无左右转向指令,则开启转向约束else Turn_Kd=0;                                                    //若左右转向指令接收到,则去掉转向约束PWM_out=Turn_Kd*gyro_Z + Turn_KP*RC;return PWM_out;
}void Tracking()
{TkSensor=0;TkSensor+=(C1<<3);TkSensor+=(C2<<2);TkSensor+=(C3<<1);TkSensor+=C4;
}
void Get_RC()
{static u8 SR04_Counter =0;static float RATE_VEL = 1;float RATE_TURN = 1.6;float LY,RX;      //PS2手柄控制变量int Yuzhi=2;  		//PS2控制防抖阈值switch(CTRL_MODE){case 97:SR04_Counter++;if(SR04_Counter>=20)									         //100ms读取一次超声波的数据{SR04_Counter=0;SR04_StartMeasure();												 //读取超声波的值}if(SR04_Distance<=30)				{Target_Speed=0,Turn_Speed=40;}else{Target_Speed=30,Turn_Speed=0;}break;case 98://蓝牙模式if((Fore==0)&&(Back==0))Target_Speed=0;//未接受到前进后退指令-->速度清零,稳在原地if(Fore==1)Target_Speed--;//前进1标志位拉高-->需要前进if(Back==1)Target_Speed++;///*左右*/if((Left==0)&&(Right==0))Turn_Speed=0;if(Left==1)Turn_Speed-=30;	//左转if(Right==1)Turn_Speed+=30;	//右转break;case 99://循迹模式Tracking();switch(TkSensor){case 15:Target_Speed=0;Turn_Speed=0;break;case 9:Target_Speed--;Turn_Speed=0;break;case 2://向右转Target_Speed--;Turn_Speed=15;break;case 4://向左转Target_Speed--;Turn_Speed=-15;break;case 8:Target_Speed=-10;Turn_Speed=-80;break;case 1:Target_Speed=-10;Turn_Speed=80;break;}break;case 100://PS2手柄遥控if(PS2_Plugin){LY=PS2_LY-128; //获取偏差RX=PS2_RX-128; //获取偏差if(LY>-Yuzhi&&LY<Yuzhi)LY=0; //设置小角度的死区if(RX>-Yuzhi&&RX<Yuzhi)RX=0; //设置小角度的死区if(Target_Speed>-LY/RATE_VEL) Target_Speed--;else if(Target_Speed<-LY/RATE_VEL) Target_Speed++;Turn_Speed=RX/RATE_TURN;}else{Target_Speed=0,Turn_Speed=0;}break;}
}

 2、编码电机编码值采集

#include "encoder.h"/**************************************************************************
函数功能:把TIM2初始化为编码器接口模式
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM2(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  TIM_ICInitTypeDef TIM_ICInitStructure;  GPIO_InitTypeDef GPIO_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器4的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PB端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;	//端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOBTIM_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);//使用编码器模式3TIM_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 counterTIM_SetCounter(TIM2,0);TIM_Cmd(TIM2, ENABLE); 
}
/**************************************************************************
函数功能:把TIM3初始化为编码器接口模式
入口参数:无
返回  值:无
**************************************************************************/
void Encoder_Init_TIM3(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  TIM_ICInitTypeDef TIM_ICInitStructure;  GPIO_InitTypeDef GPIO_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器4的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PB端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;	//端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);					      //根据设定参数初始化GPIOBTIM_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(TIM3, &TIM_TimeBaseStructure); TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3TIM_ICStructInit(&TIM_ICInitStructure);TIM_ICInitStructure.TIM_ICFilter = 10;TIM_ICInit(TIM3, &TIM_ICInitStructure);TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//Reset counterTIM_SetCounter(TIM3,0);TIM_Cmd(TIM3, ENABLE); 
}/**************************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回  值:速度值
**************************************************************************/
int Read_Encoder(u8 TIMX)
{int Encoder_TIM;    switch(TIMX){case 2:  Encoder_TIM= (short)TIM2 -> CNT; TIM2 -> CNT=0;break;case 3:  Encoder_TIM= (short)TIM3 -> CNT;  TIM3 -> CNT=0;break;	default: Encoder_TIM=0;}return Encoder_TIM;
}

3、PWM配置

#include "pwm.h"//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
//TIM1_PWM_Init(7199,0);//PWM频率=72000/(7199+1)=10Khzvoid TIM1_PWM_Init(u16 arr,u16 psc)
{  GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;TIM_OCInitTypeDef  TIM_OCInitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);  //使能GPIO外设时钟使能//设置该引脚为复用输出功能,输出TIM1 CH1 CH4的PWM脉冲波形GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM_CH1 //TIM_CH4GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  不分频TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能TIM_OCInitStructure.TIM_Pulse = 0;                            //设置待装入捕获比较寄存器的脉冲值TIM_OCInitStructure.TIM_Pulse = arr >> 1;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;     //输出极性:TIM输出比较极性高TIM_OC1Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMxTIM_OC4Init(TIM1, &TIM_OCInitStructure);  //根据TIM_OCInitStruct中指定的参数初始化外设TIMxTIM_CtrlPWMOutputs(TIM1,ENABLE);	//MOE 主输出使能	TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH4预装载使能	 TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器TIM_Cmd(TIM1, ENABLE);  //使能TIM1
}

4、蓝牙控制

#include "usart2.h"/**************************************************************************
函数功能:串口2初始化
入口参数: bound:波特率
返回  值:无
**************************************************************************/
void uart2_init(u32 bound)
{  	 //GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//使能UGPIOB时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);	//使能USART2时钟//USART2_TX  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//USART2_RX	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式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(USART2, &USART_InitStructure);     //初始化串口2USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART2, ENABLE);                    //使能串口2 
}/**************************************************************************
函数功能:串口2接收中断
入口参数:无
返回  值:无
**************************************************************************/
u8 Fore,Back,Left,Right;
void USART2_IRQHandler(void)
{int Uart_Receive;if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)//接收中断标志位拉高{Uart_Receive=USART_ReceiveData(USART2);//保存接收的数据BluetoothCMD(Uart_Receive);								}
}void BluetoothCMD(int Uart_Receive)
{switch(Uart_Receive){case 90://停止Fore=0,Back=0,Left=0,Right=0;break;case 65://前进Fore=1,Back=0,Left=0,Right=0;break;case 72://左前Fore=1,Back=0,Left=1,Right=0;break;case 66://右前Fore=1,Back=0,Left=0,Right=1;break;case 71://左转Fore=0,Back=0,Left=1,Right=0;break;case 67://右转Fore=0,Back=0,Left=0,Right=1;break;case 69://后退Fore=0,Back=1,Left=0,Right=0;break;case 70://左后,向右旋Fore=0,Back=1,Left=0,Right=1;break;case 68://右后,向左旋Fore=0,Back=1,Left=1,Right=0;break;default://停止Fore=0,Back=0,Left=0,Right=0;break;}
}void Uart2SendByte(char byte)   //串口发送一个字节
{USART_SendData(USART2, byte);        //通过库函数  发送数据while( USART_GetFlagStatus(USART2,USART_FLAG_TC)!= SET);  //等待发送完成。   检测 USART_FLAG_TC 是否置1;    //见库函数 P359 介绍
}void Uart2SendBuf(char *buf, u16 len)
{u16 i;for(i=0; i<len; i++)Uart2SendByte(*buf++);
}
void Uart2SendStr(char *str)
{u16 i,len;len = strlen(str);for(i=0; i<len; i++)Uart2SendByte(*str++);
}

5、中断处理函数

void EXTI9_5_IRQHandler(void) 
{static u8 Voltage_Counter=0;if(PBin(5)==0){EXTI->PR=1<<5;                                          //清除LINE5上的中断标志位   mpu_dmp_get_data(&pitch,&roll,&yaw);		            //得到欧拉角(姿态角)的数据MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);				//得到陀螺仪数据Encoder_Left=Read_Encoder(2);                           //读取编码器的值,保证输出极性一致Encoder_Right=-Read_Encoder(3);                         //读取编码器的值Led_Flash(100);Voltage_Counter++;if(Voltage_Counter==20)                                 //100ms读取一次电压{Voltage_Counter=0;Voltage=Get_battery_volt();		                    //读取电池电压}if(KEY_Press(100))										//长按按键切换模式并触发模式切换初始化{if(++CTRL_MODE>=101) CTRL_MODE=97;Mode_Change=1;}Get_RC();Target_Speed=Target_Speed>SPEED_Y?SPEED_Y:(Target_Speed<-SPEED_Y?(-SPEED_Y):Target_Speed);//限幅Turn_Speed=Turn_Speed>SPEED_Z?SPEED_Z:(Turn_Speed<-SPEED_Z?(-SPEED_Z):Turn_Speed);//限幅( (20*100) * 100)Balance_Pwm =balance_UP(pitch,Mechanical_angle,gyroy);   							//===直立环PID控制	Velocity_Pwm=velocity(Encoder_Left,Encoder_Right,Target_Speed);       //===速度环PID控制	 Turn_Pwm =Turn_UP(gyroz,Turn_Speed);        						  //===转向环PID控制Moto1=Balance_Pwm-Velocity_Pwm+Turn_Pwm;                              //===计算左轮电机最终PWMMoto2=Balance_Pwm-Velocity_Pwm-Turn_Pwm;                              //===计算右轮电机最终PWMXianfu_Pwm();  														  //===PWM限幅Turn_Off(pitch,12);													  //===检查角度以及电压是否正常Set_Pwm(Moto1,Moto2);                                                 //===赋值给PWM寄存器  }
}

七、PCB板设计

八、代码开源

1、寄存器版本

链接:https://pan.baidu.com/s/1NlMHsgMF2Cu8sz955n27Eg?pwd=zxf1 
提取码:zxf1 
--来自百度网盘超级会员V2的分享

2、HAL库版本

链接:https://pan.baidu.com/s/1rW5M7Dz-TK4IWJxNp57mBw?pwd=zxf1 
提取码:zxf1 
--来自百度网盘超级会员V2的分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/189178.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【使用教程】在Ubuntu下PMM60系列一体化伺服电机通过PDO跑循环同步位置模式详解

本教程将指导您在Ubuntu操作系统下使用PDO来配置和控制PMM60系列一体化伺服电机以实现循环同步位置模式。我们将介绍必要的步骤和命令&#xff0c;以确保您能够成功地配置和控制PMM系列一体化伺服电机。 一、准备工作 在正式介绍之前还需要一些准备工作&#xff1a;1.装有lin…

基本数据类型小题两道

根据公式计算A地区教师任教年薪&#xff0c;统计键盘输入的字符串中数字个数&#xff0c;按字典序输出。 (笔记模板由python脚本于2023年11月10日 18:05:18创建&#xff0c;本篇笔记适合熟悉python列表、元、字符串等基本数据类型的coder翻阅) 【学习的细节是欢悦的历程】 Pyth…

安全框架SpringSecurity-1(认证入门数据库授权)

一、Spring Security ①&#xff1a;什么是Spring Security Spring Security是一个能够为基于Spring的企业应用系统提供声明式&#xff08;注解&#xff09;的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean&#xff0c;充分利用了Spring …

ArcGIS:如何迭代Shp文件所有要素并分别导出为Shp文件?

01 前言 尝试用IDL实现&#xff0c;奈何又涉及新的类IDLffShape&#xff0c;觉得实在没有必要学习的必要&#xff0c;毕竟不是搞开发&#xff0c;只是做做数据处理&#xff0c;没必要拿IDL不擅长的且底层的东西自己造轮子。 这里想到使用Python去解决&#xff0c;gdal太久没用…

在gitlab中的使用kaniko打造流水线

文章目录 kaniko工具介绍环境说明系统版本组件版本组件部署参考链接 部署harbor下载解压、创建相关目录配置部署 gitlab集成harbor集成项目ci配置最终结果 kaniko工具介绍 kaniko 是一种从容器或 Kubernetes 集群内的 Dockerfile 构建容器镜像的工具。 kaniko 解决了使用 Doc…

国际阿里云:无法访问ECS实例中的服务的排查方法!!!

操作场景 无法访问ECS实例中的服务可能有以下原因&#xff1a; 可能原因 排查方案 ECS实例的安全组未开放相应端口 检查ECS实例安全组规则 ECS实例中&#xff0c;该服务未启动/开启或服务对应端口未被监听 检查服务状态及端口监听状态 ECS实例内防火墙设置错误 检查ECS…

ChatGPT付费创作系统V2.4.9独立版 +WEB端+ H5端 + 小程序端系统测试安装教程

播资源提供的GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序&#xff0c;是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT&#xff0c;流量超级大&#xff0c;引流不要太简单&#xff01;一键下单即可拥有自己的GPT&#xff01;…

linux入门---信号量

目录标题 为什么会有信号量信号量的概念信号量的接口和操作什么是环形队列单消费者生产者环形队列的模拟实现准备工作构造函数和析构函数push函数的模拟实现pop函数的模拟实现RingQueue完整代码测试代码 多生产多消费的环形队列 为什么会有信号量 在上一篇文章当中我们写过这么…

如何让VirtualBox系统使用Ubuntu主机的USB

如何让VirtualBox系统使用Ubuntu主机的USB 当通过 VirtualBox 尝试不同的操作系统时&#xff0c;访问虚拟机中的 USB 驱动器来传输数据非常有用。 安装Guest Additions 自行百度安装Guest Additions的方法&#xff0c;最终的效果如下&#xff1a; 将用户添加到 vboxusers 组…

Radius是什么意思? 安当加密

Radius是什么意思&#xff1f; RADIUS&#xff08;Remote Authentication Dial In User Service&#xff09;是一种远程用户拨号认证系统&#xff0c;它由RFC 2865和RFC 2866定义&#xff0c;是应用最广泛的AAA&#xff08;Authentication、Authorization、Accounting&#xf…

Unity 跑酷游戏全部脚本(完结)

脚本1 触发器脚本 这个脚本是主角身上的脚本&#xff0c;用于检测是否碰到其他触发器&#xff0c;并做出对应的行为 using System.Collections; using System.Collections.Generic; using UnityEngine; public class ColliidisonTrigger : MonoBehaviour { //触发检测 …

linux之IPC

linux之IPC 什么是IPC共享内存(shm)ftokshmgetshmatshmdtshmctl 消息队列msggetmsgrcvmsgsndmsgctl 旗语(信号量)semgetsemctlsemopsem三级标题三级标题 ipc命令守护进程查看守护进程 什么是IPC IPC: Inter(内核) Process(进程) Communicton&#xff08;通信&#xff09; 共享内…

挑战100天 AI In LeetCode Day08(热题+面试经典150题)

挑战100天 AI In LeetCode Day08&#xff08;热题面试经典150题&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-102.1 题目2.2 题解 三、面试经典 150 题-103.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&…

(离散数学)逻辑连接词

异或可以理解为不同为1相同为0 P->Q的前件和后件满足0->1的其中一个就为真 <—>可以看做 &#xff0c;相同为1不同为0 异或与等价相反

459. 重复的子字符串

459. 重复的子字符串 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;__459重复的子字符串_枚举__459重复的子字符串_字符串匹配__459重复的子字符串_KMP算法__459重复的子字符串_优化的KMP算法 错误经验吸取 原题链接&#xff1a; 459. …

搭建产品帮助中心其实很简单,方法都在这了!

网站帮助中心是一个为用户提供支持和解答问题的重要资源。它不仅可以提高用户体验&#xff0c;还能减少用户问题反馈的数量。通过提供清晰、易于理解的文档和指南&#xff0c;帮助中心可以帮助用户更好地了解产品或服务&#xff0c;并解决他们在使用过程中遇到的问题。接下来我…

冯·诺依曼结构

一、约翰冯诺依曼---计算机之父 约翰冯诺依曼&#xff08;John von Neumann&#xff0c;1903年12月28日—1957年2月8日&#xff09;&#xff0c;出生于匈牙利布达佩斯&#xff0c;匈牙利裔美籍数学家、计算机科学家、物理学家和化学家&#xff0c;美国国家科学院院士&#xff…

使用 Socks5 来劫持 HTTPS(TCP-TLS) 之旅

MITM 劫持的过程中&#xff0c;HTTP 协议并不是唯一选择。 实际在 MITM 使用过程中&#xff0c;BurpSuite 和 Yakit 提供的交互式劫持工具只能劫持 HTTP 代理的 TLS 流量&#xff1b;但是这样是不够的&#xff0c;有时候我们并不能确保 HTTP 代理一定生效&#xff0c;或者说特…

HDMI之编码篇

概述 HDMI 2.0b(含)以下版本,采用3个Channel方式输出。传输又分为3三种周期,视频数据,数据岛以及控制周期。视频传输采用8/10编码。数据岛采用4/10编码(TERC4)。控制周期采用2/10。编码都拓展成了10bits。 上图中,Pixel component(e.g.B)->D[7:0]表示视频数据周期…

74hc595模块参考

74hc595模块参考 8位串行并行输出&#xff08;SIPO&#xff09;移位寄存器 使用74HC595移位寄存器扩展微控制器上的输出引脚数量。如果你需要扩充输入引脚的数量那么你需要74HC165移位寄存器。 SER&#xff08;串行输入&#xff09;引脚用于一次一位地将数据发送到移位寄存器…