티스토리 뷰

Embedded linux 를 이용한 적외선 리모컨 학습 기능을 개발하면서 구현하였던 사항을 정리한다. 


1. 리모트 컨트롤 규격

적외선 리모컨 신호에 대한 기본적인 개요는 아래 문서를 참조할 수 있다. 

Infrared Remote Control Techniques on MC9S08RC/RD/RE/RG Family


리모컨 신호 규격은 여러 업체에서 만들었기 때문에 다양한 프로토콜이 있지만 기본적인 원리는 유사하다. 이들을 방식에 따라서 분류하면 아래와 같이 나눌 수 있다 (그림은 위 링크 발췌)


- OOK(On-Off Keying): 30~60kHz의 carrier signal의 on/off 단속을 통하여 bit 신호를 전송하는 방식


- FSK(Frequency Shift Keying): bit 1, 0에 각각 다른 carrier signal을 전송하는 방식 


- Flash, Pulse Modulation



2. ARM Linux FIQ

OOK 방식의 신호를 인식하는 것은 기본적으로 band pass filter를 통하여 30~60kHz의 신호만 필터링해서 디지털 입력으로 읽어 들이면 bitrate가 높지 않아 특별한 문제가 되지 않는다. 


하지만 FSK, Flash 방식을 모두 학습할 수 있고, 또한 리모컨의 carrier frequency 도 인식하여 이를 통하여 리모컨 신호 재생도 가능한 기능을 만드려고 한다면 좀 더 복잡한 방법이 필요할 것이다. 이 중 하나가 carrier signal 의 패턴을 읽어 들이는 것이다. 하지만 carrier signal 의 on/off 를 모두 edge trigger interrupt 로 읽어 들이려면 worst case 120K 번의 interrupt가 발생하여야 한다. 그나마 context switching overhead 가 적은 RTOS 에서도 초당 10,000 이상의 interrupt 처리는 쉬운일이 아니고, linux 의 경우에는 overhead 가 더 크기 때문에 수천번도 쉽지 않다. 결국 이를 인식하기 위하여는 이 기능만을 수행하는 전용 마이컴을 사용하거나 다른 방법을 고민 하여야 한다. 


하지만 ARM linux 에서 FIQ(Fast Interrupt reQuest)를 이용하여 이와 같은 리모컨 신호를 인식할 수 있는 hard real-time 기능을 구현하는 것이 가능하다. 



위의 그림과 같이 FIQ를 위한 R8~R14 7개의 전용 register가 있어 이를 사용하면 context switching 을 위한 별도의 software overhead가 없고, 하드웨어적으로도 일반 인터럽트와는 달리 FIQ 전용 벡터를 진입하였다 나가는 방식이기 때문에 효율적으로 작성한다면 linux 상에서도 이와 같은 고속 인터럽트 처리가 가능하다. 


Linux 에서 FIQ 는  arch/arm/include/asm/fiq.h 에 관련 함수를 제공한다. 

- void set_fiq_regs(struct pt_regs const *regs): FIQ context의 register 설정하기 

- void get_fiq_regs(struct pt_regs *regs): FIQ context의 register 읽기

- void enable_fiq(int fiq): FIQ interrupt enable

- void disable_fiq(int fiq): FIQ interrupt disable

- void set_fiq_handler(void *start, unsigned int length): FIQ interrupt 루틴은 이 함수를 이용하여 해당 루틴을 직접 vector 영역에 복사한다. 이와 같이 vector 영역에 바로 코드를 넣기 때문에 interrupt 진입 후 별도의 jump 없이 바로 FIQ 루틴이 수행된다. 

- int claim_fiq(struct fiq_handler *f): 여러 모듈에서 FIQ를 사용할 때 중재할 수 있는 함수. 더이상 사용치 않는 경우 release_fiq()를 호출

- void release_fiq(struct fiq_handler *f)


3. 구현 예

사용한 Atmel AT91SAM9X5 controller는 일반 GPIO interrupt source 를 FIQ 로 routing 을 할 수 있도록 설정할 수 있다. 이를 이용하여 both edge detection 으로 인터럽트를 설정하여 level 이 변할 때마다 FIQ 인터럽트를 받을 수 있도록 설정하였다. 


FIQ 초기화는 아래와 같이 수행한다. ir_learner_fiq_start, ir_learner_fiq_end 는 asm 으로 작성된 FIQ handler 의 시작과 끝 주소이다. 


extern unsigned char ir_learner_fiq_start, ir_learner_fiq_end;

static struct fiq_handler fh = {

.name = "ir_learner"

};


static int __init ir_learner_probe(struct platform_device *pdev)

{

struct device *dev = &pdev->dev;

          

ret = claim_fiq(&fh);

if (ret)

return ret;

set_fiq_handler(&ir_learner_fiq_start, &ir_learner_fiq_end -

       &ir_learner_fiq_start);

}


실제 FIQ 를 시작하는 시점에 아래와 같이 적절히 FIQ context 에 적절한 register를 설정해 주고 FIQ 를 enable 한다.

struct pt_regs fiq_regs;

fiq_regs.ARM_r8 = (unsigned long)at91_aic_base;

fiq_regs.ARM_r9 = (unsigned long)at91_local_tc_base;

fiq_regs.ARM_r10 = (unsigned long)buf;

fiq_regs.ARM_fp = 0;

set_fiq_regs(&fiq_regs);


FIQ handler 에서 바로 사용할 수 있도록 R8은 interrupt controller 의 base address, R9는 Timer base address, R10은 수신 버퍼 주소를 넘겨주었다. 


/*

 * IR input is low active.

 *

 * R8: AIC base address

 * R9: timer capture address

 * R10: buffer pointer

 * R11(FP): count

 * R12(IP):

 * R13(SP):

 */

.global ir_learner_fiq_end

ENTRY(ir_learner_fiq_start)

ldr r13, [r8, #OFFSET_PIOB + PIO_ISR]                 @1 인터럽트 request 클리어 

ldr r13, [r8, #OFFSET_PIOB + PIO_PDSR]              @2 데이타 입력핀 읽기

and  r13, #PIO_MASK                           @3

tst r11, r11                                          @4 count가 0인지 판단. 초기 시 low 아니면 인터럽트 무시

  eoreqs r12, r13, #PIO_MASK           @5 low 인지 판단 

subeqs pc, lr, #4                            @6 low 면 인터럽트 종료

cmp r11, #MAX_BUF                            @7 check count overflow

subges pc, lr, #4                            @8 overflow 면 종료

ldr r13, [r9, #TC0_CV]                       @9 save data

str r13, [r10, r11, LSL #2]

add r11, #1                                           @11 count 증가

mov r13, #(TC_SWTRG + TC_CLKEN)    @12 re-trigger timeout timer

str r13, [r9, #TC1_CCR]                       

subs  pc, lr, #4                                      

ir_learner_fiq_end:


위와 같이 FIQ handler는 총 14 instructions 으로 구성되어 있고 이를 기반으로 interrupt 로 걸리는 시간은 ARM manual을 보면 대략적인 시간을 계산할 수 있다. 


아래 그림은 리모콘 신호 데이타 형식 중 하나로 2~4 bytes 정도의 데이타가 전달된다.  



이와 같은 신호를 캡쳐하기 위한 알고리즘의 대략적인 구성은 다음과 같다. 

- 초기화 시 countdown timer interrupt를 적절히 설정. 이 timer 값이 timeout 되면 (이 시간동안 적외선 신호가 들어오지 않은 경우) 입력 종료 처리됨

- FIQ 인터럽트가 가능하도록 설정

- FIQ는 처음에 low 상태가 아니면 계속 무시하다가 low 부터 시작

- 입력 신호가 전환 되었을 때마다 freerun timer 의 값을 버퍼에 저장 (@9)

- 버퍼가 가득차면 무시 (@8)

- 입력이 있을 때마다 countdown timer를 재설정 (@12). 이 timer의 timeout interrupt가 발생하면 학습 입력 종료 -> Linux 또는 상위 application에서 나머지 작업 수행


즉 count down timer IRQ를 이용하여 FIQ 와 linux context의 driver 루틴과 signal을 전달하는 셈이다. 이를 수신 받은 경우 linux driver에서는 버퍼의 값을 분석하여 리모컨 신호를 찾아 내면 될것이다. 


4. 요약 

이와 같이 FIQ 를 사용하여 hard real-time 특성을 구현할 수 있다. 물론 critical 한 동작을 하는 hard real-time을 만드려고 한다면 버퍼 메모리를 cache 여부, write back 설정 등 system 내의 병목 가능성에 대해서도 충분한 검토가 되어야 할 것이다. 

하지만 리모컨 신호 입력을 받는 정도면 FIQ에 대한 이 정도면 충분한 것 같다. 


관련 소스 파일을 첨부한다. 


fiq.S


저작자 표시
신고
댓글
댓글쓰기 폼