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


리모콘 신호를 보내는 장치를 만들어 봅니다.

IR led를 이용해서 적외선 신호를 송출합니다. IR led도 파장 길이에 따라 여러가지가 있는데 리모콘에서는 보통 940nm를 많이 쓰는 듯합니다. 하지만 대부분의 IR 수신기들, 특히 전편의 글에서 사용한 603LM 모듈은 대응 파장 범위가 넓기때문에, 파장 길이가 다른 여러 IR led를 동시에 지원할 수도 있다고 합니다.

IR led로 신호를 송출하는 회로는 다음과 같습니다.

사용한 부품은
1. ATmega8L 1개
2. IR led 1개(부품통에 있는 것 사용해서 품명은 모릅니다.)
3. NPN형 TR 1개
4. 22옴 저항 1개

회로도입니다. 회로도라 하기에 민망할 정도로 간단합니다.

SendIR_M8 회로도

 

 


IR 리모콘은 38kHz의 반송파로 신호를 송출해야 합니다. ATmega8L에서 38kHz의 신호를 얻기 위해서 Timer1의 fast PWM을 이용합니다. 8,000,000 / 38,000의 값을 계산하니 대략 210.5가 나옵니다. fastPWM 모드14는 Timer1의 카운터 값(TCNT1)이 ICR1의 값과 같아질 때에 다시 카운터 0부터 시작합니다. 따라서 ICR1에 210을 넣고 fastPWM 모드 14를 이용하면 대략 38kHz로 동작시킬 수 있습니다. 계산 상으로는 210을 넣으면 되는데, 오실로스코프로 측정해 본 결과 주파수가 맞지 않아서 실제 프로그램에서는 201을 넣었습니다.

fastPWM 모드에서 COM1A1을 1로 COM1A0를 0으로하면 OC1A의 값이 다음과 같이 변화합니다.
OC1A 핀의 상태가 1인 상태에서 시작합니다. TCNT1의 값이 1씩 증가하다가 OCR1A의 값과 같아지면 OC1A 핀의 상태가 0으로 변합니다. 이후에 TCNT1의 값이 계속 증가하여 ICR1의 값과 같아지면 OC1A 핀이 상태가 다시 1로 되면서, TCNT1의 값은 0으로 초기화됩니다. 즉 OCR1A 레지스터의 값을 변화시켜서 duty ratio를 바꿀 수 있습니다. 본 프로그램에서는 ICR1의 값을 201으로 입력하였기에 OCR1A에 100을 입력하여 duty ratio를 약 50%로 하였습니다. 어디선가 본 기억으로는 NEC프로토콜의 duty ratio는 1/3이라고 본 듯한데, 50%로 해도 잘 동작하기에 그대로 둡니다.

 

//fast PWM mode 14
.EQU	TCCR1A_IROUT_VALUE		= ((1 << COM1A1) | (1 << WGM11))
.EQU	TCCR1B_IROUT_VALUE		= ((1 << WGM13) | (1 << WGM12) | (1 << CS10))
.EQU	ICR1_VALUE			= 201
.EQU	IR_DUTY_VALUE			= 100

.EQU	NEC_LEADER_LOW			= (9000 / 10 * 6)
.EQU	NEC_LEADER_HIGH			= (4500 / 10 * 6)
.EQU	TC9012_LEADER_LOW		= (4500 / 10 * 6 + 50)
.EQU	TC9012_LEADER_HIGH		= (4500 / 10 * 6 + 100)
.EQU	SS_LEADER_LOW			= (3300 / 10 * 6)
.EQU	SS_LEADER_HIGH			= (8700 / 10 * 6 + 280)
.EQU	PULSE_TIME			= (560 / 10 * 6)
.EQU	WAIT_TIME_0			= ((1125 - 560) / 10 * 6 + 10)
.EQU	WAIT_TIME_1			= ((2250 - 560) / 10 * 6 - 10)

.EQU	IR_OUT_DDR			= DDRB
.EQU	IR_OUT_PIN			= PB1
.EQU	IR_PORT				= PORTB


프로그램에서 사용할 매크로들을 정의한 부분입니다. TCCR1A_VALUE와 TCCR1B_VALUE는 Time1을 fast PWM 모드 14도 동작시키고 OC1A 핀의 상태를 적절히 제어하기 위해 필요한 값을 지정했습니다.
ICR1_VALUE에는 38kHz로 동작시키기 위한 값 201을, IR_DUTY_VALUE는 duty ratio를 50%로 하기 위한 값 100을 지정했습니다. NEC_LEADER_LOW부터 SS_LEADER_HIGH까지는 각각의 프로토콜에서 사용하는 LEADER 신호의 길이를 지정했습니다.

 

소스프로그램의 아래 부분에 지연 함수를 만들어 두었습니다. RCALL 명령의 클럭까지 세어 가면서 비교적 정확한 길이의 지연 함수를 만들었다고 생각했습니다마는, 실제로는 예상보다 훨씬 많은 시간을 소모했습니다. 지연 시간을 맞추기 위해서 앞의 글 적외선(IR) 신호 읽기 - ATmega8에서 만든 장치로 시간을 측정하여 적절한 값으로 정했습니다. 이글을 보고 참고 하여 프로그램을 만들 때에는 본인의 환경에 맞게 매크로 값들을 재정의해서 사용하십시오. 원래 LEADER 신호 길이로 계산한 값의 약 60% 정도를 사용하니까 비교적 적절한 시간 값이 나왔습니다. 여기에 기기에서 인식하는 지를 확인해서 약간 값을 더하거나 빼기도 했습니다.

 

지연 시간이 생각보다 길어지는 주요한 이유는 인터럽트때문입니다만, 이 프로그램에서는 Timer1 인터럽트 외에는 인터럽트를 사용하지 않기 때문에 인터럽트가 원인은 아닌 것 같습니다. 다만, ICR1의 값에도 계산 값보다 적은 값을 넣어줘야했던 점으로 보아 8MHz의 AVR 내장 오실레이터가 조금 더 느린 것 아닌가 싶습니다. 또한, DELAY_16_nUS 함수 자체가 수행되는 클럭은 미미할 것 같아서 계산하지 않았는데, 생각보다 비중이 큰 것 같습니다.


OC1A 핀이 fasp PWM의 결과를 반영하여 0또는 1로 바뀌기 위해서는 사전에 ATmega8L의 15번 핀인 PB1이 DDRB에서 출력으로 지정해야 합니다. 이 작업을 하기 위해서 매크로 IR_OUT_DDR, IR_OUT_PIN, IR_PORT 등을 지정했습니다.


삼성 하우젠 천장형 에어콘 리모컨의 0과 1의 시간 길이가 다른 리모콘보다 작아서 앞의 글 적외선 리모콘(IR REMOCON) 신호 분석 - ATmega8의 ANALYZE_REMOCON 함수에서 ANALYZE_REMOCON_DATA와 별도로 ANALYZE_DATA_SAMSUNG을 만들었는데, 신호를 출력하다 보니 NEC 프로토콜 및 TC9012 프로토콜에서의 0과 1의 길이를 그대로 출력해도 삼성 하우젠 천정형 에어콘이 인식한다는 사실을 발견했습니다. 즉, NEC 프로토콜, TC9012 프로토콜, 삼성 하우젠 천장형 리모콘의 LEADER 코드만 다르고, 데이터인 0과 1은 같은 길이로 보내도 대부분 인식합니다. 그래서 본 글에서는 바이트 신호를 송신하는 루틴은 세 프로토콜이 모두 IR_SEND_BYTE 함수를 사용하도록 했습니다.

LEADER 신호 길이와 관련된 매크로들은 앞의 글 적외선(IR) 신호 읽기 - ATmega8에서 사용하던 것을 참고하여 만들었기 대문에 603LM의 신호에 따른 이름을 부여했습니다. 즉, 603LM이 IR 신호를 반전한 결과를 보내오기 때문에 NEC 프로토콜을 예로 들면, 매크로 NEC_LEADER_LOW 값은 NEC 프로토콜로 LEADER 신호를 송신할 때에 high로 유지해야 하는 시간이고, NEC_LEADER_HIGH 값은 low로 유지해야 하는 시간입니다.

매크로 PULSE_TIME은 0이나 1 신호를 송신할 때에 high로 유지해야 하는 시간이고, WAIT_TIME_0와 WAIT_TIME_1을 각각 0이나 1로 인식되기 위해 기다려야 하는 시간입니다. 즉 PULSE_TIME 만큼 38kHz의 신호를 보낸 후에 WAIT_TIME_0만큼 low 상태를 유지하면 신호 0을 송신한 것입니다. PULSE_TIME 만큼 38kHZ 신호를 보낸 후에 WAIT_TIME_1만큼 low 상태를 유지하면 1을 보낸 것입니다. NEC 프로토콜의 경우 PULSE_TIME은 560uS, WAIT_TIME_0은 565uS, WAIT_TIME_1은 1,690uS으로 되어 있습니다. 앞에서 언급한대로 지연 시간에 오류가 있어, 원래의 값 * 0.6한 값을 기준으로 해서 약간 가감한 값을 사용하고 있습니다.

다음은 IR 출력을 시작하도록 Timer1을 설정하는 함수 TIMER1_IROUT_START와 IR 출력을 중지하는 함수 TIMER1_IROUT_STOP 함수입니다. IR_PULSE 함수는 위 두 함수를 이용해서 Z 레지스터에 담겨 있는 길이만큼의 38kHz 신호를 전송합니다.

//////////////////////////////////////////////////
// TIMER1_IROUT_START:
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL
//////////////////////////////////////////////////
	LDI	AL,TCCR1A_IROUT_VALUE			// set fast PWM mode 14
	OUT	TCCR1A,AL
	LDI	AL,TCCR1B_IROUT_VALUE
	OUT	TCCR1B,AL
	LDI	AL,0					// clear TCNT1
	OUT	TCNT1H,AL
	OUT	TCNT1L,AL
	LDI	AL,HIGH(ICR1_VALUE)			// set fast PWM TOP value
	OUT	ICR1H,AL
	LDI	AL,LOW(ICR1_VALUE)
	OUT	ICR1L,AL
	LDI	AL,HIGH(IR_DUTY_VALUE)			// set duty ratio
	OUT	OCR1AH,AL
	LDI	AL,LOW(IR_DUTY_VALUE)
	OUT	OCR1AL,AL
	RET

//////////////////////////////////////////////////
// TIMER1_IROUT_STOP:
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL
//////////////////////////////////////////////////
TIMER1_IROUT_STOP:
	LDI	AL,0
	OUT	TCCR1A,AL
	OUT	TCCR1B,AL
	RET
    
//////////////////////////////////////////////////
// IR_PULSE:
// PARAM	ZH:ZL (length uS)
// RETURN	NONE
// CHANGED	ZH:ZL
//////////////////////////////////////////////////
IR_PULSE:
	PUSH	AL
	RCALL	TIMER1_IROUT_START
	RCALL	DELAY_16_nUS
	RCALL	TIMER1_IROUT_STOP
	LDI	AL,0
	OUT	IR_PORT,AL
	POP	AL
	RET



TIIMER1_IROUT_START 함수는 Timer1의 레지스터들에 적절한 값을 넣어서 Timer1이 동작하도록 함으로써, OC1A에 약 38kHz의 파형이 생기도록 설정합니다. TIMER1_IROUT_STOP 함수는 Timer1이 멈추도록 합니다. DELAY_16_nUS 함수는 Z 레지스터에 담긴 값만큼 을 지연하는 함수입니다(단위는 uS). 이 함수에서 생각보다 많은 지연이 발생하는 듯합니다. 향후 타이머를 이용해 지연하는 함수로 개편해야할 듯합니다.

다음의 IR_SEND_BYTE 함수는 AL 레지스터에 있는 값에 따라 IR 신호를 송신하는 함수입니다. 가장 하위의 비트(LSB)를 송신한 다음 오른쪽으로 한 비트씩 시프트하는 방식으로 8회 반복합니다.

//////////////////////////////////////////////////
// IR_SEND_BYTE:
// PARAM	AL
// RETURN	NONE
// CHANGED	AL
//////////////////////////////////////////////////
IR_SEND_BYTE:
	PUSH	AH
	PUSH	ZL
	PUSH	ZH
	LDI	AH,8
IR_SEND_BYTE_LOOP:
	TST	AH
	BREQ	IR_SEND_BYTE_QUIT
	LDI	ZL,LOW(PULSE_TIME)			// send 38kHz for PULSE_TIME micro seconds
	LDI	ZH,HIGH(PULSE_TIME)
	RCALL	IR_PULSE
	SBRC	AL,0
	RJMP	IR_SEND_BYTE_LOOP_HIGH			// jump if LSB is 1
	LDI	ZL,LOW(WAIT_TIME_0)			// if LSB is 0 wait for WAIT_TIME_0
	LDI	ZH,HIGH(WAIT_TIME_0)
	RJMP	IR_SEND_BYTE_LOOP_LSR
IR_SEND_BYTE_LOOP_HIGH:
	LDI	ZL,LOW(WAIT_TIME_1)			// if LSB is 1 wait for WAIT_TIME_1
	LDI	ZH,HIGH(WAIT_TIME_1)
IR_SEND_BYTE_LOOP_LSR:
	RCALL	DELAY_16_nUS
	LSR	AL
	DEC	AH
	RJMP	IR_SEND_BYTE_LOOP
IR_SEND_BYTE_QUIT:
	POP	ZH
	POP	ZL
	POP	AH
	RET



다음은 위의 IR_SEND_BYTE 함수를 이용해서 각각의 프로토콜로 신호를 보내는 예입니다.
IR_SEND_BYTES 함수는 앞에서 다룬 IR_SEND_BYTE 함수를 호출하여 Z 레지스터가 가리키는 메모리에서 AH 레지스터에 담긴 수 만큼의 데이터를 송신한 다음에 stop bit를 송신합니다. SS_AIRCON 함수는 삼성 하우젠 천정형 에어콘 리모콘 LEADER 신호를 보내고, X 레지스터의 내용을 Z 레지스터에 복사한 후에 AH 레지스터에 7을 넣고 IR_SEND_BYTES 함수를 호출합니다. 즉, SS_AIRCON 함수는 X 레지스터가 가리키는 플래시 메모리에 위치한 데이터 7바이트를 IR_SEND_BYTES 함수를 이용해 송신합니다. SS_AIRCON_ON 함수는 X 레지스터에 삼성 하우젠 천정형 에어콘을 켜는 명령이 있는 위치를 가리키게 하고 SS_AIRCON 함수를 호출함으로써 삼성 하우젠 천정형 에어콘을 켭니다. SS_AIRCON_OFF 함수는 X 레지스터에 삼성 하우젠 천정형 에어콘을 끄는 명령이 있는 위치를 가리키도록 하고 SS_AIRCON 함수를 호출함으로써 삼성 하우젠 천정형 에어콘을 끕니다.


TC9012 함수는 TC9012 프로토콜의 LEADER 신호를 보낸 후에 삼성 TV 리모콘의 POWER 버튼에 해당하는 코시가 있는 위치를 가리키게하고 IR_SEND_BYTE 함수를 호출합니다. NEC 함수는 NEC 프로토콜의 LEADER 신호를 보낸 후에 LG TV 리모콘의 POWER 버튼에 해당하는 코드가 있는 플래시 메모리 위치를 Z 레지스터가 가리키도록 하고 IR_SEND_BYTES 함수를 호출합니다.

 

//////////////////////////////////////////////////
// SS_AIRCON_ON:
// PARAM	XL, XH
// RETURN	NONE
// CHANGED	AL, AH, ZL, ZH
//////////////////////////////////////////////////
SS_AIRCON_ON:						// turn on samsung hausen aircon
	LDI	XL,LOW(SS_AIRCON_TURNON)
	LDI	XH,HIGH(SS_AIRCON_TURNON)
	RJMP	SS_AIRCON

//////////////////////////////////////////////////
// SS_AIRCON_OFF:
// PARAM	XL, XH
// RETURN	NONE
// CHANGED	AL, AH, ZL, ZH
//////////////////////////////////////////////////
SS_AIRCON_OFF:						// turn off samsung hausen aircon
	LDI	XL,LOW(SS_AIRCON_TURNOFF)
	LDI	XH,HIGH(SS_AIRCON_TURNOFF)
	RCALL	SS_AIRCON

//////////////////////////////////////////////////
// SS_AIRCON:
// PARAM	XL, XH
// RETURN	NONE
// CHANGED	AL, AH, ZL, ZH
//////////////////////////////////////////////////
SS_AIRCON:
	LDI	ZL,LOW(SS_LEADER_LOW)			// send samsung hausen aircon leader
	LDI	ZH,HIGH(SS_LEADER_LOW)
	RCALL	IR_PULSE
	LDI	ZL,LOW(SS_LEADER_HIGH)
	LDI	ZH,HIGH(SS_LEADER_HIGH)
	RCALL	DELAY_16_nUS
	MOVW	ZL,XL					// send 7bytes(56bits), X register points to commands
	LDI	AH,7
	RJMP	SEND_BYTES


//////////////////////////////////////////////////
// TC9012:
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL, AH, ZL, ZH
//////////////////////////////////////////////////
TC9012:							// send TC9012 leader
	LDI	ZL,LOW(TC9012_LEADER_LOW)
	LDI	ZH,HIGH(TC9012_LEADER_LOW)
	RCALL	IR_PULSE
	LDI	ZL,LOW(TC9012_LEADER_HIGH)
	LDI	ZH,HIGH(TC9012_LEADER_HIGH)
	RCALL	DELAY_16_nUS
	LDI	ZL,LOW(SS_TV_POWER_CODE)		// send POWER code of samsung TV
	LDI	ZH,HIGH(SS_TV_POWER_CODE)
	LDI	AH,4
	RJMP	SEND_BYTES


//////////////////////////////////////////////////
// NEC:
// PARAM	NONE
// RETURN	NONE
// CHANGED	AL, AH, ZL, ZH
//////////////////////////////////////////////////
NEC:
	LDI	ZL,LOW(NEC_LEADER_LOW)			// send NEC leader 
	LDI	ZH,HIGH(NEC_LEADER_LOW)
	RCALL	IR_PULSE
	LDI	ZL,LOW(NEC_LEADER_HIGH)
	LDI	ZH,HIGH(NEC_LEADER_HIGH)
	RCALL	DELAY_16_nUS
	LDI	ZL,LOW(LG_TV_POWER_CODE)		// send POWER code of LG TV.
	LDI	ZH,HIGH(LG_TV_POWER_CODE)
	LDI	AH,4
SEND_BYTES:						// send AH bytes which Z resgister points in flash memory.
	LSL	ZL
	ROL	ZH
SEND_BYTES_LOOP:
	TST	AH
	BREQ	SEND_BYTES_QUIT
	LPM	AL,Z+
	RCALL	IR_SEND_BYTE
	DEC	AH
	RJMP	SEND_BYTES_LOOP
SEND_BYTES_QUIT:
	LDI	ZL,LOW(PULSE_TIME)			// send stop bit
	LDI	ZH,HIGH(PULSE_TIME)
	RCALL	IR_PULSE
	RET



다음은 LG TV 리모콘의 POWER 버튼 값을 송신하고 약 1초간 대기, 삼성 TV 리모콘의 POWER 값을 보내고 1초간 대기, 삼성 하우젠 천장형 에어컨을 끄는 명령을 보내고 또 1초간 대기하는 과정을 반복하는 코드의 예입니다.

 

MAIN:
	LDI	AL,LOW(RAMEND)
	OUT	SPL,AL
	LDI	AL,HIGH(RAMEND)
	OUT	SPH,AL
	LDI	AL,(1 << IR_OUT_PIN)
	OUT	IR_OUT_DDR,AL
MAIN_LOOP:
	RCALL	NEC
	RCALL	DELAY_1S
	RCALL	TC9012
	RCALL	DELAY_1S
	RCALL	SS_AIRCON_OFF
	RCALL	DELAY_1S
	RJMP	MAIN_LOOP

 

첨부 파일로 잘 동작하는 소스 프로그램을 올립니다.

 

SendIR.asm
10.6 kB



앞의 글 적외선 리모콘(IR Remocon) 신호 분석 - ATmega8과 이 글의 내용을 합하여 응용하면 만능 리모콘을 만들 수 있을 듯합니다.

블로그 이미지

엠쿠스

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

,