본문 바로가기

카테고리 없음

지능형 쓰레기 수거로봇 개발일지 Day 4 | esp32 초음파 센서 코드 작성

같이 하기로한 팀원들이 다 흩어졌다.. 그냥 1학년이라 다들 놀고 싶은거 같다. 그래서 동갑인 남자애 한명이랑 같이 하기로 했다. 오히려 지원금 n빵하면 이득이라 나쁘지 않다(?)

 

주제 정하는 부분에서 여전히 막혔다. 맘에 딱 드는 아이디어가 안 생긴다. 근데 쓰레기통 이 놈은 어떻게서든지 끝장을 봐야겠다. 공모전 주제로 선정 안돼도 나 혼자서 끝까지 가야겠다. 이거 찝찝해서 안되겠다. 바로 진행시켜~

 

esp32란 와이파이 기능이 있는 마이크로컨트롤러이다. 아두이노와 비교하면 와이파이 기능, 메모리양, 성능 모든 부분에서 유리하다

코드는 너무 길어서 맨아래에 몽땅 적어놓았다. 

 

이번에는 초음파 센서로 쓰레기 적재량 측정하고 그 데이터들을 큐 자료구조 형태로 저장하는 코드까지만 작성해보았다. 

일단 센서 오류 감지 알고리즘은 작성하지 않았다. 

아두이노 대신 esp32를 선택한 덕분에 30일치를 모두 저장할수있게됐다!! 아두이노에서는 하루치 데이터만 저장할수있었는데 획기적인 발전이다. 30일치를 모두 저장해도 전체 메모리 양의 3%만 차지한다. 가능하면 1년치도 모두 저장할수있다. 이 부분은 나중에 lstm알고리즘 적용해보면서 손봐야겠다. 

esp32의 센서 자체의 정밀도가 +-0.3cm로 꽤나 커서 소수점 둘째자리부터는 큰 의미가 없다. 이 부분은 내가 놓칠뻔했다. 그래서 소수점 한자리수만 다루도록 코드를 작성했다. 

코드를 작성하다보니 거리 측정에 실패했을때 예외처리를 해주어야할거같아서 그 부분도 추가해주었다. 

float distance = duration / 58.0f;

이 부분은 처음 알게 됐는데 초음파 센서의 코드 컨벤션이라고 한다(개발자들 사이 통일된 작성 규칙) 걸린 시간 * 음속 / 2 (왐복시간이라 2로 나눔)의 계산을 나누기 형태로 바꿔 나타낸다고 한다. 신기하네...

 

lastMeasuerTime 이라는 변수를 쓴 이유는 10분 동안 delay()함수를 통해 시간을 지연시키려고 하면 코드 자체가 멈춰서 문제가 될수있다고 한다. 이건 내가 이전 프로젝트에서 잡지 못했던 오류이다. 개선시켜서 기분이 매우 좋다 ㅎㅎㅎ 결과적으로 10분이 지났나?를 if문에서 체크만하고 맞든 아니든 코드는 계속 loop를 도는 것이다. lastMeasuerTime 변수를 통해 마지막으로 측정한 시간을 저장하는 기준점 역할을 하는 것이다. 

millis()는 ESP32가 켜진 이후로 지금까지 흐른 시간(ms)을 계속 알려주는 함수이다. 

 

esp 32코드:

초음파 센서 발사 & 큐 자료구조 형태로 저장

/*
  ==========================================================
  초음파 센서(HC-SR04)로 10분마다 거리 측정 후
  큐(Queue) 자료구조(원형 버퍼)에 저장하는 코드
  ==========================================================

  대상 보드: ESP32 (SRAM 약 520KB, 사용 가능 힙 약 300KB 기준)
  거리값: 소수점 한 자리까지 저장 (예: 123.4cm)
*/

// ---------- 핀 설정 (ESP32 GPIO) ----------
const uint8_t TRIG_PIN = 5;
const uint8_t ECHO_PIN = 18;

// ---------- 측정 주기 설정 ----------
const unsigned long MEASURE_INTERVAL = 10UL * 60UL * 1000UL; // 10분(ms)
unsigned long lastMeasureTime = 0;

// ---------- 큐(원형 버퍼) 설정 ----------
// 데이터 타입: float (4 byte)
//  - 소수점 한 자리(예: 123.4cm)까지 저장하기 위해 float 사용
//  - HC-SR04 자체 오차가 ±0.3cm 수준이라 소수점 한 자리면 충분하고,
//    그 이상 정밀도는 센서 하드웨어 특성상 의미가 없음
//  - ESP32는 힙이 넉넉해서(약 300KB) float(4byte)를 써도 부담 없음
const uint16_t QUEUE_CAPACITY = 4320; // 10분 간격 * 30일치
float distanceQueue[QUEUE_CAPACITY];

uint16_t queueFront = 0;   // 가장 오래된 데이터 위치
uint16_t queueRear   = 0;  // 다음에 데이터를 넣을 위치
uint16_t queueCount  = 0;  // 현재 저장된 데이터 개수

// ---------- 함수: 큐에 데이터 삽입 (가득 차면 가장 오래된 값 덮어쓰기) ----------
void enqueue(float value) {
  distanceQueue[queueRear] = value;
  queueRear = (queueRear + 1) % QUEUE_CAPACITY;

  if (queueCount < QUEUE_CAPACITY) {
    queueCount++;
  } else {
    // 큐가 가득 찬 상태 -> front도 한 칸 밀어서 가장 오래된 데이터 제거
    queueFront = (queueFront + 1) % QUEUE_CAPACITY;
  }
}

// ---------- 함수: 초음파 센서로 거리 측정 (소수점 한 자리) ----------
float measureDistanceCM() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // pulseIn timeout 30ms -> 약 500cm까지 대응, 센서 미응답 시 0 반환
  unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000UL);

  if (duration == 0) {
    return 0.0f; // 측정 실패(범위 밖 또는 응답 없음)
  }

  // 거리(cm) = 왕복시간(us) / 58 (음속 약 340m/s 기준 근사식)
  float distance = duration / 58.0f;

  // 소수점 한 자리로 반올림
  distance = round(distance * 10.0f) / 10.0f;

  return distance;
}

// ---------- 함수: 현재 큐 내용을 시리얼로 출력 (디버그/확인용) ----------
void printQueue() {
  Serial.print("현재 큐 저장 개수: ");
  Serial.print(queueCount);
  Serial.print(" / ");
  Serial.println(QUEUE_CAPACITY);

  Serial.print("[");
  for (uint16_t i = 0; i < queueCount; i++) {
    uint16_t idx = (queueFront + i) % QUEUE_CAPACITY;
    Serial.print(distanceQueue[idx], 1); // 소수점 한 자리까지 출력
    if (i != queueCount - 1) Serial.print(", ");
  }
  Serial.println("]");

  Serial.print("큐가 사용 중인 메모리(byte): ");
  Serial.println(QUEUE_CAPACITY * sizeof(float));
  Serial.print("현재 남은 힙(byte): ");
  Serial.println(ESP.getFreeHeap());
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  // 시작하자마자 한 번 측정
  lastMeasureTime = millis();
  float d = measureDistanceCM();
  enqueue(d);
  Serial.print("초기 측정값(cm): ");
  Serial.println(d, 1);
}

void loop() {
  unsigned long now = millis();

  // millis()는 오버플로우(약 49일 후) 되어도 아래 방식이면 안전하게 동작함
  if (now - lastMeasureTime >= MEASURE_INTERVAL) {
    lastMeasureTime = now;

    float distance = measureDistanceCM();
    enqueue(distance);

    Serial.print("측정 거리(cm): ");
    Serial.println(distance, 1);
    printQueue();
  }

  // 다른 작업(WiFi 통신, 서버 전송, 딥러닝 등)
}