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

프로젝트 개요
 실습용 임베디드 컴퓨터는 모두 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를 사용하지 않고 각 키마다 임의로 다른 주파수의 신호가 발생되게 하였다.






프로젝트 목적
- VHDL을 이용하여 생활에서 많이 사용하는 디지털 시계를 구현해 봄으로써 Clock의 활용과 7 Segment의 제어를 익히고 디지털 시계의 구현을 통해 VHDL의 응용 능력을 기른다.
- 입력 장치인 Push Button Switch, 출력 장치인 FND(7 Segment)의 특성을 이해한다.

프로젝트 개요
- 본 프로젝트에서는 7 Segment LED를 이용한 디지털 시계를 VHDL을 이용하여 구현하고자 한다.
- 프로젝트에서 사용하는 7 Segment LED는 6개의 Segment LED가 Dynamic 구동방식으로 동작한다.
- 디지털 시계는 6개의 7 Segment LED에 시, 분, 초 각각 2자리씩 표현한다. 그리고 시계를 리셋 시키는 기능과 시간을 맞추는 작업은 FPGA 보드에 있는 스위치를 이용한다.
- 본 프로젝트에서 사용하는 FPGA 보드의 기본 주파수는 33Mhz를 사용한다. 디지털 시계의 1초를 구하기 위해서는 33Mhz의 Main Clock 주파수를 변환 작업을 통하여 1초로 만들고 1초를 60분주하여 1분을 만든다.

PXA255-FPGA 보드

FPGA

Altera Cyclon EP1C6

Logic Elements

5980

Logic Gate

120000 Logic Gate

RAM Bit

92160 Bit

외부 CPU

PXA255

외부 인터페이스

32 Bit Address/Data Bus

입력 I/O

Push SW, Dip SW, ADC, Image Sensor

출력 I/O

Text LCD, LED, 7 Segment, Buzzer, DotMatrix, VGA

기타 I/O

EEPROM


- 디지털 시계에서는 7 Segment LED 제어를 위하여 EP1C6 칩 11~18의 데이터 라인 8핀과 19~25까지의 COM 라인 6핀을 출력 PORT로 사용한다.
- PXA255-FPGA 보드에서 사용하는 FND는 공통으로 사용하는 데이터 라인 8개와 7 Segment의 출력을 결정하는 COM라인 6개로 구성된다.
- FND 동작은 6개라인(FCOM[5..0])의 6비트 중 비트 값이 0인 Segment에 데이터(FDATA[7..0])를 출력한다.
- 각각 다른 데이터를 7 Segment LED에 출력 하려면 6개의 FCOM의 비트들을 쉬프트 시켜가면서 FCOM의 값이 바뀌는 시점에 데이터 라인을 세팅하면 선택된 7 Segment LED에 데이터를 출력한다. 이와 같은 방법을 Dynamic Display 방식이라고 한다.
- Dynamic Display 방식은 빠른 속도로 쉬프트 시킴으로서 사람의 눈의 잔상효과를 이용하여 마치 6개의 7 Segment가 동시에 출력되는 것처럼 보여준다.

디지털 시계 프로그램의 블록도


- 위의 블럭도에서는 입력으로 Main Clock과 리셋 버튼, 그리고 시, 분, 초를 제어할 수 있는 스위치 3개를 가진다.
- 디지털 시계의 출력으로는 6개의 7 Segment를 제어하기 위한 8개의 데이터 라인 FDATA_SM[7..0]과 출력 7 Segment 선택 라인인 FCOM[5..0]의 6개 출력을 가진다.

구현
- clk_div.vhd -
library ieee;
use ieee.std_logic_1164.all;

entity clk_div is
           generic(count : integer range 0 to 30000 := 2);
           port(CLK_IN : in std_logic;
                      SM_CLK : out std_logic );
end clk_div;

architecture sample of clk_div is
begin
           process(CLK_IN)
                     variable tmp : integer range 0 to 30000 := 0;
           begin
                     if(CLK_IN'event and CLK_IN = '1') then
                                if(tmp = count-1) then
                                          tmp := 0;
                                          SM_CLK <= '1';
                                else
                                          tmp := tmp + 1;
                                          SM_CLK <= '0';
                                end if;
                     end if;
           end process;
end sample;

- DigitalTimer.vhd -
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity DigitalTimer is
           port( CLK                    : in std_logic;
                       rset              : in std_logic;
                       ih_key          : in std_logic;
                       im_key         : in std_logic;
                       is_key          : in std_logic;
                       FDATA_SM    : out std_logic_vector(7 downto 0) := "00111111";
                       FCOM                     : out std_logic_vector(5 downto 0) := "001111" );
end DigitalTimer;

architecture rt1 of DigitalTimer is
           function fnd_disnum(cnt : integer range 0 to 9) return std_logic_vector is
                     variable seg_decode : std_logic_vector(7 downto 0);
           begin
                     case cnt is
                                when 0 => seg_decode := "00111111";
                                when 1 => seg_decode := "00000110";
                                when 2 => seg_decode := "01011011";
                                when 3 => seg_decode := "01001111";
                                when 4 => seg_decode := "01100110";
                                when 5 => seg_decode := "01101101";
                                when 6 => seg_decode := "01111101";
                                when 7 => seg_decode := "00100111";
                                when 8 => seg_decode := "01111111";
                                when 9 => seg_decode := "01100111";
                                when others => seg_decode := "00000000";
                     end case;
                     return (seg_decode);
           end fnd_disnum;                    

           -- clock division
           component clk_div
                     generic(count : integer range 0 to 30000);
                     port( CLK_IN : in std_logic;
                                  SM_CLK : out std_logic );
           end component;

           -- used signal
           signal temp : integer range 0 to 5;
           signal cnt_sec1 : integer range 0 to 10 := 0;
           signal cnt_min : integer range 0 to 10 := 0;
           signal cnt_min1 : integer range 0 to 10 := 0;

           signal seg0 : std_logic_vector(7 downto 0) := "00111111";
           signal seg1 : std_logic_vector(7 downto 0) := "00111111";
           signal seg2 : std_logic_vector(7 downto 0) := "00111111";
           signal seg3 : std_logic_vector(7 downto 0) := "00111111";
           signal seg4 : std_logic_vector(7 downto 0) := "00111111";
           signal seg5 : std_logic_vector(7 downto 0) := "00111111";
           signal sclk0, sclk1, sclk2, sclk3 : std_logic := '0';

           signal temp_clk : std_logic;
           signal fnd_clk : std_logic;
           signal sec_clk : std_logic;

begin
           LED_CLK_DIV : clk_div generic map(330) port map(CLK, fnd_clk);                    -- fnd_clk : 100Khz
           TEMP_CLK_DIV : clk_div generic map(3300) port map(CLK, temp_clk);              -- temp_clk : 10Khz
           SEC_CLK_DIV : clk_div generic map(10000) port map(temp_clk, sec_clk);         -- sec_clk : 1hz

           -- FND Output Process
           process(fnd_clk)
           begin
                     if(fnd_clk'event and fnd_clk = '1') then
                                case temp is
                                          when 0 => temp <=1; FCOM <= "111110"; FDATA_SM <= seg5;
                                          when 1 => temp <=2; FCOM <= "111101"; FDATA_SM <= seg4;
                                          when 2 => temp <=3; FCOM <= "111011"; FDATA_SM <= seg3;
                                          when 3 => temp <=4; FCOM <= "110111"; FDATA_SM <= seg2;
                                          when 4 => temp <=5; FCOM <= "101111"; FDATA_SM <= seg1;
                                          when 5 => temp <=0; FCOM <= "011111"; FDATA_SM <= seg0;
                                          when others => FCOM <= "111111"; FDATA_SM <= "00111111";
                                end case;
                     end if;
           end process;

           -- 1 second Process
           process(sec_clk, rset, is_key)
                     variable cnt : integer range 0 to 10 := 0;
           begin
                     if(rset = '1' or is_key = '1') then
                                cnt := 0;
                                sclk0 <= '0';
                                seg0 <= fnd_disnum(0);
                     elsif(sec_clk'event and sec_clk = '1') then
                                if(cnt = 9) then
                                          cnt := 0;
                                          sclk0 <= '0';
                                elsif(cnt = 8) then
                                          sclk0 <= '1';
                                          cnt := cnt + 1;
                                else
                                          cnt := cnt + 1;
                                          sclk0 <= '0';
                                end if;
                                seg0 <= fnd_disnum(cnt);
                     end if;
           end process;

           -- 10 second process
           process(sec_clk, rset, is_key)
           begin
                     if(rset = '1' or is_key = '1') then
                                cnt_sec1 <= 0;
                     elsif(sec_clk'event and sec_clk = '1') then
                                if(sclk0 = '1') then
                                          if(cnt_sec1 = 5) then
                                                     cnt_sec1 <= 0;
                                          else
                                                     cnt_sec1 <= cnt_sec1 + 1;
                                          end if;
                                end if;
                     end if;
                     seg1 <= fnd_disnum(cnt_sec1);
           end process;
           sclk1 <= '1' when(cnt_sec1 = 5 and sclk0 = '1') else '0';

           -- 1 miniute process
           process(sec_clk, rset, im_key)
           begin
                     if(rset = '1') then
                                cnt_min <= 0;
                     elsif(sec_clk'event and sec_clk = '1') then
                                if(im_key = '1') then
                                          if(cnt_min = 9) then
                                                     cnt_min <= 0;
                                           else
                                                     cnt_min <= cnt_min + 1;
                                          end if;
                                elsif(sclk1 = '1') then
                                          if(cnt_min = 9) then
                                                     cnt_min <= 0;
                                          else
                                                     cnt_min <= cnt_min + 1;
                                          end if;
                                end if;
                     end if;
                     seg2 <= fnd_disnum(cnt_min);
           end process;
           sclk2 <= '1' when(cnt_min = 9 and sclk1 = '1') else
                                 '1' when(cnt_min = 9 and im_key = '1') else '0';
          
           -- 10 miniute process
           process(sec_clk, rset)
           begin
                     if(rset = '1') then
                                          cnt_min1 <= 0;
                     elsif(sec_clk'event and sec_clk = '1') then
                                if(sclk2 = '1') then
                                          if(cnt_min1 = 5) then
                                                     cnt_min1 <= 0;
                                          else
                                                     cnt_min1 <= cnt_min1 + 1;
                                          end if;
                                end if;
                     end if;
                     seg3 <= fnd_disnum(cnt_min1);
           end process;
           sclk3 <= '1' when(cnt_min1 = 5 and sclk2 = '1') else '0';

           -- hour process
           process(sec_clk, rset, ih_key)
                     variable cnt : integer range 0 to 24 := 0;
                     variable a, b : integer range 0 to 50 := 0;
           begin
                     if(rset = '1') then
                                cnt := 0;
                     elsif(sec_clk'event and sec_clk = '1') then
                                if(ih_key = '1') then
                                          if(cnt = 23) then
                                                     cnt := 0;
                                          else
                                                     cnt := cnt + 1;
                                          end if;
                                elsif(sclk3 = '1') then
                                          if(cnt = 23) then
                                                     cnt := 0;
                                          else
                                                     cnt := cnt + 1;
                                          end if;
                                end if;
                     end if;
                     a := cnt / 10;
                     b := cnt mod 10;
                     seg4 <= fnd_disnum(b);
                     seg5 <= fnd_disnum(a);
           end process;
end rt1;


결론

 이번 프로젝트에서는 디지털시계를 HDL언어인 VHDL를 사용하여 설계하여 실습 보드인 PXA255 FPGA에 직접 올려보았다.

디지털시계를 구현하기 위해서 33Mhz의 메인 클럭에서 1hz의 클럭을 얻기 위한 묘듈이 필요했다. 이 묘듈은 카운터를 이용하여 원하는 수만큼 카운트 한 후 신호를 내보내는 원리로 동작한다.

디지털시계는 1초, 10초, 1분, 10분, 시간을 각각 처리하는 프로세스 문으로 작성되며, 모든 동작은 1hz인 second clock에 동기 되어 동작하게 된다. 그 외 sclk0, sclk1, sclk2, sclk3 의 클럭들이 내부에서 발생 된다. sclk0은 9초에서 10초로 넘어 갈 때, sclk1은 59초에서 1분으로 넘어 갈 때, sclk2는 9분에서 10분으로 넘어 갈 때, sclk3는 59분에서 1시간으로 넘어 갈 때 사용 되는 신호이다.

이번 프로젝트를 통해서 타이밍에 대한 이해와 VHDL의 활용능력 및 쿼터스 같은 툴 사용에 대해 익숙해 진 것 같다. 또한 자신이 작성한 디지털시계를 직접 FPGA에 올려 보고 제대로 동작을 하는지 확인해봄으로써, 평소 시뮬레이션만 해보는 것과 다르게 회로설계에 대한 자신감을 가질 수 있었다.





 학교 다닐 때 했던 프로젝트

 프로젝트 목적
- 다수의 컴퓨터의 효율적 관리
- 컴퓨터 사용 기록 관리
- 비 인가자의 접근 제한

 개발 내용
- 1(server)대 다(client) 관리
- 서버 클라이언트 간 메신져
- 사용자 정보 관리
- 사용 기록 관리
- Time-out 기능

 개발 방법
- CAsyncSocket 사용
- Gina 및 전역 후킹으로 시스템 키 차단
- MySQL을 사용한 DB 연동






결과 화면

- server 화면 -




- client 화면 -




 이 프로젝트에서 UI 구성, DB관련 프로그래밍,  Gina/전역후킹 등을 이용한 시스템 키를 차단하는 부분을 맡아서 진행했다. MFC를 이용하여 간단하게 UI를 만들고, DBMS로는 MySQL를 이용했다. Ctrl-alt-del 키를 차단 하기 위해서 Gina를 이용했으며, 나머지 시스템 키들을(alt-f4, alt-tab 등) 차단 하기 위해서 전역 후킹을 이용했다.


+ Recent posts