아두이노 프로 미니로 적외선 안구 운동 추적 - LED 제어 데모
부품 및 소모품
| 부품 |
수량 |
비고 |
| 스파크펀 아두이노 프로 미니 328 - 5V/16MHz |
1 |
|
| 아다프루트 리튬이온/리튬폴리머 백팩 애드온 (프로 트링킷/이치비츠이용) |
1 |
|
| 네오픽셀 스트립 |
1 |
|
| QTR-1A 반사율 센서 |
1 |
|
| 3.7V 리포 배터리 |
1 |
|
앱 및 온라인 서비스
프로젝트 소개
적외선 센서로 안구 운동을 감지해 LED를 제어합니다.
네오픽셀 LED 안구
네오픽셀 LED 테이프를 사용했습니다. LED는 68개입니다.
LED는 양면 테이프로 볼에 고정하고 다음과 같은 순서로 배선했습니다.
안구 운동 추적
회로도
센서
눈 추적용으로 QTR-1A 센서 두 개를 사용했습니다. 두 센서는 눈 너비 정도의 간격으로 플라스틱 시트에 배치했습니다.
센서 부분과 마이크로컨트롤러 부분은 각각 클립으로 안경에 고정했습니다.
아두이노 코드
홍채가 한 센서에 가까워지면 반사광이 감소하고 센서 값이 증가합니다. 반대로 홍채가 멀어지면 반사광이 증가하고 센서 값이 감소합니다.
LED 안구의 동공 좌우 움직임은 한 센서 값의 증감을 감지해 제어합니다.
깜빡일 때는 두 센서 값이 동시에 감소하므로, 두 센서 값이 동시에 감소하면 LED 안구의 눈꺼풀이 내려옵니다.
#include <QTRSensors.h>
#include <Adafruit_NeoPixel.h>
#define NUM_SENSORS 2
#define NUM_SAMPLES_PER_SENSOR 10
#define EMITTER_PIN QTR_NO_EMITTER_PIN
int iniSensorValL, sensorValL;
int iniSensorValR, sensorValR;
#define PIN A3
Adafruit_NeoPixel led = Adafruit_NeoPixel(68, PIN, NEO_GRB + NEO_KHZ800);
int blackNum = 24;
int pupilNum = 12;
uint32_t color;
int brightness = 40;
byte eyeColor;
int LR = 7;
boolean lid = false;
int cnt = 0;
// 검은 눈동자 좌우 애니메이션
int blackLED[15][24] = {
{12,32,35,55,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{12,13,31,36,54,55,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{11,13,14,30,37,53,54,56,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{10,11,14,15,29,38,52,53,56,57,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{ 9,10,11,12,15,16,28,33,34,39,51,52,55,56,57,58,68,68,68,68,68,68,68,68},
{ 0, 8, 9,10,11,12,13,16,17,27,32,35,40,50,51,54,55,56,57,58,59,67,68,68},
{ 0, 1, 7, 8, 9,10,13,14,17,18,26,31,36,41,49,50,53,54,57,58,59,60,66,67},
{ 1, 2, 6, 7, 8, 9,14,15,18,19,25,30,37,42,48,49,52,53,58,59,60,61,65,66},
{ 2, 3, 5, 6, 7, 8,15,16,19,20,24,29,38,43,47,48,51,52,59,60,61,62,64,65},
{ 3, 4, 5, 6, 7,16,17,20,21,23,28,39,44,46,47,50,51,60,61,62,63,64,68,68},
{ 4, 5, 6,17,18,21,22,27,40,45,46,49,50,61,62,63,68,68,68,68,68,68,68,68},
{ 4, 5,18,19,26,41,48,49,62,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{ 4,19,20,25,42,47,48,63,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{20,21,24,43,46,47,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68},
{21,23,44,46,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68}
};
// 동공 좌우 애니메이션
int pupilLED[15][12] = {
{33,34,68,68,68,68,68,68,68,68,68,68},
{32,33,34,35,68,68,68,68,68,68,68,68},
{12,31,32,33,34,35,36,55,68,68,68,68},
{12,13,30,31,32,33,34,35,36,37,54,55},
{13,14,29,30,31,32,35,36,37,38,53,54},
{14,15,28,29,30,31,36,37,38,39,52,53},
{15,16,27,28,29,30,37,38,39,40,51,52},
{16,17,26,27,28,29,38,39,40,41,50,51},
{17,18,25,26,27,28,39,40,41,42,49,50},
{18,19,24,25,26,27,40,41,42,43,48,49},
{19,20,23,24,25,26,41,42,43,44,47,48},
{20,21,22,23,24,25,42,43,44,45,46,47},
{21,22,23,24,43,44,45,46,68,68,68,68},
{22,23,44,45,68,68,68,68,68,68,68,68},
{22,45,68,68,68,68,68,68,68,68,68,68}
};
// 깜빡임 애니메이션
int eyelid = 0;
int eyelidNum[8] = {0,4,8,16,24,34,44,56};
int eyelidLED[56] = {64,65,66,67,58,59,60,61,56,57,62,63,49,50,51,52,47,48,53,54,38,39,40,41,46,55,36,37,42,43,26,27,28,29,35,44,24,25,30,31,15,16,17,18,34,45,23,32,13,14,19,20,6,7,8,9};
QTRSensorsAnalog qtra((unsigned char[]) {0, 1}, NUM_SENSORS, NUM_SAMPLES_PER_SENSOR, EMITTER_PIN);
unsigned int sensorValues[NUM_SENSORS];
void blink(int eyelid, int LR) {
if (eyelid != 8) {
// 회색 배경
for(uint16_t i=0; i<led.numPixels(); i++) {
led.setPixelColor(i, led.Color(66, 66, 66));
}
// 검은 눈동자
for(uint16_t i=0; i<blackNum; i++) {
led.setPixelColor(blackLED[LR][i], color);
}
// 동공
for(uint16_t i=0; i<pupilNum; i++) {
led.setPixelColor(pupilLED[LR][i], led.Color(0, 0, 66));
}
// 눈꺼풀
for(int i=0; i < eyelidNum[eyelid]; i++) {
led.setPixelColor(eyelidLED[i], 0);
}
} else if (eyelid == 8) {
led.clear();
}
led.show();
}
void setup() {
Serial.begin(115200);
led.begin();
led.setBrightness(brightness);
led.show();
color = led.Color(0, 177, 55); // 동공 색상
delay(100);
qtra.read(sensorValues);
iniSensorValL = sensorValues[0];
iniSensorValR = sensorValues[1];
blink(eyelid, LR);
}
void loop() {
qtra.read(sensorValues);
sensorValL = sensorValues[0];
sensorValR = sensorValues[1];
double rasioL = (double)sensorValL / iniSensorValL;
double rasioR = (double)sensorValR / iniSensorValR;
Serial.print(rasioL);
Serial.print(" ");
Serial.println(rasioR);
if(rasioL > 0.985 && rasioR < 0.985) { // 오른쪽
for(int i = LR; i < 12; i++) {
blink(0, i);
delay(40);
LR = i;
}
} else if(rasioL < 0.985 && rasioR > 0.985) { // 왼쪽
for(int i=LR; i>2; i--) {
blink(0, i);
delay(40);
LR = i;
}
} else if(lid == false && rasioL < 0.96 && rasioR < 0.96) { // 깜빡임 감기
for(int i = 1; i < 9; i++) {
blink(i, LR);
delay(40);
lid = true;
}
} else if(lid == true && rasioL > 0.96 && rasioR > 0.96) { // 깜빡임 뜨기
for(int i = 8; i > 0; i--) {
blink(i, LR);
delay(40);
lid = false;
}
} else if(lid == false && rasioL > 0.96 && rasioR > 0.96) { // 정상
if(LR <= 7) {
for(int i=LR; i<=7; i++) {
blink(0, i);
delay(40);
LR = i;
}
} else {
for(int i=LR; i>=7; i--) {
blink(0, i);
delay(40);
LR = i;
}
}
Serial.println("정상");
}
// 초기값 갱신
if (cnt > 10) {
iniSensorValL = sensorValL;
iniSensorValR = sensorValR;
cnt = 0;
}
}
작동
착용자의 안구 움직임에 따라 LED 안구의 동공이 좌우로 움직이고, 깜빡임 시 눈꺼풀이 닫히는 것을 확인할 수 있습니다.