[임베디드/프로젝트] 집 밖에서 가전제품을 제어해 보자 4탄

2 분 소요

적외선 송신을 어떻게 할까요

ATmega128의 핀넘버는 다음과 같다.(출처: https://m.blog.naver.com/ga1267/220079623919) pinNum

ATmega128의 1번 타이머는 16비트 타이머이고 이 타이머/카운터를 이용해서 신호를 송신한다고 가정할 때,
CTC 모드로 설정한 타이머/카운터에 비교일치 인터럽트를 두 개(하나는 560us마다 인터럽트를 발생하도록, 다른 하나는 38kHz로 인터럽트가 발생하도록)만들어 놓으면 된다. 참고로 분주비를 256으로 하면 1클럭당 16us가 지나는데 35클럭돌면 560us로 562us와 비슷하게 된다. 또, 분주를 안한 상태에서 210번 클럭이 돌았을때 인터럽트가 발생하도록하면 대략 38kHz가 된다.
출력 핀인 OC1A, OC1B, OC1C는 각각 OCR1A, OCR1B, OCR1C에 따른 인터럽트를 출력하게 된다. 문제는, 우리는 하나의 핀이 두 개의 인터럽트를 동시에 적용되도록 해야 한다는 점이다.

가령 printIR()이라는 함수를 구현한다고 가정해보자. 그 함수의 매개변수로는 내가 미리 디코딩한 NET format 코드가 배열로 들어갈 것이다.
함수 내부에서 논리 0, 논리 1, 리드 코드의 경우를 따진다. 평소에는 OCR1A를 0로 두어 파형이 생성되지 않게 하고 출력이 시작 될 때, 34로 고치면 된다.
volatile int형 cnt 변수를 하나 선언하고 파형이 두 번 반복되었을 때 한 번 더 cnt를 1로 둬서 HIGH가 나오는 시기를 한 타임 늦추면 될 것 같다.

논리 0과 논리 1 인코딩을 해보자

// TCCR1A = _BV (WGM12); OCR1A  = 34; TIMSK1 = _BV (OCIE1A); TCCR1B =  _BV (CS21) | _BV (CS22); 해야 함
// TCCR1A |= _BV(COM1A0) || _BV(COM1A1); OCR3A = 209;해놓으면 처음 인터럽트가 발생 할 때 HIGH가 출력된다.
volatile int cnt = 0;
volatile int zeroFlag = 0;

ISR(TIMER1_COMPA_vect)
{
  	if(cnt == 0) {
		OCR3A = 209;
    	TCCR1A |= _BV(COM1A1) & ~_BV(COM1A0);
	}
	else if(cnt == 1 && zeroFlag) {
		OCR3A = 0;
    	TCCR1A |= _BV(COM1A1) & ~_BV(COM1A0);
	}
  	else {
		OCR3A = 0;
		TCCR1A |= _BV(COM1A0) | _BV(COM1A1);
	}
	++cnt;
	cnt %= 4;
}

여기까지 해두면 560us마다 인터럽트가 걸리게 하는 건 끝났다.

두 번째로, 38kHz 반송파에 싣는 것인데 아마 TCCR1B에 COM1Bn 비트들을 디폴트로 놔두면 OC1B에서 출력이 되지 않을 것이고
인터럽트가 발생 할 때마다 OC1A핀의 출력을 toggle하면 될 것 같다.

//TCCR3A = 0;
//TCCR3B = _BV(WGM12) | _BV (CS10);
//OCR3A =  209;

ISR(TIMERS3_COMPA_vect)
{
  	PORTB ^= (1 << 5); //PB5 is OC1A
}

리드코드를 인코딩 해보자.

리드코드는 일반 데이터와 반복 데이터 두 가지로 나뉜다. 본격적인 데이터 전송에 앞서 전송을 시작하겠다는 시작신호인 것인데, 그냥 신호와 연속 신호를 구분하기위해 두 가지가 있다. 두 가지 모두 처음 시작은 9ms의 HIGH값이지만 실제 데이터 전송이 이루어지는 시점까지 LOW로 기다리는 시간이 다르다. 일반 데이터는 4.5ms, 반복 데이터는 2.25ms이다. 분주비를 1024로 하면 144클럭마다 9ms가 지난다. 72클럭이면 4.5ms일테고 36클럭이면 2.25ms가 지날 것이다. 어차피 한 번 출력하고 끝이니까 다음과 같이 구성하면 된다.

//TCCR0 |= (1 << CS02) | (1 << CS01) | (1 << SC00);
//TCCR0 |= (1 << WGM01);
//OCR0 = 35;

volatile int cntLead = 0;
volatile int repeat = 0;

ISR(TIMER0_COMP_vect)
{
  	if(cntLead < 4) {
		OCR3A = 209;
    	PORTB |= (1 << 5);
	}
  	else {
		OCR3A = 0;
    	PORTB = 0;
	}
  	++cntLead;
  	cntLead %= 7;
}

얼추 된 것 같다.

의미있는 신호를 송신해보자

어차피 에어컨, 공기청정기, TV 켜고끄는 정도만 할테니깐 리드코드는 무조건 일반데이터로 들어간다고 가정하고 코드를 짜보자.

void printIR(int* dataArr, int len)
{
	int i;
	//리드코드
	OCR3A = 209;
	OCR0 = 35;
	while(cntLead < 6){}
	cntLead = 0;
	OCR0 = 0;
	
	//데이타
	for(i = 0; i < len; ++i) {
		OCR3A = 209;
		OCR1A = 34;
		if(dataArr[i]) {
			while(cnt < 2) {
				if(cnt) OCR3A = 0;
				else OCR3A = 209;
			}
			cnt = 0;
			OCR3A = 0;
			OCR1A = 0;
		}
		else {
			zeroFlag = 1;
			while(cnt < 3) {
				if(cnt == 2) cnt = 1;
				if(cnt) OCR3A = 0;
				else OCR3A = 209;
			}
			cnt = 0;
			OCR3A = 0;
			OCR1A = 0;
		}
	}
}

이정도가 되겠다. 코로나 때문에 휴가를 못나가서 코드를 시험해 볼 수가 없는데 다음번 휴가를 나갈 때, 그러니까 전역하면 시험해봐야겠다.

댓글남기기