恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于STM32F103C8T6,通过MPU6050采集姿态数据,驱动两个SG90-360°舵机实现云台随姿态调节,OLED实时显示采集数据和舵机速度(Keil5 + STM32标准库)
首页
资讯中心
/
基于STM32F103C8T6,通过MPU6050采集姿态数据,驱动两个SG90-360°舵机实现云台随姿态调节,OLED实时显示采集数据和舵机速度(Keil5 + STM32标准库)
基于STM32F103C8T6,通过MPU6050采集姿态数据,驱动两个SG90-360°舵机实现云台随姿态调节,OLED实时显示采集数据和舵机速度(Keil5 + STM32标准库)
发布时间:2026/10/21 11:03:41
目录源码链接视频一.简介二.软件I2C的配置三.基于软件I2C配置MPU6050:四.数据映射数据解释实际数据参考五.main文件源码链接Github:https://github.com/Emy-Lxo/stm32f103-mpu6050-servo-gimbal.git通过网盘分享的文件陀机云台链接: 链接:https://pan.baidu.com/s/13J5nc-i48P81xY8wJzFPnQ?pwd6ekh 提取码:6ekh 复制这段内容后打开百度网盘手机App操作更方便哦视频基于stm32和mpu6050控制陀机【基于stm32和mpu6050丝滑控制陀机云台】https://www.bilibili.com/video/BV14gf1BnEsw?vd_source36252c0ff0868bf2923cbb5e0ce8ca71一.简介在嵌入式系统和机器人应用中实时感知物体姿态并据此进行精确控制的需求日益广泛。本项目利用意法半导体ST的STM32F103C8T6微控制器作为核心处理器结合InvenSense公司的MPU6050六轴运动处理传感器实现了一个能够根据姿态变化实时调整的简易云台系统。该系统通过采集MPU6050输出的三轴加速度计和三轴陀螺仪数据解算得到物体的实时姿态信息并据此生成相应的控制信号驱动两个SG90-360°连续旋转舵机使云台平台能够跟随姿态的变化而运动。要让STM32成功“对话”MPU6050首先要打通I²C这条物理通道。在此使用的是软件I2C,STM32F103C8T6,SG90—360°陀机二.软件I2C的配置要让I2C开启就要先把SDA和SCL置高电平其次再一起置低电平。要让I2C停止就要先把SDA置低电平其次再一起置高电平。我们发送数据就要在Start和Stop两函数之间进行。void Myself_I2C_Start(void) { W_I2C_SDA(1); W_I2C_SCL(1); W_I2C_SDA(0); W_I2C_SCL(0); } void Myself_I2C_Stop(void) { W_I2C_SDA(0); W_I2C_SCL(1); W_I2C_SDA(1); }接下来就是I2C的发送字节函数和接收字节函数。以for循环发送8个比特。根据I2C通信原则SCL高电平读取低电平等待SDA发送数据。这样就封装好了发送一字节的函数。接收字节函数的原理也是一样以for循环转运字节。接着看设置的SDA引脚是否有电平变化若没有则SDA发送完数据。最后SCL置低电平这样就封装好了接收字节函数。void MyI2C_SendByte(uint8_t Byte) { uint8_t i; for (i 0; i 8; i) { W_I2C_SDA(!!(Byte (0x80 i))); W_I2C_SCL(1); W_I2C_SCL(0); } } uint8_t MyI2C_ReceiveByte(void) { uint8_t i; uint8_t Byte 0X00; W_I2C_SDA(1); for (i 0; i 8; i) { W_I2C_SCL(1); if (R_I2C()) { Byte | (0X80 i); } W_I2C_SCL(0); } return Byte; }接下来就是I2C的是否连续发送节函数和应答函数。若想继续发送字节就置AckByte值为1反之为0。应答函数原理是判断SDA是否变化若没有则操做完成紧接着就返回应答值。每次操作完都要进行应答让硬件知道你做完了。void MyI2C_SendAck(uint8_t AckByte) { W_I2C_SDA(AckByte); W_I2C_SCL(1); W_I2C_SCL(0); } uint8_t MyI2C_ReceiveAck(void) { uint8_t AckByte; W_I2C_SDA(1); W_I2C_SCL(1); AckByte R_I2C(); W_I2C_SCL(0); return AckByte; }三.基于软件I2C配置MPU6050:I2C_Start后跟上MPU6050的地址MPU6050_Address等待标志位后跟上写的地址Rg_Address再次等待标志位之后跟上写入的数据Date再次等待标志位最后使用I2C_Stop停止即可完成MPU6050的写入操作。void MPU6050_Write(uint8_t Rg_Address, uint8_t Date) { Myself_I2C_Start(); MyI2C_SendByte(MPU6050_Address); MyI2C_ReceiveAck(); MyI2C_SendByte(Rg_Address); MyI2C_ReceiveAck(); MyI2C_SendByte(Date); MyI2C_ReceiveAck(); Myself_I2C_Stop(); }前几行一样发送MPU6050的地址发送写入地址。紧接着就发送MPU6050_Address | 0x01表示现在更改为读模式最后返回读到的数据。uint8_t MPU6050_Read(uint8_t Rg_Address) { uint8_t Date; Myself_I2C_Start(); MyI2C_SendByte(MPU6050_Address); MyI2C_ReceiveAck(); MyI2C_SendByte(Rg_Address); MyI2C_ReceiveAck(); Myself_I2C_Start(); MyI2C_SendByte(MPU6050_Address | 0x01); MyI2C_ReceiveAck(); Date MyI2C_ReceiveByte(); MyI2C_SendAck(1); Myself_I2C_Stop(); return Date; }接下来用写函数配置MPU6050的内部寄存器就初始化好了MPU6050电源管理寄存器1取消睡眠模式选择时钟源为X轴陀螺仪电源管理寄存器2保持默认值0所有轴均不待机采样率分频寄存器配置采样率配置寄存器配置DLPF陀螺仪配置寄存器选择满量程为±2000°/s加速度计配置寄存器选择满量程为±16gvoid MPU6050_Init(void) { Myself_I2C_Init(); MPU6050_Write(MPU6050_PWR_MGMT_1, 0x01); //电源管理寄存器1取消睡眠模式选择时钟源为X轴陀螺仪 MPU6050_Write(MPU6050_PWR_MGMT_2, 0x00); //电源管理寄存器2保持默认值0所有轴均不待机 MPU6050_Write(MPU6050_SMPLRT_DIV, 0x09); //采样率分频寄存器配置采样率 MPU6050_Write(MPU6050_CONFIG, 0x06); //配置寄存器配置DLPF MPU6050_Write(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器选择满量程为±2000°/s MPU6050_Write(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器选择满量程为±16g }最后就是把MPU6050的内部数据寄存器用指针返回出来返回值为16位所以要移位。void MPU6050_GetDate(int16_t *AccelX, int16_t *AccelY, int16_t *AccelZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) { uint8_t DateH, DateL; DateH MPU6050_Read(MPU6050_ACCEL_XOUT_H); DateL MPU6050_Read(MPU6050_ACCEL_XOUT_L); *AccelX (DateH 8) | DateL; DateH MPU6050_Read(MPU6050_ACCEL_YOUT_H); DateL MPU6050_Read(MPU6050_ACCEL_YOUT_L); *AccelY (DateH 8) | DateL; DateH MPU6050_Read(MPU6050_ACCEL_ZOUT_H); DateL MPU6050_Read(MPU6050_ACCEL_ZOUT_L); *AccelZ (DateH 8) | DateL; DateH MPU6050_Read(MPU6050_GYRO_XOUT_H); DateL MPU6050_Read(MPU6050_GYRO_XOUT_L); *GyroX (DateH 8) | DateL; DateH MPU6050_Read(MPU6050_GYRO_YOUT_H); DateL MPU6050_Read(MPU6050_GYRO_YOUT_L); *GyroY (DateH 8) | DateL; DateH MPU6050_Read(MPU6050_GYRO_ZOUT_H); DateL MPU6050_Read(MPU6050_GYRO_ZOUT_L); *GyroZ (DateH 8) | DateL; }四.数据映射用以上所封装的函数就可以配置完所有硬件了接下来是数据的处理。数据解释-2000°陀螺仪的取值为-3276732768 陀机速度取值为0180对于陀螺仪大多数情况下都是取不到最大或最小值的所以就用Sensitive设置陀螺仪的最大值。陀机速度最大值是经常取到的所以就用比例关系得出两者间的比例 K ,再用陀螺仪GY原始值乘以比例 K 就可以的出来陀螺仪关于陀机的速度。即 舵机最大速度/陀螺仪最大值*陀螺仪GY原始值360°陀机速度值只要不是90都会转动所以这里不难看出还要再加相位差若想静止时陀机不转就取90。即 舵机最大速度/陀螺仪最大值*陀螺仪GY原始值 相位差考虑到很难有绝对水平面的情况 所以就加了误差值。放在水平桌子上OLED显示的SGX陀螺仪GX的陀机实际速度和SGY陀螺仪GY的陀机实际速度通常都会在8793间。要让它的值为90保证水平的时候陀机不转动所以就需要-3得到SGX和SGY都为90。可以得出陀螺仪GX关于陀机的速度舵机最大速度/陀螺仪最大值*陀螺仪GY原始值相位差 误差同理陀螺仪GY关于陀机的速度舵机最大速度/陀螺仪最大值*陀螺仪GY原始值相位差 误差实际数据参考Sensitive是陀螺仪的最大值 如果选择—2000°的陀螺仪 范围为 -32767.0 ~ 32768.0 正负旋转反向相反 越接近0灵敏越高 测试的合适值为7000.0。Gyro_macgine_speed是360°陀机的最大速度参数90停止, 0~90顺时针, 90~180逆时针, 0和180最快若取x 则最大速度为(x 90)。建议取90.0。Phase_difference是相位差范围0 ~ 180若想静止时陀机不转就取90, 根据实际需求情况选择。Error是误差值 经测试在水平面下误差通常在(-3,3)之间只有绝对水平面下误差才为0, 可以根据环境的倾斜角度取值。在我的环境下我取1。此代码也消除了速度大于陀机最大速度的情况避免速度过大导致陀机无法转动。int8_t Groy_Servo_GY(void) { float Speed (Gyro_macgine_speed / Sensitive)*GY (Phase_difference Error); if (Speed 180 Speed 0) { Speed_GY Speed; } else if(Speed 180) { Speed 180; Speed_GY Speed; } else if(Speed 0) { Speed 0; Speed_GY Speed; } return Speed_GY; }GX也是同理。int8_t Groy_Servo_GX(void) { float Speed (Gyro_macgine_speed / Sensitive)*GX (Phase_difference Error); if (Speed 180 Speed 0) { Speed_GX Speed; } else if(Speed 180) { Speed 180; Speed_GX Speed; } else if(Speed 0) { Speed 0; Speed_GX Speed; } return Speed_GX; }五.main文件下面是main文件数据实时显示在OLED上面。int16_t AX, AY, AZ, GX, GY, GZ; int main(void) { OLED_Init(); MPU6050_Init(); Servo_Init(); while(1) { MPU6050_GetDate(AX, AY, AZ, GX, GY, GZ); Servo_SetAngle2_1(Groy_Servo_GY()); Servo_SetAngle3_1(Groy_Servo_GX()); OLED_ShowString(1, 1, SGX:); //GX对应的陀机速度值 OLED_ShowString(1, 9, SGY:); //GY对应的陀机速度值 OLED_ShowNum(1, 5, Speed_GY, 3); //GX对应的陀机速度值 OLED_ShowNum(1, 13, Speed_GX, 3); //GY对应的陀机速度值 OLED_ShowSignedNum(2, 1, AX, 5); //x轴上的角度原始值 OLED_ShowSignedNum(2, 8, GY, 5); //x轴上的加速度原始值 OLED_ShowSignedNum(3, 1, AY, 5); //y轴上的角度原始值 OLED_ShowSignedNum(3, 8, GX, 5); //y轴上的加速度原始值 } }有问题或有大佬指点可以发邮件给我谢谢谢谢1472464885qq.com