본문 바로가기
AVR

[AVR] ATmega128 RGB LED 제어 (16bit Timer, PyQT)

by hseoy 2020. 11. 29.
반응형

[ATmega128 RGB LED 제어]

이 글에서는 크게 2단계에 걸쳐서 RGB LED를 다뤄볼 것이다. 첫 번째로 코드 상에서 0~255까지의 RGB 값을 넘겨주면 해당 값으로 RGB를 설정하여 LED를 키는 함수를 구현해볼 것이며 두 번째로 PyQT를 사용해서 GUI상에서 RGB를 입력하여 PC에서 Atmega128을 제어하는 실험을 수행할 것이다. 

# 실험 1. RGB LED 제어

RGB 값들은 0~255, 0x00~0xFF까지의 값인데 이를 표현하기 위해서는 PWM을 사용해서 전압을 값에 따라 바꿔줘야 한다. PWM을 사용하기 위해서는 8bit Timer와 16bit Timer를 사용해야 한다. 여기서는 8bit Timer가 아니라 16bit Timer를 사용했는 데 이것에 대해서는 나중에 다시 언급하도록 하겠다. 먼저 RGB LED의 R,G,B에 각각 220옴 정도의 저항을 연결하고 각각 PORTB 5,6,7번 핀에 연결하는 회로를 구성한다. 나 같은 경우는 RGB LED 모듈을 사용하였다. 

Kicad로 작성한 회로도

코드는 아래와 같다. main 함수 내에서 PWM을 사용하기 위한 설정 함수인 timer_init()함수를 호출하여 설정을 완료하고 set_color함수를 사용하여 RGB값을 설정한다. 이 코드에서는 R:255, G:255, B:0인 색상을 LED에 출력하고 있다. 

#define F_CPU 16000000UL
#include <avr/io.h>

#define BV(n) (1 << n)

#define OCR1_R OCR1B 
#define OCR1_G OCR1C 
#define OCR1_B OCR1A 

void set_color(int red, int green, int blue)
{
    OCR1_R = red   & 255;
    OCR1_G = green & 255;
    OCR1_B = blue  & 255;
}

void timer_init()
{
    // Fast PWM 8bit
    // WGM13(0), WGM12(1), WGM11(0), WGM10(1)
    // COM1A1/COM1B1/COM1C1 : 1, COM1A0/COM1B0/COM1C0 : 0
    TCCR1A |= BV(WGM10) | BV(COM1A1) | BV(COM1B1) | BV(COM1C1);
    TCCR1B |= BV(WGM12) | BV(CS11);
    set_color(0, 0, 0);
}

int main(void)
{
    DDRB = BV(5) | BV(6) | BV(7);
    timer_init();
    
    set_color(255, 255, 0);
    while (1) 
    {
    }
}

ATmega128 칩의 Pin configuration, 핀맵을 살펴보면 PWM을 사용할 수 있는 핀의 수는 7개이다. 먼저 타이머 3을 사용하는 PE3, PE4, PE5, 그리고 타이머 0을 사용하는 PB4, 타이머 1을 사용하는 PB5, PB6, PB7이 있다. PB7은 타이머 1과 타이머 2가 같이 사용한다. 여기서 타이머 0과 타이머 2는 8bit timer이고, 타이머 1과 3은 16bit 타이머이다. RGB LED에서 PWM을 사용해야 하는 핀은 3개이기 때문에 2개의 출력핀만을 가지는 8bit 타이머만으로는 안된다. 따라서 16bit 타이머 출력핀을 사용하긴 해야 하는 데 8bit 타이머와 16bit 타이머를 혼용해서 사용하게 되면 더 복잡하다고 생각했다.

그래서 위 코드에서는 16bit 타이머인 타이머 1을 사용하고 PB5, PB6, PB7을 출력핀으로 사용하고 있다. 

흔한 RGB의 값은 0~255의 8bit 값이다. 그러므로 여기서도 8bit의 값으로 쉽게 제어할 수 있으면 좋을 것 같았다. 

Datasheet의 Timer/Counter Mode of Operation, 타이머/카운터의 동작 모드를 살펴보면 16bit 타이머라도 8bit로 동작시킬 수 있는 모드가 있다. 바로 'PWM, Phase Correct, 8-bit'와 'Fast PWM, 8-bit' 모드이다. 위 코드 같은 경우에는 "Fast PWM, 8-bit' 모드를 사용했다. 

아래가 타이머 설정을 위한 레지스터 설정 코드이다. 위 표를 봤을 때 'Fast PWM, 8-bit' 모드로 동작시키기 위해서는 WGM1n을 0, 1, 0, 1로 설정해야 한다. 그리고 non-inverting mode로 동작시키기 위해서 COM1A1/COM1B1/COM1C1을 1로, COM1A0/COM1B0/COM1C0을 0으로 설정한다. OC1A, OC1B, OC1C 세 핀을 모두 사용하기 때문에 각각 다 1/0으로 설정해준다.

void timer_init()
{
    // Fast PWM 8bit
    // WGM13(0), WGM12(1), WGM11(0), WGM10(1)
    // COM1A1/COM1B1/COM1C1 : 1, COM1A0/COM1B0/COM1C0 : 0
    TCCR1A |= BV(WGM10) | BV(COM1A1) | BV(COM1B1) | BV(COM1C1);
    TCCR1B |= BV(WGM12) | BV(CS11);
    set_color(0, 0, 0);
}

non-inverting mode로 설정하기 위해서는 1/0으로 값을 설정해줘야 한다.

non-inverting mode에 대해서 설명을 하기 위해서는 Fast PWM Mode의 Timing Diagram을 살펴봐야 한다. Fast PWM Mode에서 non-inverting mode의 OCnx를 보면 계속 HIGH 상태를 유지하다가 TCNTn이 특정 값이 되면 출력핀이 LOW가 되었다가 TCNTn이 다시 0이 되면 HIGH 상태가 된다. 예를 들어 이 특정 값을 0이라고 하면 처음부터 LOW가 되었다가 끝이 되어 다시 처음으로 돌아가서 HIGH가 되어도 바로 LOW가 된다. 즉, 계속 LOW인 상태가 되는 것이다. 만약 특정값을 최대값으로 설정하면 끝까지 도달할 때까지 HIGH였다가 처음으로 돌아가서 HIGH가 된다. 이 경우는 계속 HIGH인 상태이다. non-inverting mode는 이 특정값에 따라 PWM으로 출력되는 전압이 비례한다. 특정값이 최대에 가까울 수록 PWM으로 출력되는 전압도 ATmega128에서 출력하는 전압인 5V에 가까워지는 것이다.

이 특정값을 지정하는 레지스터가 OCRnx 레지스터다. 따라서 위 코드에서 OCR1A를 지정하면 TCNTn이 OCR1A의 값에 도달하게 되면 OC1A핀이 LOW로 되었다가 TCNTn이 overflow되어 0이 되면 다시 HIGH 상태가 된다. 

위 코드에서 non-inverting mode를 사용한 이유는 0~ 255의 값을 그대로 OCRnx 레지스터에 넣기 위해서다. 그러면 좀 더 코드를 이해하기 쉽지 않을까하는 발상이랄까.

OCR1x 레지스터 설정은 set_color() 함수에서 하고 있다. OCR1x에 대입하는 것이 곧 RGB 색상을 설정하는 것이기 떄문이다. 의미를 확실히 하기 위해 아래와 같이 define으로 A,B,C가 아니라 R,G,B로 정의해서 사용하고 있다. 

#define OCR1_R OCR1B 
#define OCR1_G OCR1C 
#define OCR1_B OCR1A 

void set_color(int red, int green, int blue)
{
    OCR1_R = red   & 255;
    OCR1_G = green & 255;
    OCR1_B = blue  & 255;
}

 

... 작성중 ><

반응형

댓글