자작 정수기용 컨트롤러 6. EEPROM 다루기
컨트롤러 제작을 위해 또 한가지 고려사항은 데이터 관리입니다. 크게 2가지 데이터가 필요한데, 첫번째는 유효정수량입니다. 각 필터마다 유효정수량이 있고, 이 정수량을 초과하면 필터를 교환해야 합니다. 지금 만들고 있는 컨트롤러의 기능도 크게 두 가지인데, 그 중 하나가 바로 이 유효정수량을 초과해서 정수했을 경우 필터를 교환 하도록 알려주는 기능입니다. 이게 없어서 현재는, 각 필터마다 3개월, 6개월, 1년 정도 있다가 교환해주는데, 거의 제때 교환한적이 없습니다. 잊고 지나가는 경우가 태반이죠!^^;;
이 교환주기 알림을 위해서, 유효정수량과 현재까지의 실제정수량을 체크하도록 코딩할 예정입니다. 두번째 필요한 데이터가 바로 실제정수량입니다.그 외에 몇가지 더 필요할 듯한 정보는 다음에 고려하겠습니다.
아두이노 소스코드상에서 데이터를 저장하는 변수는 전원이 끊어질 경우 데이터를 유실합니다. 그래서 전원 공급이 중단되고 아두이노가 꺼져도 데이터를 보관할 수 있는 비휘발성 저장소가 필요한데, EEPROM 이 바로 이러한 특징을 갖습니다.
제가 사용하게 될 보드인 DFROBOT WiDO 보드는 Arduino Leonardo 보드와 호환되고 아두이노 레오나르도 보드가 갖고 있는 저장소(메모리)는 3가지로 다음과 같습니다.
- 32KB Flash Memory ( 4KB는 부트로더에 할당)
- 2.5KB SRAM
- 1KB EEPROM
또, 제가 테스트용으로 쓰고 있는 아두이노 우노(Arduino UNO)보드는 아래와 같습니다.
- 32KB Flash Memory ( 0.5KB는 부트로더에 할당)
- 2KB SRAM
- 1KB EEPROM
아래가 전체 소스입니다.
/*
* EEPROM Write
*
* Stores values read from analog input 0 into the EEPROM.
* These values will stay in the EEPROM when the board is
* turned off and may be retrieved later by another sketch.
*/
#include <EEPROM.h>
/** the current address in the EEPROM (i.e. which byte we're going to write to next) **/
int addr = 0;
void setup(){ /** Empty setup. **/}
void loop()
{
/***
Need to divide by 4 because analog inputs range from
0 to 1023 and each byte of the EEPROM can only hold a
value from 0 to 255.
***/
int val = analogRead(0) / 4;
/***
Write the value to the appropriate byte of the EEPROM.
these values will remain there when the board is
turned off.
***/
EEPROM.write(addr, val);
/***
Advance to the next address, when at the end restart at the beginning.
Larger AVR processors have larger EEPROM sizes, E.g:
- Arduno Duemilanove: 512b EEPROM storage.
- Arduino Uno: 1kb EEPROM storage.
- Arduino Mega: 4kb EEPROM storage.
Rather than hard-coding the length, you should use the pre-provided length function.
This will make your code portable to all AVR processors.
***/
addr = addr + 1;
if(addr == EEPROM.length())
addr = 0;
/***
As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an
EEPROM address is also doable by a bitwise and of the length - 1.
++addr &= EEPROM.length() - 1;
***/
delay(100);
}
코드별로 알아보겠습니다.
/*
* EEPROM Write
*
* Stores values read from analog input 0 into the EEPROM.
* These values will stay in the EEPROM when the board is
* turned off and may be retrieved later by another sketch.
*/
데이터를 아날로그 0번 핀으로 부터 입력 받아 EEPROM 에 저장합니다. 저장된 데이터는 전원이 꺼져도 보존되며, 다른 소스코드를 올려서 해당 메모리를 참조하는 것도 가능합니다.
/** the current address in the EEPROM (i.e. which byte we're going to write to next) **/
int addr = 0;
메모리를 액세스하기 위한 주소값을 저장하는 변수입니다. 현재 저장된 값은 다음 데이터가 저장될 위치를 가리키게 됩니다. EEPROM 메모리 전체 크기가 1KB 이기 때문에 총 1024개의 Byte로 구성되고 이는 0 에서 1023 의 숫자를 사용해서 직접 액세스할 수 있습니다. 즉, 0번 이면 첫번째 byte, 9번이면 10번째 byte 값에 접근할 수 있습니다. 즉, addr 처럼 따로 주소값을 다루기 위한 변수를 선언하지 않더라도 마치 절대주소처럼 직접 해당하는 숫자를 통해서 원하는 번지수에 읽고 쓰기가 가능하게 됩니다. 하지만, 아두이노 보드 종류에 따라서 이 메모리의 양이 다르고, 또 항상 byte 단위의 데이터만 다루지는 않기 때문에, 위 예제처럼 번지값을 위한 변수를 따로 두는 것이 좋습니다.
int val = analogRead(0) / 4;
아두이노 보드의 아날로그(Analog) 입력값은 0 에서 1023 까지입니다. 하지만 이 예제에선 바이트(Byte) 단위로 저장하기 때문에 0에서 255까지의 값만 저장할 수 있습니다. 이것을 해결하기 위해 아날로그 입력값을 4로 나눠 줍니다.
EEPROM.write(addr, val);
실제로 EEPROM에 변수값을 저장하는 코드입니다. addr에 저장된 현재 번지수에 val 값을 저장합니다. 만약 "EEPROM.write(0, val);"라고 한다면 0번지에 값을 저장하게 됩니다.
addr = addr + 1;
if(addr == EEPROM.length())
addr = 0;
오버플로우를 방지하기 위해서 번지값 증가 후 주소 범위안에 있는지 체크합니다.
다음엔, 이 소스를 약간 바꿔서 1 부터 20까지의 숫자를 저장하는 코드를 작성했습니다.
#include <EEPROM.h>
int addr = 0;
int val = 0;
void setup()
{
Serial.begin(9600);
}
void loop()
{
while(val < 20 )
{
val = val + 1;
EEPROM.write(addr, val);
addr = addr + 1;
if ( val == 20)
{
Serial.println("Store Completed!");
Serial.print("Last Address : ");
Serial.print(addr);
}
delay(100);
}
}
숫자 1 부터 20까지 1씩 증가한 값을 0번지 부터 19번지까지 저장합니다. 마지막 20번째에는 결과를 시리얼 모니터에 출력합니다. setup() 함수에 넣었으면 한번만 실행되므로 괜찮을 텐데, loop() 함수에 넣느라고 무한반복 하지 않도록 중단점을 만들어야 했네요! 아래 그림은 예제 결과 화면입니다.
실제 정수량은 유효정수량을 넘을 수 없기 때문에(넘어가도 따질 필요는 없기 때문에....) 저장된 값은 총 숫자 4자리입니다. 우선 주소를 다루기 쉽기 때문에 byte 단위로 값을 저장하기로 구상했습니다. 바이트 데이터형은 0~255까지 저장할 수 있기 때문에 숫자 4자리를 저장할 수 없습니다. 그래서, 두자리씩 나눠서 저장하기로 했습니다.
address 0번은 1단계 필터의 유효정수량 앞 두자리, 5번은 뒤 두자리이고, address 10번은 1단계 필터의 실제 정수량 앞 두자리, 15번은 뒤 두자리, 이런 식으로 구성했습니다. 너무 무식한가요?^^;; 아두이노 입문한지 몇 개월 안되어 예제 이후에 처음으로 만드는 프로젝트라서, 이게 최선입니당!^^
위 사진에서 보듯이, 1단계 필터는 유효정수량 1880리터, 2단계 1850, 3단계 5500, 4단계 3650, 5단계는 사진에 는 안보이지만 필터 자체에 3650 리터로 표시되어 있습니다. 저장될 번지와 값은 아래 표와 같습니다.
Address |
Filter |
Value |
0 |
1 단계 |
18 |
1 |
2 단계 |
18 |
2 |
3 단계 |
55 |
3 |
4 단계 |
36 |
4 |
5 단계 |
36 |
5 |
1 단계 |
80 |
6 |
2 단계 |
50 |
7 |
3 단계 |
00 |
8 |
4 단계 |
50 |
9 |
5 단계 |
50 |
위 표대로 저장하는 프로그램은 아래와 같이 코딩했습니다. 역시 setup() 함수에 넣을 코드를 loop() 함수에 구현해서, 정수량 저장을 무한반복하네요!
#include <EEPROM.h>
int addr = 0;
int value;
void setup()
{
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
}
void loop()
{
int i;
byte temp;
EEPROM.write(0, 18);
EEPROM.write(1, 18);
EEPROM.write(2, 55);
EEPROM.write(3, 36);
EEPROM.write(4, 36);
EEPROM.write(5, 80);
EEPROM.write(6, 50);
EEPROM.write(7, 00);
EEPROM.write(8, 50);
EEPROM.write(9, 50);
delay(1000);
for ( i = 0; i < 9; i++ )
{
value = (EEPROM.read( i ) * 100) + EEPROM.read( i+5 );
Serial.println(value, DEC);
delay(1000);
}
Serial.println("");
Serial.println("");
delay(2000);
}
그 결과 값입니다.
위 프로그램을 통해 각 필터의 유효 정수량을 EEPROM 메모리에 디폴트값으로 저장했습니다. 실제 정수량은 아직 0(zero) 이지만, 프로그램 테스트를 위해서 유효정수량과 동일한 값을 저장하도록 하겠습니다.
이 외에 추가로 필요한 값이 있습니다. 바로 Flowrate 값인데, 실제 정수량을 체크하기 위해서 Flowmeter(유량센서)를 사용할 예정입니다. 이 유량센서는 물의 흐름이 있을 때마다 펄스값을 생성해서 보내는데, 이 펄스값을 카운트하면 실제 정수량을 알 수 있습니다. 이 때, 이 펄스값을 우리가 필요한 리터값으로 계산해야 하는데, 이 때 펄스 카운트에 곱해주는 값을 Flowrate 라 하겠습니다. 이 Flowrate 값을 코드에 직접 쓸 수도 있지만, 혹시나, 정수기 설치 후 실측한 리터값과 다를 경우 미세조정(켈리브레이션)을 해줄 필요가 있을 듯 해서 변수값으로 처리하고, EEPROM에 저장해서 사용할 생각입니다.
또, Flowrate 값은 소수점 이하 자리도 필요한데 byte 값은 0 ~ 255 정수값만 저장 가능합니다. 이를 위해서 소수점은 따로 보관하도록 했습니다.
언급한 내용을 포함한 소스코드는 아래와 같습니다.
#include <EEPROM.h>
void setup()
{
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
}
void loop()
{
int addr;
int value;
float val1;
float val2;
float flowrate;
EEPROM.write(0, 18);
EEPROM.write(5, 80);
EEPROM.write(1, 18);
EEPROM.write(6, 50);
EEPROM.write(2, 55);
EEPROM.write(7, 0);
EEPROM.write(3, 36);
EEPROM.write(8, 50);
EEPROM.write(4, 36);
EEPROM.write(9, 50);
EEPROM.write(10, 18);
EEPROM.write(15, 80);
EEPROM.write(11, 18);
EEPROM.write(16, 50);
EEPROM.write(12, 55);
EEPROM.write(17, 0);
EEPROM.write(13, 36);
EEPROM.write(18, 50);
EEPROM.write(14, 36);
EEPROM.write(19, 50);
EEPROM.write(20, 7);
EEPROM.write(21, 50);
delay(1000);
for ( addr = 0; addr < 5; addr++ )
{
value = (EEPROM.read(addr) * 100) + EEPROM.read(addr + 5);
Serial.println(value, DEC);
value = (EEPROM.read(addr + 10) * 100) + EEPROM.read(addr + 15);
Serial.println(value, DEC);
delay(1000);
}
val1 = EEPROM.read(20);
val2 = EEPROM.read(21);
flowrate = val1 + ( val2 / 100 );
Serial.println(flowrate, DEC);
Serial.println("");
delay(2000);
}
이상입니다.