Atmega128 for 문 지연

 이번 글에서는 delay 함수를 사용하지 않고 for문으로 시간 지연을 만드는 간단한 방법에 대해서 설명드리겠습니다.

 정말 기초적인 것이지만 오히려 기초적인 부분이라 확인하지 못하고 넘어가는 사소한 문제로 처음 Atmega를 접하시는 분들이라면 왜 안되지? 하는 생각을 가질 수 있을 법한 상황들이 많습니다. 이를 알아보기 위해서 avr studio에 정의되어 있어 바로 사용할 수 있는 DELAY 함수에 대해 간략히 알아보고 for문으로 시간을 지연시키는 동작에 대해 알아보겠습니다.

 


 

DELAY 함수

 

 Atmega128에서는 이러한 방법을 쉽게 사용하기 위해서 DELAY 함수가 헤더파일로 존재합니다. 헤더파일(delay.h)의 시간지연 방식을 구현해 놓은 방법을 보면 다음과 같습니다.

void
_delay_ms(double __ms)
{
	uint16_t __ticks;
	double __tmp ; 
#if __HAS_DELAY_CYCLES && defined(__OPTIMIZE__) && !defined(__DELAY_BACKWARD_COMPATIBLE__)
	uint32_t __ticks_dc;
	extern void __builtin_avr_delay_cycles(unsigned long);
	__tmp = ((F_CPU) / 1e3) * __ms;

	#if defined(__DELAY_ROUND_DOWN__)
		__ticks_dc = (uint32_t)fabs(__tmp);

	#elif defined(__DELAY_ROUND_CLOSEST__)
		__ticks_dc = (uint32_t)(fabs(__tmp)+0.5);

	#else
		//round up by default
		__ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
	#endif

	__builtin_avr_delay_cycles(__ticks_dc);

#elif !__HAS_DELAY_CYCLES || (__HAS_DELAY_CYCLES && !defined(__OPTIMIZE__)) || defined (__DELAY_BACKWARD_COMPATIBLE__)
	__tmp = ((F_CPU) / 4e3) * __ms;
	if (__tmp < 1.0)
		__ticks = 1;
	else if (__tmp > 65535)
	{
		//	__ticks = requested delay in 1/10 ms
		__ticks = (uint16_t) (__ms * 10.0);
		while(__ticks)
		{
			// wait 1/10 ms
			_delay_loop_2(((F_CPU) / 4e3) / 10);
			__ticks --;
		}
		return;
	}
	else
		__ticks = (uint16_t)__tmp;
	_delay_loop_2(__ticks);
#endif
}

 

 대략적으로 보면 이 헤더 파일에서는 _delay_ms(); 와 _delay_us(); 두 가지를 사용할 수 있게 형성되어 있습니다. 그 중 _delay_ms(); 구문이 위와 같이 형성이 되어있고, 이 _delay_ms();는 1/10ms의 분해능을 가지고 있다고 합니다 (0.0001초, 최대 지연을 초과하는 경우 F_CPU 값을 고려하지 않았을 경우에 최대 6.5535초 지연 가능(16bit)).

 

결국 F_CPU라고 하는 클럭 값(주파수)을 사용해 시간을 계산하는 것으로 보입니다.

 

 이 헤더파일은 F_CPU 값이 1Mhz로 정의되어 있습니다. 따라서 DELAY 함수를 사용할 때는 자신이 사용하는 보드나 회로의 내부클럭을 재정의해주는 것이 필요합니다. Atmega128의 경우에는 외부 크리스털이 달려있다면 일반적으로 16Mhz를 사용하는 것이 대다수입니다. 따라서 다음과 같이 사용하면 DELAY 함수를 쉽게 사용할 수 있습니다.

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

volatile unsigned long i;

int main()
{
    DDRA = 0xff;
    PORTA = 0x00;
    
	while(1)
    {
    	PORTA =~ PORTA;
        _delay_ms(1000); // 1000ms = 1s
    }
}

 사실 MCU 내의 퓨즈비트로 설정되어 있는 내부 클럭 주파수 값도 영향을 주는 것 같아 보입니다. 즉, F_CPU/내부 클럭 = 1 의 값으로 맞아떨어져야 delay.h 파일에 정의되어 있는 것처럼 정확하게 시간이 유지되는 것으로 보입니다. 즉 초기 부터 납땜이 필요한 Atmega128 MCU의 경우는 초기 제품의 내부 클럭이 1Mhz로 설정이 되어 있는데 8Mhz 이상의 클럭을 가지진 못합니다.

 

 조금 더 자세히 보자면, 외부 크리스털이 없이 동작하기 때문에 RC 오실레이터로 동작하게 되며, 이 RC 오실레이터는 최대 8Mhz로 동작되도록 설정되어 있고, Prescaler에서 8분주시켜 CPU를 비롯하여 주변 장치가 초기 설정에서는 최종적으로 1Mhz로 동작하게끔 되어있는 것이죠.

 

 즉, 초기 클럭이 1Mhz인 128에 위의 그림처럼 코딩하면 PORTA 반전이 16초마다 이루어지는 것이 아닌 8초마다 이루어집니다.


 

DELAY 함수를 for문으로 구현해보기

 

 위에서 설명한 동작은 결국 DELAY 함수에서 아무런 동작을 하지 않고 머물게 함으로써 시간적인 지연을 발생시키는 경우입니다. 이것을 for문을 사용해 똑같은 방법을 적용할 수 있습니다. 아래 예제를 통해 문제를 확인해보고 작성해보겠습니다.

 

#include <avr/io.h>

unsigned int i;

int main()
{
    DDRA = 0xff;
    PORTA = 0x00;
    
	while(1)
    {
        PORTA =~ PORTA;
        for(i=0; i<100000; i++);
    }
}

 위의 코드는 A 포트전체를 출력으로 지정하고, 모두 0인 상태에서 for문이 10만번 돈 후에 출력을 반전시키는 동작을 하는 코드입니다. 그런데 막상 빌드에서 프로그램을 해보면 0인 입력값에서 반전되지가 않습니다.(avr studio 기준.)

 

 이러한 현상은 결국 자료형의 설정값과 관련이 있습니다. 일반적인 c언어를 코딩할 수 있는 visual studio의 경우에서는 4byte 였던 int 형이 avr studio의 경우 2byte 로 설정되어 있습니다. 즉 short int 의 방식으로 적용되어 있는 것입니다.

 2byte = 16bit 이며 이는 Atmega128에서 16비트 이상의 값을 사용하는 것이 많지 않아서 그런 것 같습니다. (128에서 사용할 수 있는 타이머의 경우 최대 16bit 타이머이다.)

 

 즉, unsigned int 의 경우 부호가 없는 2byte의 자료형이며, 0~65535 숫자 값을 가질 수 있어 for문에서 65536의 값이 들어오는 순간 overflow 현상으로 0으로 초기화가 되는 것입니다. 결국 이러한 현상으로 for문을 빠져나오지 못하고 무한루프를 돌게 되는 것입니다.

 따라서 긴 시간지연을 생성하고 싶은 경우에는 int 대신에 long과 같은 4byte 형으로 대체해주면 더 긴 시간지연을 생성할 수 있게 됩니다.

 


Volatile 은 어떻게 동작하는 걸까

 

 

 일반적으로 avr studio에서는 최적화 옵션이 -Os로 적용되어 있으며 의미없는 구문을 최적화 합니다.

int i;

for(i=0; i<10; i++);

printf("%d", i);

 위의 코드처럼 작성하면 아래와 같이 최적화를 시킵니다. 즉, for문을 돌리지 않고 값을 바로 출력하게끔 만들어버리는 겁니다.

int i=10;

printf("%d", i);

하지만 volatile을 사용하게 되면 최적화에서 제외하게 되며, 반드시 그 변수와 관련된 것들은 정해진 코드에 따라 동작하게끔 진행됩니다. 따라서 for문 지연을 위해서는 사용해야 하는 함수라고 볼 수 있습니다.

 


CKSEL을 8M로 변경할 수 있다.

 위의 설명을 바탕으로 작성된 아래의 코드를 사용하면 for문으로 시간 지연이 가능해집니다.

#include <avr/io.h>

volatile unsigned long i;

int main()
{
    DDRA = 0xff;
    PORTA = 0x00;
    
	while(1)
    {
        PORTA =~ PORTA;
        for(i=0; i<100000; i++);
    }
}

 

 제가 기억하고 있는 대로 작성하였기 때문에 일부는 정확하지 않을 수도 있다는 점을 말씀드립니다. 설명이 잘못되었거나 궁금하신 점이 있으시다면 댓글 부탁드립니다.

'IT > Atmega128, Raspberry pi' 카테고리의 다른 글

Atmega128 관련 카테고리입니다.  (1) 2020.05.11
TAGS.

Comments