2011년5월23일_두더지 잡기 게임2, 문자열 처리 함수(숫자를 문자로 변환)

 

 

<두더지잡기 게임2>

통신 방향
DK128 ----------> PC

- 전원 ON하면
    "<<<<<<<<<<<<< Power ON >>>>>>>>>>>>" 전송

- 하나의 게임을 시작하면
    "************ Game Start ***********" 전송

- 첫번째 ON인 LED에 대응하는 스위치를 누르면
    "1. [LED 번호]_[누른 스위치 번호] OK" 전송

- 두번째 ON인 LED에 대응하는 스위치를 누르면
    "2. [LED 번호]_[누른 스위치 번호] OK" 전송

- 대응하는 스위치를 누르지 못한 경우
    "2. [LED 번호]_[누른 스위치 번호] Fail" 전송

- 하나의 게임이 종료되면 PC로 총점수 전송

[출력화면 예]
<<<<<<<<<<< Power ON >>>>>>>>>>>
************ Game Start ***********
1. LED[4]_Input Value[4] OK
2. LED[7]_Input Value[7] OK
3. LED[2]_Input Value[6] Fail
4. LED[1]_Input Value[4] Fail

.....(생략)....

20.LED[2]_Input Value[2] OK
                        총 점수: 70점

************ Game Start ***********
1. LED[3]_Input Value[3] OK
2. LED[2]_Input Value[2] OK
3. LED[2]_Input Value[2] OK
4. LED[1]_Input Value[4] Fail

.....(생략)....

20.LED[2]_Input Value[2] OK
                       총 점수: 85점

 

★두더지게임 프로젝트를 진행하고 이번 주 금요일(5/27)까지 보고서를 제출할 것.
서식 : image

 

 

도면

 

image image 
                      Main Board                                                      Target Board


PORTC는 키입력
PORTE는 FND
PORTF는 LED에 연결되어 있음.

 

 

프로그램정보가 담긴 주석과 전처리부

 

   1:  /***************************************************************************
   2:      <두더지잡기 게임2>
   3:  
   4:      통신 방향
   5:      DK128 ----------> PC
   6:  
   7:      - 전원 ON하면 
   8:          "<<<<<<<<<<<<< Power ON >>>>>>>>>>>>" 전송
   9:  
  10:      - 하나의 게임을 시작하면 
  11:          "************ Game Start ***********" 전송
  12:  
  13:      - 첫번째 ON인 LED에 대응하는 스위치를 누르면
  14:          "1. [LED 번호]_[누른 스위치 번호] OK" 전송
  15:  
  16:      - 두번째 ON인 LED에 대응하는 스위치를 누르면
  17:          "2. [LED 번호]_[누른 스위치 번호] OK" 전송
  18:  
  19:      - 대응하는 스위치를 누르지 못한 경우
  20:          "2. [LED 번호]_[누른 스위치 번호] Fail" 전송
  21:  
  22:      - 하나의 게임이 종료되면 PC로 총점수 전송
  23:  
  24:  
  25:      [출력화면 예]
  26:      <<<<<<<<<<< Power ON >>>>>>>>>>>
  27:      ************ Game Start ***********
  28:      1. LED[4]_Input Value[4] OK
  29:      2. LED[7]_Input Value[7] OK
  30:      3. LED[2]_Input Value[6] Fail
  31:      4. LED[1]_Input Value[4] Fail
  32:  
  33:      .....(생략)....
  34:  
  35:      20.LED[2]_Input Value[2] OK
  36:                              총 점수: 70점
  37:  
  38:      ************ Game Start ***********
  39:      1. LED[3]_Input Value[3] OK
  40:      2. LED[2]_Input Value[2] OK
  41:      3. LED[2]_Input Value[2] OK
  42:      4. LED[1]_Input Value[4] Fail
  43:  
  44:      .....(생략)....
  45:  
  46:      20.LED[2]_Input Value[2] OK
  47:                          총 점수: 85점
  48:  
  49:  
  50:      소속: 내장형하드웨어
  51:      작성자: 김수만
  52:      작성일자: 2011년 5월 23일
  53:  
  54:  ***************************************************************************/
  55:  #include "./../../ATmega128.h"
  56:  #include <stdlib.h>
  57:   
  58:  //포트 정의
  59:  #define DDR_LED        DDRF
  60:  #define PORT_LED        PORTF
  61:  #define DDR_FND        DDRE
  62:  #define PORT_FND        PORTE
  63:  #define DDR_KEY        DDRC
  64:  #define PIN_KEY        PINC
  65:  #define PORT_KEY        PORTC
  66:   
  67:  //통신관련
  68:  #define CPU_CLOCK        16000000
  69:  #define BAUD_RATE        4800            // 통신시 이용할 속도
  70:  #define BAUD_RATE_L        (CPU_CLOCK / (16l * BAUD_RATE)) - 1
  71:  #define BAUD_RATE_H        ((CPU_CLOCK / (16l * BAUD_RATE)) - 1) >> 8
  72:  /*    통신속도의 결과 값을 입력하기 위해 상하위 비트로 구분
  73:      16l은 16 + L이며, 연산 시 값이 너무 커져 overflow가 발생하므로 32비트 연산을
  74:      위해 16에 Long을 의미하는 l을 붙인다.*/
  75:      
  76:  //타이머관련
  77:  #define TICKS_PER_SEC    1000            // Ticks per sec = 1,000
  78:  #define    PRESCALER        64                // Prescaler = 64

 

처음 프로그램 명세와 작성일과 버전관련 정보가 담긴 주석은 이렇게 작성하면 많이 부족하나…
나는 학생이라 계속 개선해 나아가는 중이다. 그러니 너무 따지지 말 것.

전처리부분은 처음 나의 I/O헤더파일이 놓이고, 다음에 난수발생함수가 놓인다.
우선 순위는 보통 I/O관련 헤더파일이 높게 작성하는 것 같고 나도 그게 옳다고 동의하고 이렇게 적고 있다.

포트 정의는 역시 I/O관련이라 I/O헤더파일에 넣는게 좋다고 생각할 수도 있으나,
나는 본 프로그램인 main.c에 넣는 것이 더 옳다고 생각한다.
그 이유는 외부I/O PORT에 무엇을 연결할지 설계자가 정하는 것이라 언제든지 쉽게 바꿀 수 있는 위치에..
놓여야 된다고 생각하기 때문이다.

통신관련과 타이머관련도 마찬가지로 쉽게 바꿀 수 있는 위치에 놓는 것이 좋다고 생각한다.
예를 들어 CPU_CLOCK의 경우 다른 프로젝트 진행시에 주파수를 수정할 일이 있고,
            통신속도도 고객의 요구에 따라 쉽게 바꿀 수 있어야 한다. (모두 주파수관련이라 같이 묶어야 한다.)
그런데 CPU_CLOCK은 Makefile에 컴파일옵션으로 들어간다. 이것을 이용할 수 있을까?

 

전역변수와 함수의 프로토타입(원형) 선언

 

   1:  volatile unsigned int g_elapsed_time = 0;    // 시간 변수
   2:  volatile unsigned char key = 0;                // 키입력값 저장 버퍼
   3:   
   4:  //ISR선언
   5:  void __vector_15 (void) __attribute__ ((signal, used, externally_visible));     
   6:  void init_port(void);
   7:  void init_TC0(void);
   8:  void init_USART1(void);
   9:  void sleep(unsigned int elapsed_time);
  10:  void uart_send_byte(unsigned char byte);
  11:  void uart_send_string(unsigned char *str, unsigned int len);
  12:  void uart_send_string_suman(char *str);
  13:  unsigned char USART_Receive(void);
  14:  unsigned char hex_to_dec(unsigned char cNum);
  15:  void printf_suman(char *str, unsigned char cNum, unsigned char cNum_LED, unsigned char cNum_KEY);
  16:  void dec_to_ascii(unsigned char cNum, char *p);
  17:  char log_2(unsigned char cNum);

 

전역변수는 쉽게 위치를 확인할 수 있게 주석으로 잘 표시하는게 좋으나 이번에는 그렇게 하지 못 하였다.
함수의 원형들에 변수명은 필요 없으나 넣어도 큰 문제는 없다. (자료형만 적는 것이 좋다고 생각한다.)

 

 

본 프로그램의 시작 main( ) 함수

 

   1:  //프로그램 시작
   2:  int main(void)
   3:  {
   4:      unsigned char cLED = 0;            //LED
   5:      unsigned char cScore = 0;        //점수
   6:      unsigned char cLoop = 0;        //루프제어변수
   7:      unsigned char cTemp = 0;        //임시 중간계산결과저?
   8:   
   9:      
  10:      init_port();        // LED, FND, KEY에 해당하는 포트만 초기화
  11:      init_USART1();        // UART1 초기화
  12:      init_TC0();            // 타이머/카운터0 초기화
  13:      sei();                // 인터럽트 활성화
  14:   
  15:      uart_send_string_suman("<<<<<<<<<<<<< Power ON >>>>>>>>>>>>\n\r");
  16:      uart_send_string_suman("게임을 시작하려면 아무 키나 입력하세요.\n\r");
  17:   
  18:      /* srand()에 seed값을 시작할 때마다 다르게 넣어주기 위해
  19:         PC에선 내부RTC의 시간을 이용하나 AVR은 RTC가 없기 때문에
  20:         언제 누를지 알 수 없는 KEY와 항상 변화하고 있는 TCNT0의 값을 
  21:         이용한다. */
  22:      USART_Receive();
  23:      srand(TCNT0);                    //시드값을 넣어줌.
  24:      sleep(100);                        //100ms대기
  25:      
  26:      while(1)    //무한루프
  27:      {
  28:          uart_send_string_suman("************ Game Start ***********\n\r");
  29:   
  30:          //5에서 0까지 카운트 다운! (두더지1-2)
  31:          for(cLoop = 5 ; 0 < cLoop ; --cLoop)
  32:          {
  33:              uart_send_byte(cLoop + '0');    //54321 (카운트다운)        
  34:                                              //숫자를 문자로 바꾸어 전송.
  35:              PORT_FND = cLoop | 0xF0;        //5초 부터 1초까지
  36:                                              //10단위자리는 blank
  37:              sleep(1000);                    //카운트다운하며 대기.
  38:          }    
  39:          
  40:          PORT_FND = 0x00;                //FND초기화
  41:          uart_send_string_suman("\n\r\n\r");
  42:      
  43:          for(cLoop = 1 ; 20 >= cLoop ; ++cLoop)
  44:          {
  45:              key = 0;                 //키값 리셋                
  46:              //1 ~ 7까지 난수를 발생하여 그 수 만큼 좌로 시프트
  47:              cLED = 1 << (rand() % 8);        
  48:              PORT_LED = ~cLED;        //반전하여 출력        
  49:              sleep(1000);            //1000ms 대기
  50:   
  51:              //횟수와 점수를 출력.
  52:              cLED = log_2(cLED);    
  53:              key = log_2(key);
  54:              printf_suman("%d. LED[%d]_Input Value[%d]", cLoop, cLED, key);  
  55:   
  56:              //두더지가 맞았으면 
  57:              if(cLED == key)            //두더지를 맞추었으니
  58:              {
  59:                  cScore = cScore + 5;    //5점증가
  60:                  uart_send_string_suman("...OK!\n\r");
  61:              }
  62:              else
  63:              {
  64:                  uart_send_string_suman("...Fail!!\n\r");
  65:              }
  66:              
  67:              //100점은 제외. 16진수변환 함수가 3자리를 처리 못 함.
  68:              if(100 != cScore)
  69:              {
  70:                  PORT_FND = hex_to_dec(cScore);        //FND에 점수표시
  71:              }
  72:   
  73:              PORT_LED = 0xFF;    //LED모두 OFF
  74:              sleep(500);            //500ms 대기
  75:          }
  76:   
  77:          //점수 출력.
  78:          printf_suman("                        총 점수: %d점\n\r", cScore, 0, 0);
  79:   
  80:   
  81:          if(100 == cScore)            //만약 모두 맞추었다면
  82:          {
  83:              PORT_FND = 0x00;    // "00"표시
  84:              uart_send_string_suman("100점이네요. ㅊㅋㅊㅋ\n\n\r");
  85:   
  86:              for(cLoop = 0; 7 > cLoop ; ++cLoop)    //LED깜빡깜빡
  87:              {
  88:                  PORT_LED = 0x00;    // 모두 ON
  89:                  sleep(500);
  90:                  PORT_LED = 0xFF;    // 모두 OFF
  91:                  sleep(500);
  92:              }
  93:          }
  94:          else                    //100점이 아니면 현재 점수 보여줌.
  95:          {
  96:              PORT_LED = 0xFF;        // LED 모두 OFF
  97:              sleep(5000);            // 5초 딜레이
  98:          }    
  99:   
 100:          cScore = 0;                    // 점수 초기화
 101:          key = 0;                    // 키값 리셋
 102:   
 103:      }
 104:      
 105:      return 1;    //종료
 106:  }

(조금 복잡해서 설명은 나중에 붙임. 머리아파유 ㅠㅠ)

 

 

 

인터럽트 서비스 루틴과 관련 함수

 

   1:  /*******************************************************
   2:      타이머/카운터0 비교매치 인터럽트 서비스 루틴
   3:  ********************************************************/
   4:  void __vector_15(void)
   5:  {
   6:      ++g_elapsed_time;    // 시간변수 1증가
   7:      key = key | ~PIN_KEY;    //키입력값을 반전하여 저장. 
   8:  }

 

나는 main( )함수 다음으로 중요하다고 생각되는 함수가 인터럽트 서비스 루틴(이하 ISR)이라고 생각한다.
그래서 주석도 눈에 확 띄도록 불필요하게 많이 할당하여 강조하고 있다.
vector_15라는 이름의 루틴이 어떤 인터럽트에 해당하는지 쉽게 알 수 있게 하는게 좋으나 이번엔 그렇게 하지 못 하였다고 생각된다.

타이머/카운터 인터럽트의 경우 보통 주기적으로 동작을 하므로 여기에 입력관련 처리를 간단히 하고,
main( )함수에서 값을 상세히 처리하는게 좋다.
그 이유는 LED출력같이 MCU입장에서 조절가능한 출력의 경우 언제 이벤트가 일어날지 쉽게 프로그래머가 파악할 수 있으나,
키입력같은 경우엔 사람이 언제 키를 누를지 알 수 없다. 그러니 항상 체크를 하도록 해야 하는 것이다.

주의 할 점은 보통 ISR에는 너무 길게 코드를 넣으면 좋지 않다.
그리고 제어문 중 반복문은 가급적 넣지 않는게 좋다.

   1:  //1초 대기
   2:  void sleep(unsigned int elapsed_time)
   3:  {
   4:      g_elapsed_time = 0;                            //시간 변수 초기화
   5:      while(elapsed_time > g_elapsed_time);        //참이 될 때까지 대기.
   6:      
   7:      return ;
   8:  }


sleep( )함수의 경우 전역변수가 사용되고,
시간관련 함수인 시간지연함수라 ISR과 같이 엮는 것이 좋다고 생각하나…
사용자 정의 함수계열에 넣는 것이 좋다고 생각되기도 한다. (망설여 지는군.)

 

 

 

각종 초기화 함수

 

   1:  //포트 초기화
   2:  void init_port(void)
   3:  {
   4:      DDR_KEY = 0x00;        // 모두 입력
   5:      DDR_LED = 0xFF;        // 모두 출력
   6:      DDR_FND = 0xFF;        // 모두 출력
   7:      PORT_LED = 0xFF;    // 모든 LED꺼짐. 
   8:      PORT_FND = 0x00;    // 00출력
   9:      
  10:      return ;
  11:  }
  12:      
  13:   
  14:  //타이머0 초기화
  15:  void init_TC0(void)
  16:  {
  17:      //CTC Mode, prescanler = 1/64
  18:      TCCR0 = (1 << WGM01) | (1 << CS02);        
  19:      //8bit Timer0 = 16,000,000 / 1000 / 64
  20:      //            = 250
  21:      OCR0 = CPU_CLOCK / TICKS_PER_SEC / PRESCALER;
  22:      //타이머0 비교 매치 인터럽트 활성
  23:      TIMSK = (1 << OCIE0);        
  24:      
  25:      return ;
  26:  }
  27:   
  28:  void init_USART1(void)
  29:  {
  30:      //baud rate설정
  31:      UBRR1L = (unsigned char)BAUD_RATE_L;    
  32:      UBRR1H = (unsigned char)BAUD_RATE_H;
  33:      
  34:      // no parity, 1stop bit, 8bit data설정
  35:      UCSR1C = (0 << UPM1) | (0 << UPM0) | (0 << USBS) | (1 << UCSZ1) | (1 << UCSZ0);
  36:      // rx/tx interrupt설정, 8bit설정
  37:      UCSR1B = (1 << TXEN) | (1 << RXEN) | (0 << UCSZ2);
  38:   
  39:      return ;
  40:  }

I/O포트 초기화 함수, 타이머 초기화 함수, USART 초기화 함수등은,
모두 잘 보면 하드웨어 수정 시 모두 바뀔 수 있는 부분이다.
예를 들어,
I/O포트 초기화함수의 경우는 설명할 필요도 없이 배선이 바뀌면 당연히 바뀌는 부분이고,
타이머의 경우 만약에 다른 MCU를 쓴다면 같은 주변장치가 구비되어 있지 않을 수도 있고,
사양이 조금 틀릴 수도 있다.
그리고 통신포트의 경우 I/O포트, 타이머초기화함수와 같이,
배선이 바뀌면 UART1이 아닌 다른 UART0을 사용할 수도 있다. (설계가 바뀌는 것은 회의를 거친 다음에 이루어 질 것이다.)
다른 MCU에는 UART1이 없을 수도 있고, 레지스터 설정이 다를 수도 있다.

그러니 모두 한 덩어리로 잘 묶어서,
하드웨어와 직접 관련이 있어 설계사양이 바뀌면 수정되는 코드는 모두 같이 엮어서 하나의 파일에 넣는 것이 좋겠다.
하드웨어와 직접 관련이 없어 설계사양이 바뀌어도 수정되지 않는 코드는 마지막에 설명하겠다.

 

 

하드웨어와 밀접한 관련이 있는 함수들 (통신주변장치)

 

   1:  //1바이트 수신함수
   2:  unsigned char USART_Receive(void)
   3:  {
   4:      //RXC의 값이 0이어 수신버퍼가 비어 있으면 계속 대기.
   5:      while(!(UCSR1A & (1 << RXC)));
   6:      
   7:      return UDR1;
   8:  }
   9:   
  10:  //1바이트 전송함수
  11:  void uart_send_byte(unsigned char byte)
  12:  {
  13:      while(!(UCSR1A & (1 << UDRE)));        //전송 버퍼가 빌 때까지 대기
  14:      UDR1 = byte;                            //문자1개를 전송한다.
  15:      
  16:      return ;
  17:  }

 

위의 두 사용자정의 함수를 자세히 보면 레지스터명이 들어가 있다.
이는 다른 하드웨어에서 이 함수를 호출하면 문제가 될 수 있으니 따로 분류하는 것이 좋다.
예를 들어,
통신관련 함수는 모두 comm.h comm.c에 넣는 것.

아래에 설명할 문자열을 전송하는 함수들도 같이 취급하는 것이 좋겠다.

 

 

문자열 전송 함수

 

   1:  /*****************************************************************************
   2:          2011년 5월 23일에 급조한 printf함수.
   3:          
   4:          일정 포맷에 맞게 제작된 함수. 
   5:          3개의 10진수 입력만 받는다. (1Byte크기의 정수)
   6:          
   7:          printf_suman("const char *", num1, num2, num3)
   8:  
   9:  ******************************************************************************/
  10:  void printf_suman(char *str, unsigned char cNum1, unsigned char cNum2, unsigned char cNum3)
  11:  {
  12:      int i;                //문자열 시작부터의 Offset값.
  13:      char cCnt = 0;        //%d형식지정자에 전달할 값의 위치 기억.
  14:      char aStr[4];        //숫자를 문자로 바꿀 때 사용하는 문자배열
  15:      
  16:      for(i = 0 ; '\0' != *(str + i) ; ++i)            //문자열의 끝까지 반복.
  17:      {
  18:          if('%' == *(str + i))                        //형식지정자를 만나면
  19:          {
  20:              switch(cCnt)
  21:              {
  22:                  //첫 번째 인자를 문자로 변환하여 출력.
  23:                  case 0:                                
  24:                      dec_to_ascii(cNum1, aStr);
  25:                      uart_send_string_suman(aStr);
  26:                      break;
  27:                  //두 번째 인자를 문자로 변환하여 출력.
  28:                  case 1:
  29:                      dec_to_ascii(cNum2, aStr);
  30:                      uart_send_string_suman(aStr);
  31:                      break;
  32:                  //세 번째 인자를 문자로 변환하여 출력.
  33:                  case 2:
  34:                      dec_to_ascii(cNum3, aStr);
  35:                      uart_send_string_suman(aStr);
  36:                      break;
  37:                  //인자를 추가하려면 여기다 하이소.
  38:                  default:
  39:                      break;
  40:              }
  41:              
  42:              ++i;            //다음 문자를 가리키도록 1증가.
  43:              ++cCnt;            //다음 인자를 가리키도록 1증가.
  44:          }
  45:          else                                //형식지정자가 아닌 일반 문자이면,
  46:          {                                    //문자를 출력.
  47:              uart_send_byte(*(str + i));    //시작번지로 부터 i번째 문자 전송.
  48:          }
  49:      }
  50:      
  51:      return ;
  52:  }    
  53:   
  54:  //문자열 전송함수 len은 문자열의 길이.
  55:  void uart_send_string(unsigned char *str, unsigned int len)
  56:  {
  57:      int i;
  58:      
  59:      for(i = 0 ; i < len ; i++)        //문자열의 길이만큼 반복.
  60:      {
  61:          uart_send_byte(*(str + i));    //시작번지로 부터 i번째 문자 전송.
  62:      }
  63:      
  64:      return ;
  65:  }
  66:   
  67:  //문자열 전송함수(수만)
  68:  void uart_send_string_suman(char *str)
  69:  {
  70:      int i;        //Offset
  71:      
  72:      for(i = 0 ; '\0' != *(str + i) ; ++i)        //NULL문자까지 반복.
  73:      {
  74:          uart_send_byte(*(str + i));    //시작번지로 부터 i번째 문자 전송.
  75:      }
  76:      
  77:      return ;
  78:  }    

 

상기의 문자열 전송함수 3개의 공통점은 모두 하드웨어 의존성이 없다는 것이다.
그래서 다른 하드웨어에 이식해도 된다. (단, 하드웨어와 밀접한 관련이 있는 함수를 대체할 함수로 바꿔야함.)

함수 뒤에 나의 이름을 적은 이유는 내가 만든 함수니까!! ㅎㅎㅎ

(printf에 대한 설명은 추후 장황하게 하겠다. 지금은 머리가 ㅠㅠ)

 

 

 

어느 하드웨어에나 이식이 자유로운 수치처리함수

 

   1:  //16진수를 10진수로 바꾸는 함수 (100미만의 값만 취급한다.)
   2:  unsigned char hex_to_dec(unsigned char cNum)
   3:  {
   4:      unsigned char temp;
   5:      
   6:      //10의 자리 잘라내 곱하기 16 + 1의 자리
   7:      temp = (cNum / 10) * 16 + (cNum % 10);
   8:      
   9:      return temp;
  10:  }
  11:   
  12:  //정수를 문자로 바꾸는 함수. (0~255까지만 취급함.)
  13:  void dec_to_ascii(unsigned char cNum, char *p)
  14:  {
  15:      unsigned char cLoop = 3;
  16:   
  17:      *(p + cLoop) = '\0';                    //문자열끝.
  18:      
  19:      for(; 0 < cLoop ; --cLoop)
  20:      {
  21:          *(p + cLoop - 1) = (cNum % 10) + '0';         //일자리부터 백자리까지 저장 후 문자로 변환.
  22:          cNum = cNum / 10;                            //일자리부터 10단위로 잘라냄.
  23:      }
  24:      
  25:      return ;
  26:  }
  27:   
  28:  //log2 N의 값을 구함. (128~0 -> 8~0)
  29:  char log_2(unsigned char cNum)
  30:  {
  31:      unsigned char cLoop = 0;        //log2 N에서 N의 값이 0이면 0이다.
  32:      
  33:      //계속 2로 나누어 나눈 횟수를 반환.
  34:      while(cNum >= 1)                
  35:      {
  36:          cNum = cNum / 2;
  37:          cLoop++;
  38:      }
  39:      
  40:      return cLoop;
  41:  }

 

16진수를 10진수로 바꾸는 함수, (hex_to_dec)
정수를 문자로 바꾸는 함수,      (dec_to_ascii)
log2 N의 값을 구하는 수학함수 (log_2)

상기의 함수들은 모두 하드웨어 의존(?)성이 매우 낮은 사용자 정의 함수들이다.
그래서 이름들도 모두 기능을 쉽게 알 수 있게 작명하였다.

 

16진수를 10진수로 바꾸는 함수

hex_to_dec( )

이 함수는 FND표시용으로 급조한 함수로 다른 시스템에서 사용하면 조금 많이 무리가 있을 듯 해 보이는 함수이다.
그리고 실제 16진수의 값이라는 것이 무엇인가?

컴퓨터는 2진수를 취급하지 10진수를 취급하지 않는다. 잘 생각해 보도록…

 

정수를 문자로 바꾸는 함수

dec_to_ascii( )

이 함수는 3자리 정수…그것도 양의 정수인 자연수만 취급하는 조잡한 함수로 0~255사이의 값만 취급하며,
정수를 문자로 바꾸는 해법은 다음과 같다,

정수 255가 입력되었다고 가정하고,
최초 정수 255의 값과 세 자리 문자를 저장할 4Bytes문자배열(배열)의 시작주소를 인자로 받아,
최초 끝 주소(시작주소 + 3)에 문자열의 끝을 알리기 위해 NULL문자를 삽입한다.
세 번째 주소(시작주소 + 2)에 1단위를 넣는다. (255 % 10) → 5를 얻음.
그리고, 1단위를 제거한다.                         (255 / 10) → 25가 되었다.
두 번째 주소(시작주소 + 1)에 10단위를 넣는다. (25 % 10) → 5를 얻음.
그리고, 10단위도 마저 제거한다.                  (25 / 10) →  2가 되는군.
첫 번째 주소(시작주소 + 0)에 100단위를 넣음. (2 % 10) → 2를 얻음.

저장할 때 0x30(십진수 48)을 더하여 ASCII변환해도 되고,
한 번 더 루프를 돌려 ASCII로 변환해도 된다.

 

 

밑수가 2인 log함수

log_2( )

이 함수는 밑수가 2인 log함수로 상용로그의 밑수10과 다르게 밑수가 2이다.
예를 들어,
log10 100은 100이 10의 2승이므로 결과는 2이다.
log10 10은 10이 10의 1승이므로 결과는 1이고,
log10 1은 1이 10의 0승이므로 결과는 0이다.

log2는 위와 같이,
log2 128은 128이 2의 7승이므로 결과는 7이다.
log2 64는 64가 2의 6승이므로 결과는 6이다.
log2 32는 32가 2의 5승이므로 결과는 5이다.
log2 16은 16이 2의 4승이므로 결과는 4이다.
log2 8은 8이 2의 3승이므로 결과는 3이다.
log2 4는 4가 2의 2승이므로 결과는 2이다.
log2 2는 2가 2의 1승이므로 결과는 1이다.
log2 1은 1이 2의 0승이므로 결과는 0이다.


먼저 이 함수가 필요한 이유를 설명해야 하나 함수의 입/출력 관계를 먼저 명시하고,
아래에 이유를 설명하겠다.

두더지게임에선,
DK-128보드의 키입력과 LED출력이 모두 0~128사이의 값이 1의 보수처리되어 I/O를 통해 나가고 들어온다.

KEY 입력

LED 출력

1의 보수

내부 처리 값

●●●● ●●●● ●●●● ●●●● 1111 1111 0000 0000 (0)
○●●● ●●●● ○●●● ●●●● 0111 1111 1000 0000 (128)
●○●● ●●●● ●○●● ●●●● 1011 1111 0100 0000 (64)
●●○● ●●●● ●●○● ●●●● 1101 1111 0010 0000 (32)
●●●○ ●●●● ●●●○ ●●●● 1110 1111 0001 0000 (16)
●●●● ○●●● ●●●● ○●●● 1111 0111 0000 1000 (8)
●●●● ●○●● ●●●● ●○●● 1111 1011 0000 0100 (4)
●●●● ●●○● ●●●● ●●○● 1111 1101 0000 0010 (2)
●●●● ●●●○ ●●●● ●●●○ 1111 1110 0000 0001 (1)

● LED 꺼짐 또는 KEY 누르지 않음.
○ LED 켜짐 또는 KEY 누름.

상기의 표와 같이 내부적으로 처리되는 값이 128, 64, 32, 16, 8, 4, 2, 1, 0이므로, (2에 bit승)
이를 그대로 터미널창에 표시하면 보기가 좋지 않다.
그러니 변환을 해줘야 하는데,
먼저 어떤 관계인지 알기 위해 엑셀이라는 강력한 계산도구를 사용해 그래프를 그려 보았다.

image


y = 0.5e^0.6932x

라는 매우 기괴한 함수가 얻어지고,
밑에 거듭제곱은 혹시 이 함수가 x의 제곱이 아닌가 하여 추세선을 그려 보았으나 R2의 값이 0.92로 일치하지 않았다.
그래서 지수함수를 하니 R2의 값이 1로 완벽히 일치하는데,
문제는 이 함수를 C언어로 어떻게 구현할 것인가? (이 함수는 x와 y의 값이 반대라 역을 구해야 한다.)

이 함수의 역은 로그인데 밑수가 e이다.
그리고 부동소수점 연산을 해야 하고 0.6931이라는 값이 IEEE-754포맷으로 바꾸었을 때 무한소수가 아닐 수 없어 보인다.
아무튼 이런 함수의 역을 구해 봤자 C로 구현하기 힘들 뿐더러 수행속도, 코드사이즈, 정밀도등 여러 문제가 발생할 것으로 예상된다.
그래서 배열에 변환테이블값을 넣어 볼까 생각도 했지만 129Bytes다 쓸데 없이 소비될 것으로 예상된다.

어떻게 해야 할까?