Chrome Dino 오토 아두이노 제작 일기

조홍기·2023년 9월 18일

arduino

목록 보기
2/3


크롬에서 인터넷이 나갈 때마다 만날 수 있는 친구 DINO입니다.

구성 설명

회로에 대해 간단하게 설명하자면
서보모터1, 조도센서1, 아두이노 UNO 를 사용하였습니다.
서보모터는 5V, 9번 디지털(PWM) 포트로 다이렉트로 꽂았고
조도센서는 서보모터 사용으로 인한 간섭을 피하기 위해 3.3V를 사용하였고, 10K 저항과 함께 A0 아날로그 포트로 꽂았습니다.
220K는 너무 쎄서 센서값이 전반적으로 너무 낮게 나와 힘들더라고요.

문제 접근

다음의 방식으로 문제를 접근하였습니다. (낮 기준)
1. 배경은 밝기 때문에 센서에 높은 값으로 읽히고 장애물은 어둡기 때문에 센서에 낮은 값으로 읽힌다.
2. 점점 빨라지기에 시간에 따라 속도를 조절해야한다.
3. 장애물을 뛰어넘는 도중 중간의 밝은 틈새로 인해 허공답보를 하지 않아야 한다.
4. 중간으로 날아오는 까마귀는 서보모터의 각도가 0일 때 디폴트로 아래키가 눌리게 하여 넘긴다.
낮에서의 기능 구현이 완료되고나서 밤의 기능을 구현했습니다.
조도센서는 주변 불빛에 굉장히 민감하기 때문에 실제 테스트 때에는 휴대폰으로 조도센서 주변을 가리고 하였습니다.

1번 코드 (실패작)

1번 코드는 우선 장애물에 맞게 점프 기능을 제대로 구현하고
그 부분이 완성되고 나서 밤 판별 부분을 복붙을 하였으며, 백그라운드 배경 확인을 위해 버퍼를 사용하여 최근 20개의 센서값의 평균으로 낮과 밤을 판별했습니다.

문제점 : 낮과 밤의 경계에서 수 초 동안 헤메는 모습을 보였고 어떻게 수정할까 하던 도중
더욱 간결하고 효율적인 2번 코드의 처리 방식을 떠올리게 되어 폐기하였습니다.

#include <Servo.h>

Servo servo;

int dnflag = 0;
int day_threshhold = 470;
int night_threshhold = 340;
int angle = 60;
int ground_flag = 1;
int prev_sum;
int current_background = 500;
int cnt = 0;
unsigned int differtime;
unsigned int down_interval = 400;
unsigned int up_interval = 50;
unsigned int dec_down_time;
unsigned int now_time;

const int BUFFER_SIZE = 20;
int prev_val[BUFFER_SIZE];

void setup() {
  // put your setup code here, to run once:
  // Serial.begin(9600);
  servo.attach(9);
  servo.write(0);
  for (int i=0; i<BUFFER_SIZE; i++){
    prev_val[i] = 500;
  }
  dec_down_time = millis();
}

void loop() {
  // put your main code here, to run repeatedly:
  int light = analogRead(A0);
  

  // 최근 버퍼사이즈 갯수의 색깔을 평균내서 백그라운드 밝기 계산
  prev_val[cnt] = light;
  cnt++;
  if (cnt >= BUFFER_SIZE) cnt = 0;
  prev_sum = 0;
  for (int i=0; i<BUFFER_SIZE; i++){
    prev_sum += prev_val[i];
  }
  current_background = prev_sum / BUFFER_SIZE;
  
  // 시간에 따라 속도 빨라지는 것 처리하기 위한 부분
  // 공중에서 아래키 누르면 빨라지는 기능 활용
  // 22초마다 다운키까지의 딜레이가 줄어들며, 딜레이는 최대 0.1초까지 감소.
  now_time = millis();
  if (now_time - dec_down_time >= 22000 && down_interval >= 100) {
    dec_down_time = now_time;
    down_interval -= 50;
  }

  // 밤 배경일 때
  if (current_background <= 360) {
    // 첫번째 if문 (점프 기능)
    // 조건 1: 밤 배경일 경우 장애물이 밝기가 밝음
    // 조건 2: 현재 시간과 착지 시간 간격이 up_interval 이상일 경우에만 조건문 실행
    // 조건 3: ground_flag는 공중에 있을 때 0, 착지 상태시 1이며 여러번 수행을 막기 위한 flag
    if (light >= night_threshhold & now_time-differtime >= up_interval & ground_flag==1) {
      servo.write(angle);
      differtime = now_time;
      ground_flag = 0;
      // Serial.println(light);
    }
    // 두번째 if문 (착지 기능)
    // 조건 1: 밤 배경에서 장애물이 없는 경우
    // 조건 2: 현재 시간과 착지 시간 간격이 down_interval 이상일 경우에만 조건문 실행
    // 조건 3: 지상에서 여러번 수행을 막기 위한 flag
    else if (light < night_threshhold & now_time-differtime >= down_interval & ground_flag == 0){
      servo.write(0);
      differtime = now_time;
      ground_flag = 1;
    }
  }

  // 낮 배경일 때
  else {
    // 첫번째 if문 (점프 기능)
    // 조건 1: 낮 배경일 경우 장애물이 밝기가 어두움
    // 조건 2: 현재 시간과 착지 시간 간격이 up_interval 이상일 경우에만 조건문 실행
    // 조건 3: ground_flag는 공중에 있을 때 0, 착지 상태시 1이며 여러번 수행을 막기 위한 flag
    if (light <= day_threshhold & now_time-differtime >= up_interval & ground_flag == 1) {
      servo.write(angle);
      differtime = now_time;
      ground_flag = 0;
      // Serial.println(light);
    }
    
    // 두번째 if문 (착지 기능)
    // 조건 1: 낮 배경에서 장애물이 없는 경우
    // 조건 2: 현재 시간과 착지 시간 간격이 up_interval 이상일 경우에만 조건문 실행
    // 조건 3: ground_flag는 공중에 있을 때 0, 착지 상태시 1이며 여러번 수행을 막기 위한 flag
    else if (light > day_threshhold & now_time-differtime >= down_interval & ground_flag == 0){
      servo.write(0);
      differtime = now_time;
      ground_flag = 1;
    }
  }

  // Serial.print(light);
  // Serial.print(", ");
  // Serial.println(current_background);
}

2번 코드

backgroundobstacle
day490190
night65370

2번 코드는 낮과 밤의 배경과 장애물의 센서값을 측정하다보니 문득 떠올라서 작성하게 됐습니다.
표를 참고하면 65 190 370 490 순으로 수가 나열되며, 중간의 190 370은 장애물의 센서값이었습니다.
따라서 190, 370의 중간값인 280을 이용하여 | (센서값)-280 | 을 계산하면
밤 낮 구분 없이 Threshhold 이내의 값은 장애물, 밖의 값은 배경이겠구나 생각했습니다.
1번 코드에 비해 비교적 낮과 밤의 경계에서 비교적 빠르게 제정상으로 돌아와서 상황만 맞으면 기록이 훨씬 잘나왔습니다.
(최고기록 1550)

문제점 : 여전히 낮과 밤의 경계에서 점프를 한번 하는 모습을 보이며 따라서 경계에서 장애물이 튀어나올 경우 장애물에 걸리게 됩니다.

#include <Servo.h>

Servo servo;

int threshhold = 150;
int angle = 60;
int ground_flag = 1;
int middle = 280;
unsigned int differtime;
unsigned int down_interval = 400;
unsigned int up_interval = 50;
unsigned int dec_down_time;
unsigned int now_time;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  servo.attach(9);
  servo.write(0);
  dec_down_time = millis();
}

void loop() {
  // put your main code here, to run repeatedly:
  int light = analogRead(A0);
  Serial.println(light);
  
  // 낮과 밤의 장애물 센서값의 중간값과 센서값의 차이를 통해 장애물 판별
  light = abs(middle - light);

  // 시간에 따라 속도 빨라지는 것 처리하기 위한 부분
  // 공중에서 아래키 누르면 빨라지는 기능 활용
  // 22초마다 다운키까지의 딜레이가 줄어들며, 딜레이는 최대 0.1초까지 감소.
  now_time = millis();
  if (now_time - dec_down_time >= 22000 && down_interval >= 100) {
    dec_down_time = now_time;
    down_interval -= 50;
  }

  // 조건 1: 전처리한 light 값이 일정 값 이하면 장애물이 있는 것으로 판단
  // 조건 2: 현재 시간과 착지 시간 간격이 up_interval 이상일 경우에만 조건문 실행
  // 조건 3: ground_flag는 공중에 있을 때 0, 착지 상태시 1이며 여러번 수행을 막기 위한 flag
  if (light <= threshhold & now_time-differtime >= up_interval & ground_flag == 1) {
    servo.write(angle);
    differtime = now_time;
    ground_flag = 0;
    // Serial.println(light);
  }

  // 조건 1: 전처리한 light 값이 일정 값 초과면 일반 평지로 판단
  // 조건 2: 현재 시간과 점프 시간 간격이 down_interval 이상일 경우에만 조건문 실행
  // 조건 3: 여러번 수행을 막기 위한 flag
  else if (light > threshhold & now_time-differtime >= down_interval & ground_flag == 0){
    servo.write(0);
    differtime = now_time;
    ground_flag = 1;
  }
}
profile
ROS, Python, Cpp 공부 중입니다.

1개의 댓글

레전드네요 ㄷㄷ...

답글 달기