Seeeduino XIAOとSDカードで人感センサーの動作履歴を保存する

組み込みエンジニア

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

Seeeduino XIAOはSDカードを使用するための標準ライブラリが実装されているため簡単にSDカードにアクセスしてデータの読み書きができます。人感センサーの動作履歴をSDカードに書き込んで動作確認を行いました。

SDカードモジュールにはmicroSDカードスロットレベルシフタ付きブレークアウト基板キット:AE-microSD-LLCNV(秋月電子)を使用しています。人感センサーはSB612A(秋月電子)を使用しています。Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。

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

人感センサー(SB612A)の使い方

SB612Aは人体検出用に開発された集電センサーモジュールであり動作確認しやすいようにボート化されています。人感センサーはPIR(Passive Infrared Ray)と表現され周囲の赤外線の変化によって人間の接近を検知するセンサーです。下記記事では人感センサー(SB612A)を使って動作確認したことをまとめています。

出力方法の選択

人感センサに供給する電源と出力をSeeeduino XIAOのDIに入力するとDIの状態変化によってセンサーの動作が確認できます。電源はモジュール内にコンバータが内蔵されているためDC3.3VからDC12Vまで印加することができます。

人感センサーの出力はCMOS出力及びオープンコレクタ出力を選択して使用することができます。今回はCMOS出力を使用しています。

調整できる項目

人感センサーSB612A

SB612Aでは調整できる項目が3つあります。

  1. 感度調整(SENS)
  2. 調光(DARK_ADJ)
  3. 遅延時間調整(DELAY_TIME)

SB612A内部のセンサードライバBM612に入力する電圧を可変抵抗で分圧して調整します。抵抗を表面から見た時反時計回りに回すと可変抵抗の抵抗が大きくなります。

感度調整(SENS)

BM612のSENSに入力する電圧(PIR信号を検出するための閾値)を調整します。1MΩと可変抵抗で分圧することで閾値が決まります。VSSは最小の閾値となりVDD/4が最大の閾値となります。SENSの値が大きいほどPIRの信号検出の感度が低くなります。

調光(DARK_ADJ)

BM612のOENに入力する電圧を調整します。OEN電圧がLOWからHIGHに立ち上がる際に0.4VDD(1.2V)より高い場合に検出してVOUTが有効になります。HIGHからLOWに立ち下がる場合0.2VDD(0.6V)よりも低い場合にVOUTが無効になります。

OENの調整は可変抵抗を高くすると分圧の関係から電圧が低くなるためVOUTが出力しにくくなります。抵抗を高くするほど明るい場所での感度が鈍くなります。

遅延時間調整(DELAY_TIME)

BM612のTIMEに入力する電圧を調整します。TIMEに入力される電圧が低いほど遅延時間が短くなりVOUTを出力する期間が短くなります。

遅延時間を10秒程度にしたい場合は可変抵抗を82kΩ程度に調整することで実現できます。データシートの正規化のグラフと表のレンジが異なっていますが表の値が正しそうです。

SDカードに動作履歴を保存する

Arduino環境では標準ライブラリでSDカードを操作することができます。人感センサーが動作したときの情報をSDカードに動作履歴として保存します。

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

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

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

void setup() {

  if(!SD.begin(SD_CS)){
    //初期化失敗の処理
  }
}

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

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

SDライブラリのbegin()関数を使用してSDカードに関する情報を初期化します。引数にはSDカードを選択するためのスレーブセレクト(SS)のピン番号を指定します。SDカードが挿入されていない場合など失敗した場合は戻り値がfalseになるので失敗したときの処理を入れます。

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

データの読み込み

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()関数を使って改行コードが見つかるまでデータを読み込んでいます。

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

Seeeduino XIAOではシリアルモニタを開いた際にソフトがリスタートしないため初期化時にSDカードの情報をリードしてモニター上に表示する処理を入れていてもシリアルモニタ起動のタイミングによって表示が間に合わない事があります。

TECH::CAMPプログラミング教養【無料体験会】

データの書き込み(動作履歴の保存)

  myfile = SD.open(filepath,FILE_WRITE);

  if( myfile ){ //ファイルが開けたら書き込む
    myfile.print("Sensor->OK:");
    myfile.println(seccnt);
    myfile.close(); //ファイルを閉じる
  }

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

print()関数で「Sensor->OK」と電源ONからの経過秒数(変数seccnt)をテキストデータで書き込みます。SDカードのデータ区切りには改行コード(println()関数)を入れています。

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

動作確認

動作確認用の回路図
動作確認用の回路図

Seeeduino XIAOのSPIをSDカードで使用します。人感センサーが動作すると人感センサーの2番ピンがHIGHになるためSeeeduino XIAOはDIがLOWからHIGHになったタイミングで電源ONから経過した秒数をSDカードに保存します。SW1を5秒間長押しするとファイルを削除するようにしています。

SDカードに保存するデータはシリアルモニタでも同様の表示を行い動作確認を行いました。

動作確認結果 上部:シリアルモニタ 下部:SDカードのデータ
動作確認結果 上部:シリアルモニタ 下部:SDカードのデータ

人感センサーに手をかざして反応させるとシリアルモニタに「Sensor->ok」に続けて電源ONからの経過秒が表示されていることが確認できました。SDカードの中にファイルが生成されておりシリアルモニタで確認したデータと同様のデータが保存されていました。

SW1を5秒長押しするとSDカードのデータのファイルが削除されているのも確認できました。

ソースコード全体

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

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

#define SD_CS 0
#define PIN_DI1 2
#define PIN_DI2 3

#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
#define TIM_1SEC 100

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;
int16_t timsec = TIME_UP;
int8_t cnt10ms;
DIFILT diData[2];
bool flg;
File myfile;
String str;
uint32_t seccnt;

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

void setup() {
  uint8_t i;

  pinMode(PIN_DI1,INPUT);
  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 == 1 ){
    if( flg == false ){
      flg = true;
      myfile = SD.open(filepath,FILE_WRITE);

      if( myfile ){ //ファイルが開けたら書き込む
        myfile.print("Sensor->OK:");
        myfile.println(seccnt);
        myfile.close(); //ファイルを閉じる
        Serial.print("Sensor->OK:");
        Serial.println(seccnt);
      }
    }
  }
  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");
  }
  if( timsec == TIME_UP ){
    timsec = TIM_1SEC;
    ++seccnt;
  }
}
/* タイマ管理 */
void mainTimer(void){

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

    if( timSdWait > TIME_UP ){
      timSdWait--;
    }
    if( timDifilter > TIME_UP ){
      --timDifilter;
    }
    if( timsec > TIME_UP){
      --timsec;
    }
  }
}
/* 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;
      }
    }
  }
}

初期化時にSDカードの情報をリードしてモニター上に表示する処理を入れていますが、シリアルモニタの起動とタイミングが合わないため表示されないケースが多くあります。

関連リンク

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

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

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

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

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

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

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