여러 LED로 패턴 만들기) 포트 전체 제어와 시각적 효과

와, 적다보니 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가 만들어내는 다양한 패턴들을 직접 구현해보세요. 단순한 점멸에서 복잡한 시각 효과까지, 무궁무진한 가능성이 있습니다!