프로젝트 목적
- 소리의 발생 원리와  주파수 등을 이해한다.
- 임베디드 컴퓨터의 소리 발생 방법을 익힌다.
- 키패드 장치와 소리 발생 장치의 상호 연동 프로그램을 구현한다.

프로젝트 개요
 실습용 임베디드 컴퓨터는 모두 12개의 다이얼패드를 가지고 있다. 임의의 키를 눌렀을 때 그 키에 해당되는 다이얼 톤을 발생시키도록 한다. 다이얼 톤은 임베디드 컴퓨터의 Phone 단자에 이어폰을 꽂아서 발생여부를 확인하게 된다. 12개의 다른 키 값에 대해 각기 다른 주파수의 다이얼 톤이 발생되어야 한다. 톤 발생과 더블어 당연히 키패드 드라이버의 활용도 필요하다. 즉, 어떤 키가 눌려졌는지, 또 눌려진 시간은 얼마인지 등을 알게 하는 드라이버를 작성해야하며, 응용 프로그램은 키패드 드라이버와 톤 발생기 드라이버를 동시에 사용하도록 해야 한다.

* 키패드 드라이버 구현
 - 어느 키가 얼마 동안 눌러졌는지를 확인할 수 있어야 한다.
 - 톤은 특정키가 눌러져 있는 동안 계속 유지되어야 한다.
* 톤 발생기
 - 주어진 입력 값에 따라 각기 다른 주파수를 갖는 톤을 발생시킨다.
 - 임베디드 컴퓨터의 phone 단자로 실제 출력이 이루어져야 한다.


프로젝트 진행

- 키패드 드라이버
 우리가 개발 장비로 사용한 PXA255-PRO2의 키패드는 인터럽트에 의해 작동된다. 즉, 평소에는 아무런 일이 일어나지 않다가 키패드의 특정키를 누르면 인터럽트가 발생하여 인터럽트 처리가 일어나야 한다는 뜻이다. 여기서 키패드는 GPIO0번 핀을 인터럽트 입력 핀으로 사용한다.
 인터럽트 처리를 위해서는 먼저 set_GPIO_IRQ_edge() 함수를 사용하여 인터럽트의 감지 상태 등을 설정하고, 인터럽트 처리 루틴을 작성, 이 루틴을 request_irq() 함수를 사용하여 등록해야 한다.
 어느 키가 얼마 동안 눌러졌는지 확인할 수 있기 위해서 인터럽트의 Rising Edge와 Falling Edge를 모두 감지하는 디바이스 드라이버를 작성하였다. 즉, 인터럽트의 Rising Edge에서 키의 누름을 감지하고, Falling Edge에서 키의 때어짐을 감지하는 것이다. 이를 위해 먼저 다비이스 open시 인터럽트의 Rising Edge를 감지하도록 설정하고, 인터럽트가 Rising Edge에서 발생하면 이데 따른 인터럽트 처리 루틴에서 다시 Falling Edge를 감지하도록 설정 하는 방식으로 구현하였다.

- 톤 발생기
 키패드의 눌러진 키에 따라 각기 다른 주파수를 가지는 톤이 발생되어야 한다. 이를 위해서 일정한 주파수를 가진 사인파형의 디지털 신호를 만들어주는 프로그램을 작성하였다. 또한 이 일정한 주파수의 톤이 임베디드 컴퓨터의 phone 단자로 출력하기 위해서 이미 커널에 올려진 AC97 디바이스 드라이버(/dev/dsp)를 이용하였다.
 사인 웨이브를 만드는 방법으로는 룩업 테이블을 이용하는 방식, 보간법 그리고 다항식법 등 여러 가지 방법이 있다. 본 프로젝트에서는 순환형 필터를 사용하여 일정한 주파수를 가진 신호를 생성했다. 이 디지털 신호를 만들어 내는 수식은 다음과 같다.


 나이키스트 이론에 의해 톤 주파수는 0에서 (샘플링 주파수/2)사이의 값만을 가질 수 있다. 또한 초기 값으로

으로 설정하였다.

- 간략적인 코드

// 키패드
// 인터럽트 처리 루틴 1
void key_handler(int irq, void *dev_id, struct pt_regs *regs) {
  unsigned long *keyport;

  keyport = (unsigned long *)ioremap(KEY_BASE, 0x10);
  key_value = *(keyport) & 0xf;
  iounmap(keyport);

  // FALLING EDGE Detect...
  free_irq(KEY_IRQ, NULL);
  set_GPIO_IRQ_edge(0, GPIO_FALLING_EDGE);
  request_irq(KEY_IRQ, key_handler1, SA_INTERRUPT, "Keypad", NULL);
  wake_up_interruptible(&wq);
}

// 인터럽트 처리 루틴 2
void key_handler1(int irq, void *dev_id, struct pt_regs *regs) {
  key_value = 0xff;

  // Rising Edge Detect...
  free_irq(KEY_IRQ, NULL);
  set_GPIO_IRQ_edge(0, GPIO_RISING_EDGE);
  request_irq(KEY_IRQ, key_handler, SA_INTERRUPT, "Keypad", NULL);
  wake_up_interruptible(&wq);
}

// 시스템 콜 구현
int dev_open(struct inode *inode, struct file *fp) {
  // set keypad interrupt handler
  GPDR0 &= ~(GPIO_0);
  GAFR0_L &= ~(0x3);
  set_GPIO_IRQ_edge(0, GPIO_RISING_EDGE);
  return request_irq(KEY_IRQ, key_handler, SA_INTERRUPT, "Keypad", NULL);
}

ssize_t dev_read(struct file *fp, char *buf, size_t len, loff_t *pos) {
  interruptible_sleep_on(&wq);  // wait until key pressed
  copy_to_user(buf, &key_value, 1);
  return len;
}

ssize_t dev_write(struct file *fp, const char *buf, size_t len, loff_t *pos) {
  return 0;
}

int dev_close(struct inode *inode, struct file *fp) {
  free_irq(KEY_IRQ, NULL);
  return 0;
}

// SinGenerator(톤 발생기)
void SinGenerator(double *buffer, int BUFFSIZE, double f0, double fs, short ampl)
{
  double w0;  
  double m_2cosW0;
  const double PI = 4.0 * atan(1.0);
  int i;

  w0 = 2 * PI * f0 / fs;  // fs: sampling frequency, f0 : tone frequency
  m_2cosW0 = 2 * cos(w0);

  buffer[BUFFSIZE-2= (double)(ampl * -sin(w0));
  buffer[BUFFSIZE-1= 0;

  buffer[0= (double)(m_2cosW0 * buffer[BUFFSIZE-1] - buffer[BUFFSIZE-2]);
  buffer[1= (double)(m_2cosW0 * buffer[0] - buffer[BUFFSIZE-1]);
  for(i = 2 ; i < BUFFSIZE ; i++)
    buffer[i] = (double)(m_2cosW0 * buffer[i-1] - buffer[i-2]);
}


프로젝트 후기

먼저 키패드 드라이버 작성은 교수님이 제공해 주신 키패드 드라이버 관련 문서를 보고 작성하였다. 이를 바탕으로 작성된 디바이스 드라이버를 올리고 프로그램 내에서 open한 후 read를 하여 결과를 출력 하는데, 프로그램을 처음 실행 할 때는 아무 문제없이 잘 작동하였다. 하지만 이 프로그램의 두 번째 실행에서 부터는 인터럽트 발생이 폭주 하는 듯, 키를 누르지도 않았는데 이상한 결과가 계속해서 출력이 되는 것이었다. 문제는 프로그램 실행에서 open시 인터럽트를 활성화 시키는데 반해 close시 이 인터럽트를 다시 비활성화 시키지 않는데서 비롯된 것이었다. 첫 번째 실행에서 활성화된 인터럽트를 두 번째 실행에서 다시 활성화함으로써 발생한 것이었다. 이에 close 시스템 콜 구현 부분에서 free_irq()함수를 이용하여 활성화된 인터럽트를 비활성화 시킴으로써 해결할 수 있었다.

톤 발생기 구현은 첨에 막막한 상태였다. 어디서부터 시작해야 될지 몰랐다. 여러 자료를 검색하던 중 MultiMedia Sound Programming 이라는 영진닷컴에서 출판된 책에 특정 주파수 신호 발생에 대한 내용을 찾을 수 있었다. 이 책을 참조하여 특정주파수 신호를 만들어 내는 모듈을 작성할 수 있었다.

첨에 신호를 만들어 호스트 컴퓨터에서 소리를 발생 시켰다. 원하는 결과가 잘 나왔다. 하지만 임베디드 컴퓨터에서는 소리가 나오지 않는 것이었다. 이것 때문에 상당한 시간을 허비하였는데, 알고 보니 임베디드 컴퓨터의 mixer 초기 설정 상태가 mute(음소거)상태였던 것이었다. "mixer vol unmute", "mixer pcm unmute" 명령으로 mute 상태를 해제하니 소리가 잘 나왔다.

마지막으로 키패드 마다 각기 다른 주파수 톤을 발생시키기 위해 처음에는 일반 전화기에서 사용하는 DTMF(Dual Tone Multi-Frequency)를 이용하여 구현하였으나, 약간의 문제로 인해 그냥 각 키마다 임의로 다른 주파수를 발생시키도록 구현하였다. 여기서 DTMF 란 여러 개의 주파수를 가진 이중톤이라는 의미로 서로 다른 2개의 주파수를 합성하여 하나의 주파수로 할당되게 된다. 약간의 문제란 DTMF 원리를 이용하여 신호를 발생시키면, 전화기 버튼을 눌렀을 때 발생하는 톤과 비슷하였지만, 서로 다른 주파수 신호를 합성하는 과정에서 잡음이 상당히 많이 발생해서 듣기가 별로 좋진 않았다. 또한 서로 다른 2개의 신호를 생성하고 합성하는데 상당한 시간이 걸려서, 키패드 버튼을 빠르게 누르고 땠을 경우 Falling Edge 인터럽트를 놓치는 경우가 발생하여, 오동작하는 경우도 생겼다. 물론 키를 누르기 전에 미리 신호를 다 생성시켜 놓으면 되지만, 그렇게 되면 각 키마다 신호를 저장해야 될 버퍼가 필요하게 되므로 비효율적이다. 이러한 문제로 인해 DTMF를 사용하지 않고 각 키마다 임의로 다른 주파수의 신호가 발생되게 하였다.





+ Recent posts