74LS47을 이용한 7-Segment 제어

글 내용

7-Segment를 제어하기 위해서는 8개의 포트가 필요하다.
즉, ATmega 8535에는 32개의 포트가 있기 때문에 총 4개의 7-Segment를 제어 할 수 있다.

그런데 이를 두배로 뻥 튀겨 주는게 있으면 어떨까?
이런 기능을 하는 것 중 대표적인 것이 74LS47이다.
유사한 칩으로는 74LS46, 74LS48, 74LS49, 74LS246, 74LS247, 74LS248, 74LS249가 있다.
일단 이들은 7-Segment가 어떤 타입이냐, 어떤 전압이 필요하냐에 따른 구분으로 기능은 동일하다.
이들 칩을 BCD to 7-Segment Decoder/Driver이라고 부른다.
기능은 BCD 값을 입력하면 그에 맞는 값을 7-Segment에 출력 한다.
여기서 BCD는 Binary-Coded Decimal의 약자로서 이진수와는 조금 다른 개념이다.
간단하게 말하면 십진수의 각 자리를 2진수로 표현 한 것이다.

예를 들어 보면 십진수로 261은
2는 2진수로 0010
6은 2진수로 0110
1은 2진수로 0001
이므로 0010 0110 0001 로 표현 한 것이 BCD이다.
잘 이해가 안 되면 검색해 보도록 한다.

7-Segment 하나를 제어하는데는 8개의 포트가 필요 했지만
BCD를 사용하면 0에서 9까지 표현하는데 있어서 4개의 포트만 있으면 된다.
(이때, 7-Segment의 DP는 제외다.)

사용자 삽입 이미지


핀 이름

기능

A0 ? A3

BCD 데이터 입력

RBI

Ripple Blanking Input (Active LOW)

LT

램프 테스트 입력 (Active LOW)

BI/RBO

Blanking Input (Active LOW) /

Ripple Blanking Output (Active LOW)

a ? f

7- Segment 출력 (Active LOW)

74LS47은 위와 같이 생겼다.
핀 A3, A2, A1, A0에는 BCD 코드를 입력으로 준다. 만약 1010이라는 BCD 코드를 입력 할려면

A3 : High     A2 : Low     A1 : High     A0 : Low

위와 같이 데이터를 주면 된다.
그러면 핀 a ~ g에서 출력이 나온다.
이는 7-Segment에 있는 핀 a ~ g와 일치하게 연결 하면 된다.
단 이때, 보통 출력이 5V이기 때문에 7-Segment와 74LS47사이에 330옴 저항을 달아 줘야 한다.

그리고 나머지 포트는 사실 잘 사용하지 않는다.
74LS47을 사용하는 대부분의 목적이 포트를 줄이는 것인데
나머지 포트를 사용하면 이 효과가 없기 때문이다.
그래도 알아둬서 나쁠 것은 없기 때문에 간단히 설명하겠다.
RBI 포트가 Low인 경우 0에 해당하는 BCD 코드가 입력되었을 때, 7-Segment의 모든 램프가 꺼진다.
만약 4개의 7-Segment를 제어 시 BCD 코드로 10을 출력 한다면 RBI를 사용하지 않는다면 출력이 “0010”이 되겠지만
1보다 위의 코드 경우 RBI를 Low로 set 한 후 출력하면 “  10”과 같이 불필요한 0을 제거 해 준다.
이때 RBO는 LOW가 된다. BI는 입력된 BCD 코드와 관계 없이 7-Segment의 모든 램프를 끈다.
데이터 조작 없이 램프를 점멸 하고자 할 때 사용한다.

사용자 삽입 이미지

입력한 값에 따라 아래와 같은 형태로 출력된다.
(위에 인덱스 값이 왜 BCD가 아닌 그냥 십진수인지는 모르겠다.)

그런데 10부터 15깨지는 A ~ F가 나와야 할 것 같지만 그렇지 않다.
나는 처음에 저 문자들이 뭔가 의미가 있은 것인 줄 알았는데......    사실 아무 의미가 없는 값 들이다.
74LS47은 0부터 9까지 출력하도록 설계 되었기 때문에 나머지는 신경을 쓰지 않았다.
데이터 시트를 보년 74LS47의 내부 구조가 그려져 있는데 이 회로에 따른 부산물일 뿐이다.
유식하게 don't care라고 부르기도 한다.

이제 직접 실험을 해 보도록 하자.
아래와 같이 회로를 꾸민다.

사용자 삽입 이미지


나는 여기서 74LS47과 7-Segment를 2개로 묶어서 모듈을 만들었다.
앞으로 디지털 시계 등을 제작할 것인데 이렇게 해 두면 상당히 편리하다.

사용자 삽입 이미지Canon PowerShot A620 | Pattern | 1/60sec | Flash fired, auto mode, red-eye reduction mode | 2007:04:06 05:23:40

프로그램 소스는 다음과 같다.

#include <avr/io.h>
#include "delay.h"
int main(void)
{
   DDRA = 0xFF;
   int i = 0;

   while(1)
   {
      PORTA = ((i / 10) << 4) | (i % 10);
      delay_ms(200);
      i++;
      if( i == 100 ) i = 0;
   }
   return 0;
}



여기서 PORTA = ((i / 10) << 4) | (i % 10); 이 부분을 잘 이해 해야 한다.
(이 소스는 아래에서 다시 다루기로 한다.)
숫자를 다룰때 자주 사용하는 기법이다.
잘 이해가 안 되면 연습장을 꺼내서 i값에 따라 어떤 데이터가 PORTA에 입력되는지
한번 손으로 계산해 보는 것도 좋은 방법이다.

실행 결과는 다음과 같다.


------------------------------------------------------------------------------------------------

[ PORTA = ((i / 10) << 4) | (i % 10); ]  이해해봅시다.

PORTA란 말그대로 포트A겠지요.. atmega128이나 기타 at계열 칩들은 포트가 저런식으로 8개씩 묶여있더군요.

(i / 10) << 4)     // i를 10으로 나눈 값을 4자리 왼쪽으로 shift 시킵니다. 7세그먼트의 앞자리를 표시하기 위함입니다.

(i % 10)  // i를 10으로 나눈 나머지값을 구합니다. 이는 2개의 7세그먼트중 뒷자리 숫자를 표시하기 위함입니다.

((i / 10) << 4) | (i % 10) // 이 두개를 | (or연산)을 하는 이유는 2개의 7세그먼트에 차례로 숫자를 표시하기 위함입니다. 1, 2, 3, ...... 97, 98, 99  이렇게요..
그렇다면 그냥 PORTA=i++; 하면 되지 않느냐.. 하고 의문이 생기지만.. 그 의문은 금방 풀렸습니다.
우리는 01부터 99까지 숫자를 카운트하는게 목적이 아니고, 7세그먼트에 숫자를 차례로 표시하는것입니다.
때문에 PORT=i++; 라고 한다면 0에서 9까진 제대로 표현되겠지만, 10부터는 이상한 문자로 표시되게 될것입니다.
10은 2진수로 0000 1010 이니까요 (1010은 c자처럼 표시되겠지요)
2자리 이상은 10의 자리는 앞의 7세그먼트에, 1의자리는 뒤에 7세그먼트에 표시돼야하기 때문입니다.
그래서 앞의자리 숫자와 뒤의자리 숫자를 나누어줄 필요성이 있습니다.

예를들어 i=9 라고 했을때.
(9/10)<<4  = 0   //9를 10으로 나누면 몫이 0이므로.
9%10 = 9   //9를 10으로 나누면 몫은 없으나 나머지가 9이다.
여기서..
2진수로 두 값을 OR연산하면,
 0000 1001 이 되겠습니다.
이렇게하면 7세그먼트엔  09 가 표시되겠지요.

다시, i가 36이라고 해봅시다.
(36/10)<<4 = 48
( 2진수로 0011 0000)
36%10 = 6 (2진수로 0000 0110)
0011 0000 과 0000 0110 을 OR연산하면  0011 0110 이다. 그래서 36이 표시되겠지요..(10진수로는 54가 됩니다)

지금생각하면 간단한데 한참을 생각했네요...