아두이노 프로젝트 5. 스마트 자전거 헬멧: 두 판 사이의 차이
(새 문서: ㅇㅇ) |
편집 요약 없음 |
||
(같은 사용자의 중간 판 9개는 보이지 않습니다) | |||
1번째 줄: | 1번째 줄: | ||
{{#seo:|title=아두위키 : 아두이노 프로젝트|title_mode=append|keywords=아두이노, 정보과학, 메이커학습, 수행평가, LED, 네오픽셀, 아두이노 작품, 캡스톤작품, 아두이노 예제코드, 엔트리 아두이노, 파이썬 아두이노|description=아두이노를 활용한 프로젝트(스마트 자전거 헬멧)를 소개합니다.|image=https://arduwiki.com/html/resources/assets/arduwiki.png}} | |||
== '''개요''' == | |||
=== 스마트 자전거 헬멧 === | |||
원하는 방향으로 고개를 꺾으면 해당 방향에 네오픽셀이 켜지며 방향등의 역할을 하게 되는 스마트 자전거 헬멧입니다. | |||
=== 고려사항 === | |||
* 가속도 센서를 부착해 해당 데이터를 바탕으로 사람 고개 방향을 측정합니다. | |||
* 헬멧의 좌우에는 네오픽셀 스트랩이 부착되어 고개 방향에 따라 밝아져 방향등의 역할을 합니다. | |||
* 전원은 9V 배터리를 사용하며 쉽게 교체할 수 있으며 on/off 기능을 포함합니다. | |||
=== '''사용 하드웨어''' === | |||
* [https://gongzipsa.com/shop/1699939462 아두이노 우노] 1개 | |||
* [[네오픽셀(WS2812)|네오픽셀]] 스트랩 2개 | |||
* [https://gongzipsa.com/shop/1699939372 미니 브레드보드] 1개 | |||
* [https://gongzipsa.com/shop/1699939460 MPU-6050] 가속도/자이로 센서 1개 | |||
* [https://gongzipsa.com/shop/1699939289 점퍼 케이블] | |||
== '''회로 구성''' == | |||
=== [[파일:자전거 헬멧 회로도.png|가운데|class=coders100]] === | |||
== '''기능 구현''' == | |||
<syntaxhighlight lang="c++" line="1"> | |||
#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; | |||
} | |||
</syntaxhighlight> | |||
=== [[네오픽셀(WS2812)|네오픽셀]] === | |||
헬멧의 양 옆에는 네오픽셀 스트랩이 부착되어있습니다. | |||
처음에는 정면을 바라본 상태로 전원을 켠 후 가속도 센서가 정상적으로 연결된다면 네오픽셀이 초록색으로 3번 깜빡거립니다. | |||
만약 네오픽셀이 붉은 색으로 연속해서 점등한다면 에러가 발생한 상황으로 연결 확인 및 아두이노 리셋이 필요합니다. | |||
이후에는 고개를 기울여 MPU-6050을 통해 수집된 데이터가 조건에 부합할 때 점등하며, blinkTime 변수에 선언된 값을 주기(ms 단위)로 깜빡거립니다. | |||
고개를 다시 똑바로 세우면 네오픽셀은 off 상태가 됩니다. | |||
=== MPU-6050 가속도 센서 데이터 수집 === | |||
헬멧에 부착된 MPU-6050 가속도 센서를 통해 x, y, z축 가속도 데이터를 수집합니다. | |||
수집된 데이터를 원하는 값으로 가공한 후 left_limint, right_limint에서 미리 설정해 둔 임계값과 비교해 네오픽셀 점등 여부를 결정합니다. | |||
left_limint, right_limint 값이 커질수록 고개를 더 많이 기울여야 작동합니다. | |||
== '''결과''' == | |||
=== 전원 On 후 네오픽셀 초록색 점등 === | |||
[[파일:전원on.png|가운데|class=coders50]] | |||
=== 좌우 기울임 === | |||
[[파일:스마트헬멧 완성사진.png|가운데|class=coders100]] | |||
=== Comment === | |||
사진 상으로 깜빡임이 표현되지 않지만 전원이 켜진 후 가속도 센서 연결이 확인되면 네오픽셀이 초록색으로 빠르게 3번 깜빡입니다. | |||
이후 좌우측 기울임에 따라 양쪽에 부착된 네오픽셀이 0.5초 간격으로 점등하는 것을 확인할 수 있습니다. | |||
== '''추신''' == | |||
공집사의 아두이노 프로젝트 결과물은 판매되는 제품이 아니며, 프로젝트 수준에서 간단하게 진행되었습니다. | |||
문의 및 의뢰는 [https://kmong.com/@%EA%B3%B5%EC%A7%91%EC%82%AC 크몽 공집사]로 연락주시면 감사하겠습니다. |
2024년 6월 12일 (수) 20:37 기준 최신판
개요
스마트 자전거 헬멧
원하는 방향으로 고개를 꺾으면 해당 방향에 네오픽셀이 켜지며 방향등의 역할을 하게 되는 스마트 자전거 헬멧입니다.
고려사항
- 가속도 센서를 부착해 해당 데이터를 바탕으로 사람 고개 방향을 측정합니다.
- 헬멧의 좌우에는 네오픽셀 스트랩이 부착되어 고개 방향에 따라 밝아져 방향등의 역할을 합니다.
- 전원은 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초 간격으로 점등하는 것을 확인할 수 있습니다.
추신
공집사의 아두이노 프로젝트 결과물은 판매되는 제품이 아니며, 프로젝트 수준에서 간단하게 진행되었습니다.
문의 및 의뢰는 크몽 공집사로 연락주시면 감사하겠습니다.