[참조]
<Mode 사용법>
<Register 설명>
[PWM 활용]
CTC mode
: Clear Timer on Compare Match Mode
위 차트와 같이, TCNTn = OCR0 에서 Compare match interrupt가 발생할 때마다 OCn의 모양이 바뀌는 것을 볼 수 있다.
COMn1, COMn0 세팅을 조작하여 interrupt 발생 시, toggle할 수도 0 또는 1로 바꿀수도 아니면 출력을 차단할 수도 있다.
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
// CTC mode //
int main(void)
{
//DDRB = 0x10;
//DDRB = 0b00010000; // 세 개 다 같은 의미
DDRB = (1<<PINB4); // PORTB의 PIN4 출력 설정
//TCCR0 = (0<<FOC0);
TCCR0 |= (1<<WGM01) | (0<<WGM00); // CTC mode
TCCR0 |= (0<<COM01) | (1<<COM00); // Toggle OC0 on compare match
TCCR0 |= (1<<CS02) | (0<<CS01) | (0<<CS00); // 64분주
//TCCR0 = (1<<WGM01) | (1<<COM00) | (1<<CS02);
//TCCR0 = 0x1C; // 0b0001_1100
//TCCR0 = 0x1D; // 0b0001_1101
OCR0 = 124; // 64분주, 1,000Hz
//OCR0 = 249; // 128분주, 250Hz
while (1)
{
while((TIFR & 0x02) == 0);
TIFR = 0x02;
//OCR0 = 124;
//OCR0 = 0;
}
}
1) OCF0
: 타이머/카운터0와 OCR0의 출력 비교 레지스터의 데이터간 비교 일치가 발생하면, OCF0 bit가 1로 set된다.
ISR 함수로 인해(인터럽트 발생하면) 클리어되고, 해당 bit에 1을 넣어주면 클리어된다(헷갈릴 수 있음)
▶ Compare match가 일어났는가? 를 체크하는 bit
2) TOV0
: 타이머/카운터0에서 Overflow interrupt가 발생하면, TOV0가 1로 set된다.
ISR 함수로 인해(인터럽트 발생하면) 클리어되고, 해당 bit에 1을 넣어주면 클리어된다
▶ Overflow가 일어났는가? 를 체크하는 bit
※ OC 와 OC PIN, OCR 비교
- OC : Output Compare의 약자로, 하드웨어적으로 타이머의 신호를 전달
- OC PIN : 타이머 발생 시, 특정 작업을 수행하는 핀의 이름(OCnB 등)
- OCR : 출력 비교 레지스터.(OCRxA 등)
계속해서 TCNT와 비교되며 비교의 결과가 일치하게 되면
외부 핀인 OC단자에 신호가 출력되도록 설정할 수 있다.
Normal Mode
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
// Normal Mode //
int main()
{
DDRC = 0xff; // Normal mode는 아무 포트에서나 쓸 수 있다.(8개 포트에서 다 반전되는 중)
PORTC = 0x00; // 출력은 0부터 시작
TCCR0 |= (0<<WGM01) | (0<<WGM00); // Normal mode
TCCR0 |= (0<<COM01) | (1<<COM00); // Compare Match - Toggle 모드
// Normal mode에서는 OC0핀이 사용되지 않으므로 출력에 영향을 주지 않는다.
TCCR0 |= (1<<CS02) | (0<<CS01) | (1<<CS00); // 128분주
TCNT0 = 131; // 타이머/카운터의 초기값을 설정(131~255 범위를 가짐)
// x축을 끌어 올린 것으로 보면 된다.
while(1)
{
// 타이머/카운터0의 Overflow를 기다림.
// Overflow가 발생하면, TOV0 플래그가 1로 set
while((TIFR & 0x01) == 0) // TOV0와 비교해야 하므로, 0x01
PORTC = ~PORTC; // PORTC의 출력을 반전
TCNT0 = 131; // 타이머/카운터0의 초기값을 재설정하여 주기를 조절한다.
TIFR = 0x01; // TOV0 클리어
}
}
8bit Fast PWM
OCRn 값이 변하더라도, TOP값은 일정하므로 주기는 동일하다.
비반전 모드 기준, High로 출력되다가 TCNTn = OCRn 이 되면 Low로 출력된다.
이후 TOP에서 Overflow되면 다시 High
※ Duty Cycle 계산 방법
▶ N % = TOP x N/100
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
// 8bit Fast PWM Mode //
int main()
{
DDRB = (1<<PINB4); // PINB4 에서 출력
TCCR0 |= (1<<WGM01) | (1<<WGM00); // Fast PWM mode
TCCR0 |= (1<<COM01) | (1<<COM00); // 비반전 모드
TCCR0 |= (1<<CS02) |(1<<CS01) | (0<<CS00); // 256분주
// OCR0 = 191; // duty cycle 75%로 변경
while (1)
{
for (int i=0 ; i < 255 ; i++) // 0~100%까지 연속적으로 출력하라.
{
OCR0 = i;
_delay_ms(10);
}
}
}
16bit Fast PWM
8bit Fast PWM과 다른 점은, TOP 값의 조절이 가능하여 주기 변경을 할 수 있다는 점이다.
또한 Wave Generation Mode가 4가지인 8bit Fast PWM에 비해 16가지로 훨씬 다양하다.
이와 같은 특징을 활용하여, 서보 모터의 기동이나 각종 부품들의 스펙에 맞게 구현파를 출력할 수 있다.
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
// 16bit Fast PWM Mode //
// Timer1 PWM(mode 14) OC1A 100Hz
int main() // 64분주, 100Hz 만들 것이다. -> TOP = 2499
{
DDRB = (1<<PINB5); // OC1A PIN
TCCR1A |= (1<<WGM11) | (0<<WGM10); // Fast PWM(mode 14) 세팅
TCCR1B |= (1<<WGM13) | (1<<WGM12);
TCCR1A |= (1<<COM1A1) | (0<<COM1A0); // 비반전모드 & OCRnA 쓸 것이므로
//TCCR1A |= (1<<COM1B1) | (0<<COM1B0); // OCRnB
//TCCR1A |= (1<<COM1C1) | (0<<COM1C0); // OCRnC
TCCR1B |= (0<<CS12) | (1<<CS11) | (1<<CS10);
TCCR1C = 0x00; // default가 0이라 안 써줘도 됨
ICR1 = 2499; // 0~2499까지 카운팅하는 것 반복한다는 의미
while (1)
{
for (uint16_t i = 0; i < 2500 ; i++) // int는 4byte, uint16_t도 4byte
{
OCR1A = i;
_delay_ms(5);
}
}
}
[Servo Motor]
1. Servo Motor란?
: 명령에 따라 정확한 위치 및 속도를 제어할 수 있는 Motor
원하는 각도와 속도를 명령에 따라 정밀하게 제어할 수 있다.
2. Servo Motor의 원리
PWM의 Duty Cycle에 따라서 각도를 변환시킬 수 있고
회전 속도는 각도 변화(△Duty cycle) 사이에 딜레이를 추가해주면 속도 조절도 가능하다.
해당 서보 모터는 0~180˚의 범위를 가지며 360˚의 범위를 가진 서보모터도 존재한다.
3. Connection Diagram(SG90)
4. Servo Motor의 동작
<0˚, 90˚, 180˚ 회전 동작>
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
// 16bit Fast PWM Mode //
int main() // 64분주, 50Hz
{
DDRB = (1<<PINB5);
TCCR1A |= (1<<WGM11) | (0<<WGM10); // Fast PWM(mode 14) 세팅
TCCR1B |= (1<<WGM13) | (1<<WGM12);
TCCR1A |= (1<<COM1A1) | (0<<COM1A0);
TCCR1B |= (0<<CS12) | (1<<CS11) | (1<<CS10);
TCCR1C = 0x00; // default가 0이라 안 써줘도 됨
ICR1 = 4999;
while (1)
{
OCR1A = 125; // 1ms (0')
_delay_ms(500);
OCR1A = 375; // 1.5ms (90')
_delay_ms(500);
OCR1A = 625; // 2ms (180')
_delay_ms(500);
}
}
<각도 조절 함수를 활용한 동작1>
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
void servo_stop();
void servo_run(uint8_t degree);
// 16bit Fast PWM Mode //
int main() // 64분주, 50Hz
{
DDRB = (1<<PINB5);
TCCR1A |= (1<<WGM11) | (0<<WGM10); // Fast PWM(mode 14) 세팅
TCCR1B |= (1<<WGM13) | (1<<WGM12);
TCCR1A |= (1<<COM1A1) | (0<<COM1A0);
TCCR1B |= (0<<CS12) | (1<<CS11) | (1<<CS10);
TCCR1C = 0x00; // default가 0이라 안 써줘도 됨
ICR1 = 4999; // 0~2499까지 카운팅하는 것 반복한다는 의미
while (1)
{
servo_run(0);
_delay_ms(1500);
servo_run(45);
_delay_ms(1500);
servo_run(90);
_delay_ms(1500);
servo_run(180);
_delay_ms(1500);
}
}
// 모터 stop //
void servo_stop()
{
TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0)); // PWM 출력 안 되도록
}
// 회전 각도 설정 함수 //
void servo_run(uint8_t degree)
{
// 0도 : 125 / 180도 : 625
uint16_t degree_value;
TCCR1A |= (1<<COM1A1); // PWM 다시 출력되도록
if (degree_value < 0) // 제한 범위를 벗어나 고장나지 않도록 설정
{
degree = 0;
}
else if(degree > 180)
{
degree = 180;
}
degree_value = (uint16_t)((degree/180.0)*500 + 125); // 우리가 흔히 쓰는 각도로 표현하기 위한 식
OCR1A = degree_value;
}
<각도 조절 함수를 활용한 동작2>
#define F_CPU 16000000
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
void servo_stop();
void servo_run(uint8_t degree);
// 16bit Fast PWM Mode //
int main() // 64분주, 50Hz
{
DDRB = (1<<PINB5);
TCCR1A |= (1<<WGM11) | (0<<WGM10); // Fast PWM(mode 14) 세팅
TCCR1B |= (1<<WGM13) | (1<<WGM12);
TCCR1A |= (1<<COM1A1) | (0<<COM1A0);
TCCR1B |= (0<<CS12) | (1<<CS11) | (1<<CS10);
TCCR1C = 0x00; // default가 0이라 안 써줘도 됨
ICR1 = 4999; // 0~2499까지 카운팅하는 것 반복한다는 의미
while (1)
{
for (int i = 0; i<180 ; i++)
{
servo_run(i);
_delay_ms(15);
}
for (int i = 180 ; i>0 ; i--)
{
servo_run(i);
_delay_ms(10);
}
}
}
void servo_stop()
{
TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0)); // PWM 출력 안 되도록
}
void servo_run(uint8_t degree)
{
// 0도 : 125 / 180도 : 625
uint16_t degree_value;
TCCR1A |= (1<<COM1A1); // PWM 다시 출력되도록
if (degree_value < 0)
{
degree = 0;
}
else if(degree > 180)
{
degree = 180;
}
degree_value = (uint16_t)((degree/180.0)*500 + 125); // 우리가 흔히 쓰는 각도로 표현하기 위한 식
OCR1A = degree_value;
}