티스토리 뷰

입문

LED 제어 - 부록

Just4Fun 2016. 3. 7. 22:14

아래 그림은 AVR에서 SPI 통신을 수행하는데 필요한 SPCR이라는 레지스터이다. 

SPI통신은 주로 AVR에 연결되어 있는 다른 부품과의 통신을 위해 주로 사용된다.  이때 사용되는 부품들은 SPI 통신이 안정적으로 동작될 수 있는 최대 통신 속도에 대한 규격을 명확히 알려준다.  때로는 그 속도가 AVR CPU의 동작 속도에 비해 많이 낮은 속도가 될 수 있다.  따라서 AVR은 부품 규격에서 제안하는 통신 속도를 맞추기 위하여 여러가지 통신 속도를 설정할 수 있어야 한다.  통신 속도 조절을 위하여 SPCR 레지스터에는 SPR이라는 필드가 있다.  SPR은 두개의 비트로 이루어져 있다.  SPCR레지스터는 8비트 레지스터이므로 2비트를 SPR로 할당하면 6비트가 남게 된다.   SPI통신을 위해서는 SPR과 같은 통신 속도를 제어하는 기능 이외에 다른 많은 기능이 필요한데, 이럴때마다 새로운 레지스터를 만드는게 아니고 이미 만들어져 있는 레지스터의 빈 영역에 필요한 기능을 추가한다.  물론 어느정도는 비슷한 기능별로 묶어서 하나의 레지스터안에 할당한다.

이렇게 I/O 레지스터에 빈공간이 생기지 않도록 최대한 비트단위로 꽉꽉 채우는 이유는 레지스터 개수가 부품 가격에 영향을 미치기 때문이다.  새로운 레지스터를 만들기 위하여 그만큼 게이트도 더 추가되어야 하고, 또 레지스터는 각각의 주소를 가지고 있으므로 레지스터 개수가 많아지면 그만큼 필요로 하는 주소가 많아지게 되어 CPU 구조가 복잡해지게 된다.  

결과적으로 이와 같이 임베디드 시스템의 I/O 레지스터는 비트 단위로 필요한 값들을 읽고 쓰기를 할 수 있어야 정확한 기능을 동작 시킬수 있다.  따라서 특정 비트값만 1로 만들거나 0으로 만드는것을 자유자재로 할 수 있어야 한다.  그러기 위해서는 2진수와 16진수를 10진수 다루듯이 할줄 알아야 한다.

그래서 이번에는 LED 8개를 이용하여 원하는 위치의 LED에 불이 켜지도록 하는 프로젝트를 만들어 보겠다.  이를 통하여 bit operation 동작을 프로그램으로 구현하는 연습을 해보겠다.

프로젝트를 수행하는데 필요한 회로는 다음과 같다.

PD0 ~ PD7까지 PORT D에 8개의 LED를 연결한다.  PDx핀에 1을 쓰면 그 핀에 연결된 LED에 불이 켜지게 된다.

먼저 회로가 잘 연결 되어 있는지 확인하기 위하여 모든 LED를 켜보기로 하겠다.

#include <avr/io.h>
 
int main(void)
{
    DDRD  = 0xFF;
    PORTD = 0xFF;
 
    while (1) 
    {
    }
}

위의 코드를 적용하여 모든 LED에 불이 켜지는 지 확인한다.  만약 8개 LED 모두에 불이 켜지지 않으면 회로를 잘못 구성 했을 가능성이 있으므로 다시 확인하여 회로를 구성한다.  회로를 문제없이 구성 하였음에도 불이 켜지지 않는 LED가 있다면 부품 불량일수 있으니 다른 부품으로 교체하여 보도록 한다.

임베디드 시스템 프로젝트를 진행할 때 아무리 사소한 기능이더라도 프로그램을 수정하면 바로 바로 보드에서 확인해 보는것이 좋다.  그렇게 하지 않고 코드를 모두 작성한 이후에 보드를 동작 시켰을 때 프로그래머가 예상 했던대로 동작하면 다행이지만, 만약 어떤 기능이 제대로 동작하지 않게 되면 어디에 잘못된 부분이 있는지 그 원인을 밝혀내기가 쉽지 않다.  당연히 시간도 많이 걸리게 된다.  그것보다는 코드를 수정할 때마다 그  코드가 문제가 없는지 실제 보드에서 확인해 보는 것이 결과적으로 시간을 절약하는 길이다.

지금 진행하고 있는 프로젝트를 예로 들면 모든 LED에 불이 켜지게 하는것이 프로젝트의 최종 목적이 아니다. 다양한 패턴으로 LED를 켜지게 하고 싶은것이 프로젝트의 목적이다.  원하는 기능을 예상하여 코드를 작성했는데 만약 한두개의 LED가 켜지지 않으면 코드가 잘못된건지, 회로를 잘못 연결하였는지, 부품불량인지 그 원인을 밝혀내기가 쉽지 않다.  그것보다는 위의 코드를 이용하여 일단 하드웨어가 정상이고 회로 구성도 문제 없는지부터 확인하고 넘어가는 것이 좋다.

일단 모든 LED에 불이 켜지면 그 다음 단계로는 LED의 순서가 핀 순서대로 배열되어 있는지 확인해 본다.

#define F_CPU   1000000
#include <avr/io.h>
#include <util/delay.h>
 
int main(void)
{
    uint8_t     count = 0;
 
    DDRD  = 0xFF;
    PORTD = 0xFF;
 
    while (1) 
    {
        PORTD = count++;
        _delay_ms(100);
    }
}

위의 코드를 보드에 내려 실행 하면 100msec마다 증가되는 count값이 LED에 표시될 것이다.  이진수로 표현되는 count값이 LED로 잘 표현되는지 확인한다.

문제없이 동작되는 것을 확인하였으면 역시나 구조체를 이용한 코드로 수정하여 동일한 동작이 되는지 확인 한다.

#include <stdint.h>
#define F_CPU   1000000
#include <util/delay.h>
 
#define PORTD_REG   0x29
struct port
{
    uint8_t   pin;
    uint8_t   ddr;
    uint8_t   port;
};
 
volatile struct port *const portd = (void*)PORTD_REG;
 
void setup(void)
{
    portd->ddr  = 0xFF;
    portd->port = 0xFF;
}
 
void loop(void)
{
    static uint8_t     count = 0;
 
    _delay_ms(100);
    portd->port = count++;
}
 
int main(void)
{
    setup();
 
    while (1) 
    {
        loop();
    }
}

 

 

자, 그럼 지금부터 8개의 LED를 활용하여 몇가지 패턴으로 LED를 제어해 보겠다.

첫번째로 해볼것은 맨끝에 있는 한개의 LED를 한칸씩 이동시켜 반대편 끝까지 갔다가 다시 원래의 위치로 되돌아 오게하는 것이다.

C언어에는 비트값을 왼쪽 혹은 오른쪽으로 이동시키기 위한 비트 이동 연산자가 있다.  ">>", "<<" 이다. 이 연산자를 사용하는 방법은 다음과 같다.

1 << 0; // 0x01 
1 << 1; // 0x02 
1 << 2; // 0x04

그러므로 하나의 비트값을 좌우로 이동하기 위해서 for문을 이용하면 쉽게 구현할 수 있을것 같다.

void loop(void)
{
    int     i;
 
    for (i=0; i<8; i++)
    {
        _delay_ms(200);
        portd->port = 1 << i;
    }
}

위의 코드를 실행시키면 PD0에서 출발하여 PD7핀에 연결된 LED가 차례로 한번씩 불이 켜질것이다.  loop() 함수는 계속 반복 수행이 되므로, PD7다음에는 다시 PD0에서부터 LED의 불이 켜지게 된다.

 

 

이번에는 PD7다음에 PD0이 아니고, PD6,PD5순으로 내려가는 방향으로 LED를 하나씩 켜보도록 하겠다.

void loop(void)
{
    int     i;
 
    for (i=0; i<8; i++)
    {
        _delay_ms(200);
        portd->port = 1 << i;
    }
 
    for (i=0; i<8; i++)
    {
        _delay_ms(200);
        portd->port = 1 << (7-i);
    }
}

 

 

다음은 위의 코드를 조금 응용하여 양끝에 있는 LED 두개를 동시에 켜지게 만들고, 그 다음부터는 하나씩 안쪽으로 들어오면서 LED를 켜지게 만들어 보는것이다. 다음과 같은 패턴을 적용해 보는것이다. 'O'는 LED를 켜는 것이고 'X'는 LED를 끄는 것이다.

OXXXXXXO 
XOXXXXOX 
XXOXXOXX 
XXXOOXXX 
XXOXXOXX 
XOXXXXOX 
OXXXXXXO
void loop(void)
{
    int     i;
 
    for (i=0; i<8; i++)
    {
        _delay_ms(200);
        portd->port = (1 << i) | (1 << (7-i));
    }
}

 

 

이번에는 좀더 난이도가 높은 패턴을 구현해 보도록 하겠다.  구현하려는 패턴은 다음과 같다.

OOOOOOOO 
XXXXXXXX 
XXXXOOOO 
OOOOXXXX 
XXOOXXOO 
OOXXOOXX 
XOXOXOXO 
OXOXOXOX

물론 각각의 패턴을 직접 PORT레지스터에 쓰면 너무나 쉽게 구현할 수 있겠지만, 이러한 패턴을 잘 연구해서 프로그램으로 구현해 보도록 하겠다.

일단 어떤 식으로 동작되는지 눈으로 확인하기 위하여 각각의 패턴을 하나씩 직접 PORT레지스터에 써 보도록 하겠다.

void loop(void)
{
    _delay_ms(500);
    portd->port = 0xFF;
    _delay_ms(500);
    portd->port = ~0xFF;
 
    _delay_ms(500);
    portd->port = 0x0F;
    _delay_ms(500);
    portd->port = ~0x0F;
 
    _delay_ms(500);
    portd->port = 0x33;
    _delay_ms(500);
    portd->port = ~0x33;
 
    _delay_ms(500);
    portd->port = 0x55;
    _delay_ms(500);
    portd->port = ~0x55;
}

이제 위의 코드를 패턴을 분석하여 알맞은 알고리듬을 찾아내어야 한다.

위의 코드에서 눈여겨 봐야 되는것은 4,9,14,19번줄이다.

0: portd->port = 0xFF; 
1: portd->port = 0x0F; 
2: portd->port = 0x33; 
3: portd->port = 0x55;

한 스텝 진행 할 때마다 1의 개수가 절반씩 감소하는것을 찾을 수 있다. 즉 11111111 > 1111 > 11 > 1의 패턴이 보인다.  어떻게 이러한 패턴을 프로그램으로 만들수 있을까?

첫번째 값은 2^8-1을 하면 구할수 있다. 두번째는 2^4-1이 되고, 세번째는 2^2-1, 네번째는 2^1-1이다.  2의 승수값은 쉬프트연산을 하면 쉽게 구할수 있다.

십진수의 예를 들어보면 다음과 같은 사실을 알 수 있다.

1에 10을 곱하면 10이 된다.  여기에 다시 10을 곱하면 100이된다.  또다시 한번더 10을 곱하면 1000이된다.  1 > 10 > 100 > 1000 이 되는 것을 볼수 있다.  1을 왼쪽으로 한번 옮길때마다 10이 곱해진다는 것을 알 수 있다.  반대로 1000에서 1을 오른쪽으로 한번 쉬프트하면 100이된다.  나누기 10이 되는것과 같다.  다시한번 오른쪽으로 한번 쉬프트하면 10이 된다. 오른쪽으로 한번씩 쉬프트할때마다 나누기 10이 되는 것이다.

이와 같이 2진수에서도 1을 왼쪽으로 쉬프트하면 2를 곱하는 것과 같고, 오른쪽으로 쉬프트하면 2로 나누는것과 같다.

따라서 8은 1을 왼쪽으로 세번 쉬프트하면 되고 4는 1을 두번만 쉬프트하면 된다.  따라서 8, 4, 2, 1은 1<<3, 1<<2, 1<<1, 1<<0 과 같이 된다.  이제 3,2,1,0이라는 1씩 감소하는 숫자를 볼수 있으므로 for문을 이용하여 111111111, 1111, 11, 1이라는 숫자를 만들어 보자.

void loop(void)
{
    int     i,val;
 
    for (i=0; i<4; i++)
    {
        val = (1 << (1 << (3-i)))-1;
        portd->port = val & 0xFF;
        _delay_ms(500);
    }
}

보드에서 실행시키면 원하는 패턴대로 LED가 켜지는 것을 볼 수 있을 것이다.

 

 

다음 단계는 한 스텝 진행할 때마다 바로 위의 패턴을 index 회수만큼 복사를 하고 한번 복사 할때마다 16,8,4,2씩 왼쪽으로 쉬프트하면 된다는 것을 알 수 있다.

void loop(void)
{
    int     i,j,val;
 
    val = 0;
    for (i=0; i<4; i++)
    {
        val = (1 << (1<<(3-i)))-1;
        for (j=0; j<i; j++)
        {
            val |= val << (1 << (4-i));
        }
        portd->port = val & 0xFF;
        _delay_ms(500);
    }
}

위의 코드를 적용하여 LED가 어떻게 켜지는지 확인해 보자.

OOOOOOOO 
XXXXOOOO 
XXOOXXOO 
XOXOXOXO

이런 패턴으로 LED가 반복해서 켜지는 것을 볼 수 있다. 

 

 

이제 남은 것은 위의 패턴을 한번씩 invert 시키는 코드를 사이 사이에 추가하면 된다.

void loop(void)
{
    int     i,j,val;
 
    val = 0;
    for (i=0; i<4; i++)
    {
        val = (1 << (1<<(3-i)))-1;
        for (j=0; j<i; j++)
        {
            val |= val << (1 << (4-i));
        }
        portd->port = val & 0xFF;
        _delay_ms(500);
        portd->port = (~val) & 0xFF;
        _delay_ms(500);
    }
}

위의 코드를 보드에 적용하여 결과를 확인해 보자.  처음 구현하고자 했던 패턴대로 LED가 켜지는 것을 볼 수 있을 것이다.

 

 

물론 간단한 것들은 이런식으로 알고리듬을 적용해서 구현할 필요까지는 없지만 실제 임베디드 시스템 개발할 때에는 사소한 것들도 어느 정도 알고리듬이 필요한 경우가 생기게 마련이다.  그런때를 대비해서 평소에 간단한 것부터 알고리듬을 적용해 보는 연습을 해두는것이 좋다.

 

입문 과정 목차

'입문' 카테고리의 다른 글

푸쉬 버튼과 LED제어 연동 2  (1) 2016.03.15
푸쉬 버튼과 LED제어 연동 1  (0) 2016.03.12
푸쉬 버튼 감지 2  (3) 2016.03.06
푸쉬 버튼 감지 1  (7) 2016.03.06
LED 제어 7  (13) 2016.03.05
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/12   »
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 31
글 보관함