2022年8月18日木曜日

ArduinoでSDカードを使用する

AdafruitのData Logger Shieldを使用して,ArduinoからSDカードへデータを保存した.


 Arduinoで測定したデータはシリアル通信経由でPC上に保存できるが,長期間にわたり記録する際などPCを使わずArduino単体で保存したいこともある.このような場合,SDカードシールドを使用することでArduinoから直接SDカードへ保存することができる.

 今回使用したSDカードシールドはAdafruitのData Logger Shieldで,RTC(Real Time Clock)機能が搭載されているのでデータの取得日時も保存できる.

 SDカードスロットは標準サイズの大きさで,FAT16またはFAT32形式のフォーマットに対応している.RTCにはPCF8523が使用されており,対応電池はCR1220相当である.詳しい仕様はこちらを参照のこと.


パッケージのインストール

 RTCを使用するにはRTClibというパッケージが必要なのでパッケージマネージャからインストールを行う.似たような名前がいくつかあるが,Adafruitが提供しているものをインストールする.


RTCの時刻合わせ

 Arduinoのサイトの手順に従い,RTCLibのサンプルスケッチpcf8523を用いてPCの時刻をRTCに設定する.シリアルモニタに表示されている時刻がPCと同じであれば成功.うまくいかない場合はrtc.adjust(DateTime(F(__DATE__), F(__TIME__)));をif文の外に出せば成功するかもしれない.

SDカードへの書き込み

 まず,SDカードが正常に認識されているかを確認するため,SDのサンプルスケッチにあるCardInfoを実行する.このとき,スケッチのコメントにあるように定数chipSelectを10に変更しておく.シリアルモニタに以下が表示されていれば成功.

 次に,実際にSDカードに書き込みテストを行う.テストにはサンプルスケッチのDataloggerを使用する.このスケッチではanalogReadで取得したアナログ入力A0-A2の値を,カンマ区切りでDATALOG.TXTという名前のファイルに保存する.

 CardInfoと同様にchipSelectの値を10に変更するのを忘れないこと.スケッチの実行は数秒間で十分.ArduinoからのSDカードの取り出しは本体の電源を切った後に行った方がいい.カードにDATALOG.TXTというファイルが存在すれば保存成功で,ファイルの中身は以下のようになっている.


 このスケッチではSDカードへのデータ保存は追記モードとなっており,記録を再開すると既存のデータの次の行からデータが追加される.また,ファイル形式はCSVもいけるようだ.CSVであればExcelでの利用が非常に楽になる.
 なお,SDカードへのデータ保存中はSDカード横の赤色LEDが点灯する.

センサデータを時刻と共にSDカードへ保存する

 例として,CTセンサで測定した交流電流値を測定時刻と共にSDカードへ保存してみる.CTセンサはこちらで解説したものを3つ使用し,アナログ入力A0-A2に接続した.スケッチはサンプルスケッチのDataloggerとpcf8523を流用しながら下のように作成した.
//サンプルのSDにあるDataLoggerを流用して変更
#include < spi.h >
#include < sd .h >
#include "RTClib.h"
RTC_PCF8523 rtc;

const int chipSelect = 10;
//保存ファイル名
const String fileName = "data.csv";
//SDカードへの記録間隔[ms]
const long loggingInterval = 2000;

long nn;

//SDカードとRTC関連の初期化処理.
void setup() {
  nn = 0;
  
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  // initialize the SD card
  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
    
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    while (1);
  }
  Serial.println("card initialized.");

  //以降はRTClibサンプルのpcf8523を流用
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }
  
  //RTCが初期化されていない場合は接続先のPCの時間を設定する
  //うまく設定できない場合はrtc.adjust(DateTime(F(__DATE__), F(__TIME__)));をifの外に出す
  if (! rtc.initialized() || rtc.lostPower()) {
    Serial.println("RTC is NOT initialized, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
    //
    // Note: allow 2 seconds after inserting battery or applying external power
    // without battery before calling adjust(). This gives the PCF8523's
    // crystal oscillator time to stabilize. If you call adjust() very quickly
    // after the RTC is powered, lostPower() may still return true.
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

  // When the RTC was stopped and stays connected to the battery, it has
  // to be restarted by clearing the STOP bit. Let's do this to ensure
  // the RTC is running.
  rtc.start();
}

void loop() {
  // make a string for assembling the data to log:
  String dataString = "";
  float sensor;
  float vol;
  float cur;
  long n;
  
  n = millis();

  //delayの値が大きすぎると動かなくなるため,mills()とnn+loggingIntervalの差がloggingInterval以上か判定するif文で代用.
  //nnはif文内を実行ごとにloggingIntervalを加算して更新.
  if((n - nn) >= loggingInterval){
    nn += loggingInterval;
    
    //日付をyyyy/M/d h:m:s形式で取得
    DateTime now = rtc.now();
    dataString += String(now.year()) + "/" + String(now.month()) + "/" + String(now.day()) + " ";
    dataString += String(now.hour()) + ":" + String(now.minute()) + ":" + String(now.second()) + ",";
  
    //3つのCTセンサアンプ基板からA0-A2に入力された電圧を取得し,交流電流の実効値を計算.
    for (int analogPin = 0; analogPin < 3; analogPin++) {
      sensor = analogRead(analogPin);
      vol = sensor*5.0/1024.0;
      cur = vol*3000.0/(60.0*4.9);
    
      dataString += String(cur);
      if (analogPin < 2) {
        dataString += ",";
      }
    }

    //SDカードに記録
    // open the file. note that only one file can be open at a time,
    // so you have to close this one before opening another.
    File dataFile = SD.open(fileName, FILE_WRITE);

    // if the file is available, write to it:
    if (dataFile) {
      dataFile.println(dataString);
      dataFile.close();
      // print to the serial port too:
      Serial.println(dataString);
    }
    // if the file isn't open, pop up an error:
    else {
      Serial.println("error opening datalog.txt");
    }
  }
}

 このスケッチでは2秒ごとにA0-A2の電圧を交流電流値に変換し,測定時刻と共にCSV形式のファイルに保存する.なお,測定間隔をdelay()で制御すると値が大きいとき処理が止まってしまう(40000ms以上で無理だった)ので,millis()とif文で代用した.
 また,setupでシリアル通信の疎通確認をしておりPCとの通信が確立しない限り処理が進まないと思うのだが,USBアダプタから電源を取ってもなぜか動いた.

保存されたファイルをエクセルで開くと以下のようになる(BCD列が電流値)


 










0 件のコメント:

コメントを投稿