CS/마이크로프로세서

[마이크로프로세서] ATmega 128 디지털 입출력 제어 (3)

nowkoes 2023. 11. 1. 00:00

본문

FND 제어

7-Segment

 

 이번 시간에는 FND(Flexible Numeric Display)를 조작해 보는 시간을 가지도록 해 보자. 7-segments로도 알려진 FND는 LED의 어느 단자를 공통 단자로 사용하느냐에 따라 Common Anode와 Common Cathode로 나뉜다.

 

Common Anode(좌), Common Cathode(우)

 

 Common Anode모든 LED의 Anode가 공통으로 연결되어 있어, LED를 점등시키려면 해당 LED의 Cathode에 GND를 공급해야 한다. 반면 Common Cathode모든 LED의 Cathode의 Cathode가 공통으로 연결되어 있어, LED를 점등시키려면 해당 LED의 Anode에 전압을 공급해야 한다. J-KIT-128-1의 경우 공동 음극형으로 설계되어 있다.

 

정적 구동 방식(좌), 동적 구동 방식(우)

 

 FND의 구동 방식은 모든 LED 제어 단자에 출력 포트를 할당정적 구동(Static Drive) 방식과, 서로 다른 FND의 제어 단자가 하나의 포트를 공유하여 FND를 선택하는 제어 핀이 별도로 존재하는 동적 구동(Dynamic Drive) 방식이 있다. 전자는 회로가 간단하여 제어가 쉽지만, 소비 전력이 크고 많은 핀이 필요하다는 단점이 있다. 반면 후자는 소비 전력이 작고 핀의 개수가 적지만, 회로가 복잡하고 제어가 어렵다는 단점이 있다. 이는 후술 할 코드에서 부가적으로 설명하도록 하겠다. J-KIT-128-1의 경우, 동적 구동 방식을 사용한다.


실습

J-KIT-128-1의 회로도 일부

 

 기본적으로 FND에 PLAY 디스플레이를 출력시키고, SW1을 누를 때마다 LED가 이동하게 해 보자. 그리고 SW2를 누르면 FND에 STOP 디스플레이를 출력시키고, 다시 SW2 입력이 들어오기 전까지 아무런 동작을 하지 않게 구현해 보자.

 

#define __DELAY_BACKWARD_COMPATIBLE__
#include <avr/io.h>
#include <util/delay.h>

#define PLAY 1
#define STOP 0
#define COUNT_MAX 15
#define DEBOUNCE_REPEAT 21

unsigned char playTable[4] = {0x66, 0x77, 0x38, 0x73};
unsigned char stopTable[4] = {0x73, 0x3F, 0x78, 0x6D};
unsigned char gTable[4] = {0x01, 0x02, 0x04, 0x08};
unsigned char patternTable[15] = {0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

void playFND(float delay, int select);

 먼저 딜레이 함수의 인자로 변수를 사용하기 위해 __DELAY_BACKWARD_COMPATIBLE__를 선언해 주고, 가독성을 위해 여러 상수들을 정의해 주었다. 또한 LED와 FND에 대한 패턴 테이블을 지정해 주었다.

 

void playFND(float delay, int select)
{
    if(select == PLAY)
    {
        for(int cnt = 0; cnt < 4; cnt++)
        {
            PORTG = gTable[cnt];
            PORTC = playTable[cnt];
            _delay_ms(delay);
        }
    }
	
    else if(select == STOP)
    {
        for(int cnt = 0; cnt < 4; ++cnt)
        {
            PORTG = gTable[cnt];
            PORTC = stopTable[cnt];
            _delay_ms(delay);
        }
    }
}

 FND를 출력하는 함수다. 앞서 J-KIT-128-1은 동적 구동 방식을 사용한다고 하였다. 따라서 FND 4칸의 LED를 한 번에 출력시키는 것이 불가능하다. 즉, 한 칸씩 따로따로 출력하고 그 간격을 짧게 하여 우리 눈에 동시에 켜진 것처럼 보이게 하는 방식이다. 만약 정적 구동 방식이라면 단순히 해당 포트에 LED를 전부 출력시키면 되지만, 상당히 불편한 부분이다. 

 

 또한 매개 변수로 delay 시간과, 어떤 문자를 출력할지 정하는 select 변수를 받아 PLAY면 PLAY를 출력하게, STOP이면 STOP을 출력하게 하였다.

 

int main(void)
{
    DDRC = 0xFF;
    DDRG = 0x0F;
    DDRA = 0xFF;
    DDRE = DDRE & ~(1 << PINE4) & ~(1 << PINE5);
    PORTA = 0x01;
	
    int selectedFND = PLAY;
    int count = 1;

 회로도를 보고 PORTC를 모두 출력으로 설정하기 위해 DDRC를 0xFF로 설정하고, 출력할 FND를 고르는 PORTG를 출력으로 설정하기 위해 DDRG를 0x0F(하위 비트 4개)로 설정하였다. DDRA와 DDRE는 앞 시간에서도 다뤘으니 알 것이라고 생각하고 넘어가겠다.

 

 FND의 출력 형태를 제어하는 변수 selectFND를 PLAY로 초깃값을 지정하고, LED의 패턴 테이블에 사용할 인덱스 변수 count를 1로 초기화하였다.

 

    while (1)
    {
        for (int i = 0; i < DEBOUNCE_REPEAT; i++)
            playFND(2.5, selectedFND);
		
        if(!(PINE & (1 << PINE4)) && selectedFND != STOP)
        {
            PORTA = patternTable[count++];
			
            if(count == COUNT_MAX)
                count = 0;
        }
		
        else if(!(PINE & (1 << PINE5)))
            selectedFND = (selectedFND == PLAY) ? STOP : PLAY;
    }
}

 여기서 특이한 점은 일반적으로 스위치의 채터링 현상을 방지하기 위한 디바운싱이 없다는 것이다. 대신 FND를 출력하는 것으로 이를 대체하였는데, 이는 디바운싱을 했을 때 상태를 관측해 보면 왜 이렇게 짰는지 알 수 있다. 

 

 만약 스위치를 누를 때 디바운싱을 위한 delay 함수를 사용한다고 가정해 보자. 스위치를 누를 때 디바운싱이 일어나면 메인 함수에 있는 무한 루프는 그 순간 동작을 멈추게 된다. 이로 인해 동적 구동 방식으로 구현되어 무한히 반복되며 디스플레이를 출력해야 하는 FND 또한 멈추게 된다. 즉, PLAY를 출력하고 있던 FND는 P, L, A, Y 중 하나만 출력하게 될 것이다. 따라서 이를 방지하고자 이러한 디바운싱 현상을 FND를 출력하는 횟수를 늘려 대체하였다.

 

총합본

더보기
#define __DELAY_BACKWARD_COMPATIBLE__
#include <avr/io.h>
#include <util/delay.h>

#define PLAY 1
#define STOP 0
#define COUNT_MAX 15
#define DEBOUNCE_REPEAT 21

unsigned char playTable[4] = {0x66, 0x77, 0x38, 0x73};
unsigned char stopTable[4] = {0x73, 0x3F, 0x78, 0x6D};
unsigned char gTable[4] = {0x01, 0x02, 0x04, 0x08};
unsigned char patternTable[15] = {0x01,0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

void playFND(float delay, int select);

int main(void)
{
	DDRC = 0xFF;
	DDRG = 0x0F;
	DDRA = 0xFF;
	DDRE = DDRE & ~(1 << PINE4) & ~(1 << PINE5);
	PORTA = 0x01;
	
	int selectedFND = PLAY;
	int count = 1;

	while (1)
	{
		for (int i = 0; i < DEBOUNCE_REPEAT; i++)
		playFND(2.5, selectedFND);
		
		if(!(PINE & (1 << PINE4)) && selectedFND != STOP)
		{
			PORTA = patternTable[count++];
			
			if(count == COUNT_MAX)
			count = 0;
		}
		
		else if(!(PINE & (1 << PINE5)))
			selectedFND = (selectedFND == PLAY) ? STOP : PLAY;
	}
}

void playFND(float delay, int select)
{
	if(select == PLAY)
	{
		for(int cnt = 0; cnt < 4; cnt++)
		{
			PORTG = gTable[cnt];
			PORTC = playTable[cnt];
			_delay_ms(delay);
		}
	}
	
	else if(select == STOP)
	{
		for(int cnt = 0; cnt < 4; ++cnt)
		{
			PORTG = gTable[cnt];
			PORTC = stopTable[cnt];
			_delay_ms(delay);
		}
	}
}

요약

FND
1. 정의: 8개의 비트를 이용하여 LED를 점등시켜 문자를 출력하는 방식
2. 종류
 a. Common Anode: Anode를 공동 단자로 사용하여 그라운드와 Cathode를 물리는 방식
 b. Common Cathode: Cathode를 공동 단자로 사용하여 전압원과 Anode를 물리는 방식
3. 구동 방식
 a. 정적 구동: 모든 FND에 숫자를 동시에 출력
 b. 동적 구동: 한 번에 하나의 FND에 숫자를 출력
반응형