AVR: DDRX 레지스터

DDRA = 0xff; 라는 코드는 어떻게 동작할 수 있을까요? avr 라이브러리 코드를 파헤쳐봅니다.


AVR 제어 프로그램의 많은 부분은 입출력 포트를 이용한 제어 코드로 이루어져 있습니다. 입출력 포트를 제어하기 위해서는 DDRX 레지스터를 사용합니다. 이하 내용은 ATmega32U4 기준입니다. 다른 코어들도 Address만 다르지 원리는 비슷합니다.

DDR의 뜻은 Data Direction Register이고, 한글로 번역하면 데이터 방향 레지스터입니다. ATmega128에는 A부터 G까지 7개의 포트가 있으므로 DDR 레지스터도 DDRA~DDRG까지 7개입니다.

DDRA = 0xff; 처럼, 저 레지스터의 값을 직접 설정해서 해당 핀을 Input으로 쓸 건지, Output으로 쓸 건지 결정합니다.

그런데 생각해보면, 프로그래머는 레지스터의 값을 변수의 형태로 직접 수정할 수 없습니다. 그런데 코드는 레지스터에 직접 값을 설정해주는 것 같은 모습을 하고 있습니다. 이게 어떻게 가능한 일일까요?

실체를 알아보기 위해, avr/io.h 파일을 열어보겠습니다. 내리다 보니, 다음과 같은 부분이 보입니다.

#elif define (__AVR_ATmega32U4__)
#  include <avr/iom32u4.h>

이 소스코드가 실행되는 보드가 ATmega32U4이면, avr/iom32u4.h 를 include 하라는 소리입니다. avr 디렉터리 아래에는 AVR의 모든 보드에 맞는 각각의 iom 헤더 파일들이 존재합니다. 이제 iom32u4.h 파일을 열어봅시다.

DDRD_SFR_IO8이라는 매크로 함수를 이용해 정의되어 있는 것을 볼 수 있습니다. 0x0A의 주소는 무엇일까요? Register Summary에서 답을 찾을 수 있었습니다.

Address 0x0A의 Name이 DDRD인 것을 볼 수 있습니다. 이제 _SFR_IO8의 정의를 찾으러 갑시다. avr/io.h 헤더를 다시 살펴보면, 이런 부분이 있습니다.

avr/sfr_defs.h 안에서, _SFR_IO8 매크로 함수의 정의를 찾을 수 있었습니다.

이 매크로는 또 _MMIO_BYTE라는 매크로 함수를 이용해 정의되어 있었습니다. 다행히도 같은 파일 내에서 _MMIO_BYTE 매크로 함수의 정의를 찾을 수 있었습니다.

매크로 함수의 인자로 넘어온 Address를 unsigned 8비트 정수 포인터로 캐스팅해주고 있었습니다.

그리고 _SFR_IO8의 정의에서, _MMIO_BYTE 매크로 뒤에 더해졌던 __SFR_OFFSET의 값을 찾아보니 다음과 같이 정의되어 있었습니다.

주석을 읽어보면, 예전의 오래된 어셈블리 코드와의 호환성을 위해 이렇게 정의를 해 놓았다는 얘기인데, 그냥 0x20으로 정의된다고 보면 될 것 같습니다.

위의 내용을 쭉 정리해보면, 아래의 문장들은 다 같은 의미를 가진다는 것을 알 수 있습니다.

DDRD = 0xff;
_SFR_IO8(0x0A) = 0xff;
_MMIO_BYTE((0x0A + __SFR_OFFSET) = 0xff;
_MMIO_BYTE((0x0A) + 0x20) = 0xff;
(*(volatile uint8_t *)((0x0A)+0x20)) = 0xff;
*(volatile uint8_t *)0x2A = 0xff;

최종적으로, 0x2A번지 레지스터를 가리키는 포인터를 통해 레지스터에 값을 넣어주고 있던 것입니다. 그리고 0x2A번지는 위의 Register Summary에서 보았듯이 DDRD 레지스터의 주소입니다.