이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.

 

ATmega8L로 적외선 리모콘 코드를 읽어 파형 정보를 출력해 보기로 합니다. 회로를 간단히 하기 위해서 AVR의 내장 8MHz RC Oscillator를 사용하기로 합니다. 필요한 부품은 다음과 같습니다.

1. ATmega8L 1개
2. 603LM 1개
3. 1㏀ 저항 1개
4. TTL to USB Serial converter 1개

적외선 신호 수신 센서로 603LM을 사용합니다.

위에 나열한 4개의 부품외에 노이즈를 줄이기 위해서 몇 개의 콘덴서를 더 사용하는 것이 좋습니다. 예를 들면 전원 라인에 104 정도의 세라믹 콘덴서, 603LM의 신호에 발생할 수 있는 노이즈를 줄이기 위해서 102 정도의 작은 세라믹 콘덴서 등등. 하여간 위의 4개 부품을 이용해서 브레드 보드에 꾸며 봤습니다.



어쩌다 보니 TTL to USB serial converter를 연결하지 않은 상태에서 사진을 찍었습니다. AVR에서 PC쪽으로 보내기만 할 것이므로 ATmega8L의 3번핀 TxD만 TTL to USB serial converter의 수신(RxD)와 연결하면 됩니다. 실제 통신은 38400bps, no parity, 8 data bit, 1 stop bit로 합니다.

적외선 수신 센서인 603LM에 관하여 간단히 알아 보겠습니다. 대부분의 리모콘은 38KHz의 주파수를 가지는 적외선 신호를 내보냅니다. 603LM은 이 신호를 받아 아래 그림과 같이 출력합니다.




38KHz의 적외선 신호가 들어오는 동안은 low(0)을 유지하고, 적외선 신호가 들어오지 않는 동안은 high(1)를 유지합니다. 이 신호를 TR 1개를 이용해서 low와 high를 바꿀 수도 있습니다만, 어차피 마이크로프로세서에서 처리할 것이므로 소프트웨어적으로 처리하도록 하겠습니다.

가장 많이 사용하는 리모콘 프로토콜의 경우 0을 보낼 때의 신호 길이는 1.125mS, 1을 보낼 때의 신호는 2.250mS, 리모콘 신호 시작을 알리는 leader code의 길이는 최대 13.5mS 등입니다. 리모콘에서 신호가 들어오는 동안은 신호의 길이를 queue에 담아 놓습니다. Low 신호의 길이는 IR_QUEUE_LOW에, high 신호의 길이는 IR_QUEUE_HIGH에 저장합니다.

일정 시간이 지나도록 신호가 더 이상 들어오지 않으면 리모콘의 신호 송출이 끝난 것으로 간주하고, 그동안 받은 신호들의 길이를 ATmega8L의 USART를 통해서 PC로 전송하도록 프로그램할 예정입니다.

리모콘 신호의 길이는 ATmega8L의 16비트 타이머인 Timer1의 Input Capture 인터럽트를 이용해서 측정하겠습니다. 먼저 ATMega8L이 내부 오실레이터 8MHz로 동작하도록 휴즈비트를 다음 그림과 같이 설정합니다.




리모콘 신호의 길이를 uS 단위로 측정하기 위해서 Timer1의 클럭으로 시스템 클럭의 1/8을 사용합니다. 또한 리모콘 신호의 최대 길이가 13.5mS이니까 넉넉 잡고 30mS동안 신호가 들어오지 않으면 송출이 종료된 것으로 간주하겠습니다. 이와 같은 내용을 담아서 Timer1을 초기화하는 함수 TIMER1_INIT 함수를 다음과 같이 작성합니다.

// falling edge, prescale 1/8, CTC OCR1A mode
.EQU	TCCR1B_IRIN_VALUE	= ((1 << CS11) | (1 << WGM12))
.EQU	TIMSK_IRIN_VALUE	= ((1 << TICIE1) | (1 << OCIE1A))

.EQU	IR_STANDBY		= 0
.EQU	IR_DATA_ON		= 1
.EQU	IR_DATA_TIMEOUT		= 30000

//////////////////////////////////////////////////
// TIMER1_IRIN:
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL
//////////////////////////////////////////////////
TIMER1_IRIN:
	LDI	AL,TCCR1B_VALUE
	OUT	TCCR1B,AL
	LDI	AL,TIMSK_VALUE
	OUT	TIMSK,AL
	LDI	AL,0
	OUT	TCNT1H,AL
	OUT	TCNT1L,AL
	LDI	AL,LOW(IR_DATA_TIMEOUT)
	OUT	OCR1AH,AL
	LDI	AL,HIGH(IR_DATA_TIMEOUT)
	OUT	OCR1AL,AL
	RET



프리스케일러를 1/8로 설정하고, Timer1을 CTC 모드로 동작하도록 TCCR1B를 설정했습니다. 리모콘 신호가 들어오면 Timer1의 Input Capture 인터럽트가 발생하고, 카운터의 값이 OCR1A와 같아지면 Output compare match 1A 인터럽트가 발생하도록 TIMSK를 설정했습니다. OCR1A에는 30000을 넣어서 30mS가 경과하도록 리모콘 신호가 들어오지 않으면 Timer1의 Output Compare match 1A 인터럽트가 걸리도록 했습니다.

각각의 인터럽트 서비스 루틴을 만듭니다. 먼저 Input Capture 인터럽트 서비스 루틴입니다.

.EQU	IR_QUEUE_SIZE			= 0x160
.DSEG
IR_QUEUE_LOW:			.BYTE	IR_QUEUE_SIZE
IR_QUEUE_HIGH:			.BYTE	IR_QUEUE_SIZE

.DEF	IR_QUEUE_TAIL_L		= R10
.DEF	IR_QUEUE_TAIL_H		= R11
.DEF	IR_QUEUE_HEAD_L		= R12
.DEF	IR_QUEUE_HEAD_H		= R13
.DEF	IR_QUEUE_DATA_L		= R14
.DEF	IR_QUEUE_DATA_H		= R15
.DEF	AL			= R16
.DEF	AH			= R17
.DEF	BL			= R18
.DEF	BH			= R19
.DEF	CL			= R20
.DEF	CH			= R21
.DEF	IR_STATE		= R24

//////////////////////////////////////////////////
// __TIMER1_CAPT (timer1 capture interrupt service routine)
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL
//////////////////////////////////////////////////
__TIMER1_CAPT:
	PUSH	AL
	IN	AL,SREG
	PUSH	AL
	PUSH	AH
	PUSH	ZL
	PUSH	ZH
	IN	AL,ICR1L
	IN	AH,ICR1H
	LDI	ZL,0
	OUT	TCNT1H,ZL
	OUT	TCNT1L,ZL
	IN	ZL,TCCR1B
	SBRC	ZL,ICES1
	RJMP	__TIMER1_CAPT_SET_FALLING
	SBR	ZL,(1 << ICES1)
	OUT	TCCR1B,ZL			// next interrupt will be at rising edge
	LDI	ZL,LOW(IR_QUEUE_HIGH)		// store high time
	LDI	ZH,HIGH(IR_QUEUE_HIGH)
	ADD	ZL,IR_QUEUE_HEAD_L
	ADC	ZH,IR_QUEUE_HEAD_H
	ST	Z+,AL
	ST	Z,AH
	MOVW	ZL,IR_QUEUE_DATA_L		// data++;
	ADIW	ZL,1
	MOVW	IR_QUEUE_DATA_L,ZL
	MOVW	ZL,IR_QUEUE_HEAD_L			
	ADIW	ZL,2
	LDI	AL,LOW(IR_QUEUE_SIZE)		// head %= IR_QUEUE_SIZE;
	LDI	AH,HIGH(IR_QUEUE_SIZE)
	CP	ZL,AL
	CPC	ZH,AH
	BRLO	__TIMER1_CAPT_STORE_QUEUE_HEAD
	CLR	ZL
	CLR	ZH
__TIMER1_CAPT_STORE_QUEUE_HEAD:
	MOVW	IR_QUEUE_HEAD_L,ZL
__TIMER1_CAPT_QUIT:
	LDI	IR_STATE,IR_DATA_ON
	POP	ZH
	POP	ZL
	POP	AH
	POP	AL
	OUT	SREG,AL
	POP	AL
	RETI
__TIMER1_CAPT_SET_FALLING:
	CBR	ZL,(1 << ICES1)			// next interrupt will be at falling edge
	OUT	TCCR1B,ZL
	LDI	ZL,LOW(IR_QUEUE_LOW)		// store low time
	LDI	ZH,HIGH(IR_QUEUE_LOW)
	ADD	ZL,IR_QUEUE_HEAD_L
	ADC	ZH,IR_QUEUE_HEAD_H
	ST	Z+,AL
	ST	Z,AH
	RJMP	__TIMER1_CAPT_QUIT



메모리 공간에 IR_QUEUE_LOW와 IR_QUEUE_HIGH를 각각 0x160(IR_QUEUE_SIZE) 바이트씩 준비해서 ring형 queue로 사용합니다. 신호 시간은 uS 단위로 두 바이트씩 기록합니다. 이로 인하여 실제 길이 데이터는 high와 low 각각 0x160 / 2 인 0xD0, 10진수로는 176개를 저장할 수 있습니다. 삼성 에어콘 리모콘의 경우 버튼 하나를 누르면 112비트의 신호가 오기 때문에 넉넉히 잡았습니다. 인터럽트 서비스 루틴은 가급적 빨리 끝내는 것이 좋으므로 QUEUE의 HEAD, TAIL, DATA 수 등의 변수들은 모두 레지스터 변수로 사용했습니다.

최초의 Input Capture 인터럽트는 falling edge에서 발생하도록 프로그램했습니다. Falling edige에서 인터럽트가 걸리면 다음 인터럽트는 rising edge에서 걸리도록 설정합니다(소스프로그램 41, 42행). 이 때의 타이머 값은 신호가 high인 시간을 측정한 것이기 때문에 IR_QUEUE_HIGH에 타이머 값을 두 바이트 넣습니다(소스프로그램 43 ~ 48행). 603LM에서 들어오는 신호가 low로 떨어질 때 즉 Input capture 인터럽트가 falling edge에서 발생했을 때에 리모콘 신호가 하나 들어 온 것입니다. 따라서 이 때에 DATA도 하나 늘리고(소스프로그램 49 ~ 51행), UEUE_HEAD를 2만큼 증가 시킵니다(소스프로그램 52 ~ 62행).

Rising edige에서 인터럽트가 걸리면 다음 인터럽트는 falling edge에서 걸리도록 설정하고(소스프로그램 73, 74행), 이 때의 타이머 값을 IR_QUEUE_LOW에 넣습니다.(소스프로그램 75 ~ 80행)

레지스터 변수 IR_STATE에 IR_DATA_ON 값을 넣습니다(소스프로그램 82행). 메인 프로그램에서는 IR_STATE 변수의 값을 검사해서, IR_STANDBY 값이 아니면 데이터가 입력되는 중이므로 입력이 끝날 때까지 대기하도록 프로그램합니다.
이렇게 프로그램했을 경우 문제점이 하나 존재합니다. 리모콘 버튼을 눌러서 발생한 최초의 Input capture 인터럽트의 경우, 타이머의 카운터 값은 신호가 들어오기 이전의 신호 길이와 관련된 값이므로 의미가 없는 값입니다. 나중에 결과가 출력되더라도 이 첫 데이터 값은 무시해야 합니다.

다음은 Timer1 Output compare 인터럽트 서비스 루틴입니다. 30mS이 지나도록 신호 입력이 없으면 이 인터럽트 서비스 루틴이 실행됩니다. 이 서비스루틴에서는 레지스터 변수 IR_STATE를 IR_STANDBY 값으로 설정하여 메인 프로그램에서 입력 받은 데이터들을 출력할 수 있게 해 줍니다.

//////////////////////////////////////////////////
// __TIMER1_COMPA (timer1 output1A compare interrupt service routine)
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL
//////////////////////////////////////////////////
__TIMER1_COMPA:
	PUSH	AL
	IN	AL,SREG
	LDI	IR_STATE,IR_STANDBY
	OUT	SREG,AL
	POP	AL
	RETI



USART를 초기화하고 위에서 측정한 값을 시리얼 통신으로 PC에 전송하는 assembly어 소스프로그램 전체를 첨부합니다.

LengthIR_M8.asm
0.01MB




IR 신호를 받아 측정한 결과 값을 출력하는 예입니다.'-' 기호 이후의 숫자는 low 상태의 길이(uS 단위)이고, '+' 기호 이후의 숫자는 high 상태의 길이(uS 단위)입니다. "Sum=" 뒤의 숫자는 low 상태의 길이와 high 상태의 길이를 합한 값입니다. 각 길이 뒤의 ()안의 수는 길이를 16진수로 표시한 것입니다. 앞에서 언급한대로 첫 데이터는 의미없는 값이므로 무시해야 합니다.

블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,