코딩교육-아두이노

아두이노 디지털 핀 (단선 프로토콜 신호 처리 2) - Arduino Digtal Pin - One-Wire Protocol, DHT11

carrotweb 2022. 3. 26. 20:38
728x90
반응형

이어서 bit를 이용하여 온도 습도 데이터로 읽어 보도록 하겠습니다.

데이터에서 40bit만 추출

1. 40개의 데이터를 배열에 저장하기 위해 기존 배열을 82개에서 40개로 수정합니다.

// 배열 선언
int readSize = 40;
byte readBit[readSize];

// 배열 초기화
for (byte index = 0; index < readSize; index++) {
  readBit[index] = 0;
}

 

2. 응답 신호는 데이터가 아니기 때문에 읽지만 배열에는 저장하지 않기 위해 for() 문의 index의 시작을 -2로 수정하고 종료 값을 82에서 80으로 수정합니다.

아두이노에서 byte는 unsigned으로 되어 있어 음수를 표현할 수 없습니다.

(A byte stores an 8-bit unsigned number, from 0 to 255.)

그래서 for() 문의 index를 byte 형에서 short 형으로 변경해야 합니다.

그리고 index가 양수이고 홀수일 때(디지털 신호가 HIGH)만 처리하게 합니다.

// 응답 신호 받기
byte readOldSignal = 0; // 이전 신호
byte readCurrentSignal = 0; // 현재 신호
for (short index = -2; index < 80; index++) {
  int elapsedTime = 0; // 경과 시간
  unsigned long startTime = micros(); // 시작 시간
  do {
    readCurrentSignal = digitalRead(2);
    elapsedTime = (unsigned long)(micros() - startTime);
  } while (readOldSignal == readCurrentSignal);
  if (index > 0 && index % 2 == 1) {
    readBit[index / 2] = elapsedTime > 50 ? 1 : 0;
  }
  readOldSignal = readCurrentSignal;
}

 

3. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다. 그러면 응답 신호 후 40bit만 나오는 것을 확인할 수 있습니다.

 

추출된 40bit

0010011100000000000110000000010101000100

 

 

bit 연산 처리로 숫자 얻기

1. 40개의 bit는 5개의 byte으로 처리됩니다. 1byte는 8bit입니다.

그래서 기존 배열을 40개에서 5개로 수정합니다. 그리고 배열 변수 명을 readBit에서 readByte로 변경합니다.

// 배열 선언
int readSize = 5;
byte readByte[readSize];

// 배열 초기화
for (byte index = 0; index < readSize; index++) {
  readByte[index] = 0;
}

 

2. bit를 byte으로 합치기 위해서는 Left Shift 연산과 OR 연산으로 처리해야 합니다.

byte는 8bit으로 0부터 255까지 숫자를 표현할 수 있습니다.

byte는 8bit를 이용하여 숫자를 표현합니다. bit는 2의 거듭제곱근(2의 0승부터 7승까지)으로 값이 지정되어 있습니다.

bit
8
7
6
5
4
3
2
1
숫자
128
64
32
16
8
4
2
1

만약, byte 형 변수가 숫자 3이면 00000011입니다.

Left Shift 연산 - byte안에 있는 모든 bit를 왼쪽으로 지정된 수만큼 이동시킵니다.

// bit로는 00000011 입니다.
byte data1 = 3;

// 모든 bit를 왼쪽으로 1칸씩 이동시킵니다.
data1 <<= 1;
// 그럼 bit가 00000110 으로 됩니다.
// 그래서 data1은 6이 됩니다.

// 다시 모든 bit를 왼쪽으로 2칸씩 이동시킵니다.
data1 <<= 2;
// 그럼 bit가 00011000 으로 됩니다.
// 그래서 data1은 24가 됩니다.

 

OR 연산 - bit와 bit를 합집합으로 처리합니다. 즉 1과 0은 1, 1과 1은 1, 0과 1은 1이 됩니다.

// bit로는 00000011 입니다.
byte data1 = 3;

// bit로는 00000001 입니다.
byte data2 = 1;

data1 |= data2;
// 00000011
// 00000001
// 00000011
// 그래서 data1은 3이 됩니다.

 

8bit를 모아서 1byte를 만들어야 하기 때문에 8번의 디지털 신호가 하나의 byte가 됩니다.

그래서 index를 16으로 나눈 목이 byte 배열의 순서가 됩니다.

// 1, 3, 5, 7, 9, 11, 13, 15 -> 0
// 17, 19, 21, 23, 25, 27, 29, 31 ->1
// 33, 35, 37, 39, 41, 43, 45, 47 -> 2
// 49, 51, 53, 55, 57, 59, 61, 63 -> 3
// 65, 67, 69, 71, 73, 75, 77, 79 -> 4

 

디지털 신호가 들어올 때마다 Left Shift 연산으로 모든 bit를 왼쪽으로 1칸씩 이동시킵니다.

그리고 디지털 신호의 유지 시간으로 얻은 데이터(0과 1)를 OR 연산합니다.

// 응답 신호 및 데이터 받기
byte readOldSignal = 0; // 이전 신호
byte readCurrentSignal = 0; // 현재 신호
for (short index = -2; index < 80; index++) {
  int elapsedTime = 0; // 경과 시간
  unsigned long startTime = micros(); // 시작 시간
  do {
    readCurrentSignal = digitalRead(2);
    elapsedTime = (unsigned long)(micros() - startTime);
  } while (readOldSignal == readCurrentSignal);
  if (index > 0 && index % 2 == 1) {
    readByte[index / 16] <<= 1; // Left Shift - 모든 bit가 왼쪽으로 1칸 이동
    readByte[index / 16] |= elapsedTime > 50 ? 1 : 0; // OR 연산
  }
  readOldSignal = readCurrentSignal;
}

 

3. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다. 그러면 5개의 숫자로 나오는 것을 확인할 수 있습니다.

 

1번째 byte는 습도 정수, 2번째 byte는 습도 소수점 이하, 3번째 byte는 온도 정수, 4번째 byte는 온도 소수점 이하, 5번째 byte는 체크섬입니다.

00100110 -> 38 습도
00000000 -> 0 습도 소수점 이하
00011000 -> 24 온도
00000101 -> 5 온도 소수점 이하
01000011 -> 67 check sum

 

 

 

Checksum

오류 검증을 목적으로 전송된 자료를 검증하는 방법입니다.

1. 1번째부터 4번째까지 4개의 byte를 더한 값과 5번째 byte의 값이 같으면 오류 없이 전송되었다는 확인할 수 있습니다. 그래서 오류가 없으면 byte를 출력하고 오류가 있으면 "error"라고 출력하게 합니다.

// 체크섬
byte checksum = 0;
for (byte index = 0; index < readSize - 1; index++) {
  checksum += readByte[index];
}
if (checksum == readByte[readSize - 1]) {
  // 응답 신호 출력
  for (byte index = 0; index < readSize; index++) {
    Serial.println(readByte[index]);
  }
} else {
  Serial.println("error");
}

 

2. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다.

 

이처럼 데이터시트를 기반으로 디지털 신호 규칙과 신호 유지 시간을 적용하여 bit단위로 데이터를 읽을 수 있습니다. 그리고 데이터 읽는 규칙을 적용하여 데이터를 읽을 수 있습니다.

DHT11 센서는 다른 센서에 비해 복잡하지 않지만 데이터시트를 보고 직접 만들기는 쉽지 않습니다. 그래서 라이브러리를 사용하시는 것을 권장합니다.

데이터 byte를 float로 변환

1. 습도는 배열에서 1번째 byte(습도 정수)와 2번째 byte(습도 소수점 이하)를 float 형으로 변환하고 2번째 byte(습도 소수점 이하)에 0.1을 곱하여 소수점으로 만들어 더합니다. 동일하게 온도는 배열에서 3번째 byte(온도 정수)와 4번째 byte(온도 소수점 이하)를 float 형으로 변환하고 4번째 byte(온도 소수점 이하)에 0.1을 곱하여 소수점으로 만들어 더합니다.

// 습도
float humidity = float(readByte[0]) + (float(readByte[1]) * 0.1);
Serial.println(humidity);
// 온도
float temperature = float(readByte[2]) + (float(readByte[3]) * 0.1);
Serial.println(temperature);

 

2. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다. float형으로 나오는 것을 확인할 수 있습니다.

 

 

온도 섭씨(Celcius)를 화씨(Fahrenheit)로 변환

1. 온도는 기본적으로 섭씨(Celcius)로 나옵니다. 섭씨(Celcius)를 변환 공식을 이용해 화씨(Fahrenheit)로 변환합니다.

변환 공식은 다음과 같습니다.

화씨 = (섭씨 x 1.8) + 32
섭씨 = (화씨 - 32) x 0.55555
// 섭씨(Celcius) -> 화씨(Fahrenheit)
float fahrenheit = temperature * 1.8 + 32;
Serial.println(fahrenheit);

 

2. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다. 화씨(Fahrenheit)를 확인할 수 있습니다.

 

 

2초마다 습도, 온도(섭씨, 화씨) 가져오기

1. 이전까지 setup() 함수에서 작업한 코드들을 분리하여 함수들로 정리합니다.

// 배열 선언
int readSize = 5;
byte readData[5];

void setup() {
  Serial.begin(9600);
}

void loop() {
  delay(2000);

  if (readSensorData()) {
    Serial.print(F("Humidity: "));
    Serial.print(getHumidity());
    Serial.print(F("%  Temperature: "));
    Serial.print(getCelcius());
    Serial.print(F("°C "));
    Serial.print(getFahrenheit());
    Serial.println(F("°F"));
  }
}

// DHT11 센서로 부터 데이터를 읽습니다.
boolean readSensorData() {
  sendStartSignal();
  return reciveResponseSignal();
}

// 시작 신호를 보냅니다.
void sendStartSignal() {
  // 배열 초기화
  for (byte index = 0; index < readSize; index++) {
    readData[index] = 0;
  }

  // 시작 신호 보내기
  pinMode(2, OUTPUT);
  digitalWrite(2, LOW);
  delay(18); // LOW를 18ms(밀리초) 유지
  digitalWrite(2, HIGH);
  delayMicroseconds(40); // HIGH를 20~40us(마이크로초) 유지
  pinMode(2, INPUT_PULLUP);
}

// 응답 신호를 받습니다.
boolean reciveResponseSignal() {
  boolean result = false;

  // 응답 신호 및 데이터 받기
  byte readOldSignal = 0; // 이전 신호
  byte readCurrentSignal = 0; // 현재 신호
  for (short index = -2; index < 80; index++) {
    int elapsedTime = 0; // 경과 시간
    unsigned long startTime = micros(); // 시작 시간
    do {
      readCurrentSignal = digitalRead(2);
      elapsedTime = (unsigned long)(micros() - startTime);
    } while (readOldSignal == readCurrentSignal);
    if (index > 0 && index % 2 == 1) {
      readData[index / 16] <<= 1; // Left Shift - 모든 bit가 왼쪽으로 1칸 이동
      readData[index / 16] |= elapsedTime > 50 ? 1 : 0; // OR 연산
    }
    readOldSignal = readCurrentSignal;
  }

  // 체크섬
  byte checksum = 0;
  for (byte index = 0; index < readSize - 1; index++) {
    checksum += readData[index];
  }
  if (checksum == readData[readSize - 1]) {
    result = true;
  }

  return result;
}

// 습도(humidity)를 가져옵니다.
float getHumidity() {
  return float(readData[0]) + (float(readData[1]) * 0.1);
}

// 온도(섭씨)를 가져옵니다.
float getCelcius() {
  return float(readData[2]) + (float(readData[3]) * 0.1);
}

// 온도(화씨)를 가져옵니다.
float getFahrenheit() {
  return getCelcius() * 1.8 + 32;
}

복잡해 보이지만 프로그램 다운 모습입니다. 이처럼 순서와 기능을 함수로 분리하면 모듈화 되어 관리하고 재사용하기가 용이합니다.

2. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다. 2초마다 습도, 섭씨, 화씨를 읽어 오는 것을 확인할 수 있습니다.

728x90
반응형