ArduinoのライブラリでSDカードにデータを保存する

組み込みエンジニア

こんにちは、ENGかぴです。

Arduino環境ではSDカードを使用するための標準ライブラリが実装されているため簡単にSDカードにアクセスしてデータの読み書きができます。Arduino UNOの拡張基板であるSD CARD SHIELDを使ってSDカードを操作しました。

SD CARD SHIELDはアイキャッチ画像のようにArduino UNOに差し込むだけで使用することができます。Arduino UNO(以下Arduinoとします。)を対象とします。Arduinoのライブラリを使用して動作確認したことをまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

SDカードライブラリを使用する

Arduinoでは標準ライブラリでSDカードを操作することができます。ライブラリのインクルードからを初期化及び関数の使用方法を説明します。

ライブラリの準備と初期化

#include <SPI.h>
#include <SD.h>
#define SD_CS 4

File myfile; //SDカードの状態を格納数変数

void setup() {

  if(!SD.begin(SD_CS)){
    Serial.println("initialization failed!");
    while (1);
  }
}

SDカードへのアクセスはSPI通信を使用するためSD.hとSPI.hの2つのライブラリをインクルードする必要があります。SDカードのスレーブ選択のためDOを指定する必要があります。

SDカードにアクセスした際の戻り値をセットするFile型のクラス変数を準備します。SDカードの中のファイルに関する情報等を管理するために使用します。

SDライブラリのbegin()関数を使用してSDカードに関する情報を初期化します。引数にはSDカードを選択するためのスレーブセレクト(SS)のピン番号を指定します。

SDカードが挿入されていない場合など失敗した場合は戻り値がfalseになるので失敗したときの処理を入れます。例ではwhile(1)として処理が進まないようにしています。

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

SDカードからデータを読み出し

String filepath = "sample.txt"; //SDカード内に保存するファイル名

 if (SD.exists(filepath)) { //ファイルが存在するか
    Serial.println("sample.txt exists.");
    myfile = SD.open(filepath,FILE_READ); //ファイルを開く

    if(myfile){ //ファイルが開けた場合
      while(myfile.available()){
        str ="";
        str = myfile.readStringUntil('\n'); //読み込み
      }
      myfile.close(); //ファイルを閉じる
    }
  } else {
    Serial.println("sample.txt doesn't exist.");
  }

SDカードからデータを読み出す場合にファイルが存在しているかをexists()関数で確認します。引数には開くファイル名を含めたパスを指定します。

ファイルが存在するかを確認せずにopen()関数で読み込みを行っても読み込み失敗になるためexists()で確認してからファイルを開くかは好みになります。

ファイルが存在する場合はopen()関数でファイルを読み込み専用で開きます。第1引数にはファイルのパスを指定し、第2引数に読み込みを示すFILE_READを指定します。

ファイルのopenに成功するとFileオブジェクト(myfileで宣言した変数)に戻り値として状態が引き継がれるためmyfileを使ってデータの読み込みを行います。

Fileオブジェクトのavailable()関数を使って読み込むデータが存在するかを確認します。データが存在する場合は0より大きな値になるためRead()関数を使ってデータを読み込みます。例ではReadStringUntil()関数を使って改行コードが見つかるまでデータを読み込んでいます。

SDカードに保存するデータの区切りを改行コードに指定するようにあらかじめルールを決めておくことで効率よくデータの読み込みができます。

データを読み込んだ後はファイルを閉じるためclose()関数を使用します。

すき間時間で資格をゲット【STUDYing(スタディング)】

SDカードにデータを書き込む

  myfile = SD.open(filepath,FILE_WRITE);

  if( myfile ){ //ファイルが開けたら書き込む
    myfile.print("push btn:");
    myfile.println(sampcnt);
    myfile.close(); //ファイルを閉じる
  }

open()関数でファイルを書き込みモードで開きます。第1引数にはファイルのパスを指定し、第2引数に書き込みを示すFILE_WRITEを指定します。ファイルが開けたらprint()関数を使用してデータを書き込みます。

write()関数を使用してもデータの書き込みはできますが文字列として書き込むのであればprint()関数の方が管理がしやすいです。

例ではpush btn:の文字列とボタンを押した回数をSDカードに書き込んでいます。データの書き込みの区切りとして改行コードを入れるためprintln()関数で改行コードを入れて書き込んでいます。

データを書き込んだ後はファイルを閉じるためclose()関数を使用します。

SDカード操作に使用する関数

SDカードの操作に頻繁に使用する関数をまとめました。

関数説明
Read()
Read(引数1,引数2)
SDカードのデータを読み込みます。1バイトずつ読み込む場合はRead()を使用します。複数バイトをまとめて読み込む場合はRead(引数1,引数2)を使用します。引数1は読み込んだデータを格納する配列などのアドレス、引数2は読み出すデータ数を指定します。
ReadStringUntil(引数)引数に指定した文字を確認するまで文字列として読み込みます。改行コードを区切りにする場合は’\n’を指定します。
remove(引数)引数で指定したファイルを削除します。
mkdir(引数)
rmdir(引数)
mkdir()は引数に指定したSDカードのパスにディレクトリを生成します。rmdir()は指定したディレクトリを削除します。ただしファイル等がある場合は削除不可となります。
write(引数)
write(引数1,引数2)
1バイトずつ書き込む場合はwrite()を使用します。引数に書き込むデータを指定します。複数バイトを一気に書き込む場合はwrite(引数1,引数2)を使用します。引数1は書き込む値を格納している配列などのアドレス、引数2は書き込むサイズを指定します。
print(引数)
println(引数)
引数に文字列を格納している配列などのアドレスを指定します。直接文字列を入力することもできます。println()関数を使用すると文字列の最後尾に改行コードが挿入されます。
SDカードライブラリでよく使用する関数

テキストデータで保存すると文字列でデータが保存されるため確認がしやすくなります。print()関数は文字列として保存できるため使用頻度が多くなります。またテキストデータではデータの区切りを判断するため改行コードを使用することが多いためReadStringUntil()関数を使用する頻度が多くなる印象です。

動作確認

SDライブラリの動作確認の回路図
SDライブラリの動作確認の回路図

SD CARD SHILDをArduino UNOに挿入して動作確認を行います。SW1とSW2はSD CARD SHILD側の6、7番に配線していますがSD CARD SHILDはUNOのピンを延長しているだけなので配線上同じになります。

SW1を押すとSDカードにボタンを押した回数を保存するようにしています。SW2は5秒間長押しするとSDカードに保存したファイルを削除します。

SW1でデータを保存しておき次の電源ONでSDカードに保存したデータを読み込んでシリアルモニタに表示して動作確認を行います。動作確認後SW2を長押ししてファイルを削除してデータが削除できているかの確認を行います。

SDカード内のデータの確認 上部:sample.txtを確認 下部:ファイルの中身
SDカード内のデータの確認 上部:sample.txtを確認 下部:ファイルの中身

SW1を押すとsample.txtが生成されていることが確認できました。ファイルの中身を確認するとpush btn:の文字列と押した回数が保存できていることが確認できました。

シリアルモニタでの確認
シリアルモニタでの確認

Arduino IDEでシリアルモニタを起動するとリセットされて初期化を行いますがSDカード内にsample.txtが存在する場合はデータを読み込んでシリアルモニタに表示しています。シリアルモニタにSDカード内に保存したデータが読み込まれていることが確認できました。

SW2を長押しするとSDカード内のsample.txtが削除されているのも確認できました。

あなたのキャリアのお供に「生涯学習のユーキャン」

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。コメントなど細かな部分で間違っていたりやライブラリの更新などにより動作しなくなったりする可能性があります。参考としてお使いいただければと思います。

#include <SPI.h>
#include <SD.h>

#define SD_CS 4
#define PIN_DI1 6
#define PIN_DI2 7

#define TIME_UP 0
#define TIME_OFF -1
#define BASE_CNT 10 //10msがベースタイマとなる
#define DIFILT_MAX 4
#define TIM_DIFILT 1
#define TIM_REMOVE 500

typedef struct DIFILT{
  uint8_t wp;
  uint8_t buf[DIFILT_MAX];
  uint8_t di;
};

String filepath = "sample.txt";
uint32_t beforetimCnt = millis();
int16_t timSdWait = TIME_OFF;
int16_t timDifilter = TIME_OFF;
int8_t cnt10ms;
DIFILT diData[2];
bool flg;
uint32_t sampcnt;
File myfile;
String str;

/* Local function prototypes */
void mainTimer(void);
void DiFilter(void);

void setup() {
  uint8_t i;

  pinMode(PIN_DI1,INPUT_PULLUP);
  pinMode(PIN_DI2,INPUT_PULLUP);
  
  Serial.begin(115200);
  Serial.print("Initializing SD card...");

  if(!SD.begin(SD_CS)){
    Serial.println("initialization failed!");
    while (1);
  }

  Serial.println("initialization done.");

  if (SD.exists(filepath)) {
    Serial.println("sample.txt exists.");
    myfile = SD.open(filepath,FILE_READ);

    if(myfile){
      while(myfile.available()){
        str ="";
        str = myfile.readStringUntil('\n');
        Serial.println(str);
      }
      myfile.close(); //ファイルを閉じる
    }
  } else {
    Serial.println("sample.txt doesn't exist.");
  }

  timDifilter = TIM_DIFILT;
  i=0;
  while( i < 10){
    mainTimer();
    DiFilter();
    delay(10);
    i++;
  }
}

void loop() {
  
  mainTimer();
  DiFilter();

  if( diData[0].di == 0 ){
    if( flg == false ){
      flg = true;
      ++sampcnt;
      myfile = SD.open(filepath,FILE_WRITE);

      if( myfile ){ //ファイルが開けたら書き込む
        myfile.print("push btn:");
        myfile.println(sampcnt);
        myfile.close(); //ファイルを閉じる
        Serial.print("push btn:");
        Serial.println(sampcnt);
      }
    }
  }
  else{
    flg = false;
  }

  if( diData[1].di == 0){
    if( timSdWait == TIME_OFF){
      timSdWait = TIM_REMOVE;
    }
  }
  else{
    timSdWait = TIME_OFF;
  }

  if( timSdWait == TIME_UP ){
    timSdWait = TIME_OFF;
    SD.remove(filepath); //ファイルを削除
    Serial.println("file delete");
  }
}
/* タイマ管理 */
void mainTimer(void){

  if( millis() - beforetimCnt >= BASE_CNT){
    beforetimCnt = millis();

    if( timSdWait > TIME_UP ){
      timSdWait--;
    }
    if( timDifilter > TIME_UP ){
      --timDifilter;
    }
  }
}
/* DIフィルタ */
void DiFilter(void){
  bool boo = true;
  uint8_t i;

  if( timDifilter == TIME_UP ){
    timDifilter = TIM_DIFILT;

    diData[0].buf[diData[0].wp] = digitalRead(PIN_DI1);
    diData[1].buf[diData[1].wp] = digitalRead(PIN_DI2);

    for(uint8_t no=0; no < 2; no++){
      for( i=1; i < sizeof(diData[no].buf);i++){
        if( diData[no].buf[i - 1] != diData[no].buf[i]){
          boo = false;
        }
      }

      if(boo){ //データがすべて一致なので採用する
        diData[no].di = diData[no].buf[0];
      }
      if( ++diData[no].wp >= sizeof(diData[no].buf)){
        diData[no].wp = 0;
      }
    }
  }
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Code Camp完全オンラインのプログラミング個人レッスン【無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました