STM32双轮平衡小车(基于STM32F103C8T6HAL库)

news2024/12/23 13:11:20

STM32双轮平衡小车参考教程

这个项目是跟做以上UP的STM32双轮平衡小车,主要是为了学习电机驱动和PID控制。这篇我就不提供源码了,我也是跟学的,原作者也提供了源码,我记录一下自己的理解。

1 PID原理

1.1 PID简介

1.2 PID演示 

KP:比例环节:向目标靠近,但是在目标附近振荡

KI:积分环节:消除稳态误差

KD:微分环节:减小振荡

(1)无任何控制

上图中,可以看到中间是一个小球和绿色的板子,我们点击修改目标后波动板子位置。如下图所示:

然后我们切换到推动小球模式下,鼠标波动一下小球,可以看到下图所示结果:

在没有任何控制的情况的下,小球会跑飞。 

(2)施加KP控制

首先要给一个输出限幅,这个输出限幅是一定要加的。在实际应用中,比如说要控制电机,电机转动是有一个速度上限的,我们先给个10,然后KP给个0.1。会产生在这样的情况,小球会以目标为中心,在目标周围振荡,并且振荡幅度会越变越大,并且KP值越大,振荡幅度越大。

所以KP的作用就是能够让小球接近目标,但是会发生震荡,并且振荡幅度会越来越大。 

(3)施加KD控制

为了减小KP产生的振荡,我们要引入微分环节KD,就是求导,这里对位置求导实际上就是速度,相当于引入了一个阻尼力,减小振荡。

引入KD以后,我们会发现振荡幅度远远减小,并且KD越大,幅度越小。具体表现为KD小的情况下作用弱,要振荡多次才能停下来,KD大的时候阻尼力变大,振荡次数减少。但是也不是越大越好,因为在实际系统中,可能受到其它因素的干扰,KD过大会将这些干扰放大,造成小球在本该静止大的位置发生抽搐现象。存在的问题就是目标位置和当前位置有一定差距,就是使用KD控制没办法减小系统平衡时和目的之间的差距,即稳态误差。

(4)引入KI控制

为了消除稳态误差,需要引入KI环节。因为PD控制器是和时间无关的,因此在整个系统达到稳定的过程中,没办法消除累计误差,因此需要引入KI环节。

1.3 平衡小车PID控制原理 

1.3.1 直立控制

我们先看一下结论:

我们为什么要得到这样的结论? 如何得到这样的结论?

中立位:这个中立位是个相对的概念,我们可能会认为中立位是小车直立(θ=0)的时候,实际上只要小车能够保持平衡不请到都是中立位。比如说我的小车要前进,那必然不可能保持完全直立,必须要有一定的θ才能前进。

a=k_{1}\theta +k_{2}\theta ^{'}:这是不是就是KP+KD(比例+微分)控制,KP让小车能够趋于平衡,KD减小振荡、缩短小车达到平衡的时间。

现在结论就来了:可以这么理解,上面标红色的那个公式是小车中立位的状态,而这个结论就是小车从非中立位靠近中立位的过程。 

2 双轮平衡小车各模块驱动

草履虫都能学会的STM32平衡小车教程(基础篇)_哔哩哔哩_bilibili

此项目的重点是PID控制,因此关于各外设不详细展开,这位UP提供的源码里面也有,所以重点记录PID的使用。

2.1 硬件原理图

2.2 OLED屏幕

因为这个外设江科大的讲的更详细,就不单独记录了。

参考:

[模块教程] 第1期 0.96寸OLED显示屏_哔哩哔哩_bilibili

7.OLED显示_哔哩哔哩_bilibili

2.3 MPU6050

这个模块江科大讲的更详细,就不单独记录了。

参考:

 江协科技STM32学习笔记(第09章 I2C通信)_xcl、xda-CSDN博客

8.MPU6050读取角度_哔哩哔哩_bilibili

2.4 超声波测距模块

超声波测距模块HC-SR04(基于STM32F103C8T6HAL库)-CSDN博客

2.5 电机驱动及编码器测速

电机驱动及编码器测速(基于STM32F103C8T6HAL库)-CSDN博客

2.6 蓝牙串口模块JDY-31

蓝牙串口模块JDY-31(基于STM32F103C8T6HAL库)-CSDN博客

3 PID控制

在实现以上驱动部分基础上实现PID控制。

3.1 PID程序编写

创建pid.c和pid.h

3.1.1 直立环PD控制器

/*直立环PD控制器
输入:期望角度、真实角度、角速度
期望角度:通过外环速度环控制器获得,
真实角度:通过MPU6050陀螺仪获得,
角速度:通过MPU6050陀螺仪获得
*/
int Vertical(float Med,float Angle,float gyro_Y)
{
	int temp;
	temp=Vertical_Kp*(Angle-Med)+Vertical_Kd*gyro_Y;
	return temp;                             //直立环的输出会反馈给速度环,所以要保持跟速度环输入一样,速度环输入是电机的PWM
}

3.1.2 速度环PI控制器

/*速度环PI控制器
输入:期望速度、左编码器测速、右编码器测速
*/
int Velocity(int Target,int encoder_L,int encoder_R)
{
	static int Err_LowOut_last,Encoder_S;
	static float a=0.7;
	int Err,Err_LowOut,temp;
	Velocity_Ki=Velocity_Kp/200;
	//1、计算偏差值
	Err=(encoder_L+encoder_R)-Target;           
	//这里应该是为了方便这样写的,因为在平衡位置,我们的小车左右速度应该是一样的,可以单独计算,为了方便,直接相加计算了
	//2、低通滤波
	Err_LowOut=(1-a)*Err+a*Err_LowOut_last;
	Err_LowOut_last=Err_LowOut;
	//3、积分
	Encoder_S+=Err_LowOut;
	//4、积分限幅(-20000~20000),防止积分值过大影响P环节
	Encoder_S=Encoder_S>20000?20000:(Encoder_S<(-20000)?(-20000):Encoder_S); 
	/*因为无法直接获得积分值,所以以上几步就是用来计算积分值的*/
	if(stop==1)Encoder_S=0,stop=0;       
	/*蓝牙下发停止指令后,让积分值清0,状态清0,给下一次使用做准备,因为下发指令后;如果有积分影响,会导致按下停止按钮后,
	不会立刻停下来,会先往前,然后再退回*/
	//5、速度环计算
	temp=Velocity_Kp*Err_LowOut+Velocity_Ki*Encoder_S;
	return temp;
}

3.1.3 转向环PD控制器 

/*转向环PD控制器
输入:角速度、目标角度值
角速度:MPU6050测得
目标角度值:在这个值是相对的,给90°就表示小车你要给我转90°
输出:电机PWM改变值,使用差速转向,转向时两个轮子转速一个高一个低
*/
int Turn(float gyro_Z,int Target_turn)
{
	int temp;
	temp=Turn_Kp*Target_turn+Turn_Kd*gyro_Z;
	return temp;
}

3.2 核心闭环控制函数

闭环控制函数就是将以上几个控制器结合到一起。

先motor.c在里添加电机速度限幅函数和电机停止函数。

#define PWM_MAX 7200
#define PWM_MIN -7200
extern uint8_t stop;
/*电机限幅函数*/
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;
}
/*电机停止函数*/
void Stop(float *Med_Jiaodu,float *Jiaodu)
{
	if(abs((int)(*Jiaodu-*Med_Jiaodu))>60)
	{
		Load(0,0);
		stop=1;
	}
}

pid.c: 

/*闭环控制函数:将上面几个控制器整合到一块*/
void Control(void)	//每隔10ms调用一次
{
	int PWM_out;
	//1、读取编码器和陀螺仪的数据
	Encoder_Left=Read_Speed(&htim2);           //读取编码器测速  
	Encoder_Right=-Read_Speed(&htim4);        
	mpu_dmp_get_data(&pitch,&roll,&yaw);       //读取姿态角
	MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);   //读取角速度值
	MPU_Get_Accelerometer(&aacx,&aacy,&aacz);  //读取加速度值
	
	//2、将数据传入PID控制器,计算输出结果,即左右电机转速值
	Velocity_out=Velocity(Target_Speed,Encoder_Left,Encoder_Right);   //速度环输出
	Vertical_out=Vertical(Velocity_out+Med_Angle,roll,gyrox);         //后面两个参数与MPU6050安装方向有关
	Turn_out=Turn(gyroz,Target_turn);                                 //转向换输出
	PWM_out=Vertical_out;                //直立环输出后的参数就是电机的PWM控制
	//差速转向控制
	MOTO1=PWM_out-Turn_out;
	MOTO2=PWM_out+Turn_out;              
	Limit(&MOTO1,&MOTO2);                //PWM的最大转速是`7200和7200
	Load(MOTO1,MOTO2);
	Stop(&Med_Angle,&roll);//安全检测
}

3.3 利用MPU6050产生中断 

现在需要完成的工作就是每隔一段时间进行一次采样和PID控制了,因此需要比较精准的定时器,这里采用MPU6050的INT引脚来进行,每当MPU6050采样好,就会输出一个低电平,告诉单片机采样好了。

每隔10ms采样一次的话,就设置MPU6050的采样率:

MPU_Set_Rate(100);						//设置采样率100Hz

这里需要配置PB5引脚为外部中断模式,并配置为上拉模式,下降沿触发方式,每隔10ms拉低一次电平上报采样数据给单片机。

先初始化陀螺仪再开启中断是因为我们读取数据的前提是陀螺仪初始化好,不然读不出来,所以要先初始化陀螺仪再开中断。

更改中断回调函数:

我们之前在超声波测距模块里调用了中断回调函数,使用HAL库配置的项目中断回调函数是共用的,所以要在这个函数里区别一下是哪个引脚产生的中断。

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin==GPIO_PIN_2)
	{
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_2)==GPIO_PIN_SET)
		{
			__HAL_TIM_SetCounter(&htim3,0);
			HAL_TIM_Base_Start(&htim3);
		}
		else
		{
			HAL_TIM_Base_Stop(&htim3);
			count=__HAL_TIM_GetCounter(&htim3);
			distance=count*0.017;
		}
	}
	if(GPIO_Pin==GPIO_PIN_5)
		Control();
}

4 PID调参

需要调以下几个参数

4.1 机械中值的确定

小车组装好后,将代码下载到单片机里,让小车分别向前向后倾倒,观察倾倒时候的角度值。

Med_Angle =(\theta _{1}+\theta _{2})/2

这个值可能每次开机都不一样,大概就行。

4.2 直立环数量级的确定

(1)Kp数量级

我们的输出是电机的PWM,电机的PWM在-7200~7200之间,角度大概是在0~60°,我们取个中间值30。

7200/30240
7200/10720

所以Kp大概在0~1000之间。

(2)Kd数量级

Kd就是角度的微分,即角速度,所以我们可以直接用OLED显示进行观察一下,这里为什么是gyrox呢,因为我们的小车前倾和后倾是绕x轴转动的。

经过观察,这个gyrox大概在2000~3000的话,7200/2000=3.6,所以Kd的数量级大概在0~10之间。

4.3 速度环参数数量级的确定 

速度环的输出是一个角度值,输入是7200的PWM,但是我们的输入是编码器测得的速度,所以我们可以直接加载一个满量程转速测一下。

可以测得PWM满量程的时候,编码器测得的转速大概为70左右。所以编码器测得的值会在0~70之间。

\frac{0~70}{70+70}=0.5

所以Kp大概在0~1之间,Ki根据工程经验得出,约为Kp/200。

4.4 直立环参数极性的确定

我们可以单独给要判断赋一个初值,小车向前倾车轮向前转就是正确极性,极性反了就会导致小车向前倾,轮子向后转。

4.5 速度环参数极性的确定

先给直立环赋一个比较小的值,然后速度环给一个较大值:

极性正确的情况下往前倾会车轮转速会变快,极性反了车轮会转得比较慢。 

4.6 直立环调参

(1)Kp调参

我们在其它参数没有得时候先给一个Kp。

观察现象:下载后朝某一个方向压一下小车,如果现象是小车移动时候倾角很明显,那就是Kp太小了,如果现象是小车振荡幅度很大,那就是超调了,Kp给的太大。反复测试得到一个合适的值,先调到一个明显临界超调的状态。

(2)Kd调参

Kd得目的是为了消除小车平衡时候的振荡,先调到一个明显临界超调的状态。

以上两个值是我们是使用明显临界超调的时候调出来的参数,所以最终我们还需要乘以一个系数0.6。

4.7 速度环调参 

在上面参数的基础上,先给速度环Kp一个参数0.4,Ki为Kp/200,然后反复修改,直至没有显著振荡。

4.8 转向环调参

这个转向换并不是一个严格的PD控制器, 不是那种目标角度值减去实际角度值的格式,因为陀螺仪测量转向角(偏航角)这个角度的时候,测的不是很准,误差非常大,因为陀螺仪在做DMP的时候,是累计误差的,是把时间上的误差都累积起来,所以算出来的偏航角误差非常大,所以没办法直接用这个角度。这里Targe_turn直接是目标的转向速度,Kp环节主要负责遥控转向,Kp值起到放大作用,Kp越大转向越快,这个值不是特别重要;Kd主要是为了保证转向时候走直线,Kd的数量值与速度环Ki量级是一样的,一般取一个经验值0.6。

5 蓝牙遥控

这一节就是为了实现手机蓝牙串口助手下发指令控制小车前后左右、停止运行。

初始化的时候要先打开串口才能接收数据

void USART3_IRQHandler(void)
{
  /* USER CODE BEGIN USART3_IRQn 0 */

  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */
	Bluetooth_data=rx_buf[0];
	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=0,Right=1;//右
	else if(Bluetooth_data==0x07)Fore=0,Back=0,Left=1,Right=0;//左
	else												 Fore=0,Back=0,Left=0,Right=0;//刹
	HAL_UART_Receive_IT(&huart3,rx_buf,1);
  /* USER CODE END USART3_IRQn 1 */
}

 这里的值要跟手机蓝牙串口设置的一样,下发指令后才会正常响应。

/*闭环控制函数:将上面几个控制器整合到一块*/
void Control(void)	//每隔10ms调用一次
{
	int PWM_out;
	//1、读取编码器和陀螺仪的数据
	Encoder_Left=Read_Speed(&htim2);           //读取编码器测速  
	Encoder_Right=-Read_Speed(&htim4);        
	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;//未接受到前进后退指令-->速度清零,稳在原地
	if(Fore==1)
	{
		if(distance<50)
			Target_Speed++;                 //这个先+还是先-要根据极性做出相应改变
		else
			Target_Speed--;
	}
	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))Target_turn=0;
	if(Left==1)Target_turn+=30;	//左转
	if(Right==1)Target_turn-=30;	//右转
	Target_turn=Target_turn>SPEED_Z?SPEED_Z:(Target_turn<-SPEED_Z?(-SPEED_Z):Target_turn);//限幅( (20*100) * 100   )
	
	/*转向约束*/
	if((Left==0)&&(Right==0))Turn_Kd=0.6;//若无左右转向指令,则开启转向约束
	else if((Left==1)||(Right==1))Turn_Kd=0;//若左右转向指令接收到,则去掉转向约束

	
	//2、将数据传入PID控制器,计算输出结果,即左右电机转速值
	Velocity_out=Velocity(Target_Speed,Encoder_Left,Encoder_Right);   //速度环输出
	Vertical_out=Vertical(Velocity_out+Med_Angle,roll,gyroy);         //后面两个参数与MPU6050安装方向有关
	Turn_out=Turn(gyroz,Target_turn);                                 //转向换输出
	PWM_out=Vertical_out;                //直立环输出后的参数就是电机的PWM控制
	//差速转向控制
	MOTO1=PWM_out-Turn_out;
	MOTO2=PWM_out+Turn_out;              
	Limit(&MOTO1,&MOTO2);                //PWM的最大转速是`7200和7200
	Load(MOTO1,MOTO2);
	Stop(&Med_Angle,&roll);//安全检测
}

6 最终代码

(1)main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "oled.h"
#include "IIC.h"
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
#include "mpu6050.h"
#include "stdio.h"
#include "sr04.h"
#include "motor.h"
#include "encoder.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
extern float roll;
extern int Encoder_Left,Encoder_Right;
uint8_t display_buf[20];
uint32_t sys_tick;
extern float distance;
extern uint8_t rx_buf[2];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void Read(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_TIM3_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_TIM4_Init();
  MX_USART3_UART_Init();
  /* USER CODE BEGIN 2 */
	OLED_Init();
	OLED_Clear();
	MPU_Init();
	mpu_dmp_init();
	OLED_ShowString(0,00,"Init Sucess",16);
	HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim1,TIM_CHANNEL_4);
	HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
	HAL_TIM_Encoder_Start(&htim4,TIM_CHANNEL_ALL);
	HAL_UART_Receive_IT(&huart3,rx_buf,1);
	Load(0,0);
	OLED_Clear();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		sprintf((char *)display_buf,"Encoder_L:%d   ",Encoder_Left);
		OLED_ShowString(0,0,display_buf,16);
		sprintf((char *)display_buf,"Encoder_R:%d   ",Encoder_Right);
		OLED_ShowString(0,2,display_buf,16);		
		sprintf((char *)display_buf,"roll:%.1f   ",roll); 
		OLED_ShowString(0,4,display_buf,16);
		GET_Distance();
		sprintf((char *)display_buf,"distance:%.1f  ",distance);
		OLED_ShowString(0,6,display_buf,12);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */


/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

 (2)motor.c

#include "motor.h"

#define PWM_MAX 7200
#define PWM_MIN -7200
extern TIM_HandleTypeDef htim1;
extern uint8_t stop;
int abs(int p)
{
	if(p>0)
		return p;
	else
		return -p;
}

void Load(int moto1,int moto2)			//-7200~7200
{
	if(moto1<0)
	{
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_SET);
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_RESET);
	}
	else
	{
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_13,GPIO_PIN_RESET);
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_12,GPIO_PIN_SET);
	}
	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_4,abs(moto1));
	if(moto2<0)
	{
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_SET);
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_RESET);
	}
	else
	{
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_14,GPIO_PIN_RESET);
		HAL_GPIO_WritePin(GPIOB,GPIO_PIN_15,GPIO_PIN_SET);
	}
	__HAL_TIM_SetCompare(&htim1,TIM_CHANNEL_1,abs(moto2));
}

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;
}
void Stop(float *Med_Jiaodu,float *Jiaodu)
{
	if(abs((int)(*Jiaodu-*Med_Jiaodu))>60)
	{
		Load(0,0);
		stop=1;
	}
}

(3)motor.h

#ifndef _MOTOR_H
#define _MOTOR_H

#include "stm32f1xx_hal.h"
void Load(int moto1,int moto2);
void Limit(int *motoA,int *motoB);
void Stop(float *Med_Jiaodu,float *Jiaodu);
#endif

(4)pid.c

#include "pid.h"
#include "encoder.h"
#include "inv_mpu.h"
#include "inv_mpu_dmp_motion_driver.h"
#include "mpu6050.h"
#include "motor.h"

//传感器数据变量
int Encoder_Left,Encoder_Right;
float pitch,roll,yaw;
short gyrox,gyroy,gyroz;
short	aacx,aacy,aacz;

//闭环控制中间变量
int Vertical_out,Velocity_out,Turn_out,Target_Speed,Target_turn,MOTO1,MOTO2;
float Med_Angle=3;//平衡时角度值偏移量(机械中值)
//参数
float Vertical_Kp=-120,Vertical_Kd=-0.8;			//直立环 数量级(Kp:0~1000、Kd:0~10)
float Velocity_Kp=-1.2,Velocity_Ki=-0.006;		//速度环 数量级(Kp:0~1)
float Turn_Kp=10,Turn_Kd=0.1;											//转向环

uint8_t stop;

extern TIM_HandleTypeDef htim2,htim4;
extern float distance;
extern uint8_t Fore,Back,Left,Right;
#define SPEED_Y 12 //俯仰(前后)最大设定速度
#define SPEED_Z 150//偏航(左右)最大设定速度 

//直立环PD控制器
//输入:期望角度、真实角度、角速度
int Vertical(float Med,float Angle,float gyro_Y)
{
	int temp;
	temp=Vertical_Kp*(Angle-Med)+Vertical_Kd*gyro_Y;
	return temp;
}

//速度环PI控制器
//输入:期望速度、左编码器、右编码器
int Velocity(int Target,int encoder_L,int encoder_R)
{
	static int Err_LowOut_last,Encoder_S;
	static float a=0.7;
	int Err,Err_LowOut,temp;
	Velocity_Ki=Velocity_Kp/200;
	//1、计算偏差值
	Err=(encoder_L+encoder_R)-Target;
	//2、低通滤波
	Err_LowOut=(1-a)*Err+a*Err_LowOut_last;
	Err_LowOut_last=Err_LowOut;
	//3、积分
	Encoder_S+=Err_LowOut;
	//4、积分限幅(-20000~20000)
	Encoder_S=Encoder_S>20000?20000:(Encoder_S<(-20000)?(-20000):Encoder_S);
	if(stop==1)Encoder_S=0,stop=0;
	//5、速度环计算
	temp=Velocity_Kp*Err_LowOut+Velocity_Ki*Encoder_S;
	return temp;
}


//转向环PD控制器
//输入:角速度、角度值
int Turn(float gyro_Z,int Target_turn)
{
	int temp;
	temp=Turn_Kp*Target_turn+Turn_Kd*gyro_Z;
	return temp;
}

void Control(void)	//每隔10ms调用一次
{
	int PWM_out;
	//1、读取编码器和陀螺仪的数据
	Encoder_Left=Read_Speed(&htim2);
	Encoder_Right=-Read_Speed(&htim4);
	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;//未接受到前进后退指令-->速度清零,稳在原地
	if(Fore==1)
	{
		if(distance<50)
			Target_Speed++;
		else
			Target_Speed--;
	}
	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))Target_turn=0;
	if(Left==1)Target_turn+=30;	//左转
	if(Right==1)Target_turn-=30;	//右转
	Target_turn=Target_turn>SPEED_Z?SPEED_Z:(Target_turn<-SPEED_Z?(-SPEED_Z):Target_turn);//限幅( (20*100) * 100   )
	
	/*转向约束*/
	if((Left==0)&&(Right==0))Turn_Kd=0.6;//若无左右转向指令,则开启转向约束
	else if((Left==1)||(Right==1))Turn_Kd=0;//若左右转向指令接收到,则去掉转向约束

	
	//2、将数据传入PID控制器,计算输出结果,即左右电机转速值
	Velocity_out=Velocity(Target_Speed,Encoder_Left,Encoder_Right);
	Vertical_out=Vertical(Velocity_out+Med_Angle,roll,gyrox);
	Turn_out=Turn(gyroz,Target_turn);
	PWM_out=Vertical_out;
	MOTO1=PWM_out-Turn_out;
	MOTO2=PWM_out+Turn_out;
	Limit(&MOTO1,&MOTO2);
	Load(MOTO1,MOTO2);
	Stop(&Med_Angle,&roll);//安全检测
}

(5)pid.h

#ifndef __PID_H__
#define __PID_H__

#include "stm32f1xx_hal.h"

void Control(void);	//每隔10ms调用一次

#endif

(6)stm32f1xx_it.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    stm32f1xx_it.c
  * @brief   Interrupt Service Routines.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */

/* USER CODE END TD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t rx_buf[2],Bluetooth_data;
uint8_t Fore,Back,Left,Right;
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* External variables --------------------------------------------------------*/
extern UART_HandleTypeDef huart3;
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/******************************************************************************/
/*           Cortex-M3 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/**
  * @brief This function handles Non maskable interrupt.
  */
void NMI_Handler(void)
{
  /* USER CODE BEGIN NonMaskableInt_IRQn 0 */

  /* USER CODE END NonMaskableInt_IRQn 0 */
  /* USER CODE BEGIN NonMaskableInt_IRQn 1 */
  while (1)
  {
  }
  /* USER CODE END NonMaskableInt_IRQn 1 */
}

/**
  * @brief This function handles Hard fault interrupt.
  */
void HardFault_Handler(void)
{
  /* USER CODE BEGIN HardFault_IRQn 0 */

  /* USER CODE END HardFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_HardFault_IRQn 0 */
    /* USER CODE END W1_HardFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */

  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

/**
  * @brief This function handles Prefetch fault, memory access fault.
  */
void BusFault_Handler(void)
{
  /* USER CODE BEGIN BusFault_IRQn 0 */

  /* USER CODE END BusFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_BusFault_IRQn 0 */
    /* USER CODE END W1_BusFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Undefined instruction or illegal state.
  */
void UsageFault_Handler(void)
{
  /* USER CODE BEGIN UsageFault_IRQn 0 */

  /* USER CODE END UsageFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  }
}

/**
  * @brief This function handles System service call via SWI instruction.
  */
void SVC_Handler(void)
{
  /* USER CODE BEGIN SVCall_IRQn 0 */

  /* USER CODE END SVCall_IRQn 0 */
  /* USER CODE BEGIN SVCall_IRQn 1 */

  /* USER CODE END SVCall_IRQn 1 */
}

/**
  * @brief This function handles Debug monitor.
  */
void DebugMon_Handler(void)
{
  /* USER CODE BEGIN DebugMonitor_IRQn 0 */

  /* USER CODE END DebugMonitor_IRQn 0 */
  /* USER CODE BEGIN DebugMonitor_IRQn 1 */

  /* USER CODE END DebugMonitor_IRQn 1 */
}

/**
  * @brief This function handles Pendable request for system service.
  */
void PendSV_Handler(void)
{
  /* USER CODE BEGIN PendSV_IRQn 0 */

  /* USER CODE END PendSV_IRQn 0 */
  /* USER CODE BEGIN PendSV_IRQn 1 */

  /* USER CODE END PendSV_IRQn 1 */
}

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

/******************************************************************************/
/* STM32F1xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f1xx.s).                    */
/******************************************************************************/

/**
  * @brief This function handles EXTI line2 interrupt.
  */
void EXTI2_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI2_IRQn 0 */

  /* USER CODE END EXTI2_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
  /* USER CODE BEGIN EXTI2_IRQn 1 */

  /* USER CODE END EXTI2_IRQn 1 */
}

/**
  * @brief This function handles EXTI line[9:5] interrupts.
  */
void EXTI9_5_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI9_5_IRQn 0 */

  /* USER CODE END EXTI9_5_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
  /* USER CODE BEGIN EXTI9_5_IRQn 1 */

  /* USER CODE END EXTI9_5_IRQn 1 */
}

/**
  * @brief This function handles USART3 global interrupt.
  */
void USART3_IRQHandler(void)
{
  /* USER CODE BEGIN USART3_IRQn 0 */

  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */
	Bluetooth_data=rx_buf[0];
	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=0,Right=1;//右
	else if(Bluetooth_data==0x07)Fore=0,Back=0,Left=1,Right=0;//左
	else												 Fore=0,Back=0,Left=0,Right=0;//刹
	HAL_UART_Receive_IT(&huart3,rx_buf,1);
  /* USER CODE END USART3_IRQn 1 */
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

其余部分不需要进行更改,保持原来各模块的驱动原来状态即可。

7 手机蓝牙串口助手的设置

连接蓝牙模块,连接的时候如果搜不到的话,开启手机的定位。

控制参数设置

UP:0x01

DOWN:0x05

LEFT:0x07

RIGHT:0x03

STOP: 0x00

上面几个参数和串口中断里对应上即可。

我又不小心玩坏了一个蓝牙模块,这个UP设计的板子上是按照引脚对引脚接的:

蓝牙VCC——板子VCC;

蓝牙GND——板子GND;

蓝牙RX———板子RX;

蓝牙TX———板子TX。

我没注意看,按照常规思维接的RX-TX,TX-RX,直接给整冒烟了。后面再接上去就马上蓝牙模块马上就烫手了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打破AI壁垒-降低AI入门门槛

AI和AGI AI&#xff08;人工智能-Artificial Intelligence&#xff09;&#xff1a; 先说说AI&#xff0c;这个大家可能都不陌生。AI&#xff0c;就是人工智能&#xff0c;它涵盖了各种技术和领域&#xff0c;目的是让计算机模仿、延伸甚至超越人类智能。想象一下&#xff0c;…

图像分割分析效果2

这次加了结构化损失 # 训练集dice: 0.9219 - iou: 0.8611 - loss: 0.0318 - mae: 0.0220 - total: 0.8915 # dropout后&#xff1a;dice: 0.9143 - iou: 0.8488 - loss: 0.0335 - mae: 0.0236 - total: 0.8816 # 加了结构化损失后:avg_score: 0.8917 - dice: 0.9228 - iou: 0.…

如何做服务迁移、重构?

思维导图 0. 前言 本文意在提供服务迁移的完整思路&#xff0c;将思考题变成填空题&#xff0c;只需要按照本文提供的思路填空&#xff0c;服务迁移至少可以做到 80 分。 本文的服务迁移指&#xff1a;将老服务的代码迁移至新服务。 1. 服务资源梳理 服务资源&#xff0c;我…

AI学习记录 - 旋转位置编码

创作不易&#xff0c;有用点赞&#xff0c;写作有利于锻炼一门新的技能&#xff0c;有很大一部分是我自己总结的新视角 1、前置条件&#xff1a;要理解旋转位置编码前&#xff0c;要熟悉自注意力机制&#xff0c;否则很难看得懂&#xff0c;在我的系列文章中有对自注意力机制的…

Win32函数调用约定(Calling Convention)

平常我们在C#中使用DllImportAttribute引入函数时&#xff0c;不指明函数调用约定(CallingConvention)这个参数&#xff0c;也可以正常调用。如FindWindow函数 [DllImport("user32.dll", EntryPoint"FindWindow", SetLastError true)] public static ext…

来啦| LVMH路威酩轩25届校招智鼎高潜人才思维能力测验高分攻略

路威酩轩香水化妆品(上海)有限公司是LVMH集团于2000年成立&#xff0c;负责集团旗下的部分香水化妆品品牌在中国的销售包括迪奥、娇兰、纪梵希、贝玲妃、玫珂菲、凯卓、帕尔马之水以及馥蕾诗等。作为目前全球最大的奢侈品集团LVMH 集团秉承悠久的历史&#xff0c;不断打破常规&…

群晖最新版(DSM 7.2) 下使用 Web Station 部署 flask 项目

0. 需求由来 为了在 DSM 7.2 版本下的群晖 NAS 里运行我基于 flask 3.0.2 编写的网页应用程序&#xff0c;我上网查了非常多资料&#xff0c;也踩了很多坑。最主要的就是 7.2 版本的界面与旧版略有不同&#xff0c;而网络上的资料大多基于旧版界面&#xff0c;且大部分仅仅说明…

记忆化搜索【下】

375. 猜数字大小II 题目分析 题目链接&#xff1a;375. 猜数字大小 II - 力扣&#xff08;LeetCode&#xff09; 题目比较长&#xff0c;大致意思就是给一个数&#xff0c;比如说10&#xff0c;定的数字是7&#xff0c;让我们在[1, 10]这个区间猜。 如果猜大或猜小都会说明…

2024AI绘画工具排行榜:探索最受欢迎的AI绘图软件特点与选择指南

AI绘画工具各有优势&#xff0c;从开放性到对特定语言和文化的支持&#xff0c;以及对图像细节和艺术性的不同关注点&#xff0c;根据具体需求选择合适的工具 MidJourney 图片品质卓越&#xff0c;充满独特创意&#xff0c;初期能够免费获取数十账高质量图片&#xff0c;整个生…

C++20中支持的非类型模板参数

C20中支持将类类型作为非类型模板参数&#xff1a;作为模板参数传入的对象具有const T类型&#xff0c;其中T是对象的类型&#xff0c;并且具有静态存储持续时间(static storage duration)。 在C20之前&#xff0c;非类型模板参数仅限于&#xff1a;左值引用类型、整数类型、指…

VMware Fusion Pro 13 Mac版虚拟机 安装Win11系统教程

Mac分享吧 文章目录 Win11安装完成&#xff0c;软件打开效果一、VMware安装Windows11虚拟机1️⃣&#xff1a;准备镜像2️⃣&#xff1a;创建虚拟机3️⃣&#xff1a;虚拟机设置4️⃣&#xff1a;安装虚拟机5️⃣&#xff1a;解决连不上网问题 安装完成&#xff01;&#xff0…

用Pytho解决分类问题_DBSCAN聚类算法模板

一&#xff1a;DBSCAN聚类算法的介绍 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;DBSCAN算法的核心思想是将具有足够高密度的区域划分为簇&#xff0c;并能够在具有噪声的空间数据库中发…

关于SpringMVC的理解

1、SpringMVC 应用 1.1、简介 1.1.1、MVC 体系结构 三层架构&#xff1a; 我们的开发架构⼀般都是基于两种形式&#xff0c;⼀种是 C/S 架构&#xff0c;也就是客户端/服务器&#xff1b;另⼀种是 B/S 架构&#xff0c;也就是浏览器服务器。在 JavaEE 开发中&#xff0c;⼏乎…

陪护系统|陪护系统源码|护理陪护小程序

随着医疗水平的不断提高&#xff0c;人们对护理服务的需求也越来越高。为了更好地满足患者和家属的需求&#xff0c;陪护系统定制开发应运而生。 陪护系统定制开发是根据医疗机构的实际需求&#xff0c;设计并开发一套专门用于陪护服务的系统。该系统拥有一系列丰富的功能&…

基于人工智能的图片生成系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图片生成是计算机视觉领域的一个重要任务&#xff0c;基于生成对抗网络&#xff08;GAN&#xff09;的图片生成系统能够从噪声中生成逼…

大数据-119 - Flink Window总览 窗口机制-滚动时间窗口-基于时间驱动基于事件驱动

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

揭秘 AMD GPU 上 PyTorch Profiler 的性能洞察

Unveiling performance insights with PyTorch Profiler on an AMD GPU — ROCm Blogs 2024年5月29日&#xff0c;作者&#xff1a;Phillip Dang。 在机器学习领域&#xff0c;优化性能通常和改进模型架构一样重要。在本文中&#xff0c;我们将深入探讨 PyTorch Profiler&#…

小白建立个人网站初步尝试

一、VScode 代码是在VScode上运行的&#xff0c;可以看作者另一篇文章&#xff1a;http://t.csdnimg.cn/mOmdF 二、代码基本框架 代码解释<!DOCTYPE html>声明为HTML5文档<html><head>头部元素&#xff0c;不显示在页面<meta charset"utf-8"…

数学建模强化宝典(14)Fisher 最优分割法

前言 Fisher最优分割法是一种对有序样品进行聚类的方法&#xff0c;它在分类过程中不允许打破样品的顺序。这种方法的目标是找到一种分割方式&#xff0c;使得各段内样品之间的差异最小&#xff0c;而各段之间的差异最大。以下是关于Fisher最优分割法的详细介绍&#xff1a; 一…

【LeetCode热题100】前缀和

这篇博客共记录了8道前缀和算法相关的题目&#xff0c;分别是&#xff1a;【模版】前缀和、【模版】二维前缀和、寻找数组的中心下标、除自身以外数组的乘积、和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和。 #include <iostream> #include <vector> …