15
2016-Jan
AVR 멀티채널 PWM (타이머 하나로 여러 PWM 구동)
작성자: Blonix
IP ADRESS: *.148.87.98 조회 수: 1651
출처 : https://gist.github.com/funkfinger/954874
Blonix 첨언 : 소프트웨어 pwm 이므로 정밀도는 떨어짐. 아두이노의 analogWrite 명령어가 이 방식일 것으로 추정됨.
이 방법을 사용하면 어떤 핀에서도 pwm을 뽑아낼 수 있다. 타이머도 아낄 수 있고 말이다.
아래 코드는 ATMega128A 가 아니라 ATTiny85 에 기반해 작성되었으나 이니셜라이즈 부분을 제외하면 뭐 그거나 이거나 딱히 차이는 없다.
PWM 코드뿐 아니라 매크로 사용법, 변수 선언 방법 등에서 좀 더 세련된 AVR 코딩을 위해 배울점이 많아보인다.
아래 코드에서 pwm 주파수는 인터럽트 발생주파수/256 이다. 고로 사용할 주파수를 고려해서 수정해 사용하자.
softcount 를 99가 되면 0 으로 돌려버린다던가 하면 pwm 주파수를 인터럽트 주파수/100으로 만들 수 있다.
인터럽트 주파수는 프리스케일러와 TCNT를 적당히 설정하면 된다.
ATTiny85 3 channel software PWM to drive RGB LED
// based largely on Atmel's AVR136: Low-Jitter Multi-Channel Software PWM Application Note: | |
// http://www.atmel.com/dyn/resources/prod_documents/doc8020.pdf | |
#include <avr/io.h> | |
#include <util/delay.h> | |
#include <avr/interrupt.h> | |
#define CHMAX 3 // maximum number of PWM channels | |
#define PWMDEFAULT 0x00 // default PWM value at start up for all channels | |
#define RED_CLEAR (pinlevelB &= ~(1 << RED)) // map RED to PB0 | |
#define GREEN_CLEAR (pinlevelB &= ~(1 << GREEN)) // map GREEN to PB1 | |
#define BLUE_CLEAR (pinlevelB &= ~(1 << BLUE)) // map BLUE to PB2 | |
//! Set bits corresponding to pin usage above | |
#define PORTB_MASK (1 << PB0)|(1 << PB1)|(1 << PB2) | |
#define set(x) |= (1<<x) | |
#define clr(x) &=~(1<<x) | |
#define inv(x) ^=(1<<x) | |
#define RED PB0 | |
#define GREEN PB1 | |
#define BLUE PB2 | |
#define LED_PORT PORTB | |
#define LED_DDR DDRB | |
void delay_ms(uint16_t ms); | |
void init(); | |
unsigned char compare[CHMAX]; | |
volatile unsigned char compbuff[CHMAX]; | |
int r_val = 0x00; | |
int g_val = 0x55; | |
int b_val = 0xAA; | |
float dim = 1; | |
int main() { | |
init(); | |
int r_dir = 1; | |
int g_dir = 2; | |
int b_dir = 4; | |
for(;;) { | |
if (r_val > 254 - 1) { | |
r_dir = -1; | |
} | |
if (r_val < 1 + 1) { | |
r_dir = 1; | |
} | |
if (g_val > 254 - 3) { | |
g_dir = -2; | |
} | |
if (g_val < 1 + 3) { | |
g_dir = 2; | |
} | |
if (b_val > 254 - 4) { | |
b_dir = -4; | |
} | |
if (b_val < 1 + 4) { | |
b_dir = 4; | |
} | |
r_val += r_dir; | |
g_val += g_dir; | |
b_val += b_dir; | |
compbuff[0] = r_val; | |
compbuff[1] = g_val; | |
compbuff[2] = b_val; | |
delay_ms(50); | |
} | |
} | |
void delay_ms(uint16_t ms) { | |
while (ms) { | |
_delay_ms(1); | |
ms--; | |
} | |
} | |
void init(void) { | |
// set the direction of the ports | |
LED_DDR set(RED); | |
LED_DDR set(GREEN); | |
LED_DDR set(BLUE); | |
unsigned char i, pwm; | |
CLKPR = (1 << CLKPCE); // enable clock prescaler update | |
CLKPR = 0; // set clock to maximum (= crystal) | |
pwm = PWMDEFAULT; | |
// initialise all channels | |
for(i=0 ; i<CHMAX ; i++) { | |
compare[i] = pwm; // set default PWM values | |
compbuff[i] = pwm; // set default PWM values | |
} | |
TIFR = (1 << TOV0); // clear interrupt flag | |
TIMSK = (1 << TOIE0); // enable overflow interrupt | |
TCCR0B = (1 << CS00); // start timer, no prescale | |
sei(); | |
} | |
ISR (TIM0_OVF_vect) { | |
static unsigned char pinlevelB=PORTB_MASK; | |
static unsigned char softcount=0xFF; | |
PORTB = pinlevelB; // update outputs | |
if(++softcount == 0){ // increment modulo 256 counter and update | |
// the compare values only when counter = 0. | |
compare[0] = compbuff[0]; // verbose code for speed | |
compare[1] = compbuff[1]; | |
compare[2] = compbuff[2]; | |
pinlevelB = PORTB_MASK; // set all port pins high | |
} | |
// clear port pin on compare match (executed on next interrupt) | |
if(compare[0] == softcount) RED_CLEAR; | |
if(compare[1] == softcount) GREEN_CLEAR; | |
if(compare[2] == softcount) BLUE_CLEAR; | |
} | |
