纷纭教育
您的当前位置:首页【嵌入式裸机开发】mpu6050控制二维云台(STM32、GY-521 MPU6050、舵机)

【嵌入式裸机开发】mpu6050控制二维云台(STM32、GY-521 MPU6050、舵机)

来源:纷纭教育

最终现象

STM32F103读取MPU6050数据控制舵机运动

一、MPU6050数据读取

使用软件IIC与MPU6050通信,这里可以直接参照之前的一篇博客:

二、舵机控制原理

通过输出占空比不同的PWM波就可以控制舵机转不同的角度。

①什么是PWM?

PWM全称脉冲宽度调制。通过对一系列脉冲的宽度进行调制来获得所需要的模拟参量,参用于电机控速等领域。
规定周期为Ts则频率为1/Ts,占空比为Ton/Ts(Ton为高电平时间)。如果频率为50Hz ,也就是说一个周期是20ms,那么一秒钟就有 50次PWM周期。

②STM32F103C8T6如何生成PWM?

想知道这部分原理直接看视频,视频最好理解:

③控制舵机需要什么样的PWM波?

三、代码分析

1、main.c

#include "stm32f10x.h"              
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
#include "usart.h"
#include <math.h>
#include "Servo.h"
#include "PWM.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;
int16_t accel_agleX; 
int16_t accel_agleY; 

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	uart_init(115200);
	Servo_Init();
	
	OLED_ShowString(1, 1, "ID:");
	ID = MPU6050_GetID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
	while (1)
	{
		MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		
		accel_agleX = (AX + 300)*1.2*1800/3.14/15384; // 这里我的转换是不标准的,因为我发现使用别人的公式舵机的位置无法达到预期,所以我就观察oled上采集到的数值进行缩放,只要能到达平的时候是0就好,但是其实不用改16384为15384也行,改前面那个300,但是我偷懒了,这样改的快,但是只要能达到目的公式不是很重要,
		accel_agleX = accel_agleX -18;//修改完上面的参数发现还是有偏差,所以在测试之后,这里减去偏差值,基本就能确保角度是在-90-90之间。
		OLED_ShowSignedNum(2, 1, accel_agleX, 5);
		
		accel_agleY = (AY + 300)*1.2*1800/3.14/15384;
		accel_agleY = accel_agleY -13;
		OLED_ShowSignedNum(3, 1, accel_agleY, 5);
		
		if(accel_agleY >= 0)
		{
			Servo_SetAngle(98-accel_agleY);
		}
		else if(accel_agleY <0)
		{
			int16_t a = ~accel_agleY;
			Servo_SetAngle(a+90);
		}
		Delay_ms(100);
		
		if(accel_agleX >= 0)
		{
			Servo_SetAngle2(98-accel_agleX);
		}
		else if(accel_agleX <0)
		{
			int16_t a = ~accel_agleX;
			Servo_SetAngle2(a+90);
		}
		Delay_ms(100);
	}
}

2、PWM.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:PWM初始化
  * 参    数:无
  * 返 回 值:无
  */
void PWM_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/ 
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC2Init(TIM2, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

void PWM2_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM2的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA1引脚初始化为复用推挽输出	
																	//受外设控制的引脚,均需要配置为复用模式
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM3);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
	
	/*输出比较初始化*/ 
	TIM_OCInitTypeDef TIM_OCInitStructure;							//定义结构体变量
	TIM_OCStructInit(&TIM_OCInitStructure);                         //结构体初始化,若结构体没有完整赋值
	                                                                //则最好执行此函数,给结构体所有成员都赋一个默认值
	                                                                //避免结构体初值不确定的问题
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;               //输出比较模式,选择PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;       //输出极性,选择为高,若选择极性为低,则输出高低电平取反
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   //输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;								//初始的CCR值
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);                        //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2
	
	/*TIM使能*/
	TIM_Cmd(TIM3, ENABLE);			//使能TIM2,定时器开始运行
}

/**
  * 函    数:PWM设置CCR
  * 参    数:Compare 要写入的CCR的值,范围:0~100
  * 返 回 值:无
  * 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
  *           占空比Duty = CCR / (ARR + 1)
  */
void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM2, Compare);		//设置CCR2的值
}

void PWM_SetCompare22(uint16_t Compare)
{
	TIM_SetCompare1(TIM3, Compare);		//设置CCR2的值
}

实际上想驱动多个电机用一个定时器的多个输出通道就好了,但是我想试试用两个定时器,上面就是用两定时器不同输出通道的代码。如果是想用一个定时器的话之间加一个代码就行:

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/**
  * 函    数:舵机初始化
  * 参    数:无
  * 返 回 值:无
  */
void Servo_Init(void)
{
	PWM_Init();									//初始化舵机的底层PWM
	PWM2_Init();
}

/**
  * 函    数:舵机设置角度
  * 参    数:Angle 要设置的舵机角度,范围:0~180
  * 返 回 值:无
  */
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle / 180 * 2000 + 500);	//设置占空比
												//将角度线性变换,对应到舵机要求的占空比范围上
}
void Servo_SetAngle2(float Angle)
{
	PWM_SetCompare22(Angle / 180 * 2000 + 500);	//设置占空比
												//将角度线性变换,对应到舵机要求的占空比范围上
}

四、完整工程代码

关注私聊

因篇幅问题不能全部显示,请点此查看更多更全内容