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어 소스프로그램 전체를 첨부합니다.
IR 신호를 받아 측정한 결과 값을 출력하는 예입니다.'-' 기호 이후의 숫자는 low 상태의 길이(uS 단위)이고, '+' 기호 이후의 숫자는 high 상태의 길이(uS 단위)입니다. "Sum=" 뒤의 숫자는 low 상태의 길이와 high 상태의 길이를 합한 값입니다. 각 길이 뒤의 ()안의 수는 길이를 16진수로 표시한 것입니다. 앞에서 언급한대로 첫 데이터는 의미없는 값이므로 무시해야 합니다.
'AVR > 작품' 카테고리의 다른 글
적외선 리모콘(IR Remocon) 신호 보내기 - ATmega8 (0) | 2019.11.02 |
---|---|
적외선 리모콘(IR remocon) 신호 분석 - ATmega8 (0) | 2019.10.28 |
Click, Double Click, Long Click 구분하기 (0) | 2019.09.07 |
KCC426V와 ATtiny2313을 이용한 FM radio 만들기(3) (0) | 2019.06.24 |
KCC426V와 ATtiny2313을 이용한 FM radio 만들기(2) (0) | 2019.05.12 |