2017年9月11日月曜日

内蔵EEPROMの操作

ArduinoUNOで使用されているATmega328には、電源が切れても値を保持できるEEPROMというメモリが1Kバイトあります。 パラメータの保存に使用するためのサンプルを作成。



#include <EEPROM.h>

/* 制御用のパラメータ定義 */
struct LightSetting_t {
  unsigned int offWaitSec;  /* 消灯時間(sec) */
  unsigned int border;      /* 検出距離(cm) */
} lightSet;

/* EEPROM用の操作ボタン */
int statusPin2 = HIGH;  /* 書き込み */
int statusPin3 = HIGH;  /* 読み込み */
int statusPin4 = HIGH;  /* ダンプ出力 */

/*
 * 設定更新
 */
void updateSetting() {
  if (LOW == digitalRead(4)) {
    if (statusPin4 == HIGH) {
      statusPin4 = LOW;
      dumpEEPROM();
    }
  } else {
    statusPin4 = HIGH;
  }
  if (LOW == digitalRead(3)) {
    if (statusPin3 == HIGH) {
      statusPin3 = LOW;
      byte* p = (byte*) &lightSet;
      unsigned int adr = 0;
      for (unsigned int i = 0; i < sizeof(lightSet); i++)
        *p++ = EEPROM.read(adr++);

      Serial.println("設定値");
      Serial.print("消灯時間(sec)=");Serial.println(lightSet.offWaitSec);
      Serial.print("検出距離(cm) =");Serial.println(lightSet.border);
      if (lightSet.offWaitSec == 0x0000 || 0xFFFF == lightSet.offWaitSec ) {
        Serial.println("設定値初期化");
        lightSet.offWaitSec = 10;
        lightSet.border = 5;
        Serial.print("消灯時間(sec)=");Serial.println(lightSet.offWaitSec);
        Serial.print("検出距離(cm) =");Serial.println(lightSet.border);
      }
    }
  } else {
    statusPin3 = HIGH;
  }
  if (LOW == digitalRead(2)) {
    if (statusPin2 == HIGH) {
      statusPin2 = LOW;
      // それぞれ+1する
      lightSet.offWaitSec++;
      lightSet.border++;

      Serial.print("設定書き込み");
      byte* p = (byte*) &lightSet;
      unsigned int adr = 0;
      for (unsigned int i = 0; i < sizeof(lightSet); i++)
        EEPROM.write(adr++, *p++);
      Serial.println(" ->書き込み完了");
    }
  } else {
    statusPin2 = HIGH;
  }
}

void dumpEEPROM() {
  unsigned int adr = 0;
  unsigned int adrLength;
  byte data;
  char ch[6];
  Serial.print(F("EEPROM size="));
  Serial.println(EEPROM.length());
  adrLength = 32; // 最初の32バイトまで表示
  for(adr = 0; adr < adrLength; adr++) {
    if ((adr % 16) == 0) {
      sprintf_P(ch, PSTR("%04X:"), adr);
      Serial.print(ch);
    }
    sprintf_P(ch, PSTR( "%02X ") ,EEPROM.read(adr));
    Serial.print(ch);

    if ((adr % 16) == 15) {
      Serial.println();
    }
  }
}
void setup() {
  /* EEPROMのダンプ出力、読み込み、書き込み用のボタン */
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  
  Serial.begin(115200);
}

void loop() {
  updateSetting();
  delay(100);
}

ボタンは3つ用意。すべて内蔵プルアップ、LOWの場合がボタンを押した状態となります。
連続して処理されないよう、ステータス管理して最初の押下のみ処理


●動作確認

・ダンプ出力ボタン
EEPROM size=1024
0000:FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 
0010:FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF F
※EEPROMの初期値はすべてのbitが1でした。

・読み込みボタン
消灯時間(sec)=0
検出距離(cm) =0
設定値初期化
消灯時間(sec)=10
検出距離(cm) =5

初期値がどうかは、構造体に読み込んだ後のbitがオール0か1で判断。
unsigned int 型は2バイト使用するので 0x0000と0xFFFFと比較

・書き込みボタン+ダンプ出力ボタン
設定書き込み ->書き込み完了
EEPROM size=1024
0000:0B 00 06 00 FF FF FF FF FF FF FF FF FF FF FF FF 
0010:FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 

「0B 00」 =11、「06 00」=6 なので書き込み成功です。


●その他
・sprintf_P(ch, PSTR("%04X:"), adr);
   PSTR("xxx")は、F("文字列")と同じくSRAMではなくFlashメモリにデータを置くときに使用します。
 sprintfで F()マクロを使おうとしたらエラーになってしまったので、色々調べると上の書き方のようです。 後ろに「_P」が付いているのが対応関数のようです。
 RAM使用量は数バイトしか変わりません。メモリが足りないとかでなければsprintfで良さそうです。
 ちなみにsprintfをすべてsprintf_Pに置き換えた場合、スケッチのサイズは変わりませんが、両方混在する場合は70バイトくらい差がでました。使う場合は混在させない方が節約になるかと。


・EEPROM.length()
 何を返しているのか、ヘッダファイルから定義内容を調べて見ました。

 〜arduino-1.8.2/hardware/arduino/avr/libraries/EEPROM/src/EEPROM.h
 uint16_t length()                    { return E2END + 1; }

 E2ENDに+1を返しているけど、このファイルには記載されていない。
 下記2つをインクルードしているので参照する。
 #include <avr/eeprom.h>
 #include <avr/io.h>

 arduino-1.8.2/hardware/tools/avr/avr/include/avr/eeprom.h
 arduino-1.8.2/hardware/tools/avr/avr/include/avr/io.h

 どちらにも記載がなかったが、io.hには下記の様にターゲットCPU毎の分岐があった。
 #if defined (__AVR_AT94K__)
 #  include <avr/ioat94k.h>

 使用CPUはATmega328なので探すと以下がヒット
 #elif defined (__AVR_ATmega328P__)
 #  include <avr/iom328p.h>
 #elif (defined __AVR_ATmega328__)
 #include <avr/iom328.h> 
 ※iom328.hはiom328p.hをインクルードしているのみだった

 以下の様に定義されていた。0x3FF=1023なので、EEPROM.length()は1024
 #define E2END        0x3F

 ちなみにATmega168の方は以下の定義となっているので、EEPROM.length()は512となる
 #define E2END        0x1FF

 データシートを確認しても同じ値なので、内蔵のダンプ出力に使っても問題ないです。
 Table 2-1. Memory Size Summary
 ATmega168A   512Bytes
 ATmega168PA 512Bytes
 ATmega328    1KBytes
 ATmega328P  1KBytes


0 件のコメント:

コメントを投稿