티스토리 뷰
앞에서 설명한 글에서처럼 빨간색 LED와 초록색LED가 서로 반대의 상태로 불이 켜지거나 꺼지는 것이 확인 되었으면, 최소한 하드웨어 구성이 제대로 되었을뿐 아니라 프로그램으로 코딩한대로 하드웨어가 제대로 동작되고 있다고 판단할 수 있다.
일반 컴퓨터에서 "hello, world" 프로그램을 이용해서 개발환경이 정상적으로 동작하는 것을 확인했다면, 임베디드 시스템에서는 LED 제어를 통해서 개발환경 확인과 더불어 프로그램을 이용해서 하드웨어를 제어하는 방법과 원리를 이해할 수 있게 된다. 따라서, LED 제어 방법을 이해하게 되면 AVR을 이용한 다른 기능들은 그 기능을 제어하는 레지스터의 주소와 그 안에 들어있는 내용만 다를뿐이지 방법은 별로 다르지 않다는 것을 알게 될 것이다.
다시한번 사용하고 있는 AVR의 핀 구성을 살펴보자.
위의 그림을 자세히 보면 7,8,20,21,22번 핀을 제외하고 나머지 핀들의 이름이 모두 'P'로 시작되는 것을 알 수 있다. PB와 PD는 0~7번까지 있고 PC는 0~6번까지 있다. 그리고 각 핀마다 ()안에 다른 이름이 들어가 있는데 이 부분은 뒤에서 설명하기로 하겠다. 여기에서는 P로 시작되는 기능에 대해서만 설명한다.
AVR의 블럭도를 보면 또다른 정보를 얻을 수 있다.
위의 그림을 보면 핀구성도에 나오는 PB,PC,PD가 AVR 칩안에 어떤 블럭과 연결되어 있는지 알 수 있다. 즉, PB는 PORT B라는 블럭에 연결되고 PC는 PORT C에 연결된다. 마찬가지로 PD는 PORT D블럭과 연결되는 것을 볼 수 있다.
전체 블럭도를 보면 AVR CPU와 각 PORT 블럭들이 DATABUS를 통해서 서로 연결된다는 것을 알 수 있다. 이를 통해서 우리는 PORT 블럭을 제어하면 P로 표현되는 핀들을 제어할 수 있겠구나 유추할 수 있다.
이제 데이터쉬트에서 PORT에 대한 설명이 되어 있는 부분을 분석하면 어떻게 각각의 핀을 제어할 수 있는지 방법을 알수 있게 된다.
I/O-Ports 라고 제목이 붙여진 챕터를 꼼꼼히 읽어본다. 물론 처음 임베디드 시스템을 접해보는 경우라면 문서 안에 있는 내용과 단어들도 생소할 것이고, 특히 영어로 되어 있어서 바로 바로 이해가 되지는 않을 수도 있을 것이다. 하지만 어쩔수 없다. 어렵더라도 몇번 읽어 보면 어느정도 전체 윤곽을 그려 볼 수 있고, 몇번의 프로젝트를 수행하다 보면 어느틈엔가 저절로 이해되는 날이 올것이다.
문서에 각각의 PORT 블럭은 세개의 레지스터를 가지고 있다는 것을 설명하고 있다.
Three I/O memory address locations are allocated for each port, one each for the Data Register – PORTx, Data Direction Register – DDRx, and the Port Input Pins – PINx.
각각의 핀을 출력으로 쓸것인지 입력으로 쓸것인지 결정하는 레지스터(DDR)와 출력으로 설정된 경우 출력값을 선택할 수 있는 레지스터(PORT), 입력으로 설정된 경우 입력 값을 읽어 볼 수 있는 레지스터(PIN)가 있음을 설명한다.
각 레지스터는 8bit(1 byte) 크기를 가지고 각 bit가 각 포트의 외부 핀과 일대일 대응이 된다.
실제 예를 들어 동작을 설명하도록 하겠다. LED 제어 1 글에서 초록색 LED는 14번 핀에 연결되어 있다. 즉, PB0 핀에 연결된 것이다. PB0 핀에 high신호와 low신호를 인가하면 LED를 켰다, 껏다 할 수 있는 것이다. 그렇게 동작하기 위해서는 PB0 핀이 CPU입장에서는 출력으로 설정되어야 한다는 뜻이 된다.
입출력을 결정하는 것이 DDR 레지스터라고 앞에서 설명했으므로 DDR 레지스터를 이용하여 PB0 핀을 출력으로 설정해주면 된다.
문서 14.2.1 Configuring the Pin을 보면 다음과 같은 설명이 나온다.
The DDxn bit in the DDRx Register selects the direction of this pin. If DDxn is written logic one, Pxn is configured as an output pin. If DDxn is written logic zero, Pxn is configured as an input pin.
간단히 말해서 DDR레지스터의 특정 bit에 1을 쓰면, 그 핀은 출력으로 동작되고, 0을 쓰면 입력으로 동작할 수 있다는 얘기다.
초록색 LED와 연결된 PB0 핀을 출력으로 만들어야 하므로 DDRB에 1을 쓰면 된다는 것을 알 수 있다. 같은 방법으로 빨간색 LED와 연결된 PB1 핀을 출력으로 설정하기 위하여 DDRB레지스터에 두번째 bit을 1로 만들기 위해서 2를 쓰면 된다. 만약 7번째 bit값을 1로 만들려면 이진수로 10000000에 해당되는 16진수 값인 0x80을 쓰면 된다. PB0와 PB1을 동시에 출력으로 설정하기 위해서 이진수로 00000011에 해당되는 16진수값인 0x03을 쓰면 된다. DDRB레지스터는 아래와 같이 생겼다. 아래 그림에서 초기값이 모두 0으로 되어 있으므로, 모든 핀의 초기 상태는 입력으로 동작되도록 설정되어 있다.
DDR레지스터를 이용하여 입출력 설정을 마쳤으면, 그 다음으로는 해당 핀으로 원하는 신호를 내보내기 위해서 PORT레지스터를 이용하여야 한다.
역시 문서에서 PORT레지스터에 대한 동작 설명을 볼 수 있다.
If PORTxn is written logic one when the pin is configured as an output pin, the port pin is driven high (one). If PORTxn is written logic zero when the pin is configured as an output pin, the port pin is driven low (zero).
간단히 말해서 PORT레지스터의 특정 bit에 1을 쓰면 핀의 신호도 1이되고, 0을 쓰면 핀의 신호도 0이 된다는 것이다.
앞의 글에서 설명했듯이 초록색 LED를 켜기 위해서는 PB0를 high로 만들고, 빨간색 LED를 켜기 위해서는 반대로 PB1을 low로 만들어야 된다.
위의 그림이 PORTB에 대한 설명이다. 레지스터 설명에 보면 Initial Value라는 것이 있다. 이것은 AVR에 전원이 처음 인가 되었을 때 이 레지스터가 갖는 초기값이 무엇인지 알려준다. 대부분의 데이터쉬트에는 이러한 초기값이 무엇인지 알려준다. 어떤 레지스터의 경우 초기값이 정해져 있지 않고 0혹은 1의 초기값을 무작위로 가지는 경우가 있다. 이럴때는 초기값을 'x'라고 표시한다. 임베디드 시스템 개발 할 때 레지스터의 초기값이 무엇인지 미리 알아두면 문제가 생겼을때 그 문제를 해결하는데 상당한 도움을 받을수 있기 때문에 레지스터의 초기값이 무엇인지 확인해 두는 습관을 가지고 있는것이 좋다.
앞의 글 첫번째 예제에서 DDRB = 0x03; 만을 이용해서 빨간색 LED를 켜지게 한 것은 PORTB의 초기값이 모두 0이므로 15번 핀의 값이 0이 되고, 그 결과 LED를 통해서 전류가 흐르게 되는 것이다. 두번째 예제에서 PORTB = 0x03;을 CPU가 수행하게 되면 14,15번 핀 모두 1의 상태를 가지게 되므로 14번 핀에 연결된 초록색 LED로만 전류가 흐르게 된다. 15번 핀은 전압 레벨이 LED 양쪽으로 동일하므로 전류가 흐를수 없어 불이 안켜지게 되는 것이다.
문서의 14.2.4 Reading the Pin Value에 PIN레지스터로 각 핀의 상태를 읽을 수 있다고 설명하고 있다.
Independent of the setting of Data Direction bit DDxn, the port pin can be read through the PINxn Register bit.
그런데 앞의 글 세번째 예제에서는 PIN레지스터에 오히려 값을 쓰는 코드를 추가하였다. 그 이유는 역시 문서 14.2.2 Toggling the Pin에 잘 설명되어 있다.
Writing a logic one to PINxn toggles the value of PORTxn, independent on the value of DDRxn.
즉, DDR 레지스터의 값과 관계없이 PIN레지스터에 값을 쓰면 PORT레지스터의 값을 토글시킨다는 것이다. 이러한 기능은 모든 AVR 칩에 적용되어 있는 것이 아니므로 데이터쉬트를 자세히 읽어 보고 기능이 있으면 사용할 수 있다.
참고로 이러한 PIN레지스터에 의해 특정 비트를 토글할 수 없으면 '^'를 이용하여 토글 기능을 구현하면 된다. 즉, PINB = 0x03; 대신에 아래 코드를 사용하면 된다.
PORTB ^= 0x03;
이번 글에서는 AVR의 DDR레지스터와 PORT레지스터를 조작하면 핀의 출력을 제어할 수 있다는 것을 설명하였다.
다음 글에서는 어떤 방법을 통하여 이들 레지스터에 값을 쓸 수 있는지 알아 보겠다.
부록:
앞에서 DDR레지스터나 PORT레지스터를 제어하기 위해서 0x03이라는 숫자를 코드안에서 사용하였다. 이렇게 코드안에 숫자를 직접 사용하게 되면 코딩 할때는 왜 그 숫자를 사용했는지 알더라도 시간이 지나게 되면 왜 그 숫자를 사용했는지 전혀 기억하지 못할때가 있다. 특히 다른사람이 만든 코드라면 두말할 나위 없다. 따라서 가급적 코드안에는 숫자를 직접 코딩하는 것을 피하는 것이 좋다. 대신에 #define을 사용하거나 enum을 이용하는것이 좋다.
숫자를 써놓고 옆에다 코멘트를 달면 되지 않냐고 얘기할 수 있지만 그것 또한 좋은 방법은 아니다. 예전에는 코드 안에 가급적 많은 코멘트를 넣으라고 권고했지만, 요즘은 코드안에 불필요한 코멘트를 넣지 말것을 권고하고 있다. 대신에 코드에서 사용하는 함수명과 변수명을 잘 정해서 코드만 보아도 무슨 기능을 하려는지 알수 있도록 하는것이 좋다고 얘기한다.
개발을 진행하다 보면 요구사항이 변경되거나 버그에 의해서 코드를 수정하게 되는 경우가 허다한데, 문제는 코멘트는 코드에 맞게 수정하지 않는 경우가 있다는 것이다. 이런 경우 코드가 제대로 구현된 것인지 코멘트가 잘못 되어 있는지 알 수 없어 혼란만 주게 되는 경우가 발생하게 되는 것이다.
앞의 글에서 구현한 코드를 다음과 같이 변경하면 코드가 보다 명확히 이해 될것이다.
#define F_CPU 1000000
#include <avr/io.h>
#include <util/delay.h>
#define GREEN_LED _BV(PB0)
#define RED_LED _BV(PB1)
int main(void)
{
DDRB = (GREEN_LED|RED_LED);
PORTB= (GREEN_LED|RED_LED);
while (1)
{
_delay_ms(500);
PINB = (GREEN_LED|RED_LED);
}
}