• 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语言实现 

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

    入口参数为编码器的位置测量值和位置控制的目标值,返回值为电机控制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语言实现的代码如下:
     

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

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

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

    六、关键代码分析

    1、编码电机PID算法控制

    1. #include "control.h"
    2. #include "usart2.h"
    3. /**************************************************************************
    4. 函数功能:所有的控制代码都在这里面
    5. 5ms定时中断由MPU6050的INT引脚触发
    6. 严格保证采样和数据处理的时间同步
    7. 在MPU6050的采样频率设置中,设置成100HZ,即可保证6050的数据是10ms更新一次。
    8. 读者可在imv_mpu.h文件第26行的宏定义进行修改(#define DEFAULT_MPU_HZ (100))
    9. **************************************************************************/
    10. #define SPEED_Y 100 //俯仰(前后)最大设定速度
    11. #define SPEED_Z 80//偏航(左右)最大设定速度
    12. int Balance_Pwm,Velocity_Pwm,Turn_Pwm,Turn_Kp;
    13. float Mechanical_angle=8;
    14. float Target_Speed=0; //期望速度(俯仰)。用于控制小车前进后退及其速度。
    15. float Turn_Speed=0; //期望速度(偏航)
    16. //针对不同车型参数,在sys.h内设置define的电机类型
    17. float balance_UP_KP=BLC_KP; // 小车直立环PD参数
    18. float balance_UP_KD=BLC_KD;
    19. float velocity_KP=SPD_KP; // 小车速度环PI参数
    20. float velocity_KI=SPD_KI;
    21. float Turn_Kd=TURN_KD;//转向环KP、KD
    22. float Turn_KP=TURN_KP;
    23. void EXTI9_5_IRQHandler(void)
    24. {
    25. static u8 Voltage_Counter=0;
    26. if(PBin(5)==0)
    27. {
    28. EXTI->PR=1<<5; //清除LINE5上的中断标志位
    29. mpu_dmp_get_data(&pitch,&roll,&yaw); //得到欧拉角(姿态角)的数据
    30. MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz); //得到陀螺仪数据
    31. Encoder_Left=Read_Encoder(2); //读取编码器的值,保证输出极性一致
    32. Encoder_Right=-Read_Encoder(3); //读取编码器的值
    33. Led_Flash(100);
    34. Voltage_Counter++;
    35. if(Voltage_Counter==20) //100ms读取一次电压
    36. {
    37. Voltage_Counter=0;
    38. Voltage=Get_battery_volt(); //读取电池电压
    39. }
    40. if(KEY_Press(100)) //长按按键切换模式并触发模式切换初始化
    41. {
    42. if(++CTRL_MODE>=101)
    43. CTRL_MODE=97;
    44. Mode_Change=1;
    45. }
    46. Get_RC();
    47. Target_Speed=Target_Speed>SPEED_Y?SPEED_Y:(Target_Speed<-SPEED_Y?(-SPEED_Y):Target_Speed);//限幅
    48. Turn_Speed=Turn_Speed>SPEED_Z?SPEED_Z:(Turn_Speed<-SPEED_Z?(-SPEED_Z):Turn_Speed);//限幅( (20*100) * 100)
    49. Balance_Pwm =balance_UP(pitch,Mechanical_angle,gyroy); //===直立环PID控制
    50. Velocity_Pwm=velocity(Encoder_Left,Encoder_Right,Target_Speed); //===速度环PID控制
    51. Turn_Pwm =Turn_UP(gyroz,Turn_Speed); //===转向环PID控制
    52. Moto1=Balance_Pwm-Velocity_Pwm+Turn_Pwm; //===计算左轮电机最终PWM
    53. Moto2=Balance_Pwm-Velocity_Pwm-Turn_Pwm; //===计算右轮电机最终PWM
    54. Xianfu_Pwm(); //===PWM限幅
    55. Turn_Off(pitch,12); //===检查角度以及电压是否正常
    56. Set_Pwm(Moto1,Moto2); //===赋值给PWM寄存器
    57. }
    58. }
    59. /**************************************************************************
    60. 函数功能:直立PD控制
    61. 入口参数:角度、机械平衡角度(机械中值)、角速度
    62. 返回 值:直立控制PWM
    63. **************************************************************************/
    64. int balance_UP(float Angle,float Mechanical_balance,float Gyro)
    65. {
    66. float Bias;
    67. int balance;
    68. Bias=Angle-Mechanical_balance; //===求出平衡的角度中值和机械相关
    69. balance=balance_UP_KP*Bias+balance_UP_KD*Gyro; //===计算平衡控制的电机PWM PD控制 kp是P系数 kd是D系数
    70. return balance;
    71. }
    72. /**************************************************************************
    73. 函数功能:速度PI控制
    74. 入口参数:电机编码器的值
    75. 返回 值:速度控制PWM
    76. **************************************************************************/
    77. int velocity(int encoder_left,int encoder_right,int Target_Speed)
    78. {
    79. static float Velocity,Encoder_Least,Encoder;
    80. static float Encoder_Integral;
    81. //=============速度PI控制器=======================//
    82. Encoder_Least =(Encoder_Left+Encoder_Right);//-target; //===获取最新速度偏差==测量速度(左右编码器之和)-目标速度
    83. Encoder *= 0.8; //===一阶低通滤波器
    84. Encoder += Encoder_Least*0.2; //===一阶低通滤波器
    85. Encoder_Integral +=Encoder; //===积分出位移 积分时间:10ms
    86. Encoder_Integral=Encoder_Integral - Target_Speed; //===接收遥控器数据,控制前进后退
    87. if(Encoder_Integral>10000) Encoder_Integral=10000; //===积分限幅
    88. if(Encoder_Integral<-10000) Encoder_Integral=-10000; //===积分限幅
    89. Velocity=Encoder*velocity_KP+Encoder_Integral*velocity_KI; //===速度控制
    90. if(pitch<-40||pitch>40) Encoder_Integral=0; //===电机关闭后清除积分
    91. return Velocity;
    92. }
    93. /**************************************************************************
    94. 函数功能:转向PD控制
    95. 入口参数:电机编码器的值、Z轴角速度
    96. 返回 值:转向控制PWM
    97. **************************************************************************/
    98. int Turn_UP(int gyro_Z, int RC)
    99. {
    100. int PWM_out;
    101. /*转向约束*/
    102. if(RC==0)
    103. Turn_Kd=TURN_KD; //若无左右转向指令,则开启转向约束
    104. else
    105. Turn_Kd=0; //若左右转向指令接收到,则去掉转向约束
    106. PWM_out=Turn_Kd*gyro_Z + Turn_KP*RC;
    107. return PWM_out;
    108. }
    109. void Tracking()
    110. {
    111. TkSensor=0;
    112. TkSensor+=(C1<<3);
    113. TkSensor+=(C2<<2);
    114. TkSensor+=(C3<<1);
    115. TkSensor+=C4;
    116. }
    117. void Get_RC()
    118. {
    119. static u8 SR04_Counter =0;
    120. static float RATE_VEL = 1;
    121. float RATE_TURN = 1.6;
    122. float LY,RX; //PS2手柄控制变量
    123. int Yuzhi=2; //PS2控制防抖阈值
    124. switch(CTRL_MODE)
    125. {
    126. case 97:
    127. SR04_Counter++;
    128. if(SR04_Counter>=20) //100ms读取一次超声波的数据
    129. {
    130. SR04_Counter=0;
    131. SR04_StartMeasure(); //读取超声波的值
    132. }
    133. if(SR04_Distance<=30)
    134. {
    135. Target_Speed=0,Turn_Speed=40;
    136. }
    137. else
    138. {
    139. Target_Speed=30,Turn_Speed=0;
    140. }
    141. break;
    142. case 98://蓝牙模式
    143. if((Fore==0)&&(Back==0))
    144. Target_Speed=0;//未接受到前进后退指令-->速度清零,稳在原地
    145. if(Fore==1)
    146. Target_Speed--;//前进1标志位拉高-->需要前进
    147. if(Back==1)
    148. Target_Speed++;//
    149. /*左右*/
    150. if((Left==0)&&(Right==0))
    151. Turn_Speed=0;
    152. if(Left==1)
    153. Turn_Speed-=30; //左转
    154. if(Right==1)
    155. Turn_Speed+=30; //右转
    156. break;
    157. case 99://循迹模式
    158. Tracking();
    159. switch(TkSensor)
    160. {
    161. case 15:
    162. Target_Speed=0;
    163. Turn_Speed=0;
    164. break;
    165. case 9:
    166. Target_Speed--;
    167. Turn_Speed=0;
    168. break;
    169. case 2://向右转
    170. Target_Speed--;
    171. Turn_Speed=15;
    172. break;
    173. case 4://向左转
    174. Target_Speed--;
    175. Turn_Speed=-15;
    176. break;
    177. case 8:
    178. Target_Speed=-10;
    179. Turn_Speed=-80;
    180. break;
    181. case 1:
    182. Target_Speed=-10;
    183. Turn_Speed=80;
    184. break;
    185. }
    186. break;
    187. case 100://PS2手柄遥控
    188. if(PS2_Plugin)
    189. {
    190. LY=PS2_LY-128; //获取偏差
    191. RX=PS2_RX-128; //获取偏差
    192. if(LY>-Yuzhi&&LY
    193. LY=0; //设置小角度的死区
    194. if(RX>-Yuzhi&&RX
    195. RX=0; //设置小角度的死区
    196. if(Target_Speed>-LY/RATE_VEL)
    197. Target_Speed--;
    198. else if(Target_Speed<-LY/RATE_VEL)
    199. Target_Speed++;
    200. Turn_Speed=RX/RATE_TURN;
    201. }
    202. else
    203. {
    204. Target_Speed=0,Turn_Speed=0;
    205. }
    206. break;
    207. }
    208. }

     2、编码电机编码值采集

    1. #include "encoder.h"
    2. /**************************************************************************
    3. 函数功能:把TIM2初始化为编码器接口模式
    4. 入口参数:无
    5. 返回 值:无
    6. **************************************************************************/
    7. void Encoder_Init_TIM2(void)
    8. {
    9. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    10. TIM_ICInitTypeDef TIM_ICInitStructure;
    11. GPIO_InitTypeDef GPIO_InitStructure;
    12. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器4的时钟
    13. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PB端口时钟
    14. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //端口配置
    15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
    16. GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOB
    17. TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    18. TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器
    19. TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
    20. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
    21. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数
    22. TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    23. TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
    24. TIM_ICStructInit(&TIM_ICInitStructure);
    25. TIM_ICInitStructure.TIM_ICFilter = 10;
    26. TIM_ICInit(TIM2, &TIM_ICInitStructure);
    27. TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
    28. TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    29. //Reset counter
    30. TIM_SetCounter(TIM2,0);
    31. TIM_Cmd(TIM2, ENABLE);
    32. }
    33. /**************************************************************************
    34. 函数功能:把TIM3初始化为编码器接口模式
    35. 入口参数:无
    36. 返回 值:无
    37. **************************************************************************/
    38. void Encoder_Init_TIM3(void)
    39. {
    40. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    41. TIM_ICInitTypeDef TIM_ICInitStructure;
    42. GPIO_InitTypeDef GPIO_InitStructure;
    43. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//使能定时器4的时钟
    44. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PB端口时钟
    45. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置
    46. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
    47. GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOB
    48. TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    49. TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器
    50. TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
    51. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频
    52. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM向上计数
    53. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    54. TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
    55. TIM_ICStructInit(&TIM_ICInitStructure);
    56. TIM_ICInitStructure.TIM_ICFilter = 10;
    57. TIM_ICInit(TIM3, &TIM_ICInitStructure);
    58. TIM_ClearFlag(TIM3, TIM_FLAG_Update);//清除TIM的更新标志位
    59. TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    60. //Reset counter
    61. TIM_SetCounter(TIM3,0);
    62. TIM_Cmd(TIM3, ENABLE);
    63. }
    64. /**************************************************************************
    65. 函数功能:单位时间读取编码器计数
    66. 入口参数:定时器
    67. 返回 值:速度值
    68. **************************************************************************/
    69. int Read_Encoder(u8 TIMX)
    70. {
    71. int Encoder_TIM;
    72. switch(TIMX)
    73. {
    74. case 2:
    75. Encoder_TIM= (short)TIM2 -> CNT;
    76. TIM2 -> CNT=0;
    77. break;
    78. case 3:
    79. Encoder_TIM= (short)TIM3 -> CNT; TIM3 -> CNT=0;
    80. break;
    81. default: Encoder_TIM=0;
    82. }
    83. return Encoder_TIM;
    84. }

    3、PWM配置

    1. #include "pwm.h"
    2. //PWM输出初始化
    3. //arr:自动重装值
    4. //psc:时钟预分频数
    5. //TIM1_PWM_Init(7199,0);//PWM频率=72000/(7199+1)=10Khz
    6. void TIM1_PWM_Init(u16 arr,u16 psc)
    7. {
    8. GPIO_InitTypeDef GPIO_InitStructure;
    9. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    10. TIM_OCInitTypeDef TIM_OCInitStructure;
    11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);//
    12. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE); //使能GPIO外设时钟使能
    13. //设置该引脚为复用输出功能,输出TIM1 CH1 CH4的PWM脉冲波形
    14. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_11; //TIM_CH1 //TIM_CH4
    15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
    16. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    17. GPIO_Init(GPIOA, &GPIO_InitStructure);
    18. TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
    19. TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 不分频
    20. TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
    21. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
    22. TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    23. TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
    24. TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
    25. TIM_OCInitStructure.TIM_Pulse = 0; //设置待装入捕获比较寄存器的脉冲值
    26. TIM_OCInitStructure.TIM_Pulse = arr >> 1;
    27. TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
    28. TIM_OC1Init(TIM1, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
    29. TIM_OC4Init(TIM1, &TIM_OCInitStructure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
    30. TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
    31. TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH1预装载使能
    32. TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); //CH4预装载使能
    33. TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
    34. TIM_Cmd(TIM1, ENABLE); //使能TIM1
    35. }

    4、蓝牙控制

    1. #include "usart2.h"
    2. /**************************************************************************
    3. 函数功能:串口2初始化
    4. 入口参数: bound:波特率
    5. 返回 值:无
    6. **************************************************************************/
    7. void uart2_init(u32 bound)
    8. {
    9. //GPIO端口设置
    10. GPIO_InitTypeDef GPIO_InitStructure;
    11. USART_InitTypeDef USART_InitStructure;
    12. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能UGPIOB时钟
    13. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART2时钟
    14. //USART2_TX
    15. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2
    16. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    17. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
    18. GPIO_Init(GPIOA, &GPIO_InitStructure);
    19. //USART2_RX
    20. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
    21. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    22. GPIO_Init(GPIOA, &GPIO_InitStructure);
    23. //USART 初始化设置
    24. USART_InitStructure.USART_BaudRate = bound;//串口波特率
    25. USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    26. USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    27. USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    28. USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    29. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
    30. USART_Init(USART2, &USART_InitStructure); //初始化串口2
    31. USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
    32. USART_Cmd(USART2, ENABLE); //使能串口2
    33. }
    34. /**************************************************************************
    35. 函数功能:串口2接收中断
    36. 入口参数:无
    37. 返回 值:无
    38. **************************************************************************/
    39. u8 Fore,Back,Left,Right;
    40. void USART2_IRQHandler(void)
    41. {
    42. int Uart_Receive;
    43. if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)//接收中断标志位拉高
    44. {
    45. Uart_Receive=USART_ReceiveData(USART2);//保存接收的数据
    46. BluetoothCMD(Uart_Receive);
    47. }
    48. }
    49. void BluetoothCMD(int Uart_Receive)
    50. {
    51. switch(Uart_Receive)
    52. {
    53. case 90://停止
    54. Fore=0,Back=0,Left=0,Right=0;
    55. break;
    56. case 65://前进
    57. Fore=1,Back=0,Left=0,Right=0;
    58. break;
    59. case 72://左前
    60. Fore=1,Back=0,Left=1,Right=0;
    61. break;
    62. case 66://右前
    63. Fore=1,Back=0,Left=0,Right=1;
    64. break;
    65. case 71://左转
    66. Fore=0,Back=0,Left=1,Right=0;
    67. break;
    68. case 67://右转
    69. Fore=0,Back=0,Left=0,Right=1;
    70. break;
    71. case 69://后退
    72. Fore=0,Back=1,Left=0,Right=0;
    73. break;
    74. case 70://左后,向右旋
    75. Fore=0,Back=1,Left=0,Right=1;
    76. break;
    77. case 68://右后,向左旋
    78. Fore=0,Back=1,Left=1,Right=0;
    79. break;
    80. default://停止
    81. Fore=0,Back=0,Left=0,Right=0;
    82. break;
    83. }
    84. }
    85. void Uart2SendByte(char byte) //串口发送一个字节
    86. {
    87. USART_SendData(USART2, byte); //通过库函数 发送数据
    88. while( USART_GetFlagStatus(USART2,USART_FLAG_TC)!= SET);
    89. //等待发送完成。 检测 USART_FLAG_TC 是否置1; //见库函数 P359 介绍
    90. }
    91. void Uart2SendBuf(char *buf, u16 len)
    92. {
    93. u16 i;
    94. for(i=0; iUart2SendByte(*buf++);
    95. }
    96. void Uart2SendStr(char *str)
    97. {
    98. u16 i,len;
    99. len = strlen(str);
    100. for(i=0; iUart2SendByte(*str++);
    101. }

    5、中断处理函数

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

    七、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的分享

  • 相关阅读:
    Deepwalk详解
    SpringBoot
    牛客网《剑指offer》专栏刷题练习之二叉树合集
    【0基础入门Python】你不得不看的Python学习方法
    代码随想录算法训练营 60天总结
    本科毕业六年,疫情期间备战一个月,四面阿里巴巴定级P7
    196、管理 RabbitMQ 的用户
    基于 VSC 的 UPFC(统一潮流控制器)研究(Simulink)
    软件测试与度量课程学习 课程的概述
    linux系统与应用
  • 原文地址:https://blog.csdn.net/m0_73931287/article/details/134233491