반응형

아두이노 우노의 저장 장치에는 스케치를 저장하는 공간인 Flash Memory(PC의 하드 디스크 또는 SSD)와 프로그램의 작동을 위한 각종 변수의 값을 임시 저장하는 SRAM(PC의 메모리 또는 캐시 메모리, 읽고 쓰기의 속도가 빠르지만 리셋 시 모든 값은 사라진다) 그리고 읽고 쓰기의 속도는 SRAM에 비해 느리지만 변수의 값을 Flash Memory에 저장하여 아두이노 리셋 시에도 그 값을 유지하는 EEPROM이 있다. 스케치를 위한 Flash Memory와 EEPROM은 같은 Flash Memory를 사용한다. 파티션 개념으로 두 영역이 구분되어 있어 아두이노에 스케치를 새로 업로드해도 EEPROM의 값은 변경되지 않는다. 

 

아두이노 우노(ATmega328P)의 저장장치 

스케치용 Flash Memory: 32 KB (이중 0.5 KB는 부트로더가 사용한다.)  
SRAM:  2 KB 
EEPROM:  1 KB 

 

EEPROM을 사용하여 변수의 값을 Flash Memory에 저장하기 위해서는 EEPROM 라이브러리를 등록하고 EEPROM 함수를 사용해야 한다. EEPROM 라이브러리는 EEPROM의 데이터를 바이트 단위로 읽고 쓰며 1바이트에 저장할 수 있는 값은 0 ~ 255이다. EEPROM을 사용하지 않은 상태의 아두이노(프로그래밍되지 않은 상태 또는 공장 출시 상태)의 EEPROM 셀 값은 0xFF(255)이다. 아두이노 우노의 EEPROM 크기가 1 Kbyte이므로 바이트 단위로 1024개의 주소(0 ~ 1023)를 가진 게 된다. 

 

EEPROM.read(address); // EEPROM의 주소(address)에 해당하는 값을 읽는다. 값의 범위: 0 ~ 255 사이(1바이트 또는 8비트)

 

EEPROM.write(address, value);    // EEPROM의 주소(address)에 값(value)을 쓴다. 값의 범위: 0 ~ 255 사이

 

EEPROM.update(address, value); // EEPROM의 주소(address)의 값을 읽고 value의 값을 비교하여 값이 다르면 해당 주소에 value의 값을 쓰고 값이 다르면 쓰지 않는다. EEPROM.write() 함수와 비슷한 기능이지만 현재 값과 새로 쓸 값을 비교하는 기능이 추가된 것이다. 값이 같다면 EEPROM에 값을 쓰는데 소요되는 시간을 줄일 수 있다. 반면에 값이 다르면 현재 값과 새로 쓸 값을 비교하는 시간이 추가로 소요되게 된다. value 값이 항상 다른 값으로 바뀐다면 EEPROM.write() 함수를 사용하는 게 시간적으로 이득이다. 참고로 EEPROM 쓰기는 약 3.3ms(밀리 초) 소요되고 Flash Memory 메모리는 약 100,000번 쓰고 지울 수 있다. value 값이 같을 가능성이 큰 경우에 EEPROM.update() 함수를 사용하면 시간을 절약하고 Flash Memory의 수명도 늘릴 수 있다. 

 

EEPROM.put(index, value) 함수 : 저장하고자 하는 value의 자료형 크기에 의해 지정 index부터 자료형 크기만큼 저장한다.  EEPROM.put(index, value) 함수 내부에서 sizeof(value) 함수로 변수 value의 크기를 측정하고 그 크기에 맞게 1바이트씩 읽고 시프트 연산을 통해 해당 크기의 변수값으로 변환하는 코드가 짜여져 있을 것이라 생각한다.    
예) uint16_t value -> EEPROM.put(0, value); // EEPROM 인덱스 0과 1에 1바이트씩 총 2바이트 저장 
    uint32_t value -> EEPROM.put(0, value); // EEPROM 인덱스 0, 1, 2, 3에 1바이트씩 총 4바이트 저장 


EEPROM.get(index, value) 함수 : 읽은 값을 저장하고자 하는 value의 자료형의 크기에 의해 지정 index부터 자료형 크기만큼 읽고 저항하고자 하는 변수에 값을 저장

예) uint16_t value;

    EEPROM.get(index, value); // index부터 변수 value의 자료형 크기인 2바이트 값을 읽는다.

 

EEPROM[adress] 함수 : 배열처럼 사용할 수 있다. 

예) uint8_t val = EEPROM[0]; // EEPROM 인덱스 0번의 값을 변수 val에 저장
    EEPROM[1] = val;           // 변수 val의 값을 EPROM 인덱스 1번에 저장

 

for (int i = 0; i < 1024; i++) EEPROM.write(i, 0);  // EEPROM CLEAR 또는 초기화 
    

eeprom.ino
0.00MB

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  uint8_t val;
  for (int i = 0; i < 1024; i++) {
    val = EEPROM.read(i);
    Serial.print(i);
    Serial.print(":"); 
    Serial.print(val);
    Serial.print(", "); 
  }
  Serial.println(); 
  for (int i = 0; i < 1024; i++) { // EEPROM CLEAR 또는 초기화
    EEPROM.write(i, 0);
  }
  EEPROM.write(0, 10);    // 인덱스 0에 10 저장 
  val = 20;
  EEPROM.update(1, val);  // 인덱스 1에 20 저장
  EEPROM.write(2, 30);    // 인덱스 2에 30 저장
  uint16_t value = 1749;  // 2바이트 자료형
  EEPROM.put(3, value);   // 인덱스 3, 4에 저장
  float temp = 18.5;      // 4바이트 자료형
  EEPROM.put(5, temp);    // 인덱스 5, 6, 7, 8에 저장
  for (int i = 0; i < 9; i++) {
    Serial.print("EEPROM index");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(EEPROM.read(i)); // 인덱스 0 ~ 8 값 읽기
  }
  uint16_t value_read;
  Serial.println(EEPROM.get(3, value_read)); // 인덱스 3에서 자료형 크기 2바이트 만큼 읽고 변수에 저장후 그 값 출력
  float temp_read;
  EEPROM.get(5, temp_read);   // 인덱스 5에서 자료형 크기 4바이트 만큼 읽고 변수에 저장
  Serial.println(temp_read);
}

void loop() {

}

 

EEPROM의 주 사용 목적은 휘발성 메모리(리셋 시 값이 사라진다)인 SRAM에서 변경된 변수의 값을 비휘발성 저장 장치인 Flash Memory에 저장하여 아두이노 리셋 시 초기화 과정에서 저장된 변수의 값을 읽어 리셋되기 전의 변경된 변숫값을 연속해서 사용할 수 있도록 하는 데 있다.

 

 

상기의 목적을 이루기 위해서는 초기화 과정인 setup() 함수 실행 부분에서 EEPROM에 저장되어 있는 값을 읽어 loop() 함수에서 사용할 전역 변수에 저장하는 코드가 있어야만 한다.

 

아두이노 초기화 과정은

1. 스케치의 업로드(프로그램 코드 변경 등)에 의해 실행되는 초기화 과정

2. 아두이노의 리셋 버튼을 누르거나 전원의 차단 및 공급에 의해 리셋되어 실행되는 초기화 과정

상기 두 가지의 경우로 구분할 수 있는데 1번의 초기화 과정에서는 전역 변수를 int temp = 18.5; 로 선언했다고 가정하면 setup() 함수의 EEPROM 코드에 의해 EEPROM상의 어떤 값(리셋되기 전의 변경된 변숫값이 아닌)이 전역 변수 temp에 저장되어 loop() 함수에서 실행되는 코드가 엉뚱한 값으로 설정된 전역 변수를 이용하여 작동하게 될 것이다. 이러한 오류를 방지하기 위해서는 1번의 초기화 과정인 경우에는 setup() 함수 내에서 EEPROM의 값을 전역 변수에 저장하지 않도록 하고 2번의 초기화 과정에서 EEPROM의 값을 전역 변수에 저장하여 리셋 전의 값을 이용한 연속된 동작을 하도록 하여야 한다. 더 나아가 1번의 초기화 과정에 전역 변수의 초기값을 할당된 EEPROM의 각 인덱스에 미리 써주도록 하면 EEPROM의 값이 코드에 의해 생성되기 전에 리셋되는 경우가 발생하여도 오류 없이 정상적으로 작동하게 된다. 

 

이러한 과정은 EEPROM의 특정 인덱스를 플래그처럼 이용하면 쉽게 해결할 수 있다. EEPROM의 인덱스 0번을 플래그로 사용하고 플래그의 값으로 1을 쓴다고 하면 1인 경우와 1이 아닌 경우로 나뉘게 된다. 1번의 초기화 과정에서는 인덱스 0번의 값은 0 또는 255일 것이므로 1이 아닌 경우가 되고 이때 인덱스 0번에 1을 써줘서 다음번 초기화에서 EEPROM 인덱스 0번의 값을 확인했을 때 1을 읽도록 하여 1번의 초기화 과정이 아닌 것을 확정하는 것이다. 이를 코드로 작성해 주면 아래와 같다. 

float temp = 18.5; 

if(EEPROM.read(0) != 1) {   
  EEPROM.write(0, 1);   // eeprom 저장 플래그   
  EEPROM.put(1, temp);  // 인덱스 1, 2, 3, 4에 4바이트 값 저장  
}  
else {  
  EEPROM.get(1, temp);  
}

 

처음 스케치를 업로드하면 시리얼 모니터에 18.5가 출력되어야 하고 아두이노의 리셋 버튼을 눌러 리셋시켰을 때에는 리셋 전의 값이 다시 출력되어야 한다. 하지만 상기 코드에서는 초기값이 변경되어 코드를 변경하고 스케치를 다시 업로드해도 EEPROM의 값은 그대로 저장되어 있어 EEPROM.read(0) == 1의 조건을 만족하므로 초기값 반영이 되지 않게 되는데 이를 해결하기 위하여 시리얼 모니터에서 "reset"을 입력하면 EEPROM의 인덱스 0번의 값을 0으로 쓰도록 하는 코드를 추가해서 해결해 주면 된다. 

if (Serial.available() > 0){    // EEPROM 초기값 반영
    String temp = Serial.readStringUntil('\n');
    if (temp == "reset") {
      EEPROM.write(0, 0);
    }
  }

 

시리얼 모니터의 전송 옵션을 "새 글"로 하고 "reset"을 입력하고 엔터를 입력한 다음 아두이노 리셋 버튼을 눌러 리셋시키면 초기값이 반영되어 18.5부터 출력될 것이다. 초기값을 변경하고 스케치를 새로 업로드할 경우에는 업로드 전에 시리얼 모니터에서 "reset"을 입력한 다음 업로드하면 변경된 초기값이 반영되어 출력 되게 된다. 

 

아래 스케치를 업로드하고 작동 상태를 확인해보자. 

eeprom_setup.ino
0.00MB

#include <EEPROM.h>

float temp = 18.5;

void setup() {
  Serial.begin(9600);
  if(EEPROM.read(0) != 1) { 
    EEPROM.write(0, 1);   // eeprom 저장 플래그 
    EEPROM.put(1, temp);  // 인덱스 1, 2, 3, 4에 4바이트 값 저장
  }
  else {
    EEPROM.get(1, temp);
  }
}

void loop() {
  Serial.println(temp);  // 스케치 업로드후 맨처음 18.5가 출력되어야 한다. 
  delay(1000);
  temp = temp + random(-5, 5);
  EEPROM.put(1, temp);
  if (Serial.available() > 0){    // EEPROM 초기값 반영
    String temp = Serial.readStringUntil('\n');
    if (temp == "reset") {
      EEPROM.write(0, 0);
    }
  }
}

 

아두이노의 EEPROM 값을 모두 0으로 초기화 하기 

#include <EEPROM.h>

void setup() {
  for (int i = 0 ; i < EEPROM.length() ; i++) EEPROM.write(i, 0); // EEPROM Clear
}

void loop() {

}

 

ARDUINO_EEPROM_INI.zip

ARDUINO_EEPROM_INI.zip
0.00MB

 

관련 글

[arduino] - 아두이노 - 와이파이 원격제어, ESP01 WiFiEsp.h 라이브러리 webserver

[arduino] - 아두이노 - 와이파이 매니저, ESP01 soft AP를 통해 공유기 연결용 아이디와 비밀번호 설정하기

 

 

+ Recent posts