본 포스트에서는 ATmega8A의 포트에 LED어레이를 연결하여 LED를 구동시키는 실습을 진행한다. 실습을 위해서 LED 8개를 PB4:0와 PC4:0에 연결하며 회로도는 다음 그림과 같다. LED는 4비트씩 나누어서 포트B와 포트C에 연결하였다.
[그림 1] LED실습을 위한 회로도
이 회로도를 살펴보면 PD5핀이 스위칭 트랜지스터의 베이스와 연결되어 있음을 알 수 있다. 트랜지스터를 온시키기 위해서는 베이스에 아주 작은 양의 전류를 흘려주어야 하는데 보통 이를 위해서 큰 저항(수십~백kΩ)을 베이스와 포트핀 사이에 연결해야 한다. 하지만 본 실험에서는 외부 저항을 생략하고 그 대신에 ATmega8(A)내부에 내장된 풀업 저항을 이용하여 트랜지스터를 on시키는 방법을 사용하였다. 이를 위해서 DDRD5비트를 입력으로 (‘0’) 설정하며, 트랜지스터를 on시키기 위해서는 PORTD5 비트를 온(‘1’)시켜서 내부 풀업을 연결한다. 그 이후에 PB4:0와 PC4:0에 표시하려는 데이터를 내보내면 LED에 그 데이터가 표시되게 되는 것이다. (PD5핀과 PD6핀을 입력으로 설정해야 함에 주의해야 한다. 이 핀들을 출력으로 설정해 버리면 트랜지스터를 on/off시킬 수 없다.)
또한, 포트핀의 내부 저항이 25Ω 정도인 것을 고려하면 외부 저항의 용량은 저항은 300Ω 정도가 적당하지만, 본 실습 장치는 USB전원으로 구동되므로 전류를 줄이고 LED가 켜졌음을 확인하는데 문제가 없는 수준에서 포트핀으로 흐르는 전류 값을 최소화하기 위해서 1KΩ의 저항을 연결하였다.
만약 PB0부터 PB4까지가 모두 동시에 세트(‘1’된다 하더라도 포트B로 흘러나가는 전류의 합은 다음 식과 같이 계산할 수 있다.
여기서 1.7V는 LED양단의 전압 강하 값이다. 이것은 포트B로 흐르는 전류의 합이 100mA 이하여야 된다는 데이터쉬트상의 스펙을 벗어나지 않게 된다. 포트C에 대해서도 같은 계산 결과가 적용될 수 있을 것이다.
다음과 같이 LED에 관련된 매크로와 함수를 정의한다.
#define sbi(sfr,bit) (_SFR_BYTE(sfr)|=_BV(bit))#define cbi(sfr,bit) (_SFR_BYTE(sfr)&=~_BV(bit)) typedef unsigned char uchar;typedef unsigned int uint;typedef unsigned long ulong;
#define TurnLEDOn ({sbi(PORTD,5);cbi(PORTD,6);})#define Turn7SegOn ({sbi(PORTD,6);cbi(PORTD,5);})#define TurnOffAll ({cbi(PORTD,5);cbi(PORTD,6);}) void LED(uchar b) { // Display b into LED TurnLEDOn; PORTB &= 0xF0; PORTC &= 0xF0; PORTB |= (b&0x0F); PORTC |= (b>>4);} |
먼저 sbi(sft, bit)와 cbi(sft, bit) 매크로는 sfr 레지스터의 bit번을 세트시키는 매크로이다. 예를 들어 PORTB레지스터의 2번 비트를 세트시키고 싶다면 sbi(PORTB,2) 라고 하면 되고 PORTD 레지스터의 5번 비트를 리셋시키고 싶다면 cbi(PORTD,5) 라고 하면 된다. 또한 unsigned char형을 uchar 로 재정의 했는데 이는 긴 데이터 형의 이름을 짧게 사용하기 위한 것이다. uint 와 ulong 도 같은 이유로 재정의했다.
LED와 관련된 매크로는 TurnLEDOn, Turn7SegOn, TurnOffAll 세 개가 정의되어 있는데 각각 LED만 켜고, 7세그먼트만 켜고, 둘 다 끄는 동작을 수행하는 매크로이다. 그리고 LED(uchar b)함수가 정의되어 있는데 이 함수에는 LED에 내보낼 데이터를 넘겨주면 된다. LED와 7세그먼트가 포트B의 하위 4비트와 포트C의 하위 4비트에 걸쳐 있기 때문에 LED( )함수의 내부에는 넘겨진 데이터의 하위 4비트를 PORTB4:0에, 그리고 상위 4비트를 PORTC4:0에 복사하는 코드가 작성되어 있다. 이 때 PORTB, PORTC레지스터의 상위 4비트는 그 값을 그대로 유지하도록 하였다.
LED실험 (1)
첫 번째 실험은 [표 1]과 같이 4개씩 LED를 번갈아서 켜는 실험이다.
[표 1] 첫 번째 LED 실험
순서 | LED상태 | 이진수 | 16진수 |
1 | ○○○○●●●● | 0b00001111 | 0x0F |
2 | ●●●●○○○○ | 0b11110000 | 0xF0 |
위의 표에서 1-2-1-2- 순으로 일정한 시차를 두고 무한히 반복하는 실험이다. 프로그램 예는 다음과 같다.
#define F_CPU 16000000#include <avr/io.h>#include <util/delay.h>
typedef unsigned char uchar;typedef unsigned int uint;typedef unsigned long ulong;
#define sbi(sfr,bit) (_SFR_BYTE(sfr)|=_BV(bit))#define cbi(sfr,bit) (_SFR_BYTE(sfr)&=~_BV(bit))
#define TurnLEDOn ({sbi(PORTD,5);cbi(PORTD,6);})#define Turn7SegOn ({sbi(PORTD,6);cbi(PORTD,5);})#define TurnOffAll ({cbi(PORTD,5);cbi(PORTD,6);}) void LED(uchar b) { // Display b into LED TurnLEDOn; PORTB &= 0xF0; PORTC &= 0xF0; PORTB |= (b&0x0F); PORTC |= (b>>4);} int main(void) { InitAM8(); uchar ucLED = 0x0F; while(1) { LED(ucLED); ucLED = ~ucLED; _delay_ms(500); }} |
이 프로그램을 보면 main()함수 안에서 while(1){ } 반복문에 의해서 무한루프에 빠지게 프로그램이 되어 있다. 일반적으로 PC상에서는 운영체제 (Operation System, OS로 줄여 표기하고 윈도우, 리눅스 등이 있다.)가 응용프로그램을 실행시키고 응용프로그램이 종료가 되면 다시 OS로 되돌아가지만 uC는 OS가 없기 때문에 응용프로그램 안에서 프로그램 실행이 계속 머물러야 하므로 무한 루프가 이용된다.
전술한 바와 같이 LED를 켜는 코드와 7세그먼트를 켜는 코드를 매크로 TurnLEDOn, Turn7SegOn 으로 각각 정의해 놓고 있어서 LED를 구동시키고 싶을 때는 TurnLEDOn이라는 매크로를 사용하면 된다. 이렇게 매크로를 정의해 놓은 이유는 사용의 편의성 때문이기도 하지만 프로그램의 가독성 때문에도 그렇다. 예를 들어서 main()함수 중간에
⋮sbi(PORTD,5);cbi(PORTD,6);⋮ |
라고 작성되어 있다고 가정해보자. 다른 사람이 이 프로그램을 읽는다면 한 눈에 이 두 줄이 무슨 역할을 하는지는 알기 힘들 것이다. 하지만
이라고 작성하면 이것은 이전의 것과 똑같은 코드를 생성하지만 (전처리기에 의해서 이것은 ({sbi(PORTD,5);cbi(PORTD,6);})로 컴파일하기 전에 치환된다.) 한 눈에 LED를 켜는 코드라고 알아볼 수 있을 것이다.