11–12. UART 시리얼 통신 · 7-세그먼트 표시
값을 "사람이 읽는 글자"로 PC와 주고받으려면 통신이 필요합니다. 가장 기본이 UART(시리얼)입니다 — 선 두 가닥(TXD/RXD)으로 텍스트를 주고받죠. UART는 비동기라 양쪽이 보율(baud)을 똑같이 약속해야 합니다. 8051은 SCON으로 모드를, Timer1로 보율을, SBUF로 한 바이트를 넣고 뺍니다. 9600bps·8N1로 문자열을 보내고, 보율 정확도를 위해 11.0592MHz를 쓰는 이유를 익힙니다. 사람이 읽을 수 있는 숫자를 표시합니다. 7-세그먼트는 막대 LED 7개(a~g)+소수점으로 0~9를 그립니다. 핵심은 ① 숫자를 "어떤 막대를 켤지"의 비트 패턴으로 바꾸는 룩업 테이블, ② 자리가 여러 개일 때 핀을 아끼며 모두 켜 보이게 하는 멀티플렉싱(동적 구동)입니다. "1234"를 한 자리씩 빠르게 번갈아 켜 잔상으로 네 자리가 동시에 켜진 것처럼 보이게 만듭니다.
이 강의에서 배우는 것
- 1동기/비동기 통신의 차이와 UART 프레임(start·data·stop)을 설명한다
- 2SCON으로 Mode1(8비트 UART)과 수신 허용(REN)을 설정한다
- 3Timer1(Mode2 자동 리로드)로 보율을 만들고 TH1을 계산한다
- 4SBUF/TI/RI로 한 바이트를 송수신한다
- 5UART용 11.0592MHz 크리스털을 쓰는 이유를 안다
- 67-세그먼트 배치(a~g·dp)와 공통 음극/양극 차이를 설명한다
- 70~9를 세그먼트 비트 패턴으로 바꾸는 룩업 테이블을 만든다
- 8자리 선택과 세그먼트 출력을 분리해 여러 자리를 구동한다
- 9멀티플렉싱(시간 분할+잔상)으로 4자리를 동시에 켜 보이게 한다
- 10잔상(ghosting)을 막는 끄기→바꾸기→켜기 순서를 적용한다
11. UART 시리얼 통신
값을 "사람이 읽는 글자"로 PC와 주고받으려면 통신이 필요합니다. 가장 기본이 UART(시리얼)입니다 — 선 두 가닥(TXD/RXD)으로 텍스트를 주고받죠. UART는 비동기라 양쪽이 보율(baud)을 똑같이 약속해야 합니다. 8051은 SCON으로 모드를, Timer1로 보율을, SBUF로 한 바이트를 넣고 뺍니다. 9600bps·8N1로 문자열을 보내고, 보율 정확도를 위해 11.0592MHz를 쓰는 이유를 익힙니다.
소개
데이터를 한 비트씩 한 선으로 보내는 방식이 시리얼입니다. UART는 별도 클럭선 없이 동작하는 비동기 방식이라, 양쪽이 같은 보율(예 9600bps)로 맞춰 두는 것이 전제입니다. 연결은 보통 TXD(P3.1)·RXD(P3.0)·GND 세 가닥.
핵심 개념
1) 프레임 8N1
유휴(1) → [start=0] → [d0..d7] → [stop=1] → 유휴(1)
↑ 데이터는 LSB(d0)부터!
/* 데이터8 · 패리티 없음(N) · 정지1 = 8N1 */2) SCON 과 보율(Timer1)
표준 설정 `SCON = 0x50` → SM1=1(Mode1, 8비트 UART) + REN=1(수신 허용). 8051은 Timer1을 Mode2(8비트 자동 리로드)로 두고 오버플로 속도로 보율을 만듭니다.
보율 = Fosc / (384 × (256 - TH1)) (SMOD=0)
/* 11.0592MHz, 9600bps: n = 11059200/(384*9600) = 3 (정수!) → TH1 = 256-3 = 0xFD */11.0592MHz는 표준 보율에서 n이 정수로 떨어져 오차 0입니다. 12MHz는 9600에서 약 8.5% 오차가 나 통신이 깨집니다 — UART용 보드가 11.0592MHz를 쓰는 이유.
3) SBUF 송수신
void uart_tx(unsigned char c){ SBUF=c; while(TI==0); TI=0; }
unsigned char uart_rx(void){ while(RI==0); RI=0; return SBUF; }
/* TI/RI 는 하드웨어가 세우기만 함 — 소프트웨어가 매번 0으로 클리어해야 한다 */핵심 예제
#include <reg52.h>
void uart_init(void){
SCON = 0x50; /* Mode1 + REN */
TMOD &= 0x0F; TMOD |= 0x20; /* Timer1 Mode2(보율) */
TH1 = 0xFD; TL1 = 0xFD; /* 9600 @ 11.0592MHz */
TR1 = 1;
}
void uart_tx(unsigned char c){ SBUF=c; while(TI==0); TI=0; }
void uart_puts(const char *s){ while(*s) uart_tx((unsigned char)*s++); }
void main(void){ uart_init(); while(1){ uart_puts("Hello, 8051!\r\n"); } }보율 리로드 검산(11.0592MHz): 1200→0xE8, 2400→0xF4, 4800→0xFA, 9600→0xFD. 시뮬레이터 View → Serial Windows → UART #1 창에서 출력을 확인합니다.
자주 하는 실수
Q. 한 글자는 나오는데 두 번째부터 멈춰요.
A. TI(송신 완료 플래그)를 클리어하지 않았습니다. 하드웨어는 TI를 1로 세우기만 하니 매 송신 후 TI=0; 으로 지워야 합니다(수신은 RI도).
Q. 글자가 깨져서 나와요.
A. 보율 불일치입니다. 터미널 보율을 9600으로, Xtal을 11.0592MHz로 맞췄는지 확인하세요. 12MHz면 TH1=0xFD라도 실제 ~10417bps라 깨집니다.
Q. 송신은 되는데 수신이 안 돼요.
A. SCON의 REN(수신 허용)을 안 켰을 수 있습니다(0x50은 REN=1 포함). RI를 읽은 뒤 클리어했는지도 확인하세요.
정리
- UART는 클럭선 없는 비동기 시리얼 — 양쪽이 같은 보율로 약속한다
- 프레임: start(0) → 데이터 8(LSB부터) → stop(1), 패리티 없으면 8N1
- SCON=0x50(Mode1·REN), Timer1 Mode2(TMOD|=0x20)로 보율 생성
- TH1 = 256 - Fosc/(384×보율). 11.0592MHz면 정수로 떨어진다
- SBUF에 쓰면 송신·읽으면 수신. TI/RI는 매번 클리어한다
과제
- 받은 문자를 그대로 되돌려 보내는 에코(echo) 프로그램 작성(uart_rx → uart_tx)
- 4800bps·19200bps 의 TH1 값을 계산하고 pc_test 로 검산
- putchar 를 UART 송신으로 재정의해 printf 로 숫자를 출력(8051에서 무거운 이유도 설명)
12. 7-세그먼트 표시 (멀티플렉싱)
사람이 읽을 수 있는 숫자를 표시합니다. 7-세그먼트는 막대 LED 7개(a~g)+소수점으로 0~9를 그립니다. 핵심은 ① 숫자를 "어떤 막대를 켤지"의 비트 패턴으로 바꾸는 룩업 테이블, ② 자리가 여러 개일 때 핀을 아끼며 모두 켜 보이게 하는 멀티플렉싱(동적 구동)입니다. "1234"를 한 자리씩 빠르게 번갈아 켜 잔상으로 네 자리가 동시에 켜진 것처럼 보이게 만듭니다.
소개
6~7편의 포트 출력 위에 룩업 테이블과 시간 분할이라는 새 도구를 얹습니다. 이 편은 공통 음극(비트=1이 ON)을 가정합니다. (μVision 시뮬레이터엔 7-세그먼트 그래픽이 없어 P0/P2 값의 순환을 Port 창/Logic Analyzer로 확인하고, 숫자→코드 변환은 pc_test 로 검증합니다.)
핵심 개념
1) 0~9 룩업 테이블 (공통 음극)
비트 배치: a=0 b=1 c=2 d=3 e=4 f=5 g=6 dp=7. 숫자마다 켤 세그먼트가 정해져 있으니 표로 만들어 SEG_CODE[n] 으로 꺼내 씁니다.
static const unsigned char SEG_CODE[10] = {
0x3F,0x06,0x5B,0x4F,0x66, 0x6D,0x7D,0x07,0x7F,0x6F
}; /* 0~9 */2) 자리 선택 — 핀 아끼기
4자리를 각각 7핀씩 따로 연결하면 28핀이 필요합니다. 세그먼트 핀(a~g)을 4자리가 공유하고 "어느 자리를 켤지"만 자리 선택 핀으로 고르면 7+4=11핀으로 충분합니다. 단, 세그먼트선을 공유하므로 한 순간에는 한 자리만 켭니다.
3) 멀티플렉싱과 잔상 방지
1) 모든 자리 끄기 → 2) 새 세그먼트 패턴 쓰기 → 3) 해당 자리만 켜기자리를 켠 채 패턴을 바꾸면 바뀌는 순간 엉뚱한 패턴이 비쳐 잔상(ghosting)이 생깁니다. 항상 끄기 → 쓰기 → 켜기 순서를 지키세요.
핵심 예제
#include <reg52.h>
#define SEG_PORT P2 /* 세그먼트 */
#define DIG_PORT P0 /* 자리 선택 */
static const unsigned char SEG_CODE[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
static void delay(unsigned int n){ unsigned int i; for(i=0;i<n;i++){} }
void main(void){
unsigned char digits[4] = {1,2,3,4};
unsigned char d;
while(1){
for(d=0; d<4; d++){
DIG_PORT = 0x00; /* 1) 끄기(잔상 방지) */
SEG_PORT = SEG_CODE[digits[d]]; /* 2) 패턴 쓰기 */
DIG_PORT = (unsigned char)(1u << d); /* 3) 이 자리만 켜기 */
delay(150);
}
}
}PC 검산: 0→ABCDEF-, 1→-BC----, 8→ABCDEFG 처럼 켜진 세그먼트를 글자로 그려 룩업 테이블 정확성을 확인합니다.
자주 하는 실수
Q. 자리가 겹쳐 보이거나 흐릿한 잔상이 생겨요.
A. 패턴을 바꾸기 전에 자리를 끄지 않아서입니다. 반드시 끄기(DIG_PORT=0) → 패턴 쓰기 → 자리 켜기 순서를 지키세요.
Q. 한 자리만 밝고 나머지는 어두워요.
A. 자리당 유지 시간이 너무 길거나 한 자리에서 멈췄습니다. 4자리를 충분히 빠르게(자리당 1~5ms, 전체 50Hz 이상) 돌아야 같은 밝기로 보입니다.
Q. 공통 양극인데 다 켜지거나 다 꺼져요.
A. 이 코드는 공통 음극(비트=1 ON) 가정입니다. 공통 양극이면 ON/OFF가 반대라 세그먼트 코드를 반전(~)해야 합니다.
정리
- 7-세그먼트는 a~g(+dp)로 숫자를 그리며 공통 음극은 비트=1이 ON
- 숫자→패턴은 룩업 테이블 SEG_CODE[n]로 빠르게 변환한다
- 세그먼트선을 공유하면 자리를 늘려도 핀을 아낀다(한 순간 한 자리)
- 멀티플렉싱: 빠르게 자리를 번갈아 켜면 잔상으로 모두 켜 보인다
- 잔상 방지: 끄기 → 패턴 쓰기 → 자리 켜기 순서를 지킨다
과제
- 공통 양극 디스플레이용으로 SEG_CODE 를 반전(~)해 동작시키기
- 타이머(9편)로 1초마다 표시 숫자를 0000→0001…로 증가시키는 카운터 만들기
- 16진 숫자(A~F)까지 표시하도록 룩업 테이블 확장
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.
댓글은 회원만 쓸 수 있습니다.
광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침