STM32二轴陀螺仪云台

基于STM32F103C8T6最小核心板设计的二轴陀螺仪云台,基于江协科技的中断和OLED例程基础上改进而成的。

江协科技的个人空间-江协科技个人主页-哔哩哔哩视频

省流版:网盘分享的文件:定时器云台.rar
链接: https://pan.baidu.com/s/14EF_TK2dO8duZfw14ER-gg?pwd=s9cg 提取码: s9cg

可以直接去获取。

本项目设计的硬件部分有,MPU6050模块、两个SG90_180度舵机、OLED(IIC协议)屏幕

屏幕主要是输出MPU6050模块的X和Y的倾斜角度,用于观察和调试。

本程序获取MPU6050的以及舵机的驱动,都是在中断里面完成的,不会阻塞主程序,这也方便我们去拓展其他的功能,比如把舵机换成步进电机,添加新的传感器等,方便后续的拓展,当然,程序也采用模块化,方便我们去做移植。

舵机采用PWM驱动,另外说一下,舵机得靠5V驱动这一点注意一下,两个舵机的信号线分别连接了STM32的PA0和PA1引脚,连接舵机的为淘宝购买的舵机打印套件。

首先让我们来看一下时钟开启部分

void Timer_Init(void)
{
	//开启TIM2 时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	//开启 GPIOA 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	TIM_InternalClockConfig(TIM2);
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period = 100000 - 1;
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
	
//nvic	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_Cmd(TIM2, ENABLE);
}

开启TIM2 时钟和开启 GPIOA 时钟,当然可以根据自己的需求去更改时钟和引脚,但要提前去查看STM32的引脚分布和复用功能。

void GPIO_Config_PWM(void)
{
	//引脚配置
    GPIO_InitTypeDef GPIO_InitStructure;

    // 配置 A0 引脚(TIM2_CH1)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 配置 A1 引脚(TIM2_CH2)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_Init(GPIOA, &GPIO_InitStructure);	
}

void TIM2_PWM_Config(void)
{
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 配置 TIM2_CH1 为 PWM 模式(对应舵机 1 的 A0 引脚)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比为 0
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);

    // 配置 TIM2_CH2 为 PWM 模式(对应舵机 2 的 A1 引脚)
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
}

这里设置对应的引脚模式以及开启PWM的模式,这样一来,时钟和PWM部分就完成了,接下来让我们来看一下舵机驱动部分

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


void Servo_Init(void)
{
	Timer_Init();
	GPIO_Config_PWM();
	TIM2_PWM_Config();
}

void Set_Servo_Angles(float angleX, float angleY)
{
	uint16_t pulse_widthX,pulse_widthY;
	//X舵机计算(A0)
	pulse_widthX = (500 + ((2500 - 500) * angleX / 180));
	TIM_SetCompare1(TIM2,pulse_widthX);
	//Y舵机计算(A1)
	pulse_widthY = (500 + ((2500 - 500) * (angleY+90) / 180));
	TIM_SetCompare2(TIM2,pulse_widthY);
}

注意,这里要提前引用Timer定时中断的头文件,防止后续编译的报错,第一部分就是初始化,第二部分就是通过传入的float的值来让舵机做出对应的驱动,值得注意的是,我这里的Y舵机,初始让处于中间的位置,所以在angle Y中加入了90的相位,这样可以保证Y舵机的左右转向,然后舵机函数传入的数值,是去捕获陀螺仪获取的,这里因为我们侧重讲的舵机方面,所以陀螺仪部分就先不侧重讲述,后续再看(挖坑中。。。。),这里我们会引用会调用就行,也可去看看江协科技里面讲陀螺仪部分的视频[10-2] MPU6050简介_哔哩哔哩_bilibili,我这里,做了一部分的改进,因为我们是想通过中断的方式去捕获这个数值,所以我在这选择再封装一个函数,

void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
						int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	uint8_t DataH, DataL;								//定义数据高8位和低8位的变量
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);		//读取加速度计X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);		//读取加速度计X轴的低8位数据
	*AccX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);		//读取加速度计Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);		//读取加速度计Y轴的低8位数据
	*AccY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);		//读取加速度计Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);		//读取加速度计Z轴的低8位数据
	*AccZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);		//读取陀螺仪X轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);		//读取陀螺仪X轴的低8位数据
	*GyroX = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);		//读取陀螺仪Y轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);		//读取陀螺仪Y轴的低8位数据
	*GyroY = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
	
	DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);		//读取陀螺仪Z轴的高8位数据
	DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);		//读取陀螺仪Z轴的低8位数据
	*GyroZ = (DataH << 8) | DataL;						//数据拼接,通过输出参数返回
}

void MPU6050_Tick(void)
{
	MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);
}

通过MPU6050_Tick函数,去获取这个数值,然后让变量的值通过extern的方式,可以让其他文件可以直接调用

void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		MPU6050_Tick();
		Set_Servo_Angles(AngleX, AngleY);
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}

主函数中,我们通过中断的方式去掉用陀螺仪的数值以及去驱动舵机,主函数需要去实现显示的功能,这样一来不会阻塞主程序,这里我们通过i++的形式,来观察,主函数的状态

#include "Delay.h"
#include "OLED.h"
#include "LED.h"
#include "Key.h"
#include "MPU6050.h"
#include "Servo.h"

uint8_t KeyNum;
uint16_t i;

float AngleX,AngleY;

int main(void)
{
	OLED_Init();
	Key_Init();
	MPU6050_Init();
	Servo_Init();

	OLED_ShowString(0,0,"i:",OLED_8X16);
	OLED_ShowString(0,15,"X:",OLED_8X16);
	OLED_ShowString(0,31,"Y:",OLED_8X16);	
	
	while (1)
	{
		OLED_ShowNum(15,0,i++,6,OLED_8X16);
		OLED_ShowSignedNum(16, 15,AngleX,5,OLED_8X16);
		OLED_ShowSignedNum(16, 31,AngleY,5,OLED_8X16);
		OLED_Update();
		if(AX<0)AX=0;
		if(AY>1800)AY=1800;
		if(AY<-900)AY=-900;
		AngleX = AX/10;
		AngleY = AY/10;//下舵机		
	}
}

在实际的运行时,我们发现并不会阻塞主函数,这样一来,我们这给例程就可以进行拓展了。

最后,感谢你能看到这里,因为这个项目也是突发奇想的,可能有很多不足的地方以及没完善的地方,希望能多多指正,下面是本套案例的全套代码

网盘分享的文件:定时器云台.rar
链接: https://pan.baidu.com/s/14EF_TK2dO8duZfw14ER-gg?pwd=s9cg 提取码: s9cg

### 陀螺仪云台 PID 控制实现与调优 #### 实现方法 为了实现陀螺仪云台的PID控制,可以按照以下逻辑构建系统: 1. **硬件准备** 需要准备的核心组件包括Arduino Nano单片机、MPU6050三陀螺仪以及SG90舵机(通常为两个用于自由度云台)。这些设备通过特定接口相连,完成数据采集和执行器驱动的任务[^1]。 2. **初始化配置** 在程序启动阶段,需要对Arduino Nano、MPU6050传感器以及舵机进行初始化操作。这一步骤确保所有模块正常工作,并设定初始状态下的目标角度[^1]。 3. **姿态解算** MPU6050提供加速度计和陀螺仪的数据,通过对这两组数据融合处理(如采用互补滤波或卡尔曼滤波),可以获得更精准的姿态角度信息。此部分是整个控制系统的基础,决定了后续PID算法输入的有效性和准确性[^1]。 4. **PID控制器设计** PID控制器由比例(P)、积分(I)、微分(D)三个参数构成,其作用在于依据当前实际角度与目标角度之间的偏差来计算合适的校正量。具体而言: - P项负责快速响应较大的误差; - I项消除稳态误差; - D项抑制超调现象并提高稳定性。 对于云台应用来说,可以通过如下公式表示PID输出值 \( u(t) \)[^1]: ```plaintext u(t) = Kp * e(t) + Ki * ∫e(τ)dτ + Kd * de(t)/dt ``` 其中\( e(t) \) 表示当前位置与期望位置间的差异;而 \( Kp, Ki, Kd \) 则分别为对应的比例系数、积分时间常数倒数及微分增益因子。 5. **串级PID结构优化** 如果希望进一步提升性能,则可考虑引入串级PID架构。在这种模式下,外层的位置环专注于维持指定方向不变,内层的速度环则根据前者产生的指令调整具体的运动速率。这种分离策略有助于更好地平衡动态特性和静态精度需求[^2]。 6. **闭环反馈机制建立** 整个过程中始终存在一个持续运行的大循环,在每次迭代期间重新评估最新的倾斜状况并通过更新后的PWM信号指导伺服动作直至达成预期效果为止。 7. **代码实例** 以下是简化版的伪代码框架展示如何组合以上各要素形成完整的解决方案: ```cpp #include <Wire.h> #include <Adafruit_MPU6050.h> // 定义变量... float kp = 1; float ki = 0.1; float kd = 0.05; int targetAngleX = 0; int targetAngleY = 0; void setup() { Serial.begin(9600); mpu.initialize(); } void loop() { // 获取实时角度... double angleX = getAngleX(); double angleY = getAngleY(); // 计算误差... double errorX = targetAngleX - angleX; double errorY = targetAngleY - angleY; // 更新PID输出... double outputX = pidCompute(errorX, &kp, &ki, &kd); double outputY = pidCompute(errorY, &kp, &ki, &kd); // 调整舵机位置... servoX.write(map(outputX, -MAX_OUTPUT, MAX_OUTPUT, MIN_SERVO_POS, MAX_SERVO_POS)); servoY.write(map(outputY, -MAX_OUTPUT, MAX_OUTPUT, MIN_SERVO_POS, MAX_SERVO_POS)); delay(20); // 建议周期约20ms (50Hz) } ``` #### 参数调优技巧 针对不同场景可能需要反复试验才能找到最佳匹配的一套PID参数集合。这里给出几点建议帮助加快这一进程: - 开始时仅启用P成分,逐步增大直到获得满意的瞬态反应但不过冲太多。 - 接着加入I成分用来解决剩余偏移问题,注意观察累积效应是否会引发震荡。 - 最后再小心增加D权重减少振荡幅度同时保留足够的敏捷性。 另外值得注意的是环境因素比如温度变化也可能影响IMU读数从而间接干扰最终表现因此必要时候还需实施补偿措施[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值