Harman 세미콘 아카데미/Atmega128a

[ATmega 128a]⑤ - CTC, NOMAL, FastPWM, PWM16BIT, dalay 함수

U_Pong 2024. 6. 18. 19:00

2024.6.5 수업날

이번에는 타이머/카운터 모드의 종류에 대해 알아보고 오실로스코프를 사용하여 파형을 관찰해본다.

본격적으로 타이머/카운트 모드의 종류에 대해 알아보기 전에 자주 나오게 되는 용어들을 정리해본다.

 

BOTTOM: 카운터가 0이 되었을 때(0x00)

MAX: 카운터가 0xFF에 도달했을 때

TOP: 각 동작 모드에 따라 카운터가 도달하는 최대 값


< Clear Timer on Compare Match (CTC) Mode >

 

아래의 표는 타이머/카운터의 4가지 모드를 나타낸 것이다.

여기서 Mode 2번을 보면

CTC모드일때 TOP이 OCR0, OCR0 업데이트는 Immediate(즉시)라고 나와있는 것을 확인할 수 있다.

CTC모드에서는 OCR0라는 레지스터에 사용자가 원하는 카운트횟수를 써 넣으면, TCNT0가 그 수까지 카운트 하고 0으로 초기화된다.

예를 들어, OCR0에 100이라는 숫자를 넣었다면 TCNTO는 0~100까지 카운트하고 바로 다음 순간 0이 되는 것이다.

 

아래의 그래프를 보면

TCNTn = OCR0이 되는 순간 Interrupt가 발생하는 동시에 TCNT0값이 0이 되고, OCn의 모양이 바뀌는 것을 확인할 수 있다.

 

OCn은 TCCR0의 4,5번 비트(COM00, COM01)에서 정할 수 있다.

 

CTC모드에서는 아래와같은 공식을 사용한다.

좌항의 focn에서 oc0은 출력되는 주파수로 주어진 Hz의 값이다.

분자에서 CPU 클럭 주파수를 16MHz로 설정했기 때문에 16000000Hz가 된다.

분모에서 N의 자리에는 1, 8, 32, 64, 128, 256, 1024와 같은 2^x형식의 숫자가 들어갈 수 있다.

때문에 분모의 값에서 OCRn을 우리가 구해야하는 값이 된다.

 

 

구해야하는 값인 OCRn을 좌항으로 옮기면 아래와 같은 식이 된다.

예를 들어, foc0의 값이 1KHz(=1000Hz)라고 가정하고 위 식의 우항을 정리하면

OCR0 = (8000/N) - 1 이 된다.

N의 자리에 1, 8, 32, 64, 128, 256, 1024를 대입하여 분수의 값이 정수로 나타나는 N의 최댓값을 구한다.

N = 32 --> 8000/32 = 250

N = 64 --> 8000/64 = 125

N = 128 --> 8000/128 = 62.5

N = 64 일때, 분수의 값이 정수가 되는 최댓값이기 때문에, OCR0의 값은 125-1=124 가 된다.

 

따라서 아래와 같이 CTC모드에서 TCCR0의 값은 0001 1100이된다.

 

 

 

CTC 파형 생성 소스코드
/*
 * CTC.c
 */ 

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

int main(void)
{
	DDRB |= (1<<DDRB4);
	TCCR0 |= (1<<COM00) | (1<<WGM01) | (1<<CS02);
	OCR0 = 124;
	
	while (1)
	{
		
	}
}

 

이 소스코드에서 자주 보이게되는' |=' 형식을 볼 수 있다.

이 형식은 논리 연산자 OR를 뜻하는 것이며

a |= b는

a = a | b 와 똑같은 형식이다.

 

또한, (1<<DDRB4)라는 형식도 자주 쓰인다.

이 형식은 DDRB의 datasheet를 보면서 알아보자.

이 datasheet에서 DDRB는 메모리 주소 이름이다.

만약, DDRB에서 4번 bit만 켜진 것을 보고싶을때 우리는 00010000으로 나타낼 수 있다.

이는 DDRB의 4번비트 자리인 DDB4에 1이 오게하고싶다 라는 의미이다.

즉, (1<<DDRB4) 형식의 의미는

0bit의 1을 4bit의 1로 옮기기 위해 4bit의 주소 DDB4로 옮긴다는 뜻이다.

 

위의 소스코드를 실행시켜 파형을 볼 수 있는데, 그러기 위해서는 오실로스코프가 필요하다.

주파수가 1kHZ일때의 CTC모드 오실로스코프 파형이다.

여기서 세로 한칸의 값은 5V이고, 가로 한칸의 값은 1.00ms이다

 

 

< Nomal mode >

 

타이머/카운터의 4가지 모드 표를 다시 살펴보자.

여기서 Mode 0번을 보면

Nomal 모드일때 TOP이 0xFF, OCR0 업데이트는 Immediate(즉시)라고 나와있는 것을 확인할 수 있다.

또한, Nomal 모드에서는 WGM01, WGM00 모두 0이여야한다.

 

아래는 TCNT0의 datasheet이다.

Nomal 모드로 설정하는 것까지 했다면, 이제 타이머가 내부 클럭을 세기 시작한다.

CPU 클럭 주파수를 16MHz라고 설정했기 때문에 1초당 1600만개의 클럭이 생긴다.

클럭이 1번 생길때마다 TCNT0이 1씩 증가하면서 카운트가 된다.

 

클럭 TCN0
1 0000 0000
2 0000 0001
3 0000 0010
.
.
.
.
.
.
256 1111 1111

 

TCNT0은 8bit 레지스터 이므로, 가질 수 있는 값의 범위는 0~255(총 256개)이다.

때문에 256까지 센 뒤, 인터럽트가 발생하면서 0000 0000으로 초기화가된다.

이 현상을 Overflow Interrupt라고 한다.

즉, TNCT0이 256번째까지 카운트 -> TCNT0 초기화(0000 0000) -> TCNT0 257번째 클럭부터 카운트

이렇게 진행되는 것이다.

 

아래는 TIFR(Timer Interrupt Flag Register)의 datasheet이다.

TCNT0이 오버플로우되면 오버플로우 인터럽트를 요청한다. 0bit의 TOV0이 set(1)이 된다.

 

따라서 아래와 같이 Nomal 모드에서 TCCR0의 값은 0000 0101이된다.

 

Nomal 파형 생성 소스코드
/*
 * Nomal.c
 */ 

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

int main(void)
{
	DDRF = 0xff;
	PORTF = 0x00;
	
	TCCR0 |= (1<<CS02) | (1<<CS00);
	TCNT0 = 256 - 250;
	
	while (1)
	{
		while((TIFR & 0x01) == 0);
		PORTF = ~PORTF;
		TCNT0 = 6;
		TIFR |= (1<<TOV0);
	}
}

 

 

< FastPWM >

 

타이머/카운터의 4가지 모드 표를 다시 살펴보자.

여기서 Mode 3번을 보면

FastPWM 일때 TOP이 0xFF, OCR0 업데이트는 Bottom 이라고 나와있는 것을 확인할 수 있다.

또한, FastPWM에서는 WGM01, WGM00 모두 1이여야한다.

 

아래의 그래프를 보면 TCNT0과 OCR0가 같은 지점이 있다.

이는 CTC 모드에서와 달리 TCNT0이 Clear(0)가 되지 않는다.

OCn이라고 된 부분이 비반전 모드, OCn위에 bar가 있는 부분은 반전모드이다.

비반전 모드에서의 OC0 -> Bottom 에서 1(set), compare match가 일어나면 0(clear)

반전 모드에서의 OC0 -> compare match가 일어나면 1(set), Bottom에서 0(clear)

이를 이용하여 OCR0 값을 적절하게 설정하면 펄스 폭을 사용자가 설정할 수 있는 것이다.

 

 

FastPWM에서는 아래와같은 공식을 사용한다.

CTC 모드와 달리 주파수가 OCR값과 상관이 없다.

OC0을 통해 생성되는 파형와 주기와 주파수는 TCNT0이 클리어되는 주기와 같기 때문에, 아래의 식은 TCNT0이 0~255까지 세고 클리어 되는 주기이다.

 

따라서 아래와 같이 Nomal 모드에서 TCCR0의 값은 0110 1110이된다.

 

 

FastPWM 파형 생성 소스코드
/*
 * FastPWM.c
 */ 

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

int main(void)
{
	DDRB |= (1<<DDRB4);
	//Fast PWM, 비반전모드, 분주비 256
	TCCR0 |= (1<<WGM00)|(1<<WGM01)|(1<<COM01)|(1<<CS02)|(1<CS01);
	//OCR0 = 60;
	
	while (1)
	{
		for (uint8_t i = 0; i < 255; i++)
		{
			OCR0 = i;
			_delay_ms(10);	//육안으로 확인하기 위하여, 주파수 폭이 늘어나는 것은 OCR 값이 올라갔다고 생각하라
		}
	}
}

 

FastPWM_결과, 영상을 찍지 못해서 당일 옆자리분이 찍어주신 영상으로 대체합니다. 감사합니다 :)

 

 

< PWM_16BIT >

FastPWM에 이어서 이번엔 오실로스코프에 파형이 연속적으로 나타나는 것이 아닌,

값을 지정해두고 끊겨서 파형이 나타나게끔 이어졌다가 다시 초기화되어 원래대로 돌아오는 소스코드를 작성한다.

/*
 * PWM16BIT.c
 */ 

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

int main(void)
{
	DDRB |= (1<<DDRB5); // 0b0010000;    // B5라는 숫자는 오실로스코프의 2번선에서 한쪽은 접지, 한쪽은 메인 부품의 왼쪽열의 중간부분(B의 5번)으로 연결하라는 뜻
	TCCR1A |= (1<<COM1A1)|(1<<WGM11);
	TCCR1B |=(1<<WGM13)|(1<<WGM12)|(1<<CS11)|(1<<CS10);
	//TCR1C |= ?
	ICR1 = 2500 - 1;
	// OCR1A = 1250;  // 50%
	// OCR1A = 625; // 25%
	
	// DDRC |= 0xff; -> DDRC = DDRC | 0xff;
	//
	
	while (1) // 끊기게끔 구현, 0%, 25%, 50%, 75%, 100%(=0%)로 되게끔, while(1)--> 무조건 참이라는 뜻
	{
		for (uint8_t i = 0; i < 4; i++)
		{
			OCR1A = i * 625; // 625=0.25*2500, 왠만하면 실수는 쓰지 말것
			_delay_ms(1000);
		}
	}
}

 

PWM_16BIT 결과, 영상을 찍지 못해서 당일 옆자리분이 찍어주신 영상으로 대체합니다. 감사합니다 :)

 

 

< delay 함수 >

 

delay 함수를 사용하여 LED_bar를 깜빡이게 하도록

h파일, c파일, main 파일로 나누어서 서로 어떤 연관이 있는지에 대한 소스코드를 작성해본다.

 

/*
 * delay.h
 */ 

#ifndef DELAY_H_
#define DELAY_H_

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

volatile uint8_t delayCompleteFlag; // delay 완료 - 전역변수
uint16_t countValue;

void delayInit(void);
void delay_ms(uint16_t ms);

#endif

 

/*
 * delay.c
 */ 

#include "delay.h"

ISR(TIMER0_OVF_vect)
{
	static uint16_t count;   // 정적변수 => 0으로 자동 초기화
	if(++count >= countValue)
	{
		count = 0;
		TCCR0 &= ~((1<<CS02)|(1<<CS00));	// 타이머/카운트 중단
		delayCompleteFlag = 1;
	}
}


void delayInit(void)
{
	delayCompleteFlag = 0;  // 안해도 되는데... 왜? 전역변수니까..
	TIMSK |= (1<<TOIE0);    // 타이머/카운트0번의 인터럽트 인에이블
	// 128분주
	TCCR0 |=(1<<CS02) | (1<<CS00);
	sei();				// 전역 인터럽트 허용
}


void delay_ms(uint16_t ms)
{
	delayCompleteFlag = 0;
	countValue = ms;
	TCNT0 = 256-125;
	TCCR0 |=(1<<CS02) | (1<<CS00);		// 다시 시작합니다.
	
	while(!delayCompleteFlag);  // 깃발 세울때 까지 대기
	
}

 

/*
 * delay_func.c
 */ 

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

#include "delay.h"

int main(void)
{
	DDRC = 0xff;
	// PORTC = 0x00;
	
	delayInit();

	while (1)
	{
		//delay_ms(300);
		//PORTC = ~PORTC;
		PORTC = 0xff;
		delay_ms(500);
		PORTC = 0x00;
		delay_ms(500);
	}
}

 

delay 함수 결과

CTC, NOMAL, FastPWM, PWM16BIT, dalay 함수 끝!