와, 적다보니 5탄입니다. 내용은 별거 아닌데 말이죠. ㅎㅎ;;;
그럼, 안녕하세요! 지금까지 LED 하나로 깜빡이고, 버튼으로 제어하는 방법을 배웠습니다. 이번에는 여러 개의 LED를 동시에 제어해서 멋진 시각적 효과를 만들어보겠습니다!
이번 편의 목표
- 포트 전체를 한번에 제어하는 방법 학습
- 배열과 반복문을 활용한 패턴 생성
- 나이트 라이더, 이진 카운터 등 다양한 효과 구현
- 효율적인 코드 구조 설계
회로 구성하기
SimulIDE에서 8개 LED 회로 만들기
이번에는 포트 B의 모든 핀(PB0~PB7)에 LED를 연결해보겠습니다:
연결 방법:
- ATmega328P PB0 → 저항 330Ω → LED1 → GND
- ATmega328P PB1 → 저항 330Ω → LED2 → GND
- ATmega328P PB2 → 저항 330Ω → LED3 → GND
- … (PB7까지 동일)
회로 배치 팁:
- LED들을 일직선으로 배치하면 패턴을 보기 쉬움
- 각 LED마다 개별 저항 사용 (공통 저항 사용 시 밝기 불균등)
포트 전체 제어의 기초
개별 핀 vs 포트 전체
개별 핀 제어 (지금까지 방식)
c
PORTB |= (1 << PB0); // PB0만 켜기
PORTB |= (1 << PB1); // PB1만 켜기
PORTB &= ~(1 << PB2); // PB2만 끄기
포트 전체 제어 (새로운 방식)
c
PORTB = 0b10101010; // 전체 포트에 패턴 출력
PORTB = 0x55; // 16진수로도 표현 가능
PORTB = 85; // 10진수로도 표현 가능
이진수와 LED 패턴의 관계
PORTB = 0b10101010;
비트: 7 6 5 4 3 2 1 0
LED: ● ○ ● ○ ● ○ ● ○
핀: PB7 PB5 PB3 PB1
1
= LED 켜짐 (●)0
= LED 꺼짐 (○)
기본 패턴 프로그래밍
1. 순차 점등 (왼쪽에서 오른쪽)
c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
// 포트 B 전체를 출력으로 설정
DDRB = 0xFF; // 0b11111111과 동일
while(1) {
// 각 LED를 순차적으로 켜기
for (uint8_t i = 0; i < 8; i++) {
PORTB = (1 << i); // i번째 LED만 켜기
_delay_ms(200);
}
}
return 0;
}
2. 역순차 점등 (오른쪽에서 왼쪽)
c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0xFF;
while(1) {
// 역순으로 LED 켜기
for (uint8_t i = 7; i != 255; i--) { // uint8_t는 0에서 -1하면 255가 됨
PORTB = (1 << i);
_delay_ms(200);
}
}
return 0;
}
3. 양방향 스캔 (나이트 라이더 효과)
c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0xFF;
while(1) {
// 왼쪽에서 오른쪽으로
for (uint8_t i = 0; i < 8; i++) {
PORTB = (1 << i);
_delay_ms(100);
}
// 오른쪽에서 왼쪽으로 (양끝 중복 제거)
for (uint8_t i = 6; i > 0; i--) {
PORTB = (1 << i);
_delay_ms(100);
}
}
return 0;
}
이진 카운터와 수학적 패턴
1. 기본 이진 카운터
c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0xFF;
uint8_t counter = 0;
while(1) {
PORTB = counter; // 카운터 값을 직접 출력
counter++; // 0, 1, 2, 3, ... 255, 0, 1, ...
_delay_ms(300);
}
return 0;
}
동작 과정:
counter = 0: 0b00000000 → 모든 LED 꺼짐
counter = 1: 0b00000001 → PB0만 켜짐
counter = 2: 0b00000010 → PB1만 켜짐
counter = 3: 0b00000011 → PB0, PB1 켜짐
counter = 4: 0b00000100 → PB2만 켜짐
...
2. 역방향 이진 카운터
c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0xFF;
uint8_t counter = 255;
while(1) {
PORTB = counter;
counter--; // 255, 254, 253, ... 1, 0, 255, ...
_delay_ms(300);
}
return 0;
}
3. 짝수/홀수 패턴
c
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB = 0xFF;
while(1) {
PORTB = 0b01010101; // 홀수 번째 LED (1, 3, 5, 7번)
_delay_ms(500);
PORTB = 0b10101010; // 짝수 번째 LED (0, 2, 4, 6번)
_delay_ms(500);
}
return 0;
}
고급 패턴과 배열 활용
1. 패턴 배열을 이용한 시퀀스
c
#include <avr/io.h>
#include <util/delay.h>
// 미리 정의된 패턴들
const uint8_t patterns[] = {
0b00000001, // 패턴 0
0b00000011, // 패턴 1
0b00000111, // 패턴 2
0b00001111, // 패턴 3
0b00011111, // 패턴 4
0b00111111, // 패턴 5
0b01111111, // 패턴 6
0b11111111, // 패턴 7
0b01111111, // 패턴 8 (역순 시작)
0b00111111, // 패턴 9
0b00011111, // 패턴 10
0b00001111, // 패턴 11
0b00000111, // 패턴 12
0b00000011, // 패턴 13
0b00000001 // 패턴 14
};
int main(void) {
DDRB = 0xFF;
uint8_t pattern_count = sizeof(patterns) / sizeof(patterns[0]);
while(1) {
for (uint8_t i = 0; i < pattern_count; i++) {
PORTB = patterns[i];
_delay_ms(150);
}
}
return 0;
}
2. 회전 패턴 (로테이트)
c
#include <avr/io.h>
#include <util/delay.h>
// 비트를 왼쪽으로 회전시키는 함수
uint8_t rotate_left(uint8_t value, uint8_t positions) {
return (value << positions) | (value >> (8 - positions));
}
// 비트를 오른쪽으로 회전시키는 함수
uint8_t rotate_right(uint8_t value, uint8_t positions) {
return (value >> positions) | (value << (8 - positions));
}
int main(void) {
DDRB = 0xFF;
uint8_t pattern = 0b00000111; // 초기 패턴: 3개 LED
while(1) {
// 왼쪽으로 회전
for (uint8_t i = 0; i < 8; i++) {
PORTB = pattern;
pattern = rotate_left(pattern, 1);
_delay_ms(200);
}
// 오른쪽으로 회전
for (uint8_t i = 0; i < 8; i++) {
PORTB = pattern;
pattern = rotate_right(pattern, 1);
_delay_ms(200);
}
}
return 0;
}
3. 파도 효과 (웨이브)
c
#include <avr/io.h>
#include <util/delay.h>
#include <math.h>
int main(void) {
DDRB = 0xFF;
uint16_t time = 0;
while(1) {
uint8_t pattern = 0;
// 각 LED에 대해 사인파 계산
for (uint8_t i = 0; i < 8; i++) {
// 위상을 다르게 해서 파도 효과 생성
float phase = (time + i * 45) * 3.14159 / 180.0; // 45도씩 위상차
float brightness = (sin(phase) + 1.0) / 2.0; // 0~1 범위로 정규화
if (brightness > 0.5) {
pattern |= (1 << i);
}
}
PORTB = pattern;
time += 10; // 시간 진행
_delay_ms(50);
}
return 0;
}
버튼과 조합한 인터랙티브 패턴
1. 패턴 선택기
c
#include <avr/io.h>
#include <util/delay.h>
typedef enum {
PATTERN_SEQUENCE,
PATTERN_KNIGHT_RIDER,
PATTERN_BINARY_COUNTER,
PATTERN_WAVE,
PATTERN_COUNT
} pattern_type_t;
pattern_type_t current_pattern = PATTERN_SEQUENCE;
void button_check(void) {
static uint8_t last_button_state = 1;
uint8_t button_state = (PINC & (1 << PC0)) ? 1 : 0;
// 버튼이 눌렸을 때 (falling edge)
if (last_button_state && !button_state) {
_delay_ms(20); // 디바운싱
current_pattern = (current_pattern + 1) % PATTERN_COUNT;
}
last_button_state = button_state;
}
void run_sequence_pattern(void) {
static uint8_t pos = 0;
PORTB = (1 << pos);
pos = (pos + 1) % 8;
}
void run_knight_rider_pattern(void) {
static uint8_t pos = 0;
static uint8_t direction = 1;
PORTB = (1 << pos);
if (direction) {
pos++;
if (pos >= 7) direction = 0;
} else {
pos--;
if (pos <= 0) direction = 1;
}
}
void run_binary_counter_pattern(void) {
static uint8_t counter = 0;
PORTB = counter++;
}
int main(void) {
DDRB = 0xFF; // LED 포트 출력 설정
DDRC &= ~(1 << PC0); // 버튼 입력 설정
PORTC |= (1 << PC0); // 풀업 활성화
while(1) {
button_check();
switch(current_pattern) {
case PATTERN_SEQUENCE:
run_sequence_pattern();
_delay_ms(200);
break;
case PATTERN_KNIGHT_RIDER:
run_knight_rider_pattern();
_delay_ms(100);
break;
case PATTERN_BINARY_COUNTER:
run_binary_counter_pattern();
_delay_ms(300);
break;
case PATTERN_WAVE:
// 파도 패턴 구현
_delay_ms(50);
break;
default:
break;
}
}
return 0;
}
성능 최적화와 코드 구조
1. 룩업 테이블 활용
c
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
// 플래시 메모리에 패턴 저장 (RAM 절약)
const uint8_t knight_rider_pattern[] PROGMEM = {
0b00000001, 0b00000010, 0b00000100, 0b00001000,
0b00010000, 0b00100000, 0b01000000, 0b10000000,
0b01000000, 0b00100000, 0b00010000, 0b00001000,
0b00000100, 0b00000010
};
int main(void) {
DDRB = 0xFF;
uint8_t pattern_index = 0;
uint8_t pattern_size = sizeof(knight_rider_pattern);
while(1) {
// 플래시 메모리에서 패턴 읽기
uint8_t pattern = pgm_read_byte(&knight_rider_pattern[pattern_index]);
PORTB = pattern;
pattern_index = (pattern_index + 1) % pattern_size;
_delay_ms(100);
}
return 0;
}
2. 타이머 기반 패턴 업데이트
c
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint8_t pattern_update_flag = 0;
volatile uint8_t led_pattern = 0;
// 타이머 오버플로우 인터럽트 (매 약 16ms마다 발생)
ISR(TIMER0_OVF_vect) {
static uint8_t prescaler = 0;
prescaler++;
if (prescaler >= 6) { // 약 100ms마다 패턴 업데이트
prescaler = 0;
pattern_update_flag = 1;
}
}
void timer_init(void) {
// Timer0 설정
TCCR0B |= (1 << CS02) | (1 << CS00); // 1024 분주
TIMSK0 |= (1 << TOIE0); // 오버플로우 인터럽트 활성화
sei(); // 전역 인터럽트 활성화
}
int main(void) {
DDRB = 0xFF;
timer_init();
uint8_t pos = 0;
while(1) {
if (pattern_update_flag) {
pattern_update_flag = 0;
// 나이트 라이더 패턴 업데이트
led_pattern = (1 << pos);
pos = (pos + 1) % 8;
PORTB = led_pattern;
}
// 다른 작업 수행 가능
}
return 0;
}
🔍 디버깅과 최적화 팁
1. 패턴 시각화 매크로
c
#define SHOW_PATTERN(pattern) do { \
printf("Pattern: 0b"); \
for(int8_t i = 7; i >= 0; i--) { \
printf("%d", (pattern & (1 << i)) ? 1 : 0); \
} \
printf(" (0x%02X)\n", pattern); \
} while(0)
2. 메모리 사용량 최적화
c
// 메모리 낭비
uint8_t patterns[100] = {0x01, 0x02, 0x04, ...};
// 계산으로 생성
uint8_t get_sequence_pattern(uint8_t step) {
return (1 << (step % 8));
}
응용 과제
과제 1: 음악 비트에 맞춰 반응하는 LED
가상의 음악 신호를 만들어서 비트에 맞춰 LED가 반응하도록 만들어보세요.
과제 2: 점수 표시기
0~255점을 8개 LED의 패턴으로 표현하는 시스템을 만들어보세요.
- 이진수 표현
- 막대 그래프 스타일 표현
- 퍼센트 표현 (0~100%)
과제 3: LED 매트릭스 시뮬레이션
8×1 LED를 마치 8×8 매트릭스처럼 보이게 하는 착시 효과를 만들어보세요.
다음 편 예고
다음 편에서는 타이머와 정확한 시간 제어를 배워보겠습니다:
- Timer0/1/2의 차이점과 활용법
- 오버플로우 인터럽트와 비교 매치 인터럽트
- 정밀한 시간 측정과 제어
- 실시간 시계 만들기
- PWM의 기초 개념
지금까지 _delay_ms()
로만 시간을 제어했다면, 이제 진짜 타이머의 힘을 느껴보세요!
8개의 LED가 만들어내는 다양한 패턴들을 직접 구현해보세요. 단순한 점멸에서 복잡한 시각 효과까지, 무궁무진한 가능성이 있습니다!