Seeeduino XIAOの拡張ボードでSDカードのデータを表示

組み込みエンジニア

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

Seeeduino XIAO用の拡張ボードはSDカードスロットやOLEDによる液晶画面での表示ができます。SDカードに保存した人感センサーの動作履歴のデータを読み込んでOLEDに表示する方法をまとめました。

Seeeduino XIAO用拡張ボード(秋月電子)を使用しています。Seeeduino XIAOを使って動作確認を行ったことを下記リンクにまとめています。

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

Seeeduino XIAOの拡張ボードと人感センサー

Seeeduino XIAO専用の拡張ボードがSeeed Studioから製作されています。このボードを使用するとOLEDに文字を表示したりSDカードを使用したりGrove端子を利用してセンサーと接続することができます。下記記事に拡張ボードについてとOLEDライブラリ追加の方法をまとめています。

Seeeduino XIAOの拡張ボードのOLEDを使用する

人感センサーの使い方については下記記事にまとめています。

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

これらの記事で確認したSDカードに保存した人感センサーの履歴データをSeeeduino XIAOの拡張ボードのOLEDに表示します。

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

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

人感センサーSB612A

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

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

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

感度調整(SENS)は可変抵抗が大きくなると感度が低くなります。調光(DARK_ADJ)は可変抵抗が大きくなると明るい場所での検出感度が鈍くなります。遅延時間調整(DELAY_TIME)は可変抵抗が大きくなるほど遅延時間が長くなります。

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

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

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

#include <SPI.h>
#include <SD.h>
#define SD_CS 2 //拡張基板では2を指定する

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

void setup() {

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

SDカードはSPI通信を使用するためSD.hとSPI.hの2つのライブラリをインクルードする必要があります。拡張基板はSDカードのスレーブ選択に2ピンが接続されているため2ピンをスレーブ選択専用のDOに割り振る必要があります。

SDカードにアクセスした際の戻り値をセットするFile型のクラス変数であるmyfile(名前は任意でよい)を準備します。myfileはSDカードの中のファイルに関する情報等を管理するために使用します。

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

myfile = SD.open(filepath,FILE_WRITE);

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

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

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

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

エンジニア転職なら100%自社内開発求人に強い【クラウドリンク】

データの読み込んで履歴表示する

uint16_t btncnt; //表示する履歴の番号

void HistShow(void){
  uint16_t cnt;
  String str;

  myfile = SD.open(filepath,FILE_READ); //読み込みモードで開く

  cnt=0;
  if(myfile){
    while(myfile.available()){
      str ="";
      str = myfile.readStringUntil('\n');
      if( str != "" ){
        ++cnt;

        if( btncnt == cnt){
          oled_str = str; //履歴番号が一致したら文字列を格納
        }
      }
      else{
        break;
      }
    }

    if( btncnt >= cnt){
      btncnt = 0;
    }

    myfile.close(); //ファイルを閉じる
  }

  u8x8.setCursor(0, 6); //6行目1列にカーソル移動
  u8x8.clearLine(6); //6行目をクリアする
  u8x8.print(oled_str); //格納した文字列をOLEDに表示
}

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

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

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

改行コードまで読み込んだデータは1行分のデータ(履歴データ)になります。btncntはボタンを押した回数(表示する履歴の番号)としているのでReadStringUntil()関数で読み込んだ履歴データの回数とbtncntが一致する場合が表示する履歴になります。一致したときの文字列をoled_strに一時格納してoledに表示します。

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

動作確認

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

Seeeduino XIAOの拡張基板の裏面にSDカードモジュールが実装されています。人感センサーが動作すると人感センサーの2番ピンがHIGHになるためSeeeduino XIAOはDIがLOWからHIGHになったタイミングで電源ONから経過した秒数をシリアルモニタに表示してSDカードに保存します。

動作結果(データの確認)
動作結果(データの確認)

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

動作結果OLEDの表示
動作結果OLEDの表示

SW1を押すとSDカードのデータを読み込んで履歴を表示しています。OLED表示のLATEST HISTは最新の動作履歴であり最新の動作状況を表示しています。HIST LISTはSW1を押した回数の履歴を表示します。

動作結果(データの確認)の4個目のデータである124がHIST LISTに表示されています。履歴データがない場合は空白の表示になります。

スポンサーリンク

ソースコード全体

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

#include <SPI.h>
#include <SD.h>
#include <TimerTC3.h>
#include <U8x8lib.h>
#include <Wire.h>

#define SD_CS 2
#define PIN_DI1 0
#define PIN_DI2 1

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

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

U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8( SCL, SDA, U8X8_PIN_NONE);

String filepath = "sample.txt";
int16_t timSdWait = TIME_OFF;
int16_t timDifilter = TIME_OFF;
int16_t timsec = TIME_UP;
int8_t cnt10ms;
DIFILT diData[2];
bool flg[2];
File myfile;
String oled_str;

uint32_t seccnt;
uint16_t btncnt;

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

void setup() {
  uint8_t i;

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

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

  u8x8.begin();
  u8x8.setFlipMode(1);   // set number from 1 to 3
  u8x8.setFont(u8x8_font_5x8_r);
  u8x8.setCursor(0, 0);
  u8x8.print("Seeeduino HIST");
  u8x8.setCursor(0, 1);
  u8x8.print("       Ver1.00");
  u8x8.setCursor(0, 3);
  u8x8.print("LATEST HIST:");
  u8x8.setCursor(0, 5);
  u8x8.print("HIST LIST:");

  TimerTc3.initialize(1000);
  TimerTc3.attachInterrupt(TimerCnt);

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

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

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

      if( myfile ){ //ファイルが開けたら書き込む
        myfile.print("Sensor->OK:");
        myfile.println(seccnt);
        myfile.close(); //ファイルを閉じる
        u8x8.setCursor(0, 4);
        u8x8.clearLine(4);
        u8x8.print("Sensor->OK:");
        u8x8.print(seccnt);
        Serial.print("Sensor->OK:");
        Serial.println(seccnt);
      }
    }
  }
  else{
    flg[0] = false;
  }

  if( diData[1].di == 0){
    if( flg[1] == false ){
      flg[1] = true;
      ++btncnt;
      HistShow();
    }
  }
  else{
    flg[1] = false;
  }

  if( timsec == TIME_UP ){
    timsec = TIM_1SEC;
    ++seccnt;
  }
}
/* callback function add */
void TimerCnt(){
  ++cnt10ms;
}

/* タイマ管理 */
void mainTimer(void){

  if( cnt10ms >= BASE_CNT ){
    cnt10ms -=BASE_CNT;
    //10msごとにここに遷移する
    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;
      }
    }
  }
}
/* HIST SET */
void HistShow(void){
  uint16_t cnt;
  String str;

  myfile = SD.open(filepath,FILE_READ);

  cnt=0;
  if(myfile){
    while(myfile.available()){
      str ="";
      str = myfile.readStringUntil('\n');
      if( str != "" ){
        ++cnt;

        if( btncnt == cnt){
          oled_str = str;
        }
      }
      else{
        break;
      }
    }

    if( btncnt >= cnt){
      btncnt = 0;
    }

    myfile.close(); //ファイルを閉じる
  }

  u8x8.setCursor(0, 6);
  u8x8.clearLine(6);
  u8x8.print(oled_str);
}

関連リンク

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

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

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

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

RUNTEQ-プログラミングで自由を手に入れる

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

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