【Arduino 动手做】DIY Arduino 云台 | 三轴自稳定平台

在这里插入图片描述

《Arduino 手册(思路与案例)》栏目介绍:
在电子制作与智能控制的应用领域:广泛涉及了Arduino BLDC、Arduino CNC、Arduino ESP32 SPP、Arduino FreeRTOS、Arduino FOC、Arduino GRBL、Arduino HTTP、Arduino HUB75、Arduino IoT Cloud、Arduino JSON、Arduino LCD、Arduino OLED、Arduino LVGL、Arduino PID 及 Arduino TFT 等方面的相关拓展思路和众多参考案例。本专栏目前博客近2300篇。
https://blog.csdn.net/weixin_41659040/category_12422453.html

Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来编写、编译和上传代码到Arduino板上。Arduino还有一个丰富的库和社区,你可以利用它们来扩展Arduino的功能和学习Arduino的知识。

Arduino的特点是:
1、开放源码:Arduino的硬件和软件都是开放源码的,你可以自由地修改、复制和分享它们。
2、易用:Arduino的硬件和软件都是为初学者和非专业人士设计的,你可以轻松地上手和使用它们。
3、便宜:Arduino的硬件和软件都是非常经济的,你可以用很低的成本来实现你的想法。
4、多样:Arduino有多种型号和版本,你可以根据你的需要和喜好来选择合适的Arduino板。
5、创新:Arduino可以让你用电子的方式来表达你的创意和想象,你可以用Arduino来制作各种有趣和有用的项目,如机器人、智能家居、艺术装置等。

在这里插入图片描述

在本教程中,我们将学习如何构建 Arduino Gimbal 或使用伺服电机的自稳定平台。本教程实际上是上一个关于 MPU6050 教程的教程的扩展。

在这里插入图片描述

目录

1、概述
DIY Arduino Gimbal - 自稳平台 STL 文件
2、装配
3、Arduino Gimbal 电路图
Arduino 代码
4、DIY Arduino Gimbal - 自稳定平台 Code

一、概述

我使用 3D 建模软件设计了万向节。它由 3 个用于 3 轴控制的 MG996R 伺服电机和一个放置MPU6050传感器、Arduino 和电池的底座组成。

Arduino Gimbal 3D 模型

在这里插入图片描述

您可以在 Thangs 上找到并下载此 3D 模型,并在浏览器中进行探索。

DIY Arduino Gimbal - 自稳平台 STL 文件(见项目下载)

在这里插入图片描述

使用我的 Creality CR-10 3D 打印机,我 3D 打印了所有部件,它们都非常完美。

DIY Gimbal 3D 打印部件

在这里插入图片描述

二、装配

组装万向节非常简单。我从安装 Yaw 伺服开始。我使用 M3 螺栓和螺母将其固定到底座上。
Arduino 云台伺服电机安装

在这里插入图片描述

接下来,使用相同的方法固定 Roll 伺服器。这些部件经过专门设计,可轻松安装 MG996R 伺服系统。

在这里插入图片描述

为了将零件相互连接,我使用了圆角,它是伺服器的附件。

在这里插入图片描述

首先,我们需要用两个螺栓将圆角固定到底座上,然后用另一个螺栓将其连接到前一个伺服器上。

组装 Arduino 云台

在这里插入图片描述

我重复了这个过程来组装其余的组件,即 Pitch 伺服器和顶部平台。

Arduino 自稳定平台
在这里插入图片描述

接下来,我将伺服线穿过支架开口,以保持它们井井有条。然后我插入 MPU6050 传感器并用螺栓和螺母将其固定在底座上。

带有 MPU6050 传感器的 Arduino 自稳定平台
在这里插入图片描述

为了给项目供电,我使用了 2 节锂离子电池,并将其放在这个电池座中。我使用两个螺栓和螺母将电池座固定到底座上。

锂离子电池座
在这里插入图片描述

2 节锂离子电池将产生大约 7.4V 的电压,但我们需要 5V 来为 Arduino 和伺服系统供电。

在这里插入图片描述

这就是为什么我使用了一个降压转换器,它可以将 7.4V 转换为 5V。

三、Arduino Gimbal 电路图

现在剩下的就是把所有东西都连接在一起。这是这个项目的电路图以及需要如何连接所有内容。

DIY Arduino Gimbal - 自稳定平台

在这里插入图片描述

您可以自行获取此 Arduino 教程所需的组件:

MPU6050 IMU …亚马逊 / Banggood / 全球速卖通
MG996R 伺服…亚马逊 / Banggood / 全球速卖通
Buck Converter … 降压转换器亚马逊 / Banggood / 全球速卖通
Arduino 板 …亚马逊 / Banggood / 全球速卖通
试验板和跳线 …亚马逊 / Banggood / 全球速卖通

最后,我将电子元件和电线挤入底座中,并使用底部的这个盖子覆盖它们。

在这里插入图片描述

这样,自平衡平台或 Arduino 万向节就完成了,并且按预期运行良好。剩下的就是看看这个程序。

DIY Arduino 云台自稳平台 带 MPU6050 传感器
在这里插入图片描述

三、Arduino 代码
此示例的 Arduino 代码是对 Jeff Rowberg 的 i2cdevlib 库中的 MPU6050_DMP6 示例的修改。

开源代码:

/*
  DIY Gimbal - MPU6050 Arduino Tutorial
  by Dejan, www.HowToMechatronics.com
  Code based on the MPU6050_DMP6 example from the i2cdevlib library by Jeff Rowberg:
  https://github.com/jrowberg/i2cdevlib
*/
// I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h files
// for both classes must be in the include path of your project
#include "I2Cdev.h"

#include "MPU6050_6Axis_MotionApps20.h"
//#include "MPU6050.h" // not necessary if using MotionApps include file

// Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation
// is used in I2Cdev.h
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
#include "Wire.h"
#endif
#include <Servo.h>
// class default I2C address is 0x68
// specific I2C addresses may be passed as a parameter here
// AD0 low = 0x68 (default for SparkFun breakout and InvenSense evaluation board)
// AD0 high = 0x69
MPU6050 mpu;
//MPU6050 mpu(0x69); // <-- use for AD0 high

// Define the 3 servo motors
Servo servo0;
Servo servo1;
Servo servo2;
float correct;
int j = 0;

#define OUTPUT_READABLE_YAWPITCHROLL

#define INTERRUPT_PIN 2  // use pin 2 on Arduino Uno & most boards

bool blinkState = false;

// MPU control/status vars
bool dmpReady = false;  // set true if DMP init was successful
uint8_t mpuIntStatus;   // holds actual interrupt status byte from MPU
uint8_t devStatus;      // return status after each device operation (0 = success, !0 = error)
uint16_t packetSize;    // expected DMP packet size (default is 42 bytes)
uint16_t fifoCount;     // count of all bytes currently in FIFO
uint8_t fifoBuffer[64]; // FIFO storage buffer

// orientation/motion vars
Quaternion q;           // [w, x, y, z]         quaternion container
VectorInt16 aa;         // [x, y, z]            accel sensor measurements
VectorInt16 aaReal;     // [x, y, z]            gravity-free accel sensor measurements
VectorInt16 aaWorld;    // [x, y, z]            world-frame accel sensor measurements
VectorFloat gravity;    // [x, y, z]            gravity vector
float euler[3];         // [psi, theta, phi]    Euler angle container
float ypr[3];           // [yaw, pitch, roll]   yaw/pitch/roll container and gravity vector

// packet structure for InvenSense teapot demo
uint8_t teapotPacket[14] = { '$', 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0x00, 0x00, '\r', '\n' };



// ================================================================
// ===               INTERRUPT DETECTION ROUTINE                ===
// ================================================================

volatile bool mpuInterrupt = false;     // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
  mpuInterrupt = true;
}

// ================================================================
// ===                      INITIAL SETUP                       ===
// ================================================================

void setup() {
  // join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
  Wire.begin();
  Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
#elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
  Fastwire::setup(400, true);
#endif

  // initialize serial communication
  // (115200 chosen because it is required for Teapot Demo output, but it's
  // really up to you depending on your project)
  Serial.begin(38400);
  while (!Serial); // wait for Leonardo enumeration, others continue immediately

  // initialize device
  //Serial.println(F("Initializing I2C devices..."));
  mpu.initialize();
  pinMode(INTERRUPT_PIN, INPUT);
  devStatus = mpu.dmpInitialize();
  // supply your own gyro offsets here, scaled for min sensitivity
  mpu.setXGyroOffset(17);
  mpu.setYGyroOffset(-69);
  mpu.setZGyroOffset(27);
  mpu.setZAccelOffset(1551); // 1688 factory default for my test chip

  // make sure it worked (returns 0 if so)
  if (devStatus == 0) {
    // turn on the DMP, now that it's ready
    // Serial.println(F("Enabling DMP..."));
    mpu.setDMPEnabled(true);

    attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
    mpuIntStatus = mpu.getIntStatus();

    // set our DMP Ready flag so the main loop() function knows it's okay to use it
    //Serial.println(F("DMP ready! Waiting for first interrupt..."));
    dmpReady = true;

    // get expected DMP packet size for later comparison
    packetSize = mpu.dmpGetFIFOPacketSize();
  } else {
    // ERROR!
    // 1 = initial memory load failed
    // 2 = DMP configuration updates failed
    // (if it's going to break, usually the code will be 1)
    // Serial.print(F("DMP Initialization failed (code "));
    //Serial.print(devStatus);
    //Serial.println(F(")"));
  }

  // Define the pins to which the 3 servo motors are connected
  servo0.attach(10);
  servo1.attach(9);
  servo2.attach(8);
}
// ================================================================
// ===                    MAIN PROGRAM LOOP                     ===
// ================================================================

void loop() {
  // if programming failed, don't try to do anything
  if (!dmpReady) return;

  // wait for MPU interrupt or extra packet(s) available
  while (!mpuInterrupt && fifoCount < packetSize) {
    if (mpuInterrupt && fifoCount < packetSize) {
      // try to get out of the infinite loop
      fifoCount = mpu.getFIFOCount();
    }
  }

  // reset interrupt flag and get INT_STATUS byte
  mpuInterrupt = false;
  mpuIntStatus = mpu.getIntStatus();

  // get current FIFO count
  fifoCount = mpu.getFIFOCount();

  // check for overflow (this should never happen unless our code is too inefficient)
  if ((mpuIntStatus & _BV(MPU6050_INTERRUPT_FIFO_OFLOW_BIT)) || fifoCount >= 1024) {
    // reset so we can continue cleanly
    mpu.resetFIFO();
    fifoCount = mpu.getFIFOCount();
    Serial.println(F("FIFO overflow!"));

    // otherwise, check for DMP data ready interrupt (this should happen frequently)
  } else if (mpuIntStatus & _BV(MPU6050_INTERRUPT_DMP_INT_BIT)) {
    // wait for correct available data length, should be a VERY short wait
    while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();

    // read a packet from FIFO
    mpu.getFIFOBytes(fifoBuffer, packetSize);

    // track FIFO count here in case there is > 1 packet available
    // (this lets us immediately read more without waiting for an interrupt)
    fifoCount -= packetSize;

    // Get Yaw, Pitch and Roll values
#ifdef OUTPUT_READABLE_YAWPITCHROLL
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);

    // Yaw, Pitch, Roll values - Radians to degrees
    ypr[0] = ypr[0] * 180 / M_PI;
    ypr[1] = ypr[1] * 180 / M_PI;
    ypr[2] = ypr[2] * 180 / M_PI;
    
    // Skip 300 readings (self-calibration process)
    if (j <= 300) {
      correct = ypr[0]; // Yaw starts at random value, so we capture last value after 300 readings
      j++;
    }
    // After 300 readings
    else {
      ypr[0] = ypr[0] - correct; // Set the Yaw to 0 deg - subtract  the last random Yaw value from the currrent value to make the Yaw 0 degrees
      // Map the values of the MPU6050 sensor from -90 to 90 to values suatable for the servo control from 0 to 180
      int servo0Value = map(ypr[0], -90, 90, 0, 180);
      int servo1Value = map(ypr[1], -90, 90, 0, 180);
      int servo2Value = map(ypr[2], -90, 90, 180, 0);
      
      // Control the servos according to the MPU6050 orientation
      servo0.write(servo0Value);
      servo1.write(servo1Value);
      servo2.write(servo2Value);
    }
#endif
  }
}

代码解读:

因此,我们使用可读的偏航、俯仰和滚转输出。

// Get Yaw, Pitch and Roll values
#ifdef OUTPUT_READABLE_YAWPITCHROLL
    mpu.dmpGetQuaternion(&q, fifoBuffer);
    mpu.dmpGetGravity(&gravity, &q);
    mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);

    // Yaw, Pitch, Roll values - Radians to degrees
    ypr[0] = ypr[0] * 180 / M_PI;
    ypr[1] = ypr[1] * 180 / M_PI;
    ypr[2] = ypr[2] * 180 / M_PI;
    
    // Skip 300 readings (self-calibration process)
    if (j <= 300) {
      correct = ypr[0]; // Yaw starts at random value, so we capture last value after 300 readings
      j++;
    }
    // After 300 readings
    else {
      ypr[0] = ypr[0] - correct; // Set the Yaw to 0 deg - subtract  the last random Yaw value from the currrent value to make the Yaw 0 degrees
      // Map the values of the MPU6050 sensor from -90 to 90 to values suatable for the servo control from 0 to 180
      int servo0Value = map(ypr[0], -90, 90, 0, 180);
      int servo1Value = map(ypr[1], -90, 90, 0, 180);
      int servo2Value = map(ypr[2], -90, 90, 180, 0);
      
      // Control the servos according to the MPU6050 orientation
      servo0.write(servo0Value);
      servo1.write(servo1Value);
      servo2.write(servo2Value);
    }
#endif

获得值后,首先将它们从弧度转换为度数。

// Yaw, Pitch, Roll values - Radians to degrees
    ypr[0] = ypr[0] * 180 / M_PI;
    ypr[1] = ypr[1] * 180 / M_PI;
    ypr[2] = ypr[2] * 180 / M_PI;

然后我们等待或进行 300 次读数,因为在此期间传感器仍处于自校准过程中。此外,我们还捕获了 Yaw 值,该值开始时不像 Pitch 和 Roll 值那样为 0,而是始终是某个随机值。

// Skip 300 readings (self-calibration process)
    if (j <= 300) {
      correct = ypr[0]; // Yaw starts at random value, so we capture last value after 300 readings
      j++;
    }

在 300 个读数之后,首先我们通过减去上述捕获的随机值,将偏航设置为 0。然后我们将 Yaw、Pitch 和 Roll 的值从 – 90 度映射到 +90 度,映射到 0 到 180 之间的值,用于驱动伺服系统。

// After 300 readings
    else {
      ypr[0] = ypr[0] - correct; // Set the Yaw to 0 deg - subtract  the last random Yaw value from the currrent value to make the Yaw 0 degrees
      // Map the values of the MPU6050 sensor from -90 to 90 to values suatable for the servo control from 0 to 180
      int servo0Value = map(ypr[0], -90, 90, 0, 180);
      int servo1Value = map(ypr[1], -90, 90, 0, 180);
      int servo2Value = map(ypr[2], -90, 90, 180, 0);
      
      // Control the servos according to the MPU6050 orientation
      servo0.write(servo0Value);
      servo1.write(servo1Value);
      servo2.write(servo2Value);
    }

最后使用 write 函数,我们将这些值作为控制信号发送到舵机。当然,如果您只想对 X 轴和 Y 轴进行稳定,您可以禁用偏航舵机,并将此平台用作相机万向节。

请注意,这与好的相机云台相去甚远。运动并不流畅,因为这些伺服系统不是为了这样的目的。真正的相机万向节使用一种特殊类型的 BLDC 电机来实现平稳的运动。因此,仅将此项目视为教育目的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

驴友花雕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值