티스토리 뷰
데이터쉬트의 Regitster Summary에 나와있는 표에서 다음과 같은 내용을 볼 수 있다.
위의 표를 보면 PORT 레지스터가 PIN, DDR, PORT순으로 놓여져 있음을 알 수 있다. 이런 경우 구조체를 이용하면 훨씬 코드를 짜임새 있게 구현할 수 있게 된다.
struct port
{
unsigned char pin;
unsigned char ddr;
unsigned char port;
};
PORT B블럭안에 있는 레지스터는 PINB, DDRB, PORTB이다. 이 세개의 레지스터를 제어하기 위해서 다음과 같이 하면 된다.
struct port *portb = (void*)0x23;
portb변수에는 0x23이라는 주소가 들어간다. portb는 struct port라고 생긴 구조체 모양을 하고 있으므로 구조체안에 들어있는 pin은 0x23번지를, ddr은 0x24번지를, port는 0x25번지를 가리키게 된다. PINB에 0을 쓰기 위해서는 portb->pin = 0; 과 같이 하면 된다.
만약 PORT D블럭의 레지스터를 제어하려면 아래와 같이 정의하면 된다.
struct port *portd = (void*)0x29;
이를 응용하여 LED제어하는 코드를 수정하여 보도록 하겠다.
#define F_CPU 1000000
//#include <avr/io.h>
#include <util/delay.h>
#define PORTB_REG 0x23
#define GREEN_LED 0x01
#define RED_LED 0x02
struct port
{
unsigned char pin;
unsigned char ddr;
unsigned char port;
};
int main(void)
{
struct port *portb = (void*)PORTB_REG;
portb->ddr = (GREEN_LED|RED_LED);
portb->port= (GREEN_LED|RED_LED);
while (1)
{
_delay_ms(500);
portb->pin = (GREEN_LED|RED_LED);
}
}
일반적인 임베디드 시스템은 AVR에서 PORT블럭이 세개 있는것처럼 동일한 기능을 가지는 블럭들이 여러개 존재 경우가 흔하다. 그리고 각각의 블럭들은 연속하여 메모리상에 존재한다.
예를 들어 어떤 AVR에 PORT가 A~F까지 6개가 있다고 치자. 그리고 모든 Port핀을 출력으로 만들고 각각의 핀을 high상태로 초기화를 해야된다면 어떻게 해야 할까?
일반적인 AVR용 코드는 다음과 같이 할것이다.
DDRA = 0xFF;
DDRB = 0xFF;
DDRC = 0xFF;
DDRD = 0xFF;
DDRE = 0xFF;
DDRF = 0xFF;
PORTA= 0xFF;
PORTB= 0xFF;
PORTC= 0xFF;
PORTD= 0xFF;
PORTE= 0xFF;
PORTF= 0xFF;
처음에 이렇게 코드를 짰는데 나중에 요구사항이 바껴서 각 포트의 상위 4비트는 출력으로 만들고 하위 4비트는 입력으로 만들어야 한다면 DDR레지스터 모두 일일이 코드를 수정해 주어야 할 것이다. PORT의 경우 레지스터가 세개밖에 없지만 좀 기능이 많고 성능이 높은 CPU의 경우 하나의 블럭안에 레지스터가 수십개 되기도 한다. 이런 블럭이 여러개 있다면 일일이 손으로 코드를 수정해 나가는 것은 고생스러울뿐 아니라 오류가 생길 가능성도 높아진다.
이러한 것을 구조체를 사용하게 되면 코드도 깔끔해지고 오류가 발생할 확률도 낮춰준다.
앞의 예에서처럼 모든 핀을 출력으로 만들고 각 핀의 상태를 high로 만들기 위한 코드는 다음과 같이 구현하면 된다.
struct port *port = (void*)0x23;
int i;
for (i=0; i<6; i++, port++)
{
port->ddr = 0xFF;
port->port= 0xFF;
}
위와 같이 하면 모든 PORT에 대해서 같은 작업을 수행 할수 있다. 만약 입출력 구성을 바꾸게 되더라도 한줄만 수정하면 전체 포트에 대해서 같은 구성으로 변경할 수 있게 된다.
PORT에 대해서는 세개의 필드를 가지는 구조체를 정의하여 사용하였지만 AVR전체 I/O 레지스터를 하나의 구조체로 만들어 사용할 수도 있다. 구조체를 다음과 같이 정의한다.
#define REG_BASE 0X20
struct atmega_port
{
uint8_t pin;
uint8_t ddr;
uint8_t port;
};
struct atmega_8bit_timer
{
uint8_t tccr_a;
uint8_t tccr_b;
uint8_t tcnt;
uint8_t ocr_a;
uint8_t ocr_b;
};
struct atmega328p_reg
{
/* 0x20 */ uint8_t rsvd1[3];
/* 0x23 */ struct atmega_port port_b;
/* 0x26 */ struct atmega_port port_c;
/* 0x29 */ struct atmega_port port_d;
/* 0x2c */ uint8_t rsvd2[9];
/* 0x35 */ uint8_t tifr[3];
/* 0x38 */ uint8_t rsvd3[3];
/* 0x3b */ uint8_t pcifr;
/* 0x3c */ uint8_t eifr;
/* 0x3d */ uint8_t eimsk;
/* 0x3e */ uint8_t gpior0;
/* 0x3f */ uint8_t eecr;
/* 0x40 */ uint8_t eedr;
/* 0x41 */ uint16_t eear;
/* 0x43 */ uint8_t gtccr;
/* 0x44 */ struct atmega_8bit_timer tmr0;
/* 0x49 */ uint8_t rsvd4;
/* 0x4a */ uint8_t gpior1;
/* 0x4b */ uint8_t gpior2;
/* 0x4c */ uint8_t spcr;
/* 0x4d */ uint8_t spsr;
/* 0x4e */ uint8_t spdr;
/* 0x4f */ uint8_t rsvd5;
/* 0x50 */ uint8_t acsr;
/* 0x51 */ uint8_t rsvd6[2];
/* 0x53 */ uint8_t smcr;
/* 0x54 */ uint8_t mcusr;
/* 0x55 */ uint8_t mcucr;
/* 0x56 */ uint8_t rsvd7;
/* 0x57 */ uint8_t spmcsr;
/* 0x58 */ uint8_t rsvd8[5];
/* 0x5d */ uint8_t spl;
/* 0x5e */ uint8_t sph;
/* 0x5f */ uint8_t sreg;
/* 0x60 */ uint8_t wdtcsr;
/* 0x61 */ uint8_t clkpr;
/* 0x62 */ uint8_t rsvd9[2];
/* 0x64 */ uint8_t prr;
/* 0x65 */ uint8_t rsvd10;
/* 0x66 */ uint8_t osccal;
/* 0x67 */ uint8_t rsvd19;
/* 0x68 */ uint8_t pcicr;
/* 0x69 */ uint8_t eicra;
/* 0x6a */ uint8_t rsvd11;
/* 0x6b */ uint8_t pcmsk[3];
/* 0x6e */ uint8_t timsk[3];
/* 0x71 */ uint8_t rsvd12[7];
/* 0x78 */ uint8_t adcl;
/* 0x79 */ uint8_t adch;
/* 0x7a */ uint8_t adcsra;
/* 0x7b */ uint8_t adcsrb;
/* 0x7c */ uint8_t admux;
/* 0x7d */ uint8_t rsvd13;
/* 0x7e */ uint8_t didr[2];
/* 0x80 */ uint8_t tccr1[3];
/* 0x83 */ uint8_t rsvd14;
/* 0x84 */ uint16_t tcnt1;
/* 0x86 */ uint16_t icr1;
/* 0x88 */ uint16_t ocr1a;
/* 0x8a */ uint16_t ocr1b;
/* 0x8c */ uint8_t rsvd15[36];
/* 0xb0 */ struct atmega_8bit_timer tmr2;
/* 0xb5 */ uint8_t rsvd16;
/* 0xb6 */ uint8_t assr;
/* 0xb7 */ uint8_t rsvd20;
/* 0xb8 */ uint8_t twbr;
/* 0xb9 */ uint8_t twsr;
/* 0xba */ uint8_t twar;
/* 0xbb */ uint8_t twdr;
/* 0xbc */ uint8_t twcr;
/* 0xbd */ uint8_t twamr;
/* 0xbe */ uint8_t rsvd17[2];
/* 0xc0 */ uint8_t ucsr0a;
/* 0xc1 */ uint8_t ucsr0b;
/* 0xc2 */ uint8_t ucsr0c;
/* 0xc3 */ uint8_t rsvd18;
/* 0xc4 */ uint8_t ubrr0l;
/* 0xc5 */ uint8_t ubrr0h;
/* 0xc6 */ uint8_t udr0;
};
ATmega 계열의 CPU는 하위 호환성때문인지 I/O 레지스터의 배열이 좋은 편은 아니다. 그러나 ATxmega CPU의 레지스터는 체계적으로 잘 구성되어 있다.
레지스터 맵이 잘 정리되어 있으면 구조체를 작성하는것이 별로 어렵지 않게 된다.
예를 들어 바로 위의 구조체에서 PORT들을 따로 정의하였다.
/* 0x23 */ struct atmega_port port_b;
/* 0x26 */ struct atmega_port port_c;
/* 0x29 */ struct atmega_port port_d;
위의 세개의 PORT의 경우 아래와 같이 배열을 이용하면 한줄로 줄일수 있다.
/* 0x23 */ struct atmega_port port[3];
port[0]은 PORT B가 되고, port[1]은 PORT C가, port[2]는 PORT D가 된다. 실제로 임베디드 시스템은 주로 이러한 구조체를 이용하여 I/O 레지스터를 제어한다.
지금까지 7개의 글로 나누어 여러가지 방법으로 LED를 제어하는 것에 대해서 설명하였다. LED 제어 1의 앞 부분에서 이미 얘기했듯이 LED제어를 하기위해서는 단 한줄의 코드로도 가능하다. 하지만 그 한줄을 설명하기 위하여 여러가지 배경 지식을 필요로 하게 되고 그로 인하여 7개의 글로 나누어서 설명할 수 밖에 없었다. 어떤 분야든 마찬가지 이겠지만 기본을 충실히 다지는 것이 중요하다. 기본이 충실하지 못하면 문제가 발생하였을때 어디서부터 어떻게 해결해 나가야 되는지 갈피를 잡을 수 없게 되고, 새로운 업무가 주어졌을때 어떤식으로 구현하면 될지 방향을 못잡게 되기 때문이다.
임베디드 시스템 개발에 뜻이 있다면 왜 그렇게 동작이 되는지 근본 지식을 알아나가는 것을 두려워하면 안된다.
실제 보드에서 동작되는 모습을 보면서 LED제어 과정을 끝내겠다.
'입문' 카테고리의 다른 글
푸쉬 버튼 감지 2 (3) | 2016.03.06 |
---|---|
푸쉬 버튼 감지 1 (7) | 2016.03.06 |
LED 제어 6 (1) | 2016.03.05 |
LED 제어 5 (4) | 2016.03.05 |
LED 제어 4 (12) | 2016.03.02 |