Embedded System/AVR

[AVR] 적외선 통신(IR) - 2. 구현(코딩)

임베지수 2017. 4. 2. 14:49

이론에 대해서 알아보았으면 실제로 어떻게 구현하는지 보자.

 

먼저 변조를 하고 프로토콜(너무 거창한가...?)을 정해야하는데 적외선통신에서 일반적으로 사용하는 변조방식은 다음과 같다.

 

  ▶ Bi-Phase

  ▶ ASK or OOK (Amplitude Shift Keying or On-Off Keying)

  ▶ PDM or PWM(Pulse Duration or Width Modulation)

 

먼저 Bi-Phase 방식은 한 비트를 반으로 쪼개서 상승엣지면 "1", 하강엣지면 "0"을 의미한다.

 

<Bi-Phase 방식>

 

 

두 번째로 ASK 혹은 OOK방식인데 가장 단순한 진폭변조방식이다. high인 시간은 같고 low인 시간을 달리하여 "0"과 "1"을 구별한다.

<OOK 방식>

 

 

세 번째로 PDM 혹은 PWM방식인데 high인 시간을 달리하여 "0"과 "1"을 구별한다.

<PDM 방식>

 

 

 

나는 여기서 가장 간단한 OOK방식을 사용하였다.

 

다음으로 프레임을 구성했다. 프레임은 "시작비트+데이터크기+데이터+스톱비트" 으로 구성했다.

 

 

프레임을 위처럼 구성한 이유는 보통의 시리얼통신은 한바이트(8 bits)씩 보내게 되는데 받는사람 입장에서는 보내는 사람이 몇 문자를 전송할지 알 수 없기 때문에 보내고자하는 프레임의 크기도 같이 실어 보냈다.

 

 

이제 실제 AVR상에서 코딩을 어떻게 할지 알아보자.

 

먼저 적외선 수신모듈(KSM-603)의 데이터 시트를 살펴보면..

 

 

 

37kHz에 실어 보내는 신호의 최소시간이 500us이상(일반적으로 600us)은 되어야 인식을 한다고 나와있다. 따라서 송신부에서의 필요사항은 37kHz를 만들기 위한 타이머, 500us를 체크하기위한 카운터 등이 필요하다. 그리고 위에서 언급했지만 나는 "0"과 "1"을 만들기 위해 OOK방식을 사용하므로 아래 그림과 같이 신호를 만들어줄 함수가 필요하다.

 

 

송신부의 전체 코드는 다음과 같다.

 

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

volatile unsigned int flag, count;

ISR(TIMER0_OVF_vect) // every 13us
{
	TCNT0 = 48;
	++count;

	if(count == 35) // 500us
	{
		flag=1;
	}
}

void _one()
{
	flag=0;
	count=0;
	while(!flag)
	{
		if(count%2)
			PORTB = 0x00;
		else
			PORTB = 0x01;
	}
	PORTB = 0x00;
	flag=0;
	count=0;
	while(!flag);
}

void _zero()
{
	flag=0;
	count=0;
	while(!flag)
	{
		if(count%2)
		PORTB = 0x00;
		else
		PORTB = 0x01;
	}
	PORTB = 0x00;
	flag=0;
	count=0;
	while(!flag);
	flag=0;
	count=0;
	while(!flag);
}

void send_frame(unsigned char *data)
{
	int i=0,j=0,size;
	
	size = strlen(data);
	
	_delay_ms(5); // wait to distinguish the new frame
		
	_one(); // start bit
	
	while(i<8) // length of data
	{
		if((size<<i)&0x80)
			_one();
		else
			_zero();
		++i;
	}
	
	while(j<size) // data
	{
		i=0;
		while(i<8)
		{
			if((*(data+j)<<i)&0x80)
				_one();
			else
				_zero();
			++i;
		}
		++j;
	}
	
	_one(); // end bit
}

int main()
{
	int i;
	
	TCCR0 = 0x01; // no prescale, normal mode
	TIMSK = 0x01; //TOV enable
	TCNT0 = 48; // 208 count => 13us
	
	DDRB = 0x01;
	sei();

	while(1)
	{
		send_frame("IR Success!!!");_delay_ms(500);
		
		for(i=48; i<58; i++) // numbers
		{
			send_frame(&i);
			_delay_ms(100);
		}
		
		for(i=97; i<123; i++) // small character
		{
			send_frame(&i);
			_delay_ms(100);
		}
		
		for(i=65; i<91; i++) // capital character
		{
			send_frame(&i);
			_delay_ms(100);
		}		
		
		send_frame("Ha-Ha-Ha-");_delay_ms(500);
	}
	
	return 0;
}

 

이제 수신부는 어떻게 구현하는지 아래 그림부터 먼저 보자.

 

 

적외선 수신모듈은 IDEL상태에서는 high를 유지하고 있다. 적외선이 high를 쏴주면 수신모듈은 반대로 low로 떨어진다. 따라서 외부인터럽트를 사용해서 falling edge에서 감지되게 한다. 외부인터럽트가 걸리면 샘플링을 위해 만들어 놓은 50us짜리 타이머의 카운트를 0으로 초기화하고 다음 외부인터럽트가 걸릴 때까지 카운터를 증가시키고 다시 외부인터럽트가 걸리면 샘플링된 카운터의 수를 체크해서 "0"인지 "1"인지 판별한다. 내가 만든 코드에서 "1"은 카운터의 갯수가 약 22개, "0"일 때는 32개 정도 카운팅 되어서 threshold를 27로 했다. 즉 카운터의 수가 27보다 작으면 "1"로 판단하고 27보다 크면 "0"으로 판단한다.

 

수신부의 전체 코드는 다음과 같다.

 

/*
 * ir_communication.c
 *
 * Created: 2014-01-09 오후 3:47:45
 *  Author: JISU
 */

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

volatile unsigned int count;
volatile int temp_count;
volatile int sample_flag;
int val,size;
char buf[32];

ISR(TIMER0_OVF_vect) // every 50us
{
	TCNT0 = 160;
	++count;		
}

ISR(INT3_vect)
{
	sample_flag=1;
	temp_count = count;
	count=0;
}

void detect_code()
{
	int i=0, j=0;

	while(1)
	{	
		if(count > 40000) // no signal for 2 seconds
		{
			PORTA = 0xff;
			memset(buf,32,sizeof(buf));
			break;
		}
		
		else if(count>90 && sample_flag)
		{
			memset(buf,32,sizeof(buf));
			val=0;
			sample_flag=0;			// start detect
			while(!sample_flag)
			{
				if(count>40000)
				{
					PORTA = 0xff;
					LCD_clear();
					break;
				}
			}
			sample_flag=0;			// count throw away
			while(!sample_flag)
			{
				if(count>40000)
				{
					PORTA = 0xff;
					LCD_clear();
					break;
				}
			}
			while(i<8)
			{
				sample_flag=0;
				while(!sample_flag)
				{
					if(count>40000)
					{
						PORTA = 0xff;
						LCD_clear();
						break;
					}
				}
				if(temp_count < 27) // distinguish 0 and 1 (if temp_count less than 27, then "1")
					val += 1<<(7-i); // receive MSB first
				++i;
			}
			size = val;
			j=0;
			while(j<size)
			{
				val=0;
				i=0;
				while(i<8)
				{
					sample_flag=0;
					while(!sample_flag)
					{
						if(count>40000)
						{
							PORTA = 0xff;
							LCD_clear();
							break;
						}
					}
					if(temp_count < 27)
					val += 1<<(7-i);
					++i;
				}
				buf[j] = val;
				++j;
			}
			break;
		}
	}
}

void init_timer()
{
	TCCR0 = 0x02; // prescale 8, normal mode
	TIMSK = 0x01; //TOV enable
	TCNT0 = 160; // 100 count => 50us
}

void init_external()
{
	EICRA = 0x80; // falling edge(INT3)
	EIMSK = 0x08; // INT3 enable
}

void init_port()
{
	DDRA = 0xff; // LED
	PORTA = 0xff; // LED off
	DDRD = 0x00; // IR sensor
	DDRC = 0xff; // CLCD data
	DDRE = 0xff; // CLCD ctrl
}

int main(void)
{
	int arr[8];
	init_port();
	init_timer();
	init_external();
	LCD_initialize();
	
	sei();
	
	while(1)
    {
		detect_code();
		LCD_string(0x80, buf);
    }
}

 

마지막으로 동작 영상이다. 송신부에서 0.1초 마다 0~9, a~z, A~Z 를 보내고 "HA-HA-HA-"랑 "IR Success!!!"라는 문자열을 보내보았다.