아두이노 프로젝트 6. 배구 토스 연습 보조 장치
개요
배구 토스 연습 보조 장치
배구 토스 연습 시에 배구 공이 팔의 정확한 부위에 맞았는지 여부와 횟수를 측정하는 장치입니다. 센서가 삽입된 팔 토시를 끼고 어플리케이션의 연습시작 버튼을 누르면 공이 닿을 때마다 정확한 토스 여부에 따라 빨간불, 파란불이 들어오며 횟수가 저장됩니다.
고려사항
- 가속도 센서를 부착해 해당 데이터를 바탕으로 사람 고개 방향을 측정합니다.
- 헬멧의 좌우에는 네오픽셀 스트랩이 부착되어 고개 방향에 따라 밝아져 방향등의 역할을 합니다.
- 전원은 9V 배터리를 사용하며 쉽게 교체할 수 있으며 on/off 기능을 포함합니다.
사용 하드웨어
회로 구성
기능 구현
#include "Adafruit_NeoPixel.h"
#include <Wire.h>
#define neoPIN 12
#define NUMPIXELS 8
#define bright 255
#define dly 1000
Adafruit_NeoPixel neo(NUMPIXELS, neoPIN, NEO_GRB + NEO_KHZ800);
////값을 변경해도 되는 변수//////////////////////////////////////////////////////////////////////////
//네오픽셀 점등 주기
const int blinkTIme = 500;
//아래 두 값은 좌측, 우측으로 얼마나 기울여야 불이 점등하는지 정하는 값이며 절대값이 클수록 많이 기울여야 합니다.
const int left_limint = 20;
const int right_limint = -20;
////////////////////////////////////////////////////////////////////////////////////////////////////
const int MPU_addr = 0x68;
int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;
unsigned long past = 0;
float dt;
float accel_angel_x, accel_angel_y, accel_angel_z;
float gyro_angel_x, gyro_angel_y, gyro_angel_z;
float filtered_angel_x, filtered_angel_y, filtered_angel_z;
float baseAcX, baseAcY, baseAcZ;
float baseGyX, baseGyY, baseGyZ;
unsigned long t_now;
unsigned long t_prev;
bool neo_state[2] = { false, false }; //{우측, 좌측}
void setup() {
neo.begin();
neo.setBrightness(bright);
neo.clear();
neo.show();
Serial.begin(9600);
Serial.println("Seirla start");
initMPU6050();
calibAccelGyro();
initDT();
bool state2 = false;
for (int i = 0; i < 6; i++) {
if (!state2) {
for (int i = 0; i < NUMPIXELS; i++) {
neo.setPixelColor(i, 0, 255, 0);
}
} else {
for (int i = 0; i < NUMPIXELS; i++) {
neo.setPixelColor(i, 0, 0, 0);
}
}
neo.show();
state2 = !state2;
delay(100);
}
}
void loop() {
readAccelGyro();
calcDT();
calcAccelYPR();
if (millis() - past > blinkTIme) {
past = millis();
Serial.println(accel_angel_y, 2);
if (accel_angel_y > right_limint) { //우측
if (neo_state[1]) {
neo_state[1] = false;
neo.clear();
}
if (!neo_state[0]) {
for (int i = 0; i < NUMPIXELS / 2; i++) {
neo.setPixelColor(i, bright, bright, 0);
}
neo_state[0] = !neo_state[0];
} else if (neo_state[0]) {
neo.clear();
neo_state[0] = !neo_state[0];
}
} else if (accel_angel_y < left_limint) { //좌측
if (neo_state[0]) {
neo_state[0] = false;
}
if (!neo_state[1]) {
for (int i = 4; i < NUMPIXELS; i++) {
neo.setPixelColor(i, bright, bright, 0);
}
neo_state[1] = !neo_state[1];
} else if (neo_state[1]) {
neo.clear();
neo_state[1] = !neo_state[1];
}
} else {
neo.clear();
}
neo.show();
}
}
void initMPU6050() {
Wire.begin();
Wire.beginTransmission(MPU_addr);
Wire.write(0x6B);
Wire.write(0);
Wire.endTransmission(true);
}
void readAccelGyro() {
Wire.beginTransmission(MPU_addr);
Wire.write(0x3B);
Wire.endTransmission(false);
Wire.requestFrom(MPU_addr, 14, true);
AcX = Wire.read() << 8 | Wire.read();
AcY = Wire.read() << 8 | Wire.read();
AcZ = Wire.read() << 8 | Wire.read();
Tmp = Wire.read() << 8 | Wire.read();
GyX = Wire.read() << 8 | Wire.read();
GyY = Wire.read() << 8 | Wire.read();
GyZ = Wire.read() << 8 | Wire.read();
}
void calibAccelGyro() {
float sumAcX = 0, sumAcY = 0, sumAcZ = 0;
float sumGyX = 0, sumGyY = 0, sumGyZ = 0;
readAccelGyro();
for (int i = 0; i < 10; i++) {
readAccelGyro();
sumAcX += AcX;
sumAcY += AcY;
sumAcZ += AcZ;
sumGyX += GyX;
sumGyY += GyY;
sumGyZ += GyZ;
delay(100);
}
baseAcX = sumAcX / 10;
baseAcY = sumAcY / 10;
baseAcZ = sumAcZ / 10;
baseGyX = sumGyX / 10;
baseGyY = sumGyY / 10;
baseGyZ = sumGyZ / 10;
}
void initDT() {
t_prev = millis();
}
void calcDT() {
t_now = millis();
dt = (t_now - t_prev) / 1000.0;
t_prev = t_now;
}
void calcAccelYPR() {
float accel_x, accel_y, accel_z;
float accel_xz, accel_yz;
const float RADIANS_TO_DEGREES = 180 / 3.14159;
accel_x = AcX - baseAcX;
accel_y = AcY - baseAcY;
accel_z = AcZ + (16384 - baseAcZ);
accel_yz = sqrt(pow(accel_y, 2) + pow(accel_z, 2));
accel_angel_y = atan(-accel_x / accel_yz) * RADIANS_TO_DEGREES;
accel_xz = sqrt(pow(accel_x, 2) + pow(accel_z, 2));
accel_angel_x = atan(accel_y / accel_xz) * RADIANS_TO_DEGREES;
accel_angel_z = 0;
}
네오픽셀
헬멧의 양 옆에는 네오픽셀 스트랩이 부착되어있습니다.
처음에는 정면을 바라본 상태로 전원을 켠 후 가속도 센서가 정상적으로 연결된다면 네오픽셀이 초록색으로 3번 깜빡거립니다.
만약 네오픽셀이 붉은 색으로 연속해서 점등한다면 에러가 발생한 상황으로 연결 확인 및 아두이노 리셋이 필요합니다.
이후에는 고개를 기울여 MPU-6050을 통해 수집된 데이터가 조건에 부합할 때 점등하며, blinkTime 변수에 선언된 값을 주기(ms 단위)로 깜빡거립니다.
고개를 다시 똑바로 세우면 네오픽셀은 off 상태가 됩니다.
MPU-6050 가속도 센서 데이터 수집
헬멧에 부착된 MPU-6050 가속도 센서를 통해 x, y, z축 가속도 데이터를 수집합니다.
수집된 데이터를 원하는 값으로 가공한 후 left_limint, right_limint에서 미리 설정해 둔 임계값과 비교해 네오픽셀 점등 여부를 결정합니다.
left_limint, right_limint 값이 커질수록 고개를 더 많이 기울여야 작동합니다.
결과
전원 On 후 네오픽셀 초록색 점등
좌우 기울임
Comment
사진 상으로 깜빡임이 표현되지 않지만 전원이 켜진 후 가속도 센서 연결이 확인되면 네오픽셀이 초록색으로 빠르게 3번 깜빡입니다.
이후 좌우측 기울임에 따라 양쪽에 부착된 네오픽셀이 0.5초 간격으로 점등하는 것을 확인할 수 있습니다.
추신
공집사의 아두이노 프로젝트 결과물은 판매되는 제품이 아니며, 프로젝트 수준에서 간단하게 진행되었습니다.
문의 및 의뢰는 크몽 공집사로 연락주시면 감사하겠습니다.