Q : 아두이노에 보조베터리로 전원을주고 블루투스모듈과 MPU6050을 아두이노에 연결한 상태에서 블루투스 시리얼 통신을 했어. 아두이노에 delay를 주지않으면 MPU6050으로부터 값을 못가져오는데 delay(100)정도 주면 값을 블루투스로 받아올수 있었어 delay 유무에 따라 왜이러는거야? 그런데 베터리를 안쓰고 PC 전원을 주면 delay를 안써도 정상적으로 보내져 베터리 전원 문제도 있는거야?
A : 보조 배터리로 전원을 줄 때와 PC 전원을 사용할 때의 차이는 전원의 안정성과 품질에 기인할 수 있습니다. 보조 배터리는 출력 전압이나 전류의 안정성이 PC USB 포트보다 상대적으로 낮을 수 있고, 이는 전자 부품의 정상 작동에 영향을 줄 수 있습니다.
MPU6050과 블루투스 모듈은 정확한 타이밍과 일정한 전원 공급이 필요한 부품들이므로, 전원 공급이 불안정하면 센서 데이터의 샘플링과 처리가 제대로 이루어지지 않을 수 있습니다. `delay(100)`을 추가함으로써, 센서가 데이터를 처리하고 준비하는데 필요한 시간을 확보하게 되며, 이는 불안정한 전원 상황에서도 데이터를 안정적으로 처리할 수 있는 여유를 제공합니다.
반면 PC USB 포트는 일반적으로 보다 안정적인 전원을 제공합니다. 따라서 전원 불안정이 센서의 데이터 처리에 미치는 영향이 적어, `delay` 없이도 데이터를 정상적으로 처리할 수 있습니다.
전원 문제를 해결하기 위해 다음과 같은 방법을 시도해보실 수 있습니다:
- 보조 배터리의 출력 전압과 전류가 아두이노와 연결된 모듈의 요구 사항을 충족하는지 확인하세요. - 전원 라인에 전압 조정기나 캐패시터와 같은 필터링 회로를 추가하여 전원 노이즈를 줄이세요. - 가능하다면, 더 안정적인 전원 공급 장치를 사용하세요.
이러한 조치를 통해 전원 공급 문제를 해결하고, 모든 조건에서 MPU6050과 블루투스 모듈이 안정적으로 작동하도록 할 수 있습니다.
#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
// 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 OUTPUT_READABLE_YAWPITCHROLL
#define LED_PIN 13 // (Arduino is 13, Teensy is 11, Teensy++ is 6)
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' };
volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
mpuInterrupt = true;
}
void setup() {
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
#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(115200);
while (!Serial); // wait for Leonardo enumeration, others continue immediately
// initialize device
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();
//pinMode(INTERRUPT_PIN, INPUT);
// verify connection
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// load and configure the DMP
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
mpu.setXGyroOffset(220);
mpu.setYGyroOffset(76);
mpu.setZGyroOffset(-85);
mpu.setZAccelOffset(1788); // 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);
// enable Arduino interrupt detection
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(0, dmpDataReady, RISING);
//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 {
Serial.print(F("DMP Initialization failed (code "));
Serial.print(devStatus);
Serial.println(F(")"));
}
// configure LED for output
pinMode(LED_PIN, OUTPUT);
}
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) {
}
// 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 & 0x10) || fifoCount == 1024) {
// reset so we can continue cleanly
mpu.resetFIFO();
Serial.println(F("FIFO overflow!"));
// otherwise, check for DMP data ready interrupt (this should happen frequently)
} else if (mpuIntStatus & 0x02) {
// 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;
#ifdef OUTPUT_READABLE_YAWPITCHROLL
// display Euler angles in degrees
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
Serial.print("ypr\t");
Serial.print(ypr[0] * 180/M_PI);
Serial.print("\t");
Serial.print(ypr[1] * 180/M_PI);
Serial.print("\t");
Serial.println(ypr[2] * 180/M_PI);
#endif
// blink LED to indicate activity
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState);
}
}
a : 아뇨, MPU6050 모듈은 I2C 통신을 통해 통신하는데, 주소를 변경하여 같은 버스 상에 여러 개의 모듈을 사용할 수 있습니다. 보통 MPU6050의 기본 주소는 0x68이지만, AD0 핀의 상태에 따라(AD0 핀이 GND에 연결되면 0x68, VCC에 연결되면 0x69) 주소를 변경할 수 있습니다. 두 개의 MPU6050 모듈을 사용할 때, 하나는 0x68 주소로 설정하고 다른 하나는 0x69 주소로 설정하여 충돌을 피할 수 있습니다.
q : 그러면 2개가 아니라 mpu6050을 5개 사용하는 경우 어떻게 하면되
a : 만약 MPU6050을 5개 사용하려면, 각각의 모듈에 고유한 I2C 주소를 할당해야 합니다. MPU6050 모듈은 AD0 핀을 통해 주소를 변경할 수 있습니다. AD0 핀을 다른 GPIO 핀에 연결하고 해당 핀을 사용하여 AD0 핀을 제어하여 주소를 변경할 수 있습니다. 예를 들어, 5개의 MPU6050 모듈을 사용하는 경우, 각 모듈에 다른 주소를 할당하기 위해 AD0 핀을 5개의 GPIO 핀에 연결하고, 해당 GPIO 핀을 사용하여 AD0 핀을 제어하여 주소를 변경할 수 있습니다. 이렇게 하면 각 MPU6050 모듈은 고유한 주소를 가지게 됩니다.
대충 얘 설명이 맞는지는 몰라도 mpu6050에 있는 ad0핀으로 mpu6050 여러개쓸때 고유 주소갖도록 변경할수 있나봄.
q : 아두이노 우노보드 mpu6050 사용하는 글 찾아봤는데 INT핀을 D2와 연결시키던데 이건왜하는거야? SCL, SDA만 연결해도 되지않아?
a : INT 핀을 D2에 연결하는 이유는 MPU6050의 인터럽트 기능을 사용하기 위함입니다. 이를 통해 특정 조건이 충족되었을 때 인터럽트를 발생시키고 처리할 수 있습니다. SCL과 SDA는 데이터 통신을 위한 핀이며, INT 핀은 MPU6050의 상태를 감지하고 처리하는 데 사용됩니다. 따라서 INT 핀을 D2와 연결하여 MPU6050의 인터럽트를 사용할 수 있습니다.
제대로 찾아본건 아니지만 국내 블로그 글에서는 int핀을 사용하는 이유를 제대로 설명하진 않고있다.
// I2C device class (I2Cdev) demonstration Arduino sketch for MPU6050 class using DMP (MotionApps v2.0)
// 6/21/2012 by Jeff Rowberg <jeff@rowberg.net>
// Updates should (hopefully) always be available at https://github.com/jrowberg/i2cdevlib
//
// Changelog:
// 2013-05-08 - added seamless Fastwire support
// - added note about gyro calibration
// 2012-06-21 - added note about Arduino 1.0.1 + Leonardo compatibility error
// 2012-06-20 - improved FIFO overflow handling and simplified read process
// 2012-06-19 - completely rearranged DMP initialization code and simplification
// 2012-06-13 - pull gyro and accel data from FIFO packet instead of reading directly
// 2012-06-09 - fix broken FIFO read sequence and change interrupt detection to RISING
// 2012-06-05 - add gravity-compensated initial reference frame acceleration output
// - add 3D math helper file to DMP6 example sketch
// - add Euler output and Yaw/Pitch/Roll output formats
// 2012-06-04 - remove accel offset clearing for better results (thanks Sungon Lee)
// 2012-06-01 - fixed gyro sensitivity to be 2000 deg/sec instead of 250
// 2012-05-30 - basic DMP initialization working
/* ============================================
I2Cdev device library code is placed under the MIT license
Copyright (c) 2012 Jeff Rowberg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
===============================================
*/
// 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
// 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
/* =========================================================================
NOTE: In addition to connection 3.3v, GND, SDA, and SCL, this sketch
depends on the MPU-6050's INT pin being connected to the Arduino's
external interrupt #0 pin. On the Arduino Uno and Mega 2560, this is
digital I/O pin 2.
* ========================================================================= */
/* =========================================================================
NOTE: Arduino v1.0.1 with the Leonardo board generates a compile error
when using Serial.write(buf, len). The Teapot output uses this method.
The solution requires a modification to the Arduino USBAPI.h file, which
is fortunately simple, but annoying. This will be fixed in the next IDE
release. For more info, see these links:
http://arduino.cc/forum/index.php/topic,109987.0.html
http://code.google.com/p/arduino/issues/detail?id=958
* ========================================================================= */
// uncomment "OUTPUT_READABLE_QUATERNION" if you want to see the actual
// quaternion components in a [w, x, y, z] format (not best for parsing
// on a remote host such as Processing or something though)
//#define OUTPUT_READABLE_QUATERNION
// uncomment "OUTPUT_READABLE_EULER" if you want to see Euler angles
// (in degrees) calculated from the quaternions coming from the FIFO.
// Note that Euler angles suffer from gimbal lock (for more info, see
// http://en.wikipedia.org/wiki/Gimbal_lock)
//#define OUTPUT_READABLE_EULER
// uncomment "OUTPUT_READABLE_YAWPITCHROLL" if you want to see the yaw/
// pitch/roll angles (in degrees) calculated from the quaternions coming
// from the FIFO. Note this also requires gravity vector calculations.
// Also note that yaw/pitch/roll angles suffer from gimbal lock (for
// more info, see: http://en.wikipedia.org/wiki/Gimbal_lock)
#define OUTPUT_READABLE_YAWPITCHROLL
// uncomment "OUTPUT_READABLE_REALACCEL" if you want to see acceleration
// components with gravity removed. This acceleration reference frame is
// not compensated for orientation, so +X is always +X according to the
// sensor, just without the effects of gravity. If you want acceleration
// compensated for orientation, us OUTPUT_READABLE_WORLDACCEL instead.
//#define OUTPUT_READABLE_REALACCEL
// uncomment "OUTPUT_READABLE_WORLDACCEL" if you want to see acceleration
// components with gravity removed and adjusted for the world frame of
// reference (yaw is relative to initial orientation, since no magnetometer
// is present in this case). Could be quite handy in some cases.
//#define OUTPUT_READABLE_WORLDACCEL
// uncomment "OUTPUT_TEAPOT" if you want output that matches the
// format used for the InvenSense teapot demo
//#define OUTPUT_TEAPOT
#define LED_PIN 13 // (Arduino is 13, Teensy is 11, Teensy++ is 6)
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();
TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
#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(115200);
while (!Serial); // wait for Leonardo enumeration, others continue immediately
// NOTE: 8MHz or slower host processors, like the Teensy @ 3.3v or Ardunio
// Pro Mini running at 3.3v, cannot handle this baud rate reliably due to
// the baud timing being too misaligned with processor ticks. You must use
// 38400 or slower in these cases, or use some kind of external separate
// crystal solution for the UART timer.
// initialize device
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();
// verify connection
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// wait for ready
Serial.println(F("\nSend any character to begin DMP programming and demo: "));
while (Serial.available() && Serial.read()); // empty buffer
while (!Serial.available()); // wait for data
while (Serial.available() && Serial.read()); // empty buffer again
// load and configure the DMP
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
mpu.setXGyroOffset(220);
mpu.setYGyroOffset(76);
mpu.setZGyroOffset(-85);
mpu.setZAccelOffset(1788); // 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);
// enable Arduino interrupt detection
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(0, 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(")"));
}
// configure LED for output
pinMode(LED_PIN, OUTPUT);
}
// ================================================================
// === 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) {
// other program behavior stuff here
// .
// .
// .
// if you are really paranoid you can frequently test in between other
// stuff to see if mpuInterrupt is true, and if so, "break;" from the
// while() loop to immediately process the MPU data
// .
// .
// .
}
// 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 & 0x10) || fifoCount == 1024) {
// reset so we can continue cleanly
mpu.resetFIFO();
Serial.println(F("FIFO overflow!"));
// otherwise, check for DMP data ready interrupt (this should happen frequently)
} else if (mpuIntStatus & 0x02) {
// 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;
#ifdef OUTPUT_READABLE_QUATERNION
// display quaternion values in easy matrix form: w x y z
mpu.dmpGetQuaternion(&q, fifoBuffer);
Serial.print("quat\t");
Serial.print(q.w);
Serial.print("\t");
Serial.print(q.x);
Serial.print("\t");
Serial.print(q.y);
Serial.print("\t");
Serial.println(q.z);
#endif
#ifdef OUTPUT_READABLE_EULER
// display Euler angles in degrees
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetEuler(euler, &q);
Serial.print("euler\t");
Serial.print(euler[0] * 180/M_PI);
Serial.print("\t");
Serial.print(euler[1] * 180/M_PI);
Serial.print("\t");
Serial.println(euler[2] * 180/M_PI);
#endif
#ifdef OUTPUT_READABLE_YAWPITCHROLL
// display Euler angles in degrees
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
Serial.print("ypr\t");
Serial.print(ypr[0] * 180/M_PI);
Serial.print("\t");
Serial.print(ypr[1] * 180/M_PI);
Serial.print("\t");
Serial.println(ypr[2] * 180/M_PI);
#endif
#ifdef OUTPUT_READABLE_REALACCEL
// display real acceleration, adjusted to remove gravity
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetAccel(&aa, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
Serial.print("areal\t");
Serial.print(aaReal.x);
Serial.print("\t");
Serial.print(aaReal.y);
Serial.print("\t");
Serial.println(aaReal.z);
#endif
#ifdef OUTPUT_READABLE_WORLDACCEL
// display initial world-frame acceleration, adjusted to remove gravity
// and rotated based on known orientation from quaternion
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetAccel(&aa, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &q);
Serial.print("aworld\t");
Serial.print(aaWorld.x);
Serial.print("\t");
Serial.print(aaWorld.y);
Serial.print("\t");
Serial.println(aaWorld.z);
#endif
#ifdef OUTPUT_TEAPOT
// display quaternion values in InvenSense Teapot demo format:
teapotPacket[2] = fifoBuffer[0];
teapotPacket[3] = fifoBuffer[1];
teapotPacket[4] = fifoBuffer[4];
teapotPacket[5] = fifoBuffer[5];
teapotPacket[6] = fifoBuffer[8];
teapotPacket[7] = fifoBuffer[9];
teapotPacket[8] = fifoBuffer[12];
teapotPacket[9] = fifoBuffer[13];
Serial.write(teapotPacket, 14);
teapotPacket[11]++; // packetCount, loops at 0xFF on purpose
#endif
// blink LED to indicate activity
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState);
}
}
일단 아두이노ide example에서 보이지는않지만
빌드는 정상적으로 된다.
그런데 위 코드에서 인터럽트핀을 내가 D2가 아닌 D4를 써서 좀바꿔주고 싶은데
어디있는지 잘안보인다.
일단 필요한 lib 가져오고
i2c 통신 설정하는 내용이 앞에나온다.
// 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
// 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
근데 다음 내용이 문제다.
인터럽트 핀을 우노보드에서 d2로 연결해야한다고 하는데
나는 d4를 쓴다.
/* =========================================================================
NOTE: In addition to connection 3.3v, GND, SDA, and SCL, this sketch
depends on the MPU-6050's INT pin being connected to the Arduino's
external interrupt #0 pin. On the Arduino Uno and Mega 2560, this is
digital I/O pin 2.
* ========================================================================= */
일단 gpt 한태 질문
q : 아두이노 우노보드 mpu6050 사용하는데 INT핀을 우노보드의 D2에다가 연결하라고 해 다른 핀을 사용하면 안될까?
a : 다른 핀을 사용할 수도 있지만, MPU6050의 INT 핀은 보통 D2 핀에 연결하는 것을 권장합니다. 이는 MPU6050 라이브러리와 관련된 코드가 기본적으로 D2 핀을 사용하기 때문입니다. 만약 다른 핀을 사용하려면 해당 핀의 인터럽트 기능을 사용할 수 있는지 확인하고, 코드를 수정하여 새로운 핀을 사용하도록 설정해야 합니다.
q : 그러면 mpu6050 여러개 사용하는 경우 int핀들을 전부 d2와 연결해야되??
a : MPU6050 여러 개를 사용하는 경우, 각 MPU6050의 INT 핀을 모두 하나의 핀(D2)에 연결할 필요는 없습니다. 각 MPU6050의 INT 핀을 서로 다른 디지털 핀에 연결하여 각각의 MPU6050의 인터럽트 신호를 처리할 수 있습니다. 따라서 각 MPU6050 모듈의 INT 핀을 서로 다른 디지털 핀에 연결하여 사용하면 됩니다.
q : 그러면 나는 mpu6050 인터럽트핀을 우노보드 d4에 연결해서 사용하면 mpu6050 헤더파일은 변경하면되는거야? 어떻게 고치면되?
a : 네, MPU6050의 INT 핀을 우노보드의 D4에 연결한 경우 MPU6050 라이브러리의 헤더 파일을 수정하여 해당 핀을 사용하도록 변경해야 합니다. 라이브러리의 소스 코드를 열어 해당 핀을 수정하거나, 라이브러리의 설정 파일에서 해당 핀을 재정의할 수 있습니다. 보통 라이브러리에는 핀 매핑에 대한 설정이 있으므로 해당 설정을 변경하여 INT 핀을 D4로 사용할 수 있습니다. 핀 매핑 설정을 찾아 해당 핀을 D4로 수정하고, 라이브러리를 다시 컴파일하여 사용하면 MPU6050의 INT 핀을 D4에 연결하여 정상적으로 사용할 수 있습니다.
#define INTERRUPT_PIN 4 // use pin 2 on Arduino Uno & most boards
void setup() {
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
#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(115200);
while (!Serial); // wait for Leonardo enumeration, others continue immediately
// NOTE: 8MHz or slower host processors, like the Teensy @ 3.3v or Ardunio
// Pro Mini running at 3.3v, cannot handle this baud rate reliably due to
// the baud timing being too misaligned with processor ticks. You must use
// 38400 or slower in these cases, or use some kind of external separate
// crystal solution for the UART timer.
// initialize device
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();
pinMode(INTERRUPT_PIN, INPUT);
// verify connection
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// wait for ready
Serial.println(F("\nSend any character to begin DMP programming and demo: "));
while (Serial.available() && Serial.read()); // empty buffer
while (!Serial.available()); // wait for data
while (Serial.available() && Serial.read()); // empty buffer again
// load and configure the DMP
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
mpu.setXGyroOffset(220);
mpu.setYGyroOffset(76);
mpu.setZGyroOffset(-85);
mpu.setZAccelOffset(1788); // 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);
// enable Arduino interrupt detection
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
//attachInterrupt(0, dmpDataReady, RISING);
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(")"));
}
// configure LED for output
pinMode(LED_PIN, OUTPUT);
}
업로드 했지만 동작이 안된다
DMP ready! Waiting for first interrupt...
원래대로 고치고 핀도 d2로 옮기니 정상동작 확인
어카지 인터럽트핀 여러개해야하는데
근데 코드안고치고 인터럽트핀 아얘 뽑아도 정상적으로 동작한다.
괜한 걱정한듯
하지만 초기화 부분에서 자꾸 멈춰서 주석처리시켜줘야할듯
근대 실행시키고나서 보면 yaw가 계속 누적되서 커지다가 어느순간 멈춘다.
ok 기존 코드는 너무 길어서
주석이나 몇가지 부분들을 빼서 조금 줄임
일단. 인터럽트 핀 안쓰고 ypr 받아오도록 정리
#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
// 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 OUTPUT_READABLE_YAWPITCHROLL
#define LED_PIN 13 // (Arduino is 13, Teensy is 11, Teensy++ is 6)
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' };
volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high
void dmpDataReady() {
mpuInterrupt = true;
}
void setup() {
// join I2C bus (I2Cdev library doesn't do this automatically)
#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
Wire.begin();
TWBR = 24; // 400kHz I2C clock (200kHz if CPU is 8MHz)
#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(115200);
while (!Serial); // wait for Leonardo enumeration, others continue immediately
// initialize device
Serial.println(F("Initializing I2C devices..."));
mpu.initialize();
//pinMode(INTERRUPT_PIN, INPUT);
// verify connection
Serial.println(F("Testing device connections..."));
Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// wait for ready
/*
Serial.println(F("\nSend any character to begin DMP programming and demo: "));
while (Serial.available() && Serial.read()); // empty buffer
while (!Serial.available()); // wait for data
while (Serial.available() && Serial.read()); // empty buffer again
*/
// load and configure the DMP
Serial.println(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
mpu.setXGyroOffset(220);
mpu.setYGyroOffset(76);
mpu.setZGyroOffset(-85);
mpu.setZAccelOffset(1788); // 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);
// enable Arduino interrupt detection
Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(0, dmpDataReady, RISING);
//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(")"));
}
// configure LED for output
pinMode(LED_PIN, OUTPUT);
}
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) {
}
// 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 & 0x10) || fifoCount == 1024) {
// reset so we can continue cleanly
mpu.resetFIFO();
Serial.println(F("FIFO overflow!"));
// otherwise, check for DMP data ready interrupt (this should happen frequently)
} else if (mpuIntStatus & 0x02) {
// 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;
#ifdef OUTPUT_READABLE_YAWPITCHROLL
// display Euler angles in degrees
mpu.dmpGetQuaternion(&q, fifoBuffer);
mpu.dmpGetGravity(&gravity, &q);
mpu.dmpGetYawPitchRoll(ypr, &q, &gravity);
Serial.print("ypr\t");
Serial.print(ypr[0] * 180/M_PI);
Serial.print("\t");
Serial.print(ypr[1] * 180/M_PI);
Serial.print("\t");
Serial.println(ypr[2] * 180/M_PI);
#endif
// blink LED to indicate activity
blinkState = !blinkState;
digitalWrite(LED_PIN, blinkState);
}
}
블링크 업로드가 안되던데, 전에 내부 클럭 제네레이터 쓰도록 부트로더 수정했던 우노보드같음
교체하니 정상적으로 블링크 동작 확인
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "VRPlayer.generated.h"
UCLASS()
class KDT_VR_API AVRPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AVRPlayer();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(VisibleAnywhere, Category="MySettings|Components")
class UCameraComponent* cameraComp;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UStaticMeshComponent* headMesh;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UMotionControllerComponent* leftMotion;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UMotionControllerComponent* rightMotion;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class USkeletalMeshComponent* leftHand;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class USkeletalMeshComponent* rightHand;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "VRPlayer.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Camera/CameraComponent.h"
#include "MotionControllerComponent.h"
// Sets default values
AVRPlayer::AVRPlayer()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
cameraComp->SetupAttachment(RootComponent);
headMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HeadMesh"));
headMesh->SetupAttachment(cameraComp);
leftMotion = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Left Motion Controller"));
leftMotion->SetupAttachment(RootComponent);
leftMotion->MotionSource = FName("Left");
leftHand = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Left Hand Mesh"));
leftHand->SetupAttachment(leftMotion);
leftHand->SetRelativeRotation(FRotator(0, 0, 90));
rightMotion = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Right Motion Controller"));
rightMotion->SetupAttachment(RootComponent);
rightMotion->MotionSource = FName("Right");
rightHand = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Right Hand Mesh"));
rightHand->SetupAttachment(rightMotion);
rightHand->SetRelativeRotation(FRotator(0, 0, 90));
}
// Called when the game starts or when spawned
void AVRPlayer::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AVRPlayer::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AVRPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
head mesh 설정
헤드셋 쓴상태에선 로그보기 힘듬
왼쪽손 로그는 왼쪽위
로그를 매시로 띄울수 있음
UTextRenderComponent
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "VRPlayer.generated.h"
UCLASS()
class KDT_VR_API AVRPlayer : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AVRPlayer();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY(VisibleAnywhere, Category="MySettings|Components")
class UCameraComponent* cameraComp;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UStaticMeshComponent* headMesh;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UMotionControllerComponent* leftMotion;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UMotionControllerComponent* rightMotion;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class USkeletalMeshComponent* leftHand;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class USkeletalMeshComponent* rightHand;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UTextRenderComponent* leftLog;
UPROPERTY(VisibleAnywhere, Category = "MySettings|Components")
class UTextRenderComponent* rightLog;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "VRPlayer.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Camera/CameraComponent.h"
#include "MotionControllerComponent.h"
#include "Components/TextRenderComponent.h"
// Sets default values
AVRPlayer::AVRPlayer()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
cameraComp->SetupAttachment(RootComponent);
headMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HeadMesh"));
headMesh->SetupAttachment(cameraComp);
leftMotion = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Left Motion Controller"));
leftMotion->SetupAttachment(RootComponent);
leftMotion->MotionSource = FName("Left");
leftHand = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Left Hand Mesh"));
leftHand->SetupAttachment(leftMotion);
leftHand->SetRelativeRotation(FRotator(0, 0, 90));
rightMotion = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Right Motion Controller"));
rightMotion->SetupAttachment(RootComponent);
rightMotion->MotionSource = FName("Right");
rightHand = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Right Hand Mesh"));
rightHand->SetupAttachment(rightMotion);
rightHand->SetRelativeRotation(FRotator(0, 0, 90));
leftLog = CreateDefaultSubobject<UTextRenderComponent>(TEXT("Left Log"));
leftLog->SetupAttachment(leftMotion);
rightLog = CreateDefaultSubobject<UTextRenderComponent>(TEXT("Right Log"));
rightLog->SetupAttachment(rightMotion);
텍스트수정가능
근데 텍스트가 카메라와 같은 방향
손목위치 기준
yaw 180도 뒤집을때
모션을 옆으로보냄
손위치 조정
이벤트 추가
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "VRPlayer.generated.h"
UPROPERTY(EditAnywhere, Category = "MySettings|Inputs")
class UInputMappingContext* vrMapping;
UPROPERTY(EditAnywhere, Category="MySettings|Inputs")
class UInputAction* rightTriggerTouch;
private:
//트리거 함수, 델리게이터 형식
void RightTriggerTouch(const FInputActionValue& val);
};
// Called to bind functionality to input
void AVRPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* enhancedInputCopmonent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (enhancedInputCopmonent != nullptr)
{
//enhancedInputCopmonent->BindAction();
}
}
void AVRPlayer::RightTriggerTouch(const FInputActionValue& val)
{
}
언리얼 델리게이터, 시그니처, 바인딩
1. 델리게이터 (Delegate): - 델리게이터는 이벤트를 선언하고 호출하기 위한 개체입니다. - 다른 함수들을 여러 개 등록하고, 이벤트가 발생할 때 등록된 함수들을 호출할 수 있습니다. - 예를 들어, 특정 상황에 대한 이벤트가 발생할 때, 여러 함수들이 실행되어야 할 때 유용하게 사용됩니다.
2. 시그니처 (Signature): - 시그니처는 델리게이터에 등록되는 함수의 형식을 정의하는 것입니다. - 함수의 매개변수와 반환 값의 형식을 지정하여 델리게이터와 함수가 상호작용할 수 있도록 합니다. - 예를 들어, 함수가 인자로 정수를 받고, 부울 값을 반환하는 경우 시그니처는 "(int32, bool)"와 같이 정의됩니다.
3. 바인딩 (Binding): - 바인딩은 델리게이터와 함수를 연결하는 과정입니다. - 델리게이터에 함수를 바인딩하여 이벤트가 발생할 때 해당 함수가 호출되도록 설정합니다. - 바인딩은 런타임 중에 동적으로 수행될 수도 있고, 블루프린트나 C++ 코드에서 정적으로 구현될 수도 있습니다.
UInputMappingContext, UInputAction
- UInputMappingContext와 UInputAction은 언리얼 엔진에서 입력 매핑과 관련된 중요한 클래스입니다.
1. UInputMappingContext: - UInputMappingContext는 입력 매핑을 그룹화하고 관리하기 위한 클래스입니다. - 게임 내에서 특정 상황 또는 컨텍스트에서 사용되는 입력 매핑을 정의하고 관리할 수 있습니다. - 예를 들어, "게임 플레이"와 "메뉴"라는 두 가지 컨텍스트에서 서로 다른 입력 매핑을 정의하고 관리할 수 있습니다. - UInputMappingContext를 사용하여 특정 컨텍스트에 대한 입력 처리 규칙을 설정하고, 해당 컨텍스트에서 사용되는 UInputAction들을 관리할 수 있습니다.
2. UInputAction: - UInputAction은 사용자의 입력에 대한 액션을 정의하는 클래스입니다. - 키보드, 마우스, 게임패드 등 다양한 입력 장치로부터의 입력을 처리할 수 있습니다. - UInputMappingContext 내에서 사용되며, 특정 입력에 대한 액션을 정의하고 해당 액션을 처리하는 함수를 바인딩할 수 있습니다. - 예를 들어, "점프"라는 UInputAction을 정의하여 특정 키 또는 버튼 입력에 대한 점프 동작을 처리할 수 있습니다.
InputMappincSubsystem에 대해
// InputMapping Context 파일을 입력 서브시스템에 등록하는 // 베이스 시스템, 기기별로 다른부분은 서브시스템으로 // 인풋맵핑 컨텍스트를 연결하는 이유는 여러종류로 만들어서 쓸수있도록 하기위함. // 이전엔 입력체계를 프로젝트 세팅즈에서 해서 런타임중에 바꿀수 없어씅나 // 지금은 인풋맵핑컨텍스트 파일을 따로만들어서 런타임중에 변경 가능하도록 수정됨. // 캐릭터때 조작법, 차 탔을때 조작법이 바뀐다. => 맵핑 컨텍스트를 변경시켜 구현
언리얼 인풋시스템에 대해 찾아보다가 잘 정리한 글 찾아서 캡처
https://upbo.tistory.com/141
Input폴더에 다음과 같이 추가
우클릭-input에
inputAction과 inputMappingContext 존재
IA_RightIndexTrigger_Touch 벨류는 bool
IA_RightIndexTrigger_Press 벨류는 bool
IA_RightIndexTrigger_Value의 벨류는 float (눌림정도
IMC_MyVRInputMap은
IA_RightIndexTrigger_Touch/Press/Value 추가
각각 oculus touch trigger 설정
VR인풋 파일을 PMI_VRTemplate로 프로젝트 셋팅즈에서 설정
PMI_VRTemplate는 여기애
여기서 IMC_MyVRInputMap 추가
VR플레이어 헤더파일에 인풋맵핑콘택스트, 액션, 바인딩함수 추가
UPROPERTY(EditAnywhere, Category = "MySettings|Inputs")
class UInputMappingContext* vrMapping;
UPROPERTY(EditAnywhere, Category="MySettings|Inputs")
class UInputAction* rightTriggerTouch;
UPROPERTY(EditAnywhere, Category = "MySettings|Inputs")
class UInputAction* rightTriggerPress;
UPROPERTY(EditAnywhere, Category = "MySettings|Inputs")
class UInputAction* rightTriggerValue;
private:
//트리거 함수, 델리게이터 형식
void RightTriggerTouch(const FInputActionValue& val);
void RightTriggerPress(const FInputActionValue& val);
void RightTriggerValue(const FInputActionValue& val);
비긴플레이에
플레이어 컨트롤러로부터 컨트롤러 가져와서 vrMapping 등록
뒤에 셋업플레이어인풋컴포넌트에서 바인딩 액션 정리
// Fill out your copyright notice in the Description page of Project Settings.
#include "VRPlayer.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Camera/CameraComponent.h"
#include "MotionControllerComponent.h"
#include "Components/TextRenderComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
// Sets default values
AVRPlayer::AVRPlayer()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
cameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
cameraComp->SetupAttachment(RootComponent);
headMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HeadMesh"));
headMesh->SetupAttachment(cameraComp);
leftMotion = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Left Motion Controller"));
leftMotion->SetupAttachment(RootComponent);
leftMotion->MotionSource = FName("Left");
leftHand = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Left Hand Mesh"));
leftHand->SetupAttachment(leftMotion);
leftHand->SetRelativeRotation(FRotator(0, 0, 90));
rightMotion = CreateDefaultSubobject<UMotionControllerComponent>(TEXT("Right Motion Controller"));
rightMotion->SetupAttachment(RootComponent);
rightMotion->MotionSource = FName("Right");
rightHand = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("Right Hand Mesh"));
rightHand->SetupAttachment(rightMotion);
rightHand->SetRelativeRotation(FRotator(0, 0, 90));
leftLog = CreateDefaultSubobject<UTextRenderComponent>(TEXT("Left Log"));
leftLog->SetupAttachment(leftMotion);
leftLog->SetRelativeLocation(FVector(20, 0, 0));
// 피치 요, 롤
leftLog->SetRelativeRotation(FRotator(90, 180, 0));
leftLog->SetHorizontalAlignment(EHTA_Center);
leftLog->SetVerticalAlignment(EVRTA_TextCenter);
leftLog->SetWorldSize(20);
leftLog->SetTextRenderColor(FColor::Yellow);
rightLog = CreateDefaultSubobject<UTextRenderComponent>(TEXT("Right Log"));
rightLog->SetupAttachment(rightMotion);
rightLog->SetRelativeRotation(FRotator(90, 180, 0));
rightLog->SetHorizontalAlignment(EHTA_Center);
rightLog->SetVerticalAlignment(EVRTA_TextCenter);
rightLog->SetWorldSize(20);
rightLog->SetTextRenderColor(FColor::Yellow);
}
// Called when the game starts or when spawned
void AVRPlayer::BeginPlay()
{
Super::BeginPlay();
// InputMapping Context 파일을 입력 서브시스템에 등록하는
// 베이스 시스템, 기기별로 다른부분은 서브시스템으로
// 인풋맵핑 컨텍스트를 연결하는 이유는 여러종류로 만들어서 쓸수있도록 하기위함.
// 이전엔 입력체계를 프로젝트 세팅즈에서 해서 런타임중에 바꿀수 없어씅나
// 지금은 인풋맵핑컨텍스트 파일을 따로만들어서 런타임중에 변경 가능하도록 수정됨.
// 캐릭터때 조작법, 차 탔을때 조작법이 바뀐다. => 맵핑 컨텍스트를 변경시켜 구현
// 입력 서브시스템을 갖고 있는것 - 플레이어 컨트롤러
// 플레이어 컨트롤러로부터 입력 서브시스템을 가져옴. getSubsystem() 함수 있음 누구로부터가져올
//InputMapping Context 파일을 입력 서브시스템에 등록하는 절차 진행
APlayerController* pc = GetController<APlayerController>();
if (pc != nullptr)
{
UEnhancedInputLocalPlayerSubsystem* subsys = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>
(pc->GetLocalPlayer());
subsys->AddMappingContext(vrMapping, 0);
}
}
// Called every frame
void AVRPlayer::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AVRPlayer::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* enhancedInputCopmonent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (enhancedInputCopmonent != nullptr)
{
// 입력 컴포넌트에 RightTriggerTouch() 함수 연결. (손댔을때, 손땟을때도)
enhancedInputCopmonent->BindAction(rightTriggerTouch, ETriggerEvent::Started, this, &AVRPlayer::RightTriggerTouch);
enhancedInputCopmonent->BindAction(rightTriggerTouch, ETriggerEvent::Completed, this, &AVRPlayer::RightTriggerTouch);
enhancedInputCopmonent->BindAction(rightTriggerPress, ETriggerEvent::Started, this, &AVRPlayer::RightTriggerPress);
enhancedInputCopmonent->BindAction(rightTriggerPress, ETriggerEvent::Completed, this, &AVRPlayer::RightTriggerPress);
enhancedInputCopmonent->BindAction(rightTriggerValue, ETriggerEvent::Triggered, this, &AVRPlayer::RightTriggerValue);
}
}
void AVRPlayer::RightTriggerTouch(const FInputActionValue& val)
{
FString result = val.Get<bool>() == true ? FString("True") : FString("False");
rightLog->SetText(FText::FromString(FString::Printf(TEXT("Right index touch : %s"), *result)));
}
void AVRPlayer::RightTriggerPress(const FInputActionValue& val)
{
FString result = val.Get<bool>() == true ? FString("True") : FString("False");
rightLog->SetText(FText::FromString(FString::Printf(TEXT("Right index pressed : %s"), *result)));
}
void AVRPlayer::RightTriggerValue(const FInputActionValue& val)
{
float pressed = val.Get<float>();
rightLog->SetText(FText::FromString(FString::Printf(TEXT("Right index Value : %.3f"), pressed)));
}