Lecture 강의

Undergraduates 학부 Graduates 대학원 Lecture 역학 & 설계 Lecture IT(IoT, AI) Lecture SAP2000 & ETABS Lecture 아바쿠스 Lecture 파이썬 Lecture 엑셀 VBA Lecture 마이다스

[박주민][파이썬과 아두이노] 13. 3축 가속도

작성자 : 박주민

(2024-05-26)

조회수 : 176

▪ 드라이버

    이번주는 라이브러리를 다운로드 받아서 진행할경우 오류가 많이 발생해서

    아두이노 자체에서 라이브러리를 추가하는 방식으로 진행 예정입니다.

    이번주에 가속도와 회전까지 진행해 볼 예정입니다

 

    I2Cdev_library.zip [LINK]

    MPU6050_library.zip [LINK]

 

▪ 3축가속도계 선서(mpu-6050) 자료

    [YouTube] 049. 아두이노 모든 부속 - MPU6050(가속도 자이로 센서), kProject Arduino DIY [LINK]

    [블러그] Dreaming Developer, 드론-MPU6050 가속도 자이로 센서 이해, 네이버 [LINK]

    

▪ MPU6050은 InvenSense에서 개발한 고성능 6축 관성 측정 장치이다.
 

  MPU6050의 구성 요소 및 기능

  1. 자이로스코프 (Gyroscope):

    • 자이로스코프는 회전 속도를 측정하는데 사용됩니다.
    • MPU6050에는 3축 자이로스코프가 내장되어 있어서 x, y, z 축 주위의 회전 속도를 측정할 수 있습니다.
    • 이러한 회전 속도 정보는 물체의 자세 및 회전 운동을 추적하는 데 사용됩니다.
  2. 가속도계 (Accelerometer):

    • 가속도계는 물체의 가속도를 측정하는데 사용됩니다.
    • MPU6050에는 3축 가속도계가 내장되어 있어서 x, y, z 축 방향의 가속도를 측정할 수 있습니다.
    • 이러한 가속도 정보는 물체의 가속 및 중력 가속도를 분석하여 움직임을 추적하는 데 사용됩니다.
  3. DMP (Digital Motion Processor):

    • MPU6050에는 Digital Motion Processor(DMP)가 내장되어 있습니다.
    • DMP는 센서 데이터를 실시간으로 처리하고, 복잡한 계산을 수행하여 오일러 각도, 가속도, 자세 등의 정보를 제공합니다.
    • 이를 통해 MCU의 부하를 줄이고, 복잡한 센서 데이터 처리를 MCU의 외부로 오프로드할 수 있습니다.
    • 또한 DMP는 센서 데이터의 필터링, 보정 및 자세 추정을 자동으로 처리하여 사용자가 더 쉽게 관성 측정을 구현할 수 있도록 지원합니다.
  4. 인터페이스 및 통신:

    • MPU6050는 I2C 또는 SPI 인터페이스를 통해 마이크로컨트롤러와 통신합니다.
    • 주로 I2C 통신이 사용되며, 이는 매우 간단하고 사용하기 쉬운 인터페이스입니다.
    • 또한 MPU6050은 내장된 FIFO(First In, First Out) 버퍼를 통해 센서 데이터를 버퍼링하고, 여러 센서 데이터 샘플을 한 번에 처리할 수 있습니다.

    MPU6050는 저전력, 고정확도, 적은 크기 및 비용 효율성 등의 장점을 가지고 있어서 드론, 자율 주행 차량, 가상 현실 헤드셋, 웨어러블 디바이스 및 로봇 제어 등 다양한 응용 분야에서 널리 사용되고 있습니다.

 

▪ 약어

    YPR은 자이로스코프와 가속도계를 사용하여 측정한 자세를 나타낸다.

    YPR은 yaw (yaw rate), pitch (pitch angle), roll (roll angle)의 약어이다.

    

 

YPR은 자이로스코프와 가속도계를 통해 측정한 자세를 나타냅니다. 이는 운동을 감지하고 그에 따른 회전 속도와 기울기를 파악하는 데에 중요한 지표입니다.

  1. Yaw (Yaw Rate):

    • Yaw는 z축을 중심으로의 회전 속도를 의미합니다.
    • 주로 지자기 센서와 결합하여 사용되어 오일러 각 중 하나인 yaw 각도를 계산하는 데에 활용됩니다.
    • 이 값은 차량이나 로봇의 방향을 조정하거나, 항공기의 헤딩을 유지하는 데 사용됩니다.
  2. Pitch (Pitch Angle):

    • Pitch는 y축을 중심으로의 회전 각도를 의미합니다.
    • 지표면에 대한 앞뒤로의 기울기를 나타내며, 드론이나 자동차의 전후방 움직임을 제어하는 데 사용됩니다.
  3. Roll (Roll Angle):

    • Roll은 x축을 중심으로의 회전 각도를 의미합니다.
    • 좌우로의 기울기를 나타내며, 비행체나 차량의 좌우 기울기를 측정하여 안정적인 운동을 유지하는 데에 사용됩니다.

이러한 YPR 값들은 다양한 응용 분야에서 사용됩니다.
예를 들어, 드론이나 자율 주행 차량에서는 안정적인 비행이나 주행을 유지하기 위해 사용될 뿐만 아니라, 항공기에서는 헤딩을 조절하며
가상 현실 헤드셋에서는 사용자의 시점을 추적하여 현실감 있는 경험을 제공합니다.

[1] 가속도 센서 코드

#include <Adafruit_MPU6050.h> // MPU6050 센서 라이브러리를 불러옵니다.
#include <Adafruit_Sensor.h> // 센서 라이브러리를 불러옵니다.
#include <Wire.h> // I2C 통신을 위한 라이브러리를 불러옵니다.

Adafruit_MPU6050 mpu; // MPU6050 객체를 생성합니다.

void setup(void) {
  Serial.begin(115200); // 시리얼 통신을 115200 보드레이트로 시작합니다.

  // MPU6050 센서 초기화
  while (!mpu.begin()) { // MPU6050이 초기화되지 않았을 때 반복합니다.
    Serial.println("MPU6050 not connected!"); // MPU6050이 연결되지 않았음을 알리는 메시지를 출력합니다.
    delay(1000); // 1초의 딜레이를 줍니다.
  }
  Serial.println("MPU6050 ready!"); // MPU6050이 초기화되었음을 알리는 메시지를 출력합니다.
}

sensors_event_t event; // 센서 이벤트 객체를 생성합니다.

void loop() {
  mpu.getAccelerometerSensor()->getEvent(&event); // 가속도 센서에서 이벤트를 가져옵니다.

  // 시리얼로 가속도 데이터 출력
  Serial.print(event.acceleration.x); // X축 가속도 값을 출력합니다.
  Serial.print(","); // 쉼표로 구분합니다.
  Serial.print(event.acceleration.y); // Y축 가속도 값을 출력합니다.
  Serial.print(","); // 쉼표로 구분합니다.
  Serial.println(event.acceleration.z); // Z축 가속도 값을 출력하고 개행합니다.

  delay(100); // 100ms의 딜레이를 줍니다.
}

[2] 자이로 센서 코드

#include <Adafruit_MPU6050.h> // Adafruit_MPU6050 라이브러리를 불러옵니다.
#include <Adafruit_Sensor.h> // Adafruit_Sensor 라이브러리를 불러옵니다.
#include <Wire.h> // I2C 통신을 위한 Wire 라이브러리를 불러옵니다.

Adafruit_MPU6050 mpu; // Adafruit_MPU6050 객체를 생성합니다.

void setup(void) {
  Serial.begin(115200); // 시리얼 통신을 115200 보드레이트로 시작합니다.

  while (!mpu.begin()) { // MPU6050 초기화가 실패할 경우 반복합니다.
    Serial.println("MPU6050 not connected!"); // MPU6050이 연결되지 않았음을 알리는 메시지를 출력합니다.
    delay(1000); // 1초 대기합니다.
  }
  Serial.println("MPU6050 ready!"); // MPU6050 초기화가 완료되면 메시지를 출력합니다.
}

sensors_event_t event; // 센서 이벤트를 저장할 객체를 선언합니다.

void loop() {
  mpu.getGyroSensor()->getEvent(&event); // 자이로 센서에서 이벤트를 읽어옵니다.

  // 시리얼 모니터로 자이로 데이터를 출력합니다.
  Serial.print(event.gyro.x); // X축 자이로 값 출력합니다.
  Serial.print(","); // 쉼표로 구분합니다.
  Serial.print(event.gyro.y); // Y축 자이로 값 출력합니다.
  Serial.print(","); // 쉼표로 구분합니다.
  Serial.println(event.gyro.z); // Z축 자이로 값 출력합니다.

  delay(100); // 100ms의 딜레이를 줍니다.
}

[3] YPR 코드

#include "I2Cdev.h"

#include "MPU6050_6Axis_MotionApps20.h"

 

#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE

    #include "Wire.h"

#endif

 

MPU6050 mpu; // MPU6050 객체를 생성합니다.

 

#define OUTPUT_READABLE_YAWPITCHROLL // Yaw, Pitch, Roll 값을 출력합니다.

#define INTERRUPT_PIN 2 // 인터럽트 핀을 2번으로 설정합니다.

 

bool dmpReady = false; // DMP 준비 상태를 나타내는 플래그 변수입니다.

uint8_t mpuIntStatus; // MPU6050의 인터럽트 상태를 저장하는 변수입니다.

uint8_t devStatus; // MPU6050의 장치 상태를 저장하는 변수입니다.

uint16_t packetSize; // DMP 패킷의 크기를 저장하는 변수입니다.

uint16_t fifoCount; // FIFO 버퍼의 현재 크기를 저장하는 변수입니다.

uint8_t fifoBuffer[64]; // FIFO 버퍼를 저장하는 배열입니다.

 

Quaternion q; // 쿼터니언을 저장하는 변수입니다.

VectorInt16 aa; // 가속도계의 가속도 값을 저장하는 변수입니다.

VectorInt16 aaReal; // 가속도계의 실제 가속도 값을 저장하는 변수입니다.

VectorInt16 aaWorld; // 가속도계의 월드 좌표계 가속도 값을 저장하는 변수입니다.

VectorFloat gravity; // 중력 벡터를 저장하는 변수입니다.

float euler[3]; // 오일러 각을 저장하는 변수입니다.

float ypr[3]; // yaw, pitch, roll 값을 저장하는 변수입니다.

 

uint8_t teapotPacket[14] = { '$', 0x02, 0,0, 0,0, 0,0, 0,0, 0x00, 0x00, '\r', '\n' }; // 티팟 패킷을 설정합니다.

 

volatile bool mpuInterrupt = false; // MPU6050 인터럽트 상태를 나타내는 플래그 변수입니다.

 

// MPU6050 인터럽트 핸들러 함수입니다.

void dmpDataReady() {

    mpuInterrupt = true; // MPU6050 인터럽트 상태를 설정합니다.

}

 

// 설정 함수입니다.

void setup() {

    #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE

        Wire.begin(); // I2C 통신을 초기화합니다.

        Wire.setClock(400000); // I2C 통신 속도를 설정합니다.

    #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE

        Fastwire::setup(400, true); // Fastwire를 설정합니다.

    #endif

 

    Serial.begin(115200); // 시리얼 통신을 초기화합니다.

    while (!Serial); // 시리얼 통신이 준비될 때까지 대기합니다.

 

    Serial.println(F("Initializing I2C devices...")); // I2C 장치 초기화 메시지를 출력합니다.

    mpu.initialize(); // MPU6050을 초기화합니다.

    pinMode(INTERRUPT_PIN, INPUT); // 인터럽트 핀을 입력으로 설정합니다.

 

    Serial.println(F("Testing device connections...")); // 장치 연결 테스트 메시지를 출력합니다.

    Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed")); // MPU6050 연결 상태를 출력합니다.

 

    Serial.println(F("\nSend any character to begin DMP programming and demo: ")); // DMP 프로그래밍 및 데모 시작 메시지를 출력합니다.

    while (Serial.available() && Serial.read()); // 시리얼 버퍼를 비웁니다.

    while (!Serial.available()); // 시리얼 입력이 있을 때까지 대기합니다.

    while (Serial.available() && Serial.read()); // 시리얼 버퍼를 비웁니다.

 

    Serial.println(F("Initializing DMP...")); // DMP 초기화 메시지를 출력합니다.

    devStatus = mpu.dmpInitialize(); // DMP를 초기화합니다.

 

    mpu.setXGyroOffset(220); // X축 자이로 오프셋을 설정합니다.

    mpu.setYGyroOffset(76); // Y축 자이로 오프셋을 설정합니다.

    mpu.setZGyroOffset(-85); // Z축 자이로 오프셋을 설정합니다.

    mpu.setZAccelOffset(1788); // Z축 가속도 오프셋을 설정합니다.

 

    if (devStatus == 0) { // 초기화 상태가 정상인 경우

        Serial.println(F("Enabling DMP...")); // DMP 활성화 메시지를 출력합니다.

        mpu.setDMPEnabled(true); // DMP를 활성화합니다.

 

        Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)...")); // 인터럽트 감지 활성화 메시지를 출력합니다.

        attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING); // 인터럽트를 설정합니다.

        mpuIntStatus = mpu.getIntStatus(); // MPU6050의 인터럽트 상태를 확인합니다.

 

        Serial.println(F("DMP ready! Waiting for first interrupt...")); // DMP 준비 완료 메시지를 출력합니다.

        dmpReady = true; // DMP 준비 상태를 설정합니다.

 

        packetSize = mpu.dmpGetFIFOPacketSize(); // DMP 패킷 크기를 가져옵니다.

    } else { // 초기화 상태가 비정상인 경우

        Serial.print(F("DMP Initialization failed (code ")); // DMP 초기화 실패 메시지를 출력합니다.

        Serial.print(devStatus); // 실패 코드를 출력합니다.

        Serial.println(F(")")); // 줄바꿈 문자를 출력합니다.

    }

}

// 루프 함수입니다.

void loop() {

    if (!dmpReady) return; // DMP가 준비되지 않았으면 함수를 종료합니다.

 

    while (!mpuInterrupt && fifoCount < packetSize) {} // MPU6050 인터럽트가 발생하거나 FIFO 버퍼 크기가 패킷 크기보다 작을 때까지 대기합니다.

 

    mpuInterrupt = false; // MPU6050 인터럽트 상태를 초기화합니다.

    mpuIntStatus = mpu.getIntStatus(); // MPU6050의 인터럽트 상태를 가져옵니다.

    fifoCount = mpu.getFIFOCount(); // FIFO 버퍼 크기를 가져옵니다.

 

    if ((mpuIntStatus & 0x10) || fifoCount == 1024) { // FIFO 오버플로우 상태인 경우

        mpu.resetFIFO(); // FIFO를 재설정합니다.

        Serial.println(F("FIFO overflow!")); // FIFO 오버플로우 메시지를 출력합니다.

    } else if (mpuIntStatus & 0x02) { // DMP 데이터가 준비된 경우

        while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount(); // FIFO 버퍼 크기가 패킷 크기보다 작을 때까지 대기합니다.

 

        mpu.getFIFOBytes(fifoBuffer, packetSize); // FIFO 버퍼에서 패킷 데이터를 읽어옵니다.

        fifoCount -= packetSize; // FIFO 버퍼 크기를 조정합니다.

 

        mpu.dmpGetQuaternion(&q, fifoBuffer); // 쿼터니언 데이터를 가져옵니다.

        mpu.dmpGetGravity(&gravity, &q); // 중력 벡터를 가져옵니다.

        mpu.dmpGetYawPitchRoll(ypr, &q, &gravity); // Yaw, Pitch, Roll 값을 계산합니다.

        Serial.print("ypr\t"); // YPR 값의 레이블을 출력합니다.

        Serial.print(ypr[0] * 180/M_PI); // Yaw 값을 출력합니다.

        Serial.print("\t"); // 탭 문자를 출력합니다.

        Serial.print(ypr[1] * 180/M_PI); // Pitch 값을 출력합니다.

        Serial.print("\t"); // 탭 문자를 출력합니다.

        Serial.println(ypr[2] * 180/M_PI); // Roll 값을 출력하고 줄을 바꿉니다.

    }

}