티스토리 뷰
지금까지 AVR의 PORT 레지스터를 제어하여 LED에 불이 들어오게 하거나, 푸쉬버튼 스위치의 ON/OFF 상태를 모니터링 하는 기능을 만들어 보았다.
LED를 제어하기 위한 PORT I/O핀은 출력으로만 동작될 수 있도록 설정을 하였고, 푸쉬버튼을 감지하기 위하여 버튼과 연결된 I/O 핀을 입력으로만 동작되도록 설정해 두었다.
이번 프로젝트는 DS18B20이라는 디지털 온도 센서를 이용하여 현재 온도를 측정한 후 LED를 제어하여 측정된 온도값을 출력하는 프로젝트를 진행해 볼 것이다. DS18B20은 하나의 신호선(1-Wire)을 이용하여 온도값을 읽어와야 하는 구조를 가지고 있으므로 AVR의 PORT I/O 핀을 입력과 출력을 번갈아 가며 사용하여야 한다. 따라서 이번 프로젝트는 I/O핀을 입,출력 하나의 모드로 고정시켜 동작하도록 만드는 것이 아니라, 필요에 따라 수시로 모드를 변경해 가며 동작되도록 할 것이다.
LED 온도계 프로젝트를 위한 회로도는 다음과 같다.
이전 프로젝트인 푸쉬 버튼과 LED제어 연동에서 사용하였던 회로에 DS18B20이라는 온도 센서를 PB1 핀에 추가로 연결시키는 회로이다. 일단 DS18B20의 제조사인 MAXIM(원래는 Dallas에서 만들었으나 Dallas를 MAXIM에서 인수하였음) 홈페이지에서 DS18B20의 datasheet 문서를 다운받아 어떤 디바이스인지 분석한다.
DS18B20은 여러가지 패키지형태로 생산되는데 이번 프로젝트에서는 TO-92 패키지를 기준으로 설명한다. TO-92 패키지는 아래 그림과 같은 모양으로 생겼다.
사진에서 보이는것처럼 다리가 세개 있고, 왼쪽부터 GND, DQ, VDD라는 이름을 가지고 있다. AVR은 DQ 핀 하나를 이용하여 현재 온도값을 읽어 온다. 이 부품의 특징은 DQ 핀으로 전원을 입력받아서 동작될수도 있다. 즉, 별도의 전원을 제공할 필요없이 신호라인으로 데이터 통신도 하고 전원도 사용할 수 있는 특징이 있다. 그러나 이번 프로젝트에서는 일반적인 회로 구성을 이용하여 GND핀은 보드의 GND에 연결하고 VDD핀에는 보드의 전원에 연결하는 회로를 적용하였다. DQ핀은 풀업 저항을 연결하여 평소에는 high 상태로 유지되도록 한다.
DS18B20은 아날로그 온도계가 아니고 디지털 온도계이다. 다시 말해서 DS18B20은 내부 회로를 이용하여 현재 온도값을 측정한 다음, 디지털 데이터로 현재 온도값을 제공한다.
DS18B20은 master, slave 구조로 CPU와 통신하게 된다. 즉, CPU가 master가 되고 DS18B20은 slave로 동작되는 구조이다. Master/slave 구조는 항상 master에서 먼저 신호를 slave로 보내어 slave로 부터 레지스터 값을 읽거나, 레지스터에 값을 써서 slave를 원하는대로 동작 시키는 구조를 가진다.
이미 앞에서 설명한것처럼 DS18B20은 데이터 통신을 위하여 DQ 핀 하나를 제공한다. 단 하나의 선을 이용하여 데이터 송수신 기능을 수행하는 것이다.
물론 이번 글을 통하여 어떻게 하나의 선을 이용하여 데이터 통신을 할 수 있는지 그 방법을 설명하겠지만, 임베디드 시스템 개발에 입문하는 단계에 있는 개발자라면 datasheet 문서를 보거나 인터넷에서 검색하여 통신 방법을 이해하기에 앞서, 만약 내가 하나의 선으로 데이터 통신을 하는 부품을 만들어야 한다면 어떻게 하면 가능할 것인지 한번쯤 고민해 보는것이 좋다. 그런 고민을 한 후 datasheet를 분석하면 훨씬 더 이해하기 쉬울수 있기 때문이다. 그리고 datasheet에 나오는 방법보다 더 좋은 방법을 생각했다면 언젠가는 실제로 구현해 볼 수 있는 기회도 있을것이다.
I2C(2-Wire) 통신이나 SPI 통신의 경우 어느 시점에 유효한 데이터를 읽어야 하는지 알 수 있도록 클럭 신호를 데이터 신호와 같이 보내준다. Slave 측에서는 클럭 신호에 맞춰 송수신 데이터를 처리하면 되지만 DS18B20처럼 1-Wire 데이터 통신에서는 클럭 신호가 없으므로 데이터 신호만 가지고 정확한 데이터를 주고 받아야 한다. 생각해 보면 여러가지 방법을 통해서 이러한 통신을 수행할 수 있을것이다. DS18B20은 데이터가 유지 되는 시간을 master와 slave간에 약속하여 데이터를 주고 받는다. 이렇게 데이터를 어떤 식으로 주고 받을지를 서로 약속하는것을 "프로토콜(Protocol)"이라고 한다.
DS18B20의 자세한 프로토콜은 역시 datasheet 문서에 잘 나와 있으므로 이 글에서 설명하고 있는 내용으로만 이해하기 어려우면 datasheet를 보고 이해하기 바란다.
DS18B20에서 사용하고 있는 프로토콜은 크게 세가지 동작으로 나누어 볼 수 있다.
첫번째는 initialization sequence다. 이 과정은 master가 slave에 있는 레지스터에 어떤 값을 쓰거나, 읽어 오기 전에 이제 통신이 시작될 것임을 slave에게 알려주고, 이 신호를 받은 slave는 준비가 되었음을 알려주는 절차이다. 두번째는 master에서 slave로 데이터를 write하는 프로토콜이다. 그리고 마지막 세번째는 slave로부터 데이터를 읽어오는 read 프로토콜이다. 각각의 세부 동작에 대해서 설명하도록 하겠다.
1. Initialization Sequence
위의 그림이 초기화 절차에 대해서 설명하는 것이다. 굵은 검은색 선은 master에 의해서 제어되는 신호의 흐름을 보여주는것이고, 굵은 회색 선은 slave에 의해 변화되는 신호를 보여준다. 그리고 그 사이에 가는 실선 부분은 풀업 저항으로 인한 신호의 변화되는 모습이다.
Master가 DQ 신호를 low 상태로 만든 후 최소 480us 이상 그 상태를 유지 시키다가 high 상태로 만든다. 풀업저항에 의하여 전압레벨이 high상태로 올라간다. DS18B20은 신호의 rising edge를 검출한 후 15~60us 정도 기다렸다가 신호를 low 상태로 만든다. 그리고 그 상태를 60us~240us 유지 시키다가 신호선에 대한 제어를 풀어준다. 그러면 신호레벨이 다시 풀업저항에 의하여 high로 올라가게 된다. AVR에서는 DS18B20에 의하여 신호가 low로 갔다가 다시 high 상태가 되면 다음 과정을 수행해도 된다고 판단하게 된다. DS18B20은 클럭에 동기되어 동작되는 디바이스가 아니므로 정확한 시간을 맞출수가 없다. 그래서 DS18B20의 동작 시간에 대한 규격이 비교적 넓은 범위를 가질수 밖에 없다.
Initialization sequence 과정을 C코드로 표현하면 다음과 같이 된다.
void reset_presence(void)
{
out_dq();
low_dq();
_delay_us(RESET_PULSE_DELAY);
high_dq();
in_dq();
while (get_dq());
while (!get_dq());
out_dq();
}
3번 라인에 I/O핀을 출력으로 설정하였다.
5번 라인은 최소 480us 이상 상태를 유지 시키기 위하여 delay를 주는 것이다. 이 값은 실제 보드에서 시험해 보면서 최적값을 찾으면 된다.
7번 라인에서 DS18B20의 데이터를 읽기 위하여 I/O핀을 입력으로 설정하였다.
8번 라인은 DS18B20에 의하여 신호가 low가 될 때까지 계속 DQ신호를 감시하는 코드이다.
9번 라인은 low로 떨어진 신호가 다시 high가 될 때까지 DQ신호를 감시한다. 이 부분은 굳이 타이머를 이용하여 시간을 측정할 필요는 없다. 단순히 신호가 low로 떨어졌다가 다시 high상태로 올라 오는것만 검출해내면 되는 것이다.
2. Write Time Slots
위의 그림은 master에서 slave로 한 비트 값을 write 하는 방법을 표현한 것이다. 당연히 '0'이나 '1'의 값을 write 하는 방법이다. Master가 DQ 신호를 low로 만들면 DS18B20은 신호가 low로 떨어진 시점으로부터 15us~60us 사이의 구간에서 데이터값을 읽어간다. 따라서 master는 그 구간동안 동일한 신호를 유지 시켜 줘야 한다. 데이터가 '0'인 경우에는 계속 신호를 low상태로 유지시켜 주면 되고 최소 60us 시간이 흐른후 DQ신호를 high로 만든다. 다음 데이터를 write하기위해서는 최소 1us 시간을 high상태로 유지 시켜줘야 한다. 데이터가 '1'인 경우 신호를 low로 만든 다음 1us 이후에 high 상태로 신호를 변경해 주면 된다. Write 절차를 C코드로 표현하면 다음과 같다.
void write_data(uint8_t bit)
{
out_dq();
low_dq();
_delay_us(WRITE_START_GAP);
if (bit & 1)
high_dq();
_delay_us(WRITE_SAMPLE_TIME);
high_dq();
_delay_us(WRITE_START_GAP);
}
3. Read Time Slots
위의 그림은 DS18B20이 master가 데이터를 읽어 갈 수 있도록 하는 절차이다.
DS18B20이 '0'의 비트값을 제공하는 절차는 다음과 같다. Master가 DQ신호를 low로 만든 후 최소 1us를 유지하고 신호에 대한 제어권을 slave에게 넘겨 준다. DS18B20은 falling edge를 검출한 이후 60us동안 0의 상태를 유지한다. Master는 DQ 신호가 high가 될때까지 기다렸다가 다음 데이터를 읽어 오는 동작을 위해서 최소 1us 시간을 기다려 주어야 한다.
DS18B20이 '1'의 비트값을 master에게 제공하는 절차는 다음과 같다. Master가 DQ신호를 low로 만든 다음 최소 1us 시간동안 유지 시킨후 신호 제어권을 DS18B20에게 넘긴다. DS18B20은 즉각 신호 레벨을 high로 만들어 풀업저항에 의하여 신호가 high 상태로 올라가도록 한다.
Master에서는 DQ신호를 low로 만든 후 15us 이내에 데이터를 읽어 가야한다.
Read data과정을 C코드로 만들면 다음과 같다.
uint8_t read_data(void)
{
uint8_t bit = 0;
out_dq();
low_dq();
_delay_us(READ_START_GAP);
in_dq();
high_dq();
_delay_us(READ_SAMPLE_TIME);
if (get_dq())
{
bit = 1;
_delay_us(READ_SLOT_TIME);
}
else
{
while(!get_dq());
}
_delay_us(READ_START_GAP);
return bit;
}
이렇게 세가지 프로토콜을 이용하면 DS18B20으로부터 온도값을 읽어 올 수 있다. 그러나 여기서 한가지 주의할 점이 있다. 위에서 설명한 프로토콜은 모두 한 비트값을 주고 받는 절차에 대해서 설명한 것이다. DS18B20에서 온도 값을 읽어 오기 위해서는 master에서 slave로 명령을 내려 주어야 하고, 이 명령을 받은 slave에서는 해당 명령에 따른 데이터를 master에게 제공한다. 이 때에는 비트 단위의 데이터를 사용하는 것이 아니라 바이트 단위의 데이터를 교환한다. 예를 들면 DS18B20은 내부 회로를 이용하여 측정한 현재 온도값을 Scratchpad 레지스터에 저장해 둔다.
Datasheet에 나와 있는 scratchpad 레지스터 구성은 위의 그림과 같이 모두 9바이트로 이루어져 있다. Byte0과 Byte1에 현재 온도값이 들어가게 되어 있다.
Master에서는 DS18B20의 scratchpad 레지스터의 값을 읽어 오기 위하여 Read Scratchpad[0xBE] 명령어를 이용한다. 0xBE는 바이너리로 표현하면 "1011 1110"이다. 이 데이터를 slave로 한 비트씩 차례대로 내려 보내면 되는데, master에서 상위비트(MSB)에서부터 차례대로 내려 보낸다고 가정해 보자. 그러나 slave에서는 수신되는 비트 값을 하위비트(LSB) 순서대로 처리하였다. 그렇게 되면 slave에서 수신한 데이터는 "0111 1101"이 된다. 헥사값으로 표현하면 "0x7D"가 된다. 완전히 다른 명령어를 수신하게 되는 것이다. 당연히 DS18B20에서는 Read Scratchpad 명령을 수행할 수 없게 된다.
이렇게 비트단위로 통신하는 것을 시리얼 통신(Serial communication)이라고 하는데 이때에는 반드시 상위비트에서부터 데이터를 보낼 것인지, 하위비트에서부터 데이터를 보낼것인지 보내는 쪽과 받는 쪽이 서로 확실히 약속을 하여야 한다.
DS18B20의 문서에는 하위비트(LSB) 순서대로 데이터를 주고 받아야 된다고 나와있다. 따라서 0xBE 명령을 slave로 보내기 위해서 "0 1 1 1 1 1 0 1" 순서대로 보내야 한다. 그러면 slave에서는 들어오는 비트 순서를 하위 비트에서 부터 채워나가게 된다. 그러면 결국 "1011 1110"이 되어 Read scratchpad 명령을 수행하여 Byte0의 LSB 비트부터 master로 데이터를 차례대로 보내주게 된다.
Scratchpad의 초기값은 0x50, 0x05 ,,, 를 가지고 있으므로 이 값을 비트스트림으로 표현하면 "0 0 0 0 1 0 1 0 1 0 1 0 0 0 0 0" 이 된다. Master에서는 들어오는 비트들을 LSB부터 채워나가면 0x50, 0x05값으로 읽을수 있게된다.
위의 내용을 코드로 표현하면 다음과 같이 된다. 먼저 한 바이트를 master에서 송신하는 방법이다.
void write_byte(uint8_t data)
{
int i;
for (i=0; i<8; i++)
{
write_data(data & 1);
data >>= 1;
}
}
Slave로부터 한 바이트값을 받아 오는 코드는 다음과 같다.
uint8_t read_byte(void)
{
uint8_t data = 0;
int i;
for (i=0; i<8; i++)
{
data |= read_data() << i;
}
return data;
}
다음 글에서는 실제로 DS18B20에서 데이터를 읽어 와서 AVR과 DS18B20 사이의 통신이 제대로 되고 있는지 확인하는 방법에 대해서 설명하도록 하겠다.
'입문' 카테고리의 다른 글
LED 온도계 3 (6) | 2016.03.24 |
---|---|
LED 온도계 2 (5) | 2016.03.22 |
푸쉬 버튼과 LED제어 연동 2 (1) | 2016.03.15 |
푸쉬 버튼과 LED제어 연동 1 (0) | 2016.03.12 |
LED 제어 - 부록 (3) | 2016.03.07 |