09–10. 타이머0/1 과 정확한 지연 · 외부·타이머 인터럽트
7편의 소프트웨어 지연은 부정확하고 그동안 CPU가 묶입니다. 정확한 시간은 하드웨어 타이머로 만듭니다 — CPU와 독립적으로 클럭을 세는 카운터죠. 8051의 Timer0/1을 16비트 모드(Mode1)로 설정해 정확한 지연을 만듭니다. 핵심은 TMOD로 모드 선택, TH/TL에 리로드 값(65536-카운트), TF(오버플로 플래그) 폴링입니다. 리로드 계산을 PC로 검증합니다. 폴링은 정확하지만 기다리는 동안 CPU가 묶입니다. 인터럽트는 평소 메인 코드가 제 일을 하다가 사건(버튼·타이머 오버플로)이 생기면 CPU가 잠깐 끼어들어 ISR을 실행하고 돌아옵니다. "사건이 생길 때만 반응"하는 이 방식이 임베디드의 핵심 패러다임입니다. IE 레지스터로 인터럽트를 켜고, C51의 interrupt N 문법으로 ISR을 쓰며, 공유 변수에 volatile이 왜 필요한지 익힙니다.
이 강의에서 배우는 것
- 1소프트웨어 지연 대비 하드웨어 타이머의 장점을 설명한다
- 2TMOD로 Timer0/1의 모드를 설정한다(Mode1=16비트)
- 3원하는 지연에 맞는 리로드 값(TH/TL)을 계산한다
- 4TR(시작)·TF(오버플로 플래그)로 폴링 지연을 구현한다
- 516비트 한계를 넘는 긴 지연은 오버플로를 여러 번 세서 만든다
- 6인터럽트(ISR·벡터·비동기 사건)를 폴링과 비교해 설명한다
- 7IE 레지스터(EA/EX0/ET0)로 인터럽트를 허용한다
- 8C51의 void isr(void) interrupt N 문법으로 ISR을 작성한다
- 9외부 인터럽트(INT0)와 타이머 인터럽트를 설정한다
- 10ISR↔메인 공유 변수에 volatile을 붙이는 이유를 안다
09. 타이머0/1 과 정확한 지연
7편의 소프트웨어 지연은 부정확하고 그동안 CPU가 묶입니다. 정확한 시간은 하드웨어 타이머로 만듭니다 — CPU와 독립적으로 클럭을 세는 카운터죠. 8051의 Timer0/1을 16비트 모드(Mode1)로 설정해 정확한 지연을 만듭니다. 핵심은 TMOD로 모드 선택, TH/TL에 리로드 값(65536-카운트), TF(오버플로 플래그) 폴링입니다. 리로드 계산을 PC로 검증합니다.
소개
타이머는 머신사이클마다 1씩 증가하는 카운터입니다. 표준 8051은 머신사이클 = 12클럭이라, 12MHz면 머신사이클이 1µs입니다. 16비트 카운터(TH:TL)가 0xFFFF에서 한 번 더 증가하면 0x0000으로 넘어가며 오버플로 플래그(TF)가 1이 됩니다.
핵심 개념
1) TMOD — 모드 레지스터
TMOD 하위 4비트는 Timer0, 상위 4비트는 Timer1. M1M0=01 이 Mode1(16비트). Timer0 Mode1 = TMOD=0x01, Timer1 Mode1 = TMOD=0x10. 다른 타이머 설정 보존: `TMOD &= 0xF0; TMOD |= 0x01;`.
2) 리로드 값 계산
/* 12MHz(1us/카운트)에서 50ms */
필요 카운트 = 50ms / 1us = 50000
리로드 = 65536 - 50000 = 15536 = 0x3CB0
TH0 = 0x3C; TL0 = 0xB0;
/* 일반식: reload = 65536 - us, TH = reload>>8, TL = reload & 0xFF */3) TCON — 시작과 플래그, 폴링 흐름
| 비트 | 역할 |
|---|---|
| TR0/TR1 | 1이면 타이머 동작(시작) |
| TF0/TF1 | 오버플로 시 1 (소프트웨어가 클리어) |
TMOD 설정 → TH/TL 리로드 → TF=0 → TR=1
→ while(TF==0) 대기 → TR=0 → TF=0(클리어)폴링은 대기 중 CPU가 묶입니다. 대기 동안 다른 일을 하려면 타이머 인터럽트(10편)를 씁니다.
핵심 예제
#include <reg52.h>
sbit LED = P1 ^ 0;
static void delay_50ms(void) {
TMOD &= 0xF0; TMOD |= 0x01; /* Timer0 Mode1 */
TH0 = 0x3C; TL0 = 0xB0; /* 50ms @ 12MHz */
TF0 = 0; TR0 = 1; /* 시작 */
while (TF0 == 0) { } /* 오버플로까지 대기 */
TR0 = 0; TF0 = 0; /* 정지·클리어 */
}
void main(void){ while(1){ LED = (bit)!LED; delay_50ms(); } }리로드 계산 검산: 1000µs→0xFC18, 5000µs→0xEC78, 50000µs→0x3CB0.
자주 하는 실수
Q. TMOD = 0x01 로 통째 대입했더니 Timer1 설정이 날아갔어요.
A. TMOD는 두 타이머를 함께 담습니다. 해당 타이머 비트만 바꾸세요: Timer0 은 `TMOD &= 0xF0; TMOD |= 0x01;`.
Q. 첫 지연은 되는데 그다음부터 즉시 통과해요.
A. TF 를 다시 클리어하지 않아서입니다. Mode1은 자동 리로드가 아니므로 매 지연마다 TH/TL을 다시 넣고 TF를 0으로 지워야 합니다.
Q. 1초처럼 긴 지연이 안 만들어져요.
A. 16비트(1µs 단위)는 한 번에 최대 ~65ms입니다. 더 길게는 짧은 오버플로(50ms)를 여러 번 세서 만듭니다(1초 = 50ms × 20).
정리
- 하드웨어 타이머는 CPU와 독립적으로 정확한 시간을 만든다
- TMOD로 모드 설정(Mode1=16비트), Timer0=하위 니블·Timer1=상위 니블
- 리로드 = 65536 - 카운트, TH=상위·TL=하위
- TR로 시작, TF 폴링으로 오버플로 시점을 잡고 매번 TF를 클리어한다
- 긴 지연은 오버플로 횟수를 세서 만든다
과제
- 12MHz에서 10ms·100ms 지연의 리로드 값(TH/TL)을 손으로 계산하고 pc_test 로 검산
- 50ms 오버플로를 20번 세어 1초 지연을 만드는 함수 작성
- Timer1을 써서 같은 지연을 만들고 TMOD 상위 니블 설정을 확인
10. 외부·타이머 인터럽트
폴링은 정확하지만 기다리는 동안 CPU가 묶입니다. 인터럽트는 평소 메인 코드가 제 일을 하다가 사건(버튼·타이머 오버플로)이 생기면 CPU가 잠깐 끼어들어 ISR을 실행하고 돌아옵니다. "사건이 생길 때만 반응"하는 이 방식이 임베디드의 핵심 패러다임입니다. IE 레지스터로 인터럽트를 켜고, C51의 interrupt N 문법으로 ISR을 쓰며, 공유 변수에 volatile이 왜 필요한지 익힙니다.
소개
사건이 발생하면 하드웨어가 자동으로 현재 코드를 멈추고 인터럽트 벡터의 함수(ISR)로 점프합니다. ISR이 끝나면 멈췄던 자리로 돌아옵니다. 폴링의 대기 낭비가 사라지고 반응이 즉각적입니다.
핵심 개념
1) 인터럽트 소스와 번호 / IE
| 번호 | 소스 |
|---|---|
| 0 | 외부 인터럽트 0 (INT0, P3.2) |
| 1 | 타이머 0 오버플로 |
| 2 | 외부 인터럽트 1 (INT1) |
| 3 | 타이머 1 오버플로 |
| 4 | 시리얼(UART) |
규칙: 개별 인터럽트를 켜고(EX0=1 등), 마지막에 전역 EA=1 을 켭니다. EA가 0이면 무엇을 켜도 동작하지 않습니다.
2) ISR 문법과 트리거 모드
void int0_isr(void) interrupt 0 /* 외부0 = 0, 타이머0 = 1 ... */
{
/* 짧고 빠르게! printf·지연 같은 무거운 작업은 피한다 */
}
/* IT0=0: 레벨 트리거(Low 동안 계속), IT0=1: 하강 에지(누르는 순간 한 번) */3) volatile — 공유 변수
volatile unsigned char press_count; /* ISR ↔ main 공유 */
/* ISR 이 비동기로 바꾸므로 매번 메모리에서 다시 읽게 해야 한다 */ISR은 짧게 유지하세요. 무거운 작업은 ISR에서 volatile 플래그만 세우고 메인 루프에서 처리합니다.
핵심 예제
#include <reg52.h>
sbit LED = P1 ^ 0;
volatile unsigned char press_count = 0;
void int0_isr(void) interrupt 0 { /* 하강 에지마다 호출 */
press_count++;
LED = (bit)!LED;
}
void main(void) {
IT0 = 1; /* 하강 에지 트리거 */
EX0 = 1; /* 외부0 허용 */
EA = 1; /* 전역 허용(마스터) */
while (1) { /* 메인은 자유 */ }
}시뮬레이터: Port 3에서 P3.2를 1→0으로 바꿀 때마다 int0_isr이 호출되어 P1.0 LED가 토글되고 press_count가 1씩 증가합니다.
자주 하는 실수
Q. ISR이 전혀 호출되지 않아요.
A. 십중팔구 EA=1(전역 허용)을 안 켰습니다. 개별 인에이블만으로는 부족합니다. interrupt N 번호가 소스와 맞는지도 확인하세요(외부0=0, 타이머0=1).
Q. ISR에서 바꾼 변수가 메인에서 안 바뀐 것처럼 보여요.
A. 공유 변수에 volatile 을 빠뜨렸습니다. 컴파일러가 메인에서 그 변수를 캐싱하면 ISR의 변경을 못 봅니다.
Q. 한 번 눌렀는데 ISR이 여러 번 불려요.
A. IT0=0(레벨)이면 누르는 동안 계속 트리거됩니다. IT0=1(에지)로 바꾸세요. 채터링이면 디바운싱(21편).
정리
- 인터럽트는 사건 발생 시에만 ISR을 실행해 폴링의 대기 낭비를 없앤다
- IE의 개별 비트(EX0/ET0)를 켜고 마지막에 EA=1로 전역 허용한다
- C51 ISR은 void isr(void) interrupt N (외부0=0, 타이머0=1…)
- 외부 인터럽트는 IT0로 레벨/에지 트리거를 고른다
- ISR↔메인 공유 변수엔 volatile, ISR은 짧게 유지한다
과제
- 타이머0 오버플로 인터럽트(interrupt 1)로 LED를 일정 주기로 토글하는 ISR 작성
- press_count 가 5가 될 때마다 메인에서 다른 LED를 토글(ISR은 카운트만)
- IT0를 0(레벨)으로 바꿔 보고 동작이 어떻게 달라지는지 관찰·설명
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 주세요.
댓글은 회원만 쓸 수 있습니다.
광고·욕설·비방·개인정보·불법 내용이 담긴 댓글은 숨기거나 지웁니다. 댓글은 닉네임과 함께 공개됩니다. 개인정보처리방침