ESP8266 & mmWave(MR60BHA1) 정리본

정유빈·2024년 8월 13일

Board 및 각 설정은 앞에 내용 참고

ESP32C3 에서는 작동하지 않는다고 명시되어 있다.
Serial1을 직접적으로 사용할 경우, usb 포트가 작동하지 않을 수 있기 때문에.

해당 센서는 단일 생명체에서만 탐지가 가능하며, 두 명 이상의 사람에게서 감지가 어렵다

Frame Structure Definition

Address allocation and data information description


데이터 값에 대해서 심층 탐구

탐구할 코드 : 60ghzbreathheart.cpp 코드로
raw데이터 들이 어떠한 변환을 거쳐서 출력되는지 나와있다.
아래는 해당 코드들에 대해 전체 내용이 담겨져 있으며, 코드에 대한 주석과 해석부분을 추가로 작성해두었다.

#include "Arduino.h"
#include "60ghzbreathheart.h"


// BreathHeart_60Hz 클래스의 생성자 : Stream 객체를 받아들여 초기화
BreathHeart_60GHz::BreathHeart_60GHz(Stream *s)
    : stream(s){
  this->newData = false;  // 데이터 수신 상태 초기화, 'newData'는 새로운 데이터가 수신되었는지 여부를 나타내는 플래그
}

/* 데이터를 스트림에서 읽으며, 'MESSAGE_HEAD1'과 'MESSAGE_HEAD2' 바이트를 확인하여 유효한 데이터 프레임인지 확인한다
또한, 유효한 데이터 프레임이 수신되면, 이를 MSG 배열에 저장하고, 'newData' 플래그를 true로 설정하여 새로운 데이터가
수신 되었음을 알린다. */

// 데이터를 수신하고 처리하는 메서드
void BreathHeart_60GHz::recvRadarBytes(){
  while (stream->available()) { // 데이터를 수신할 준비가 되었는지 확인
    if(stream->read() == MESSAGE_HEAD1){           //Receive header frame 1(첫 번째 헤더 바이트 확인)
      if(stream->read() == MESSAGE_HEAD2){         //Receive header frame 2
        dataLen = stream->readBytesUntil(MESSAGE_END2, Msg, 20);  // 데이터 끝까지 읽음
        if (dataLen > 0 && dataLen < 20){
          Msg[dataLen] = MESSAGE_END2;  // 종료 바이트 추가
          this->newData = true;  // 새로운 데이터가 수신되었음을 표시
        }
      }
    }
  }
}

/* 아래 함수는 수신된 데이터를 직렬 포트로 출력하며, 
데이터 출력을 완료한 후 'newData' 플래그를 'false'로 설정하고, 배열을 초기화*/

//데이터 출력 함수('showData') Radar transmits data frames for display via serial port
void BreathHeart_60GHz::showData(){
  if(this->newData){
    Serial.print(MESSAGE_HEAD1, HEX);  // 첫 번째 헤더 출력
    Serial.print(' ');
    Serial.print(MESSAGE_HEAD2, HEX);  // 두 번째 헤더 출력
    Serial.print(' ');
    char charVal[4];
    for (byte n = 0; n < dataLen+1; n++) {
      sprintf(charVal, "%02X", Msg[n]);  // 데이터를 16진수로 변환하여 저장
      Serial.print(charVal);
      Serial.print(' ');
    }
    Serial.println();
    this->newData = false;  // 데이터 출력을 완료했으므로 플래그를 초기화
    Msg[dataLen] = {0};  // 메시지 배열 초기화
  }
}


/* 아래 함수는 레이더 데이터를 통해 사람이 존재하는지, 그리고 움직임이 있는지를 판단한다
recvRandarBytes()를 호출하여 데이터를 수신하고, reset_val()로 이전 데이터를 초기화한다.
사람의 존재, 움직임, 신체 신호, 거리, 방향 등의 정보를 판별하고 결과를 sensor_report에 저장한다. */

// 사람의 존재와 움직임 판단 함수('HumanExis_Func') Judgment of occupied and unoccupied, approach and distance
void BreathHeart_60GHz::HumanExis_Func(){
  recvRadarBytes();   // 데이터를 수신
  reset_val();  // 이전 데이터를 초기화 
  if(this->newData){   // 새로운 데이터가 수신되었는지 확인
    switch(Msg[0]){   // 첫 번째 메시지 바이트에 따라 분기
      case HUMAN_PSE_RADAR:  
        switch(Msg[1]){   // 두 번째 메시지 바이트에 따라 분기
          case PRESENCE_INF:
            switch(Msg[4]){  // 네 번째 메시지 바이트에 따라 사람의 존재 여부 판단
              case NOONE_HERE:
                showData();
                sensor_report = NOONE;  // 사람이 없음을 나타냄
                break;
              case SOMEONE_HERE:
                showData();
                sensor_report = SOMEONE;  // 사람이 있음을 나타냄
                break;
            }
            break;
          case MOVE_INF:
            switch(Msg[4]){   // 네 번째 메시지 바이트에 따라 움직임 정보 판단
              case PSE_NONE:
                showData();
                sensor_report = NONEPSE;  // 움직임이 없음을 나타냄
                break;
              case STATIONARY:
                showData();
                sensor_report = STATION;  // 정지 상태임을 나타냄
                break;
              case MOVEMENT:
                showData();
                sensor_report = MOVE;  // 움직임이 있음을 나타냄
                break;
            }
            break;
          case BODY_SIG:
            showData();
            sensor_report = BODYVAL;  // 신체 신호가 있음을 나타냄
            bodysign_val = Msg[4];  // 신체 신호 값을 저장
            break;
          case DISTANCE:
            showData();
            sensor_report = DISVAL;   // 거리 값을 나타냄 
            distance = (Msg[4] << 8 | Msg[5]) / 100.0; // 거리를 계산(미터 단위)
            break;
          case DIRECTIONS:
            showData();
            sensor_report = DIREVAL;   // 방향 정보를 나타냄
            Dir_x = Byte2Int(Msg[4], Msg[5]);  // x축 방향 계산
            Dir_y = Byte2Int(Msg[6], Msg[7]);  // y축 방향 계산
            Dir_z = Byte2Int(Msg[8], Msg[9]);  // z축 방향 계산
            break;
        }
        break;
    }
  }
}

// 아래 함수는 레이더 데이터를 통해 호흡과 심박수를 측정한다.
//호흡과 심박수를 측정하는 함수('Breath_heart')Respiratory and heart rate data analysis
void BreathHeart_60GHz::Breath_Heart(){
  recvRadarBytes();  // 데이터를 수신
  reset_val();  // 이전 데이터를 초기화
  if(this->newData){
    switch(Msg[0]){
      case HEART_INF:
        switch(Msg[1]){
          case HEART_RATE:
            showData();
            sensor_report = HEARTRATEVAL;  // 심박수를 나타냄
            heart_rate = Msg[4];  // 심박수 값을 저장(raw value)
            break;
          case HEART_RATE_WAVE:
            showData();
            sensor_report = HEARTRATEWAVE;  // 심박수 파형을 나타냄
            heart_point_1 = Msg[4];  // 심박 파형의 첫 번째 포인트 저장
            heart_point_2 = Msg[5];  // 심박 파형의 두 번째 포인트 저장
            heart_point_3 = Msg[6];  // 심박 파형의 세 번째 포인트 저장
            heart_point_4 = Msg[7];  // 심박 파형의 네 번째 포인트 저장
            heart_point_5 = Msg[8];  // 심박 파형의 다섯 번째 포인트 저장
            break;
        }
        break;
      case BREATH_RATE_RADAR:
        switch(Msg[1]){   // 두 번째 메시지 바이트에 따라 분기
          case BREATH_INF:
            switch(Msg[4]){  // 네 번째 메시지 바이트에 따라 호흡 상태 판단
              case BREATH_NORMAL:
                showData();
                sensor_report = BREATHNOR;  // 정상 호흡
                break;
              case BREATH_RAPID:
                showData();
                sensor_report = BREATHRAPID;  // 빠른 호흡
                break;
              case BREATH_SLOW:
                showData();
                sensor_report = BREATHSLOW;  // 느린 호흡
                break;
              case BREATH_NONE:
                showData();
                sensor_report = BREATHNONE;  // 호흡 없음
                break;
            }
            break;
          case BREATH_VAL:
            showData();
            sensor_report = BREATHVAL;  // 호흡 속도를 나타냄
            breath_rate = Msg[4];  // 호흡 속도 값을 저장
            break;
          case BREATH_WAVE:
            showData();
            sensor_report = BREATHWAVE;  // 호흡 파형을 나타냄
            breath_point_1 = Msg[4];  // 호흡 파형의 첫 번째 포인트 저장
            breath_point_2 = Msg[5];  // 호흡 파형의 두 번째 포인트 저장
            breath_point_3 = Msg[6];  // 호흡 파형의 세 번째 포인트 저장
            breath_point_4 = Msg[7];  // 호흡 파형의 네 번째 포인트 저장
            breath_point_5 = Msg[8];  // 호흡 파형의 다섯 번째 포인트 저장
            break;
        }
        break;
    }
  }
}

/*아래 함수 다시 해석 */

void BreathHeart_60GHz::SleepInf_Decode(){
  recvRadarBytes();   // 데이터를 수신
  reset_val();  // 이전 데이터를 초기화
  if(this->newData){  // 새로운 데이터가 수신되었는지 확인
    switch(Msg[0]){  // 첫 번째 메시지 바이트에 따라 분기
      case SLEEP_INF:
        switch(Msg[1]){  // 두 번째 메시지 바이트에 따라 분기
          case INOUT_BED:
            switch(Msg[4]){
              case OUT_BED:
                showData();
                sensor_report = OUTBED;
                break;
              case IN_BED:
                showData();
                sensor_report = INBED;
                break;
              case INOUT_NONE:
                showData();
                sensor_report = NOINOUT;
                break;
            }
            break;
          case SLEEP_STATE:
            switch(Msg[4]){
              case AWAKE:
                showData();
                sensor_report = SLEEPAWAKE;
                break;
              case LIGHT_SLEEP:
                showData();
                sensor_report = SLEEPLIGHT;
                break;
              case DEEP_SLEEP:
                showData();
                sensor_report = SLEEPDEEP;
                break;
              case SLEEP_NONE:
                showData();
                sensor_report = SLEEPNONE;
                break;
            }
            break;
          case AWAKE_TIME:
            showData();
            sensor_report = AWAKETIME;
            awake_time = Msg[4] << 8 | Msg[5];   // Time: minutes
            break;
          case LIGHTSLEEP_TIME:
            showData();
            sensor_report = LIGHTTIME;
            light_time = Msg[4] << 8 | Msg[5];   // Time: minutes
            break;
          case DEEPSLEEP_TIME:
            showData();
            sensor_report = DEEPTIME;
            deep_time = Msg[4] << 8 | Msg[5];   // Time: minutes
            break;
          case SLEEP_SCORE:
            showData();
            sensor_report = SLEEPSCORE;
            sleep_score = Msg[4];
            break;
          case SLEEP_STATUE:
            showData();
            sensor_report = SLEEPSTATUE;  // 수면 상태를 나타냄
            switch(Msg[4]){
              case SOMEONE_HERE:
                existence = true;
                break;
              case NOONE_HERE:
                existence = false;
                break;
            }
            switch(Msg[5]){
              case DEEP_SLEEP:
                sleep_status = SLEEPDEEP;
                break;
              case LIGHT_SLEEP:
                sleep_status = SLEEPLIGHT;
                break;
              case AWAKE:
                sleep_status = SLEEPAWAKE;
                break;
              case SLEEP_NONE:
                sleep_status = SLEEPNONE;
                break;
            }
            breath_rate = Msg[6];
            heart_rate = Msg[7];
            turn_num = Msg[8];
            substantial_move_ratio = Msg[9];
            samll_move_ratio = Msg[10];
            apnea_num = Msg[11];
            break;
          case SLEEP_QUALITY:
            showData();
            sensor_report = SLEEPQUALITY;
            sleep_score = Msg[4];
            sleep_time = Msg[5] << 8 | Msg[6];
            awake_time_radio = Msg[7];
            light_time_radio = Msg[8];
            deep_time_radio = Msg[9];
            outbed_time = Msg[10];
            outbed_num = Msg[11];
            turn_num = Msg[12];
            breath_rate = Msg[13];
            heart_rate = Msg[14];
            apnea_num = Msg[15];
            break;
          case SLEEP_ERROR:
            switch(Msg[4]){
              case SLEEP_LESS4H:
                showData();
                sensor_report = SLEEPLESS4H;
                break;
              case SLEEP_OVER12H:
                showData();
                sensor_report = SLEEPOVER12H;
                break;
              case SLEEP_LONGTIMENOONE:
                showData();
                sensor_report = LONGTIMENOONE;
                break;
              case SLEEP_ERRORNONE:
                showData();
                sensor_report = ERRORNONE;
                break;
            }
            break;
        }
        break;
    }
  }
}

/* 아래 함수는 센서로 데이터를 전송하고, 그에 대한 응답을 처리한다.
cyclic 플래그가 설정되었거나 전송 횟수가 기준치('checkdata_len')보다 적을때 데이터를 전송한다 
데이터를 전송한 후, recvRadarBytes()를 호출하여 응답을 수신하고 수신된 데이터를 출력한다. */

//Send data frame
void BreathHeart_60GHz::send_func(const unsigned char* buff, int len, bool cyclic /*=false*/){
  if(cyclic || count < checkdata_len){   // 주기적으로 데이터를 전송할지 여부 확인
    if(cyclic || count < 1){
      stream->write(buff, len);  // 데이터를 전송 
      stream->flush();  // 버퍼를 비움
    }
    do{
      recvRadarBytes();  // 데이터를 수신
      delay(20);  // 약간의 대기 시간
    }while(!(this->newData));  // 새로운 데이터가 수신될 때까지 반복
    if(cyclic || count < 1){
      Serial.print("  Sent  ---> ");  
      data_printf(buff, len);  // 전송한 데이터를 출력
    }
    if(count%2 == 1){
      Serial.print("Receive <--- ");
      showData();   // 수신한(받은) 데이터를 출력
    } 
    this->newData = false;  // 새로운 데이터 플래그 초기화
  }
  count++;  // 전송 횟수 증가
}


/* 센서의 전송 모드를 설정한다.
사용자가 입력한 'mode' 값에 따라 센서를 실시간 데이터 전송 모드 또는 수면 상태 전송 모드로 설정 
잘못된 모드가 입력되면 오류 메시지를 출력한다. */


//모드 선택 함수(Transfer mode selection)
void BreathHeart_60GHz::ModeSelect_fuc(int mode){
  if (mode == 1){   // 모드 1 : 실시간 데이터 전송 모드 
    stream->write(realtime_mode_frame, mode_frame_len);  // 실시간 모드 활성화
    stream->flush();  // 버퍼를 비움
    Serial.println("Real-time data transfer mode ON!");
  }
  else if (mode == 2){   // 모드 2 : 수면 상태 전송 모드 
    stream->write(sleepstatus_mode_frame, mode_frame_len);  // 수면 상태 모드 활성화
    stream->flush();  // 버퍼를 비움
    Serial.println("Sleep state transfer mode ON!");
  }
  else Serial.println("Input error, please reselect the mode - 1: indicates real-time transmission mode, 2: indicates sleep state mode.");
}

//센서 리셋 함수(Reset radar)
void BreathHeart_60GHz::reset_func(){
  stream->write(breath_reset_frame, reset_frame_len);
  stream->flush();
  Serial.println("Radar reset!");
}

/* 두 개의 바이트를 (2byte = 16bit) 16비트 정수로 결합하고, 이를 부호있는 정수로 변환
부호 비트를 확인하여 음수인지 양수인지를 판단한다.*/

//Two Byte to signed integer
float BreathHeart_60GHz::Byte2Int(unsigned int x, unsigned int y){
  float z = ((x << 8 | y) & 0x7FFF) / 100.00;
  if (((x << 8 | y) & 0x8000) == 0x8000)z = -z;
  return z;
}

//print redirect
void BreathHeart_60GHz::data_printf(const unsigned char* buff, int len){
  char charVal[4];
  for(int i=0; i<len; i++){
    sprintf(charVal, "%02X", buff[i]);
    Serial.print(charVal);
    Serial.print(' ');
  }
  Serial.println();
}

//reset the radar values
void BreathHeart_60GHz::reset_val(){
  sensor_report = 0x00;

  bodysign_val = 0x00;
  distance = 0x00;
  Dir_x = 0x00;
  Dir_y = 0x00;
  Dir_z = 0x00;

  heart_rate = 0x00;
  heart_point_1 = 0x00;
  heart_point_2 = 0x00;
  heart_point_3 = 0x00;
  heart_point_4 = 0x00;
  heart_point_5 = 0x00;
  breath_rate = 0x00;
  breath_point_1 = 0x00;
  breath_point_2 = 0x00;
  breath_point_3 = 0x00;
  breath_point_4 = 0x00;
  breath_point_5 = 0x00;

  awake_time = 0x00;
  light_time = 0x00;
  deep_time = 0x00;
  sleep_score = 0x00;
  sleep_status = 0x00;
  turn_num = 0x00;
  substantial_move_ratio = 0x00;
  samll_move_ratio = 0x00;
  apnea_num = 0x00;
  sleep_time = 0x00;
  awake_time_radio = 0x00;
  light_time_radio = 0x00;
  deep_time_radio = 0x00;
  outbed_time = 0x00;
  outbed_num = 0x00;
}

raw 데이터를 읽어오고 어떻게 바뀌는지에 대한 내용

  • Msg[] 배열은 최대 20 바이트를 읽기 위해 할당 되어있다.
  • void HumanExis_Func()
    • 반환 값
      • unsigned int sensor_report : 인간의 움직임 정보는 변경이 발생할 때마다 보고 된다.
      • distance : ( Msg[4] << 8 | Msg[5] ) / 100.0
        << 비트 시프트 연산으로, Msg[4]의 값을 왼쪽으로 8비트 이동 시킨다. 즉, Msg[4]의 값을 256배 키우는 것과 같아서, Msg[4]의 값이 1이라면, 256 이다.
        • 비트 OR 연산(|)
          Msg[4] << 8 | Msg[5] 는 두 값을 결합한다. 먼저 Msg[4]의 값을 8비트 왼쪽으로 이동시키고, 그 결과에 Msg[5]의 값을 OR 연산하여 두 값을 하나의 정수로 합친다. 이 연산을 통해 Msg[4]와 Msg[5]를 하나의 16비트 정수로 결합하게 된다
        • 나누기(/ 100.0)
          결합된 값을 100.0으로 나누어주는데, 이는 결과를 부동 소수점 수로 변환,
          이 연산을 통해 거리(distance)를 미터 또는 다른 단위로 변환하는 것으로 보인다.
      • int bodysign_val : 반환된 값은 Human Movement Parameter의 값을 나타내며, 이 값은 1초에 한 번씩 보고된다.
      • float distance : 센서는 인체와의 현재 거리를 측정하며, 값은 미터 단위이다. 이 값은 2초마다 한 번씩 보고된다.
      • float Dir_x, Dir_y, Dir_z : 센서가 감지한 신체 위치 정보를 나타내며 위치 정보는 미터 단위로 양수 및 음수 단위가 있다. 이 값은 2초마다 한 번씩 보고된다.
  • void Breath_Heart()
    • 반환 값

      • unsigned int heart_rate : 심박수 값 3초마다 보고되며, 값 범위는 0~100 이다

      • unsigned int heart_point1, heart_point2, heart_point3, heart_point4, heart_point5 : 심박수 파형 데이터. 5바이트는 실시간으로 1초 동안 5개의 값을 나타내며, 파형은 sin파 데이터 이고 중심축은 128이다.
        즉, 심박수 강도가 0일때 128로 표시되며, 이 값은 1초에 한 번씩 보고 된다.

      • unsigned int breath_rate : 호흡 수치, 3초마다 보고되며, 값 범위는 0~20

profile
대한민국의 미래를 묻는다면 고개를 들어 나를 쳐다보거라

3개의 댓글

comment-user-thumbnail
2024년 8월 17일

열일하시네요 유빈씨

1개의 답글