티스토리 뷰

입문

LED 제어 7

Just4Fun 2016. 3. 5. 23:18

데이터쉬트의 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
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함