코딩교육-아두이노

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

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

지금부터 온도/습도 센서와 직접 디지털 신호를 주고받아 처리해 보도록 하겠습니다.

시작 신호 전송과 응답 신호 받기

 

 

1. setup() 함수에서 시리얼 통신으로 출력하기 위해서 Serial.begin() 함수를 사용하여 설정합니다.

Serial.begin(9600);
delay(1000);

2번부터 5번까지 모두 setup() 함수에 추가합니다.

2. 시작 신호를 보내기 전에 응답 신호를 저장하기 위한 버퍼로 2개의 배열을 선언합니다.

첫 번째 배열은 digitalRead() 함수로 받은 디지털 신호(0:LOW, 1:HIGH)를 저장합니다.

두 번째 배열은 경과 시간을 저장합니다.

// 배열 선언
int readSize = 100;
byte readBit[readSize];
unsigned long readTime[readSize];

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

배열 크기를 100으로 선언한 것은 단순히 센서로부터 들어오는 디지털 신호를 저장하기 위해서입니다. 단, 배열 크기를 너무 크게 선언하면 디지털 핀으로 들어오는 디지털 신호가 없을 경우 무한 루프가 발생합니다. 테스트를 위해서는 작게 선언해야 합니다.

3. 시작 신호 규칙에 따라 2번 디지털 핀을 출력 모드로 설정하고 디지털 신호 LOW를 18ms(밀리초)를 유지한 후 HIGH로 전환하여 delayMicroseconds() 함수로 40us(마이크로초)를 대기합니다. 그리고 응답을 받기 위해 2번 디지털 핀을 풀업 입력 모드로 전환합니다.

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

 

4. 2번 디지털 핀으로 들어오는 디지털 신호(0:LOW, 1:HIGH)와 경과 시간을 배열에 저장합니다.

micros() 함수는 아두이노 우노 보드에서 프로그램이 시작한 후부터 마이크로초(microsecond, us)를 반환합니다. (단, micros() 함수는 약 70분이 지나면 오버플로우가 발생하여 0으로 돌아갑니다.)

// 응답 신호 및 데이터 받기
// 센서로 부터 읽은 디지털 신호를 무조건 받아 배열에 저장
for (byte index = 0; index < readSize; index++) {
  readBit[index] = digitalRead(2);
  readTime[index] = micros(); // 경과 시간
}

 

5. 배열의 내용을 시리얼 통신으로 출력합니다.

// 응답 신호 출력
for (byte index = 0; index < readSize; index++) {
  Serial.println(String(readTime[index]) + " : " + String(readBit[index]));
}

 

6. loop() 함수의 내용은 없습니다.

void loop() {
}

 

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

시리얼 모니터의 출력 내용을 확인해 보면 들어오는 신호 규칙과 신호 유지 시간이 데이터시트에서 정의한 신호 유지 시간과 정확하지 않지만 비슷한 시간으로 디지털 신호가 번갈아 들어오는 것을 확인 할 수 있습니다.

응답 신호를 읽기 전에 delayMicroseconds() 함수로 40us(마이크로초)를 대기하지 않으면 디지털 신호 1(HIGH)이 들어옵니다. 반드시 대기 함수가 있어야 합니다.

디지털 신호를 확인하기 위해 시리얼 모니터의 출력 내용 중 일부만 표시하였습니다.

// 응답 신호 LOW 40us(마이크로초)
19:15:21.071 -> 1018164 : 0
~
19:15:21.163 -> 1018204 : 0
// 응답 신호 HIGH 80us(마이크로초)
19:15:21.163 -> 1018212 : 1
~
19:15:21.347 -> 1018292 : 1

// 1bit 0
// 데이터 신호 LOW 48us(마이크로초)
19:15:21.347 -> 1018296 : 0
~
19:15:21.439 -> 1018344 : 0
// 데이터 신호 HIGH 20us(마이크로초)
19:15:21.439 -> 1018352 : 1
~
19:15:21.486 -> 1018372 : 1

// 1bit 0
// 데이터 신호 LOW 48us(마이크로초)
19:15:21.532 -> 1018376 : 0
~
19:15:21.623 -> 1018424 : 0
// 데이터 신호 HIGH 16us(마이크로초)
19:15:21.623 -> 1018428 : 1
~
19:15:21.670 -> 1018444 : 1

// 1bit 1
// 데이터 신호 LOW 48us(마이크로초)
19:15:21.670 -> 1018448 : 0
~
19:15:21.761 -> 1018496 : 0
// 데이터 신호 HIGH 64us(마이크로초)
19:15:21.761 -> 1018504 : 1
~
19:15:21.895 -> 1018568 : 1

// 1bit 0
// 데이터 신호 LOW 44us(마이크로초)
19:15:21.943 -> 1018576 : 0
~
19:15:22.031 -> 1018620 : 0
// 데이터 신호 HIGH 12us(마이크로초)
19:15:22.031 -> 1018628 : 1
~
19:15:22.031 -> 1018640 : 1

 

그럼 데이터시트에서 정의한 신호 유지 시간과 정확하지 않게 나오는 걸까요?

그 이유는 micros() 함수가 백만 분의 일 단위로 되어 있지만 아두이노 우노 보드의 CPU의 MHz에 따라 읽는 간격이 다릅니다.

아두이노 우노 보드의 CPU가 16MHz이기 때문에 micros() 함수는 4us(마이크로초)로 단위로 처리됩니다. 즉 micros() 함수는 4 배수의 us(마이크로초)로만 응답합니다. 이 간격을 Resolution(분해능(또는 분리능) - 접근한 두 점 사이에 구별 가능한 정도를 표현하는 값)이라고 합니다. 그래서 4 배수 만큼 오차 범위가 있을 수 있습니다.

응답 신호 플로터로 보기

1. 응답 신호 출력에서 첫 번째 배열만 숫자로 출력하게 수정합니다.

// 응답 신호 출력
for (byte index = 0; index < readSize; index++) {
  //Serial.println(String(readTime[index]) + " : " + String(readBit[index]));
  Serial.println(readBit[index]);
}

 

2. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 플로터를 실행시킵니다. 그러면 신호를 눈으로 쉽게 볼 수 있습니다.

 

그럼 배열을 크기를 크게 하면 센서로부터 들오는 모든 신호를 받을 수 있을까요?

그렇지 않습니다.

디지털 입력 신호가 들오지만 읽은 타임이 오래 걸리면(읽은 후 처리에 시간이 걸리면) 뒤에 오는 신호는 읽을 수 없습니다. 다시 말하면, 중간에 큐 같은 버퍼가 있어 순서대로 저장되지만 버퍼가 가득 차면 더 이상 저장되지 않기 때문입니다. 그래서 버퍼가 가득 차기 전에 계속 읽어 들어야만 손실 없이 디지털 입력 신호를 가져올 수 있습니다.

 

응답 신호 데이터 확인

온도/습도 센서로부터 들어오는 데이터는 습도 데이터 16bit, 온도 데이터 16 bit , 체크섬 8bit로 총 40bit입니다.

 

데이터로 40bit가 전송된다는 것은 디지털 신호로 0과 1이 번갈아 가면서 80번이 전송된다는 겁니다.

응답 신호까지 계산한다면 82번이 들어옵니다.

그럼 디지털 신호로 0과 1을 데이터인 bit로 어떻게 인식하게 해야 할까요?

연속적으로 들어오는 디지털 신호를 데이터시트에서 정의한 신호 유지 시간 규칙을 이용하여 bit의 0과 1로 이 인식하게 처리하면 됩니다.

bit : 0

디지털 신호 LOW가 50~54us(마이크로초)동안 들어오고 디지털 신호 HIGH가 26~28us(마이크로초)동안 들어오면 0으로 인식합니다

bit : 1

디지털 신호 LOW가 50~54us(마이크로초)동안 들어오고 디지털 신호 HIGH가 70us(마이크로초)동안 들어오면 1로 인식합니다.

플로터의 디지털 신호를 bit로 표시하면 다음과 같습니다.

 

 

응답 신호를 데이터 변환

1. 동일하게 들어오는 디지털 신호를 하나의 디지털 신호로 인식하게 하기 위해서 do ~ while() 문을 사용합니다.

// 응답 신호 및 데이터 받기
byte readOldSignal = 0; // 이전 신호
byte readCurrentSignal = 0; // 현재 신호
do {
  readCurrentSignal = digitalRead(2);
} while (readOldSignal == readCurrentSignal);

이전에 읽은 디지털 신호(0)와 현재 읽은 디지털 신호(0)가 같으면 조건문이 true가 되어 루프를 계속 돌고 현재 읽은 디지털 신호(1)가 다르면 조건문이 false가 되어 루프를 빠져나오게 됩니다. 이러면 연속적으로 들어오는 동일한 디지털 신호를 하나의 신호로 처리할 수 있습니다.

 

이 루프(do ~ while() 문)를 for() 문안에 추가하고 82번을 반복하게 합니다. 그러면 82개의 디지털 신호가 됩니다.

// 응답 신호 및 데이터 받기
byte readOldSignal = 0; // 이전 신호
byte readCurrentSignal = 0; // 현재 신호
for (byte index = 0; index < 82; index++) {
  do {
    readCurrentSignal = digitalRead(2);
  } while (readOldSignal == readCurrentSignal);
  readOldSignal = readCurrentSignal;
}

 

82개의 디지털 신호를 배열에 저장하기 위해 기존 배열을 100개에서 82개로 수정합니다. 그리고 경과 시간은 삭제하거나 주석 처리합니다.

// 배열 선언
int readSize = 82;
byte readBit[readSize];
//unsigned long readTime[readSize];

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

 

for() 문안에서 배열에 디지털 신호를 저장하게 합니다.

// 응답 신호 및 데이터 받기
byte readOldSignal = 0; // 이전 신호
byte readCurrentSignal = 0; // 현재 신호
for (byte index = 0; index < 82; index++) {
  do {
    readCurrentSignal = digitalRead(2);
  } while (readOldSignal == readCurrentSignal);
  readBit[index] = readOldSignal;
  readOldSignal = readCurrentSignal;
}

 

2. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터를 실행시킵니다. 그러면 규칙적으로 0과 1이 번갈아 전송되는 것을 확인할 수 있습니다.

 

3. 디지털 신호를 bit로 변환하기 위해서 디지털 신호 HIGH의 신호 유지 시간을 알아야 합니다. 다시 말하면, 루프(do ~ while() 문)를 빠져나오기 전까지의 시간을 계산해야 합니다.

루프 전의 시작 시간과 루프 안에서 디지털 신호를 읽은 시간을 계산하여 경과 시간을 저장하게 합니다. 그래서 루프를 빠져나오면 동일한 신호가 유지되는 시간을 알게 됩니다.

elapsedTime = (unsigned long)(micros() - startTime);

 

신호 유지 시간이 26~28us(마이크로초)이면 0, 70us(마이크로초)이면 1이기 때문에 중간 수치인 50을 기준으로 이하면 0, 이상이면 1로 저장합니다.

elapsedTime > 50 ? 1 : 0;

 

index가 짝수이면 디지털 신호가 LOW이기 때문에 신호 유지 시간에 영향이 없습니다. 그래서 0으로 저장합니다.

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

 

4. 컴파일하고 업로드한 후 아두이노 IDE에서 시리얼 모니터와 플로터를 실행시킵니다. 그러면 bit를 확인할 수 있습니다.

728x90
반응형