2011년5월16일...두더지잡기게임1-2, ATmega128 내부EEPROM 읽고 쓰기, 예제코드분석, 실습문제

 

 

두더지잡기게임 1-2

 

image

 

   1:  /***************************************************************************
   2:      <두더지잡기 게임1-2>
   3:      - 1초 간격으로 FND에 5부터 0까지 카운트다운한 뒤 게임 시작
   4:  
   5:      <두더지잡기 게임1-1>
   6:      - 무한 루프 진행
   7:      - 랜덤으로 20번 LED 켜짐. 난수를 발생하여 LED On/Off를
   8:          각각 0.5초간 유지
   9:      - LED가 켜진 시점에 해당 스위치를 누르면 점수 5점씩 증가
  10:      - 점수는 FND로 출력(점수 0x00~0x95 표시)
  11:      - 100점인 경우 FND는 0x00 출력, LED는 모두 깜박깜박~
  12:  
  13:      소속: 내장형하드웨어
  14:      작성자: 김수만
  15:      작성일자: 2011년 5월 16일
  16:  
  17:  ***************************************************************************/
  18~48행까지 생략  
  49:  //프로그램 시작
  50:  int main(void)
  51:  {
  50~72행까지 생략
  73:      while(1)    //무한루프
  74:      {
  75:          //5에서 0까지 카운트 다운! (두더지1-2)
  76:          for(cLoop = 5 ; 0 < cLoop ; --cLoop)
  77:          {
  78:              PORT_FND = cLoop | 0xF0;    //5초 부터 1초까지 
  79:              sleep(1000);                //카운트다운하며 대기.
  80:          }    
  81:          
  82:          PORT_FND = 0x00;                //FND초기화
  83:          key = 0;                        //key값 리셋
  84: 본 게임 시작 

205: 프로그램 끝

 

 

 

ATmega128 내부EEPROM

 

image

○ ATmega128 내부EEPROM 4KBytes Data Memoryimage
   4KByte = 4 * 1K * Byte
            = 2^2 * 2^10 * 1Byte
              (주소의 개수)  (메모리크기)
            = 2^12 * 1Byte = 4096 * 1Byte
              (세로)   (가로)

 

● 사용되는 레지스터
1.EECR ( Control Resister) : 읽기/쓰기 제어
2.EEAR ( Address Resister) : 주소지정. (0~4095) ex) EEAR = 1; (1번지)
3.EEDR ( Data Resister) : 8bit 데이터. (0~255)   ex) EEDR = 1; (십진수1) 
                                                              a = EEDR; (a에 저장)

 

 

image

12번~15번 비트는 사용하지 않음. (읽기만 가능한 비트들)
0번~11번까지 12개의 비트를 이용하여 EEPROM의 주소지정. 그러므로 2^12 = 4096가 되므로 4KBytes의 크기를 갖음.

주소지정은 읽을 필요가 없으므로 상위 4비트에 어떤 값이 들어가더라도 걱정없다.

 

image

0~255의 값을 넣어 줄 수 있는 8bit의 공간.
EEPROM에 쓰기전에 넣어줘야 한다.

 

image

1. EERIE (EEPROM Ready Interrupt Enable) : 쓸 준비가 되었다는 것을 알려주는 인터럽트를 활성시키는 비트.이번에는 사용하지 않음.
2. EEMWE (EEPROM Master Write Enable) : EEPROM에 쓰려면 이 비트를 먼저 세트시켜야 한다.
3. EEWE (EEPROM Write Enable) : EEPROM에 직접 쓰는 제어를 수행하는 비트로 이 비트를 세트시키면 EEPROM에 쓰기 시작함.
                                         데이터를 다 기록할 때 쯤에 자동으로 EEMWE가 먼저 리셋되고 다음에 EEWE가 리셋된다.

 

image

EEMWE를 세트시키고, EEWE를 세트시키면 EEPROM에 데이터를 기록하기 시작한다.
EEWE가 1인 상태에선 현재 기록 중이라는 뜻이다.
기록이 완료되면 하드웨어적으로 시스템이 알아서 EEMWE를 먼저 리셋시키고 다음에 EEWE가 리셋시킨다.

 

 

쓰기 절차

 

image

 

   1:          while(EECR & (1 << EEWE));
   2:  //쓰기 중 0000 0010 & 0000 0010 = 0000 0010
   3:  //대기 중 0000 0000 & 0000 0010 = 0000 0000
   4:
   
   5:  EEAR = Address;
   6:   
   7:   
   8:  EEDR = Data;
   9:   
  10:   
  11:  EECR = EECR | ( 1 << EEMWE);
  12:  EECR = EECR | ( 1 << EEWE);

 

 

EECR레지스터의 1번비트인 EEWE의 상태를 계속 체크하여 1이면 쓰고 있으므로 쓰기 명령을 내릴 수 없으니 대기하다가..
EEWE가 0이 되면 쓸 준비가 된 상태이므로,
EEAR레지스터에 주소값을 집어 넣고,
EEDR레지스터에 데이터를 집어 넣은 다음에,
EECR레지스터의 2번비트인 EEMWE를 세트한 뒤에 EEWE를 세트하여 ATmega128에 쓸 명령을 내린다.
그럼 자동으로 시스템이 쓴 다음에 자동으로 리셋할 것이다.

 

읽기 절차

image


   1:  while(EECR & (1 << EEWE));    //쓰기 작업 중이면 루프
   2:   
   3:   
   4:      
   5:  EEAR = uiAddress;             //읽을 주소 설정
   6:   
   7:   
   8:   
   9:  EECR = EECR | (1 << EERE);    //읽기 명령
  10:   
  11:  //데이터 읽기    
  12:  return EEDR;                  

 

 

 

EECR레지스터의 1번비트인 EEWE의 상태를 계속 체크하여 1이면 쓰고 있으므로 EEPROM은 바쁘기 때문에 읽기 명령을 내릴 수 없으므로 EEWE가 0이 될 때까지 계속 대기한다.
EEWE가 1이되면 EEPROM이 쉬고 있으므로,
EEAR에 주소를 넣고,
EECR의 0번째 비트인 EERE를 세트하여 EEAR번지의 데이터를 읽겠다고 알려준다.
그럼 EEDR에는 EEAR번지의 데이터가 들어가게 된다.
함수의 반환값으로 EEDR을 넘겨주던지 아니면 포인터를 이용하여 직접main( )함수 변수의 값을 수정하던지 아니면,
main( )함수에 읽기와 쓰기 코드를 넣어도 된다.

 

I/O헤더파일에 EEPROM관련 레지스터추가


   1:  /******************************************************
   2:                  상태 레지스터
   3:  ******************************************************/
   4:  #define SREG    *((volatile unsigned char *) 0x5F)
   5:  //전체 인터럽트 활성
   6:  #define I        7
   7:  #define    sei()    SREG = SREG | (1 << I);
   8:   
   9:   
  10:  /******************************************************
  11:                  내부 EEPROM
  12:  ******************************************************/
  13:  #define EEAR    *((volatile unsigned int *) 0x3E)
  14:  #define EEDR    *((volatile unsigned char *) 0x3D)
  15:  #define EECR    *((volatile unsigned char *) 0x3C)
  16:  //EECR 각 bit
  17:  #define EEMWE    2
  18:  #define EEWE    1
  19:  #define EERE    0

EEAR의 경우 16bit크기의 레지스터이므로 하위Byte의 주소인 EEARL의 주소를 unsigned int 포인터형으로 캐스팅한다.

image

EEDR과 EECR은 모두 8bit이니 I/O포트와 같이 unsigned char 포인터형으로 캐스팅하면 된다.
(포인터가 가리키는 곳의 자료형이 무엇인가?에 관한 내용이다.)

EECR의 각 비트들 중 EERIE만 빼고 비트번호만 적어준다.
그리고 SREG의 최상위 비트인 전체인터럽트 활성비트 7을 I로 정의한다.

 

 

EEPROM읽고 쓰기 연습


   1:  /****************************************************************
   2:  
   3:      1번 키를 누르면 EEPROM에 데이터 기록,
   4:      8번 키를 누르면 clear
   5:      
   6:      소    속: 내장형하드웨어
   7:      작 성 자: 김수만
   8:      작성일자: 2011년 5월 16일
   9:      
  10:  ****************************************************************/
  11:   
  12:  #include "./../../ATmega128.h"
  13:   
  14:  #define CPU_CLOCK        16000000
  15:  #define TICKS_PER_SEC    1000
  16:  #define PRESCALER        64
  17:   
  18:  #define KEY_IN        PINC
  19:  #define FND_DDR        DDRE
  20:  #define FND_OUT        PORTE
  21:   
  22:  volatile unsigned int g_elapsed_time = 0;        //시간변수
  23:   
  24:  //ISR
  25:  void __vector_15 (void) __attribute__ ((signal, used, externally_visible));     
  26:  //프로토타입
  27:  void init_FND(void);        //FND연결포트 초기화
  28:  void init_TC0(void);        //타이머/카운터0 초기화
  29:  void sleep(unsigned int elapsed_time);        //delay함수
  30:  void eeprom_write(unsigned int uiAddress, unsigned char ucData);
  31:  unsigned char eeprom_read(unsigned int uiAddress);
  32:   
  33:  //프로그램 시작
  34:  int main()
  35:  {
  36:      unsigned char ch = 0;
  37:      
  38:      DDRC = 0x00;    //모두 입력    
  39:      init_FND();        //FND초기화
  40:      init_TC0();        //타이머/카운터0 초기화
  41:      
  42:      SREG = SREG | (1 << I);            //전체 인터럽트 활성화
  43:      
  44:   
  45:      for(unsigned int i = 0 ; ; i++)    //무한루프
  46:      {
  47:          ch = KEY_IN;
  48:          
  49:          if(0b11111110 == ch)            //1번 스위치를 누르면 EEPROM에 데이터 기록
  50:          {
  51:              ch = 0;
  52:              for(unsigned int i = 0 ; 10 > i ; i++)
  53:              {
  54:                  eeprom_write(i, ch++);
  55:              }
  56:              
  57:              ch = 9;
  58:              for(unsigned int i = 0 ; 10 > i ; i++)
  59:              {
  60:                  eeprom_write(10 + i, ch--);
  61:              }
  62:          }
  63:          
  64:          if(0b01111111 == ch)            //8번 스위치를 누르면 EEPROM 데이터 초기화
  65:          {
  66:              ch = 0;
  67:              for(unsigned int i = 0 ; 20 > i ; i++)
  68:              {
  69:                  eeprom_write(i, ch);
  70:              }
  71:          }
  72:              
  73:          ch = eeprom_read(i);        //EEPROM의 i주소에 들어있는 데이터를 읽어옴.
  74:          FND_OUT = ch;                //읽어온 데이터를 FND에 출력
  75:              
  76:          if(19 == i)
  77:          {
  78:              i = 0;
  79:          }
  80:          
  81:          sleep(200);
  82:      }
  83:      
  84:      return 1;
  85:  }
  86:      
  87:  /*******************************************************
  88:      타이머/카운터0 비교매치 인터럽트 서비스 루틴
  89:  *******************************************************/
  90:  void __vector_15(void)
  91:  {
  92:      ++g_elapsed_time;        // 시간변수 1증가    
  93:  }
  94:   
  95:  //FND초기화
  96:  void init_FND(void)
  97:  {
  98:      FND_DDR = 0xFF;        //모두 출력
  99:      FND_OUT = 0x00;        // "00"출력
 100:      
 101:      return ;
 102:  }
 103:   
 104:   
 105:  //타이머0 초기화
 106:  void init_TC0(void)
 107:  {
 108:      //CTC Mode, prescanler = 1/64
 109:      TCCR0 = (1 << WGM01) | (1 << CS02);        
 110:      //8bit Timer0 = 16,000,000 / 1000 / 64
 111:      //            = 250
 112:      OCR0 = CPU_CLOCK / TICKS_PER_SEC / PRESCALER;
 113:      //타이머0 비교 매치 인터럽트 활성
 114:      TIMSK = (1 << OCIE0);        
 115:      
 116:      return ;
 117:  }
 118:   
 119:  //시간 지연
 120:  void sleep(unsigned int elapsed_time)
 121:  {
 122:      g_elapsed_time = 0;                            //시간 변수 초기화
 123:      while(elapsed_time > g_elapsed_time);        //참이 될 때까지 대기.
 124:      
 125:      return ;
 126:  }
 127:      
 128:          
 129:  /*******************************************************
 130:              EEPROM에 데이터 읽기/쓰기 함수
 131:  *******************************************************/
 132:  void eeprom_write(unsigned int uiAddress, unsigned char ucData)
 133:  {
 134:      while(EECR & (1 << EEWE));        //쓰기 작업 중이면 루프
 135:      
 136:      EEAR = uiAddress;                //쓸 주소 설정
 137:      EEDR = ucData;                    //쓸 데이터
 138:      EECR = EECR | (1 << EEMWE);    //EEWE할 준비해라
 139:      EECR = EECR | (1 << EEWE);        //쓰기 명령
 140:      
 141:      return ;
 142:  }
 143:   
 144:  unsigned char eeprom_read(unsigned int uiAddress)
 145:  {
 146:      while(EECR & (1 << EEWE));        //쓰기 작업 중이면 루프
 147:      
 148:      EEAR = uiAddress;                //읽을 주소 설정
 149:      EECR = EECR | (1 << EERE);        //읽기 명령
 150:      
 151:      return EEDR;                    //데이터 읽기
 152:  }
 153:          


(순서도 + 분석내용 + 설명추가)

 

EEPROM실습문제

1.문제인식
KEY와 대응하는 LED를 ON시키고, 이전 상태를 유지한다. 그리고 EEPROM에 저장하여 전원이 다시 투입되어도 이전상태를 유지해야함.
EEPROM의 데이터는 최초에 0x00이거나 0xFF와 같이 초기화되었다고 할 수 없다.

2.해법찾기
EEPROM의 LED데이터가 들어가는 번지의 값을 최초구동시 초기화하기 위해 임의의 번지를 하나 더 사용하여,
그 번지에 있는 데이터가 0x55가 아닐 경우 LED데이터가 들어가는 곳을 초기화한다. (0x55일 확률이 1/256이므로 매우 낮다.) 
만약 하나의 번지를 더 사용한다면 확률은 더 낮아질 것이다.
1/256확률이나 EEPROM의 값이 0x55일 수도 있으니 그렇게 좋은 방법은 아니다.

3.해법을 순서도나 의사코드로 표현해보자.
설정
…0번지는 LED데이터가 보관되는 곳.
       1번지는 한 번이라도 켜졌었는지 알 수 있는 데이터가 저장되는 곳. (0x55)
수행...ⓐ1번지의 값을 읽어와 0x55가 아니면, 0번지의 데이터를 0으로 초기화함. 그리고 1번지는 0x55를 기록.
                                  0x55이면, ⓑ로 감.
       ⓑ0번지의 값을 읽어와 cLED변수에 저장.
       ⓒcLED변수의 값을 LED가 연결된 PORT로 출력. 
       ⓓcLED의 값을 유지하면서 바꾸기 위해 KEY입력값과 AND연산을 수행함.
         (AND연산을 하면 강제적으로 해당bit를 0으로 클리어할 수 있음.)
       ⓔ0번지에 cLED의 값을 저장.
       ⓕ ⓒ로 점프. (계속 반복)

만약 전원을 끈 후에 다시 켜도 1번지의 값이 0x55이므로 실제 데이터가 들어간 0번지를 0으로 초기화하지 않고,
ⓑ 0번지의 값을 불러와 다시 출력하므로 전원을 끄기 이전 상태를 계속 유지할 수 있다.

이 문제의 단점은 KEY를 눌러 LED가 ON이 될 수 있어도 OFF가 되는 조건이 없다.
OFF가 되는 조건을 굳이 추가하자면 ON상태의 LED를 다시 눌렀을 때 OFF시키면 되겠다.

 

 

   1:  /****************************************************************
   2:  
   3:      <실습문제>
   4:      1.LED와KEY사용
   5:      2.각LED는이웃한각KEY버튼과짝이된다.
   6:      3.KEY를누르면LED는이전상태유지하면서해당LED가ON 됨
   7:      4.현재의상태를매번EEPROM에기억시킨다.
   8:      5.DK-128의전원을껐다가켰을때, 이전의LED 상태를그대로표시해야한다.
   9:      
  10:      소    속: 내장형하드웨어
  11:      작 성 자: 김수만
  12:      작성일자: 2011년 5월 16일
  13:      
  14:  ****************************************************************/
  15:   
  16:  #include "./../../ATmega128.h"
  17:   
  18:  #define DDR_KEY        DDRC
  19:  #define PIN_KEY        PINC
  20:  #define DDR_LED        DDRF
  21:  #define    PORT_LED    PORTF
  22:   
  23:  void eeprom_write(unsigned int uiAddress, unsigned char ucData);
  24:  unsigned char eeprom_read(unsigned int uiAddress);
  25:   
  26:  //프로그램 시작
  27:  int main()
  28:  {
  29:      unsigned char cLED;
  30:   
  31:      DDR_LED = 0xFF;
  32:      PORT_LED = 0xFF;
  33:      DDR_KEY = 0x00;    
  34:   
  35:      /* 1번지에 0xAA가 들어 있으면,
  36:       * 최소 1번 구동하였다고 판단.
  37:       * 0xAA라는 값이 있을 확률이 낮으므로 */
  38:      cLED = eeprom_read(1);
  39:      if(0x55 != cLED)        //한 번도 구동되지 않으면,
  40:      {
  41:          eeprom_write(1, 0x55);        //0x55저장.
  42:          eeprom_write(0, 0xFF);            //0번지를 초기화
  43:      }
  44:      
  45:      cLED = eeprom_read(0);        //0번지 읽어와 저장.
  46:      
  47:      while(1)
  48:      {
  49:          PORT_LED = cLED;        //출력
  50:          cLED = cLED & PIN_KEY;    //이전 상태유지하며 해당 LEDON
  51:          eeprom_write(0, cLED);    //0번지에 기록.
  52:      }
  53:      
  54:      return 1;
  55:  }
  56:      
  57:  /*******************************************************
  58:              EEPROM에 데이터 읽기/쓰기 함수
  59:  *******************************************************/
  60:  void eeprom_write(unsigned int uiAddress, unsigned char ucData)
  61:  {
  62:      while(EECR & (1 << EEWE));        //쓰기 작업 중이면 루프
  63:      
  64:      EEAR = uiAddress;                //쓸 주소 설정
  65:      EEDR = ucData;                    //쓸 데이터
  66:      EECR = EECR | (1 << EEMWE);    //EEWE할 준비해라
  67:      EECR = EECR | (1 << EEWE);        //쓰기 명령
  68:      
  69:      return ;
  70:  }
  71:   
  72:  unsigned char eeprom_read(unsigned int uiAddress)
  73:  {
  74:      while(EECR & (1 << EEWE));        //쓰기 작업 중이면 루프
  75:      
  76:      EEAR = uiAddress;                //읽을 주소 설정
  77:      EECR = EECR | (1 << EERE);        //읽기 명령
  78:      
  79:      return EEDR;                    //데이터 읽기
  80:  }
  81: