[임베디드/프로젝트] 집 밖에서 가전제품을 제어해 보자 2탄
적외선 신호 디코딩
저번 포스트에서 NEC 프로토콜을 설명한 적이 있다.
NEC 프로토콜은 상승 에지 사이의 시간을 통해 0과 1을 구분하는 방법인 펄스 거리 인코딩, OOK 방식을 쓰는데, 시간간격은 다음과 같다.
유형 | . | 시간간격(ms) | 클록 수 |
---|---|---|---|
리드 코드 | 일반 데이터 | 13.5 | 210.94 |
리드 코드 | 반복 데이터 | 11.25 | 175.78 |
논리 코드 | 논리 0 | 1.125 | 17.58 |
논리 코드 | 논리 1 | 2.25 | 35.16 |
적외선 신호를 디코딩하려면 적외선을 수신하는 모듈이 있어야 할 것이다. 다양한 수신모듈이 있겠지만 우리가 사용하는 37~8kHz 주파수 대역을 수신할 수 있는 모듈을 사용해야 하고 대부분의 모듈의 경우 평소에 high 상태를 유지하다가 신호를 받을 경우 low가 될 것이다.
저번 포스트의 맨 마지막 부분에도 적어뒀지만, 하강에지 때마다 외부 인터럽트가 걸리게 하고 외부 인터럽트 안에서 타이머/카운터를 초기화, 다음 외부 인터럽트걸릴 때 지금까지 센 타이머/카운터를 통해 논리 0과 논리 1을 구분하면 된다.
적외선 신호 수신 예제
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdio.h>
//여기서 부터 UART설정
void UART1_init(void)
{
UBRR1H = 0x00; // 9600 baud rate set
UBRR1L = 207;
UCSR1A |= _BV(U2X1); // 2배속 모드
UCSR1C |= 0x06; // 비동기, 8비트 데이터, 패리티 없음, 1비트 정지비트 모드
UCSR1B |= _BV(RXEN1);
UCSR1B |= _BV(TXEN1); // tx, rx enable
}
void UART1_transmit(char data)
{
while(!(UCSR1A & (1 << UDRE1))); // stay until buffer flushed
UDR1 = data; // data trnasmit
}
unsigned char UART1_receive(void)
{
while(!(UCSR1A & (1 << RXC1))); // stay until buffer filled
return UDR1;
}
void UART1_print_string(char *str)
{
for(int i = 0; str[i]; i++)
UART1_transmit(str[i]); // transmit char until meet \0
}
void UART_print_1_byte_number(uint8_t n)
{
char numString[4] = "0";
int i, index = 0;
if(n > 0) {
for(i = 0;n != 0; ++i){
numString[i] = n % 10 + '0';
n /= 10;
}
numString[i] = '\0';
index = i - 1;
}
for(i = index; i >= 0; --i)
UART1_transmit(numString[i]);
}
FILE OUTPUT = FDEV_SETUP_STREAM(UART1_transmit, NULL, _FDEV_SETUP_WRITE);
FILE INPUT = FDEV_SETUP_STREAM(NULL, UART1_receive, _FDEV_SETUP_READ);
// UART로 전달되는 데이터를 스트림형태로 바꾸어 주기 위한 객체 생성, 이후 표준 입출력장치와 연결
// 여기서부터 NEC format decoding
volatile int bitCount;
volatile unsignd long receivedData;
void print_received_data(int repeat)
{
if(repeat == 1)
printf("** Repeat...\r\n");
else
printf("0x%lX\r\n", receivedData);
}
ISR(INT0_vect)
{
int time = TCNT0; // time that interrupt occured
int overflow = TIFR & (1 << TOV0); // check overflow occured
if(bitCount == 32) { // bitCount가 32면 리드코드라는 뜻
if((time > 201) && (time < 221) && (overflow == 0)) {
receivedData = 0;
bitCount = 0;
} // 일반 데이터 리드코드 길이 13.5ms는 약 211클록
else if((time > 166) && (time < 186) && (overflow == 0)) {
print_received_data(1);
} // 반복 데이터 리드코드 길이 11.25ms는 약 176클록
else
bitCount = 32;
}
else {
if((time > 4) || (overflow != 0))
bitCount = 32;
// 논리 0이 약 18클록, 논리 1이 약 35클록
else {
if(time > 26)
receivedData = (receivedData << 1) + 1;
else
receivedData = (receivedData << 1);
if(bitCount == 31)
print_received_data(0); // bitCount 31일때 수신 완료
bitCount++;
}
}
TCNT0 = 0;
TIFR = TIFR | (1 << TOV0);
}
int main()
{
stdout = &OUTPUT;
stdin = &INPUT;
UART1_init();
// 0번 타이머/카운터 분주비를 1024로 설정
TCCR0 |= (1 << CS00) | (1 << CS01) | (1 << CS02);
// PD0핀에 연결된 적외선 수신기에 대한 외부 인터럽트 설정
EIMSK |= (1 << INT0); // 외부인터럽트0 활성화
EICRA |= (1 << ISC01); // 하강에지일때 인터럽트 발생
sei(); // 전역적으로 인터럽트 허용
bitCount = 32; // 시작 신호 대기(리드코드)
printf("** Initialization Completed...\r\n");
while(1);
return 0;
}
이 코드의 경우 읽어들인 신호를 UART로 전달했지만, 응용해서 리모컨 키맵핑을 할 수 있다. 이후 적외선 신호를 인코딩하여 송신을 해야하는데 그 부분은 PWM부분을 공부한 후 타이머/카운터의 파형생성기로 구현해 보겠다.
끝
댓글남기기