본문 바로가기
코스웨어/10년 스마트폰BSP

[BSP]업무일지-송동규-20100618

by 알 수 없는 사용자 2010. 6. 19.
728x90
반응형

Atmega 128 의 내장 메모리는 총 3가지이다.    

Flash Memory - 128Kbyte
EEPROM - 4Kbyte
SRAM - 4Kbyte    

그 중 EEPROM 은 데이터를 지속적으로 관리하기 위해 사용한다. EEPROM 은 1BYTE 단위로 데이터를 읽고 쓰며 전원이 꺼지더라도 지속적으로 데이터를 유지해야 할 경우 EEPROM 에 데이터를 입력하면 된다. 예를 들어 핸드폰 설정이 저장 되어 있는데 진동 모드나 알람 등등은 전원 OFF 되더라도 저장 되어 있어야 하는 정보들이다. 이런 정보들은 보통 EEPROM 에 저장하여 사용하게 된다.

EEPROM 의 용량은 4Kbyte 이며 4Kbyte 는 4096 x 1byte 이다. 그래서 메모리의 주소는 0 ~ 4095 번지까지 이다.    

EECR 제어 관련 레지스터 - EEPROM 을 읽을 것인지 쓸 것인지 설정하는 레지스터
EEAR 주소관련 레지스터 - 우리가 읽거나 쓰려는 주소를 지정해 줄 때 사용하는 레지스터
EEDR 데이터 관련 레지스터 - 실제로 데이터가 저장되는 레지스터    

최종적으로 우리가 EEPROM 에 데이터를 읽거나 쓰려면 ATmega128 은 EECR 의 상태를 보고 EEDR 의 데이터를 DDAR 의 주소에 읽던지 쓰던지 한다.    

이제 좀 더 세부적으로 EEPROM 의 관련된 레지스터들을 살펴보자.    

EECR ( Control Register )    

 

시작주소는 0X3C 이고 총 8비트 레지스터지만 상위 4비트는 사용하지 않고 하위 4비트만 사용한다. 사용하는 4비트는 각각 다음과 같은 기능을 한다.    

EERE (Read Enable) - EEAR 에 지정된 주소의 1BYTE 를 읽어 EEDR 에 저장 ( EEAR 과 EEDR 은 아래의 설명을 참조 )
EEWE (Write Enable) - high 값이 들어오면 쓰기 기능이 동작한다.
EEMWE (Master Write Enable) - EEPROM 에 기록 가능한 상태를 지정하는 비트. EEWE 와 EEMWE 가 둘 다 high 값일 때 데이터 기록이 가능하게 된다. 만약 이 비트가 low 라면 EEMW 의 상태와는 무관하게 EEPROM 에는 데이터를 기록하지 않는다.
EERIE (Ready Interrupt Enable) - EEWE 가 클리어 될 때 (쓰기 작업이 끝날 때) 인터럽트를 발생한다. 여기서 SREG에서 7번 비트를 1로 해놓지 않으면 인터럽트를 발생시켜도 감지 하지 못하기 때문에 SREG I 비트도 high 값을 주어 활성화 시켜놓아야 한다.

EEAR (Address Register)

시작주소는 0x3E 이고 총 16비트 레지스터지만 EEARH 하위 4비트와 EEARL 의 8비트 를 합쳐 12비트만 사용한다. 12비트만 사용해도 EEPROM 의 데이터 4096 BYTE 의 주소를 모두 접근 할 수 있기 때문이다. 주소값을 12bit 로 읽어 오기 때문에 char 형 변수를 대입하면 안 되고 int형 (ATmega 에서 int 형은 2 byte 이다.) 변수를 대입해주어야 한다.

EEDR (Data Register)

 

시작주소는 0x3D 이고 총 8비트 레지스터이다. EEPROM 에 데이터를 기록할 때 기록할 데이터를 보관하거나 데이터를 읽을 때 읽혀진 데이터를 가진다. 8bit 라서 0 ~ 255 의 데이터를 가질 수 있다.    

이제는 코드를 보고 실제 EEPROM 에 데이터를 쓰고 읽는 작업을 해보자.    

#include "dk.h"        //사용자 정의 헤더 파일
#include <avr/interrupt.h>  //인터럽트가 발생하면 SIGNAL(SIG_OVERFLOW0) 함수를 호출하기 때문에 헤더파일 선 

volatile
 unsigned int g_elapsed_time;    //전역 변수 선언 
   

void setTCCR0(void);  //TCCR0  mode  분주기를 설정

void initTCNT0(void);  //TCCR0  실행횟수를 카운팅하는 함수. overflow  발생하면 다시 6으로 초기화.

void setTIMSK(void);  //TIMSK  인터럽트를 사용하겠다고 초기화 하는 함수

void sleep(unsigned int elapsed_time);    //1초간 대기하는 함수

void eeprom_write(unsigned int uiAddress, unsigned char ucData);  //EEPROM  데이터를 쓰는 함수

unsigned char eeprom_read(unsigned int uiAddress);    //EEPROM  있는 데이터를 읽어 오는 함수

SIGNAL(SIG_OVERFLOW0);    //overflow  발생하면 시스템에서 알아서 함수를 호출.  avr/interrupt.h  포함  

   

int main(void)

  unsigned char ch = 0;   
    
  DDRF = 0xFF;    //PORT F  출력으로 설정 
  PORTF = 0xFF;    //PORTF 레지스터를 0xFF  초기화 
    
  DDRC = 0x00;    //PORT C  입력으로 설정 
  PORTC = 0xFF;    //PORTC 레지스터를 0xFF  초기화 
   
  setTCCR0();    //TCCR0  mode  분주기를 설정하기 위한 함수 
   
  initTCNT0();  //TCNT0  TCCR0  실행횟수를 카운팅 하는 레지스터호출되면 값을 항상 6으로 초기화 
   
  setTIMSK();    //Interrupt  활성화, 0번에 1값을 넣어주면 overflow interrupt  사용하는 것이다. 
   
  sei();      //SREG = SREG | (1<<7); 인터럽트를 사용하기 위해 SREG 최상위 비트를 1값을  . 
        //SREG   비트마다 다른 기능을 가진다  최상위 비트가 interrupt  관한 비트이다. 
          
  for(unsigned int i = 0; ; i++)    //무한 반복을 for 으로 정의 
  { 
    ch = PINC;            //ch  값을 PINC  값으로 초기화 
    if(ch==0b11111110)        //S/W  첫번째 버튼을 누른다면 
    { 
      ch = 0;            //ch  0 으로 초기화 
      for(unsigned int i = 0; i<10; i++)    //반복문 실행 
      { 
        eeprom_write(i,ch++);        //eeprom 함수에 매개변수로 i ch++ 넘겨주면서 호출  10 호출 
      } 
      ch = 9;                  //반복문이 종료되면 ch  9 초기화 
      for(unsigned int i = 0; i<10; i++)    //반복문 실행 
      { 
        eeprom_write(i,ch--);        //eeprom 함수에 매배견수로 i ch-- 넘겨주면서 호출  10 호출 
      } 
    }     
    ch = eeprom_read(i);    //eeprom  있는 데이터를 읽어 오는 함수 호출 
    PORTF = ch;          //eeprom  있는 데이터를 PORTF 값으로 대입 
    if(i==19)          //만약 전체 for 문의 i 19라면 
    { 
      i = 0;          //0으로 초기화 
    }     
    sleep(25);          //딜레이를 주기 위해 sleep 함수 호출 
  } 
  return 1;

   

void setTCCR0(void)

  TCCR0 = (1<<CS02);  //normal mode  분주기를 64 설정 CS02  사용자 헤더 파일에 2 설정 
             //왼쪽으로 2만큼 shift 시킨 값을 TCCR0  대입 TCCR0 = 0x04  같은 문장

   
void initTCNT0(void)

  TCNT0 = 6;  //TCNT0  6이여야 250 카운팅 되면 overflow 발생

   
void setTIMSK(void)

  TIMSK = (1<<TOIE0);   //Counter Interrupt  사용하겠다는 초기화 값으로 설정. TOIE0  사용자 헤더파일에 
               //0으로 설정. TIMSK = 0x01  같은 문장

   
SIGNAL(SIG_OVERFLOW0)    //overflow  발생하면 시스템에서 자동으로 호출 

  TCNT0 = 6//TCNT0 초기화 
  g_elapsed_time++;    //overflow  한번 발생  때마다 전역변수 +1 시킴  

   
void sleep(unsigned int elapsed_time)    //매개변수 elapsed_time 으로 넘어오는 값만큼 대기하는 함수

  g_elapsed_time = 0;            //sleep 함수 호출  마다 시간변수를 0으로 초기화 해주고 
//인자로 들어오는 (1000)까지 sleep 함수 내에 잡아둔다. 
  while(g_elapsed_time<elapsed_time)    //시간변수가 elapsed_time 보다 작다면 루프 
  { 
      
  }

   void eeprom_write(unsigned int uiAddress, unsigned char unData)    //첫번째 매개변수로 주소두번째 매개변수로 데이터를 받아옴

  while(EECR&(1<<EEWE))    //쓰기 작업이 끝나지 않았다면 무한 루프 
  { 
    
  } 
    
  EEAR = uiAddress;      //매개변수로 넘어온 uiAddress  주소로 설정 
  EEDR = unData;        //매개변수로 넘어온 unData  데이터 레지스터에 저장 
  EECR |= (1<<EEMWE);    //EEMWE 사용자 헤더파일에 2 저장 
  EECR |= (1<<EEWE);      //EEWE 사용자 헤더파일에 1 저장  
                //데이터 쓰기 명령을 실행 
    
  return;

   
unsigned char eeprom_read(unsigned int uiAddress)  //첫번째 매개변수로 주소를 받아옴

  while(EECR&(1<<EEWE))    //쓰기 작업이 끝나지 않았다면 무한 루프 
  { 
    
  } 
    EEAR = uiAddress;      //매개변수로 넘어온 uiAddress  주소로 설정 
  EECR |= (1<<EERE);      //읽기 명령을 실행 
    
  return EEDR;        //데이터값을 가지고 있는 EEDR  리턴해줌
} 


   사용자 헤더 파일    

보통 툴마다 기본적으로 제공하는 헤더파일이 있지만 사용자가 직접 자신에게 맞게 헤더파일을 만들어서 사용할 수 있다. 위 코드에서 나 또한 나에게 맞게 헤더파일을 정의해서 사용하였다. 그 내용을 잠시 들여다 보겠다.    

 

     

실습 문제    

main 함수 내의 반복문만 아래와 같이 수정 해주면 된다.    

  while(1
  { 
    if (PINC != 0xff)      //PINC  어떤 값이라도 들어온다면 
    {       
      ch |= ~PINC;      //ch ~PINC 값을 or 시킴 
      eeprom_write(0, ~ch);  //ch  값을 eeprom 0 비트에 쓰기 
    } 
      
    PORTF = eeprom_read(0); 
  } 

아래는 실습 문제를 시뮬레이터로 돌린 모습이다. 중간에 전원을 뺐다가 다시 넣어도 데이터가 이전 상태로 유지 되어있다. 그리고 EEPROM 을 삭제 한 후 다시 한번 입력 받는 모습도 볼 수 있다.




   

정적 멤버
 
   

정적 멤버는 객체마다 하나씩 생기는 것이 아니라 하나의 멤버를 모든 클래스가 공유하게 된다. 선언은 class 안에 static 키워드를 이용해 멤버변수를 선언해주고 class 밖에서 타입 클래스이름::변수이름=초기화값; 으로 정의 해준다. (class 안에서 정적멤버를 초기화 하면 error 가 발생한다.) ※정적 멤버는 Data 영역에 올라가고 class 멤버들은 stack 에 올라간다.

정적 멤버와 객체는 별개다. 정적 멤버는 객체를 생성하지 않아도 클래스이름::정적멤버이름 으로 접근이 가능하다. 하지만 객체의 멤버들은 반드시 객체를 생성 해주고 생성시 정의해준 이름을 통해 접근이 가능하다.    

정적 멤버는 반드시 class 안에 선언해주어야 하며, 정적 멤버에 접근하기 위해서는 클래스 이름과 접근 연산자를 통해서 접근하기 때문에 클래스 마다 이름이 같은 정적 멤버를 가질 수도 있다   

정적 멤버는 일반 멤버에 접근 할 수 있을 수도 없을 수도 있다. 만일 객체가 생성 되어 있는 상태에서 접근한다면 접근이 가능하지만 객체 생성 없이 바로 접근하려 한다면 접근이 불가능하다.   

다음은 정적 멤버로 변수 와 함수를 하나 생성해서 객체를 생성할 때 마다 정적 멤버 변수의 count 가 증가 되고 객체가 소멸 될 때 마다 count 가 감소하는 소스이다.    

#include <iostream>
#include <string>
using namespace std; 
   
class Student
{
private
  string name; //class 멤버 변수는 접근 제어자를 private 선언 
  int sno; 
   
public
  Student(const string& name_arg, int stdNumber); 
  ~Student(); 
    
  static int student_count; //정적 멤버 변수 선언 
  static void PrintStdCount(); //정적 멤버 함수 선언
}; 
   
int Student::student_count = 0; //정적 멤버 변수 초기화 
   
void Student::PrintStdCount() //정적 멤버 함수 구현

  cout << " Student 객체  = " << student_count << endl;

   
Student::Student(const string& name_arg, int stdNumber)

  student_count++;    //객체 생성  정적 멤버 student_count  값을 1 증가 
   
  name = name_arg; //인자로 넘어 오는 값을 class 내의 변수에 대입 
  sno = stdNumber; //함수를 통해 class 내의 접근 제어자가 private 변수에 접근 가능

   
Student::~Student() //객체 소멸시 소멸자 호출

  student_count--; //정적 멤버 변수 student_count 값을 -1 시킴

   
void Func() //일반 함수

  Student std1("Bill",342); 
  Student std2("James",214); 
   
  Student::PrintStdCount();

   
int main(void)

  Student::PrintStdCount();    //접근 제어자가 public 이고 정적 멤버이기 때문에 객체생성 없이 바로 호출 가능 
   
  Student std("Jeffrey"123); 
   
  Student::PrintStdCount(); 
   
  Func(); 
   
  Student::PrintStdCount(); 
   
  return 0;

   

아래는 위의 코드를 3가지 파일로 나누어서 작성한 코드이다. 이렇게 코드를 나누어 줌으로써 각각 기능별로 파일을 나누어 줄 수 있다.

 

student.h

 

#ifndef STUDENT_H

#define STUDENT_H

#include <string>

 

using namespace std;

 

class Student

{

  public:

    Student(const string& name_arg, int stdNumber);

    ~ Student();

 

    static int student_count;

    static void PrintStdCount();

 

  private:

    string name;

    int sNo;

};

 

#endif

 

 

student.cpp

 

#include "student.h"

#include <iostream>

 

 

int Student::student_count = 0;

 

void Student::PrintStdCount()

{

  cout<<"Student 객체 = "<<student_count <<"\n";

}

 

Student::Student(const string& name_arg, int stdNumber)

{

  student_count++;

 

  name = name_arg;

  sNo = stdNumber;

}

 

Student::~Student()

{

  student_count--;

}

 

 

main.cpp

 

#include <iostream>

#include "student.h"

 

using namespace std;

 

void Func()

{

  Student std1("Bill"342);

  Student std2("James"214);

 

  Student::PrintStdCount();

}

 

int main()

{

 

  Student::PrintStdCount();

 

  Student std("Jeffrey",123);

  Student::PrintStdCount();

 

  Func();

 

  Student::PrintStdCount();

 

  return 0;

}

 

두 가지의 파일을 서로 합쳐서 컴파일하여 하나의 실행파일로 만들어야 한다.

g++ 컴파일러에서 파일 생성 과정

g++ -c main.cpp main.o 생성
g++ -c student.cpp student.o 생성
g++ -o main main.o student.o main.o 와 student.o 를 가지고 main 이라는 실행 파일을 만듦.

cl 컴파일러에서 파일 생성 과정   

cl student.cpp main.cpp 라고 하면 두 파일을 같이 컴파일하여 main 이라는 실행 파일을 생성
cl main.cpp student.cpp 라고 하면 두 파일을 같이 컴파일하여 student 라는 실행 파일을 생성

   

   

/* 
 입력 받은 값을 2차원 배열로 메모리를 할당 받는다생성한 배열을 1부터 차례대로 값을 대입한다.
*/
#include <iostream> 
   
using namespace std; 
   
int main(void)

  int a;  
  int b; 
  int i; 
  int j; 
  int k=0
   
  cout<<"Input The Numbers : ");  
  cin>>a; 
  cin>>b;                
   
  int **dp = new int*[a]; 
  for (i = 0; i <= a; i++)                         
  { 
    dp[i] = new int[b]; 
  } 
  for(i=0;i<=a;i++) 
  { 
    for(j=0;j<=b;j++) 
    { 
      dp[i][j]=i*(b+1)+j; 
      cout<<"dp["<<i<<"] ["<<j<<"]= "<<dp[i][j]<<endl; 
      k++; 
    } 
  } 
    
  for(i=0;i<a;i++) 
  { 
    delete[] dp[i]; 
    dp[i]=NULL; 
  } 
  delete [] dp; 
  dp = NULL; 
    
  return 0;

   
위 코드를 객체지향개념으로 바꾸어서 코딩하기 

     
2차원 배열을 메모리로 할당 받는 코드를 객체지향개념으로 바꾸어 보았다. 일단은 기존 코드에서 class 로 뺄 수 있는 것을 찾아본다.
존 함수들과 변수들은 대부분이 class 에 선언이 가능하다. class 에 선언할 때는 변수들은 private 의 접근 권한을 주고 함수들은 public 으로 주어서 외부에서 변수들에 직접 접근하지 못하도록 정보를 은닉 시켜 놓는다. 그리고 class 의 변수를 초기화 하거나 값을 변경할 때 public 접근 권한을 가진 함수들을 통해 접근하도록 코딩한다. 
  
우선 main 함수에서 객체를 생성한다. print 함수를 호출해서 문자열을 화면에 출력. input 함수를 호출해서 값을 입력 받는다. bir 함수
 호출해서 입력한 값만큼 2차원 배열을 생성하여 0부터 차례대로 값을 입력한다.
프로그램이 종료되면 객체도 사라지므로 소멸자가 할당받은 메모리를 해제하고 더블포인터 변수 dp가 NULL 을 가리키게 한다.

과제 4-2 

      
과제 4-3 
   

과제 4-4 
   

  
   

728x90