チャーハンノート

チャーハンの作り方に関する覚書

データロガーの作製 (前編)

f:id:friedrice_mushroom:20181103121732p:plainゲームボーイ本体と、その電源電圧を測定するの図

定番マイコンArduino(のパチモン)でデータロガーを作製しました。データロガーは、いろいろなセンサの値を一秒毎にマイクロSDカードに記録するツールです。これを使って改造ゲームボーイのバッテリー寿命を測定してみました。今回はその製作過程について記します。

動機

データロガーを作ろうと思った動機は、手持ちの色んな電子機器のバッテリー寿命を図ってみたいという素朴な欲求でした。
市販のデータロガーは存在しますが、市販のものは高額なものが多いです。そこで練習がてらArduinoで作ってみることにしました。”Arduino”+”Logger” でネット検索すると、様々な事例が見つかります。特にYouTubeで多数投稿されていることがわかりました。
英語の動画が大半ですが、精度のよい自動字幕が表示されるため、字幕とにらめっこしつつマイペースに情報収集してました。

参考動画

特に参考になった動画について紹介します。


Make your own Power Meter/Logger

Arduino Nanoを使用した作例です。市販のUSB電圧・電流チェッカーのアップグレード版を作製することがコンセプトらしく、電流を測定するモジュールが使われています。私は電圧を測定できればよかったため、そこは省きました。


Arduino SD Card and Data Logging to Excel Tutorial

Arduino MEGAを使用した作例です。RTCモジュールも使用しており、時刻と測定値の紐付けができます。私も当初はRTCモジュールの追加を検討してみましたが、Arduino Leonardoではメモリが足らず断念しました。
そんなこんなで色々と調べた結果イケそうな気がしたので、データロガーの自作に着手してみました。

準備

前置きが長くなりました。

要件

ざっくりの要件です。

  • 1秒ごとに測定・記録を行う
  • 電圧を測定できる入力が2つ以上あること
  • 0〜10Vまで測定できる入力が1つあること
  • 測定した電圧をディスプレイに表示、それと同時にSDカードに記録する
  • できれば24時間は測定したい

上記要件からの大まかな構成は下図の通りです。 1秒ごとにマイコン(Pro Micro)が測定電圧を読み取り、OLEDディスプレイへの表示とMicroSDカードへの記録を行います。
また、MicroSDカードへの記録する/しないを制御するためのスイッチ(Logging ON/OFF Switch)を設けました。

f:id:friedrice_mushroom:20181103122431p:plain

使用部品一覧

今回使用する部品の列挙です。

試作で使用したもの

ちなみに、今回使用したArduinoは正確にはArduinoではなく、ProMicroというSparkFun社のArduinoMicro互換ボードです。私が購入したのはHiLetgoという謎のメーカ(?)がコピーしたもので、とても安価でした。USBコネクタのないNanoシリーズのほうがもっと安いのですが、SRAMが2.5kByteと比較的多めなことと、MicroUSB端子が使われていて配線が入手しやすいことからProMicroを選びました。
注意点として、ProMicroにはリセットスイッチがありません。リセットするには、RST端子をGNDに2回連続で接続する必要があります。そのためのスイッチも設けました。
その他、SDカードモジュールとOLEDモジュールもHiLetgo社のものをポチりました。

Arduinoで使用するライブラリ

Adafruit_SSD1306.h (OLEDディスプレイ使用のため)

GitHub - adafruit/Adafruit_SSD1306: SSD1306 oled driver library for ‘monochrome’ 128x64 and 128x32 OLEDs!

f:id:friedrice_mushroom:20181103122458p:plain

OLEDモジュールのI2Cアドレスは2通りあるようです。購入したときにどちらのアドレスになっているかは、↓のように”0x78”と”0x7A”のどちらにチップ抵抗がついているかで判別します。

f:id:friedrice_mushroom:20181103122512p:plain

今回届いたものは”0x78”と書かれているほうに抵抗が付いていました。なので、このI2cアドレスは0x78…かと思いきや、そうではありませんでした。実際には16進数の78を2で割った値(→0x3C)となるようです。なぜかはわかりません(調べてない) 。 ここで指定されたI2Cアドレス(0x3C)はスケッチのvoid setup()内で↓のように記述します。

display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

SdFat.h (SDカードモジュール用)

SdFat - Arduino Libraries

f:id:friedrice_mushroom:20181103122531p:plain

デフォルトでインストールされているものにSD.hがありますが、参考動画のGreatScott!さんがこちらをお勧めされていたため、私もそれに倣いました。

SPI.h (SDカードモジュールのSPI通信用。SdFat.hとセット)

f:id:friedrice_mushroom:20181103122547p:plain

試作

まずはブレッドボードで試作をしました。

フローチャート

大まかな動作状態です。記録スイッチON/OFF時の動作が若干複雑です。それについては”注1”として後述します。

f:id:friedrice_mushroom:20181103122645p:plain

注1…記録スイッチON/OFF動作の詳細

データロガーとして使うには、単純に記録スイッチのON/OFFで待機モード/記録モードに切り替えれば十分です。ただし、このままでは記録スイッチをOFFにした途端、記録時間が”00:00:00”と表示されます。
個人的には、記録スイッチをOFFにしてもそれまでの記録時間が表示され続けたほうが便利だと思い、条件分岐を1段追加しました。表にすると↓のようになります。

f:id:friedrice_mushroom:20181103122702p:plain

初期状態は記録スイッチがOFFのとき、としました。まず待機モードの①が実行されます。
記録スイッチをONにすると記録モードに遷移します。ここで記録状態(logging_state : true/false)によって条件分岐となります。

記録モードでの最初の記録状態はfalseのため、記録モードの②が実行されます。ここで記録状態はtrueとなり、記録時間(logging_time)は0にリセットされます。ディスプレイには”start”と表示されます。

次回(1000ms後)の記録状態はtrueとなったため、記録モード③が実行されます。記録時間は1秒加算され、ディスプレイには”logging”と表示されます。記録モードの②③いずれの処理でもSDカードへの記録は実行されます。
この状態で記録スイッチをOFFにすると、待機モード①に遷移します。記録状態はfalseとなり、ディスプレイには”stop”と表示されます。このとき、ディスプレイにはそれまでの記録時間が表示されたままになります。再び記録スイッチをONにすると、記録モード②となり、記録時間が0にリセットされます。
(よくよく考えると、記録スイッチOFFのときは単純にOLEDを更新しなければよいのでは……という気がしてきました。)

回路図

Fritzingで回路図を作製しました。例として9V電池に豆電球を接続し、電池の電圧をモニターしつつ光センサ(Cdsセル)で光量の変化をモニターする回路としました。

f:id:friedrice_mushroom:20181103122716p:plain 回路図のPDFファイルはこちらDatalogger_回路図.pdf - Google ドライブ

配線図(ブレッドボード)

ブレッドボードで配線したときの図(例)です。

f:id:friedrice_mushroom:20181103122736p:plain

ここで使われている豆電球ソケットについて、ネットを探してみたのですが見つからなかったため、自作しました。Googleドライブに保存したのを公開します。
Light Bulb_01.fzpz - Google ドライブ

Arduinoのアナログ入力の測定電圧範囲に関して、デフォルトはUSBの電源電圧を基準に0〜5Vです。今回の製作では精度を優先して0〜2.56Vに設定しました。スケッチのvoid setup()内に以下の記述をすることで、Arduinoの内部基準電圧(2.56V)を参照するようにしています。

analogReference(INTERNAL);

USBの電源電圧は正確に5Vではなく、4.75〜5.25V(±5%以内)です。給電方法によるバラツキが大きいため、より安定しているArduino内部の基準電圧(2.56V)を参照する必要があります。
一方、Arduinoで測定できる電圧は2.56Vまでとなります。このままでは、要件である10Vまでの電圧を測定することができません。そこで、外付抵抗で分圧を行い被測定電圧の4分の1の電圧がArduinoに印加されるようにしました。精度を確保するため、規格がシビアな金属皮膜抵抗(±1%)を使用しました。

f:id:friedrice_mushroom:20181103191930p:plain

アナログ入力(A0)に印加された電圧は、基準電圧(2.56V)を1024当分した値(0〜1023)に変換されます。例えばA0=500ならば、A0に印加された電圧は1.25Vとなります。また、そのときのVinの値はA0の電圧(1.25V)を単純に4倍すれば5.0Vだったと推定できます。

スケッチ

Arduinoに書き込むスケッチです。先述の各種ライブラリがインストールされていれば、コピペだけでコンパイルまでは可能のはずです。

// 有機ELディスプレイ ライブラリ使用
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

// SDカード使用
#include <SPI.h>
#include "SdFat.h"
SdFat SD;
File loggingdatafile; //SDカード認識
const int chipSelect = 10; //SPI CSピンの定義

// 記録開始・停止切り替え(logging_switching)判定用ピンを定義
#define input_pin 7

// AnalogReadにより値を読むピンを定義
int a0=0; // A0ピンを"a0"として定義
int a1=1; // A1ピンを"a1"として定義
int v0=0; // A0ピンから読み取った値をV0に格納(0〜1023)
int v1=0; // A1ピンから読み取った値をV1に格納(0〜1023)

// 書き込み時Lチカ用LEDを定義
int LED = 17;

void setup() {

  //SDカード セットアップ
  SD.begin(chipSelect);

  //OLEDディスプレイ セットアップ
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 
  display.display();
  delay(1000); 
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.clearDisplay();

  pinMode(input_pin,INPUT) ;
  analogReference(INTERNAL); //内部基準電圧を参照(2.56V)

  // 書き込み時Lチカ用LEDを定義
  pinMode(LED, OUTPUT);
  
}

void loop() {

  // next_time: 次回のLoopが切れる時間(ミリ秒)
  static unsigned long next_time=millis()+1000;

  // logging_time: 記録開始からの時間を格納[秒]
  static unsigned long logging_time=0;

  // logging_sw: 記録スイッチの状態(pin7, HIGH/LOW)
  boolean logging_sw ;
  
  // logging_state: データロガー動作状態(true/false)
  static boolean logging_state = false;

  int hhmmss_buff; // 記録時間をhh:mm:ssに再計算するときの一時バッファ
  char hhmmss_array[]="00:00:00"; // 記録時間をhh:mm:ssとして格納するための配列
  
  if ((next_time < millis() ) ) {
    //起動後の経過時間が、前回loopから1000ミリ秒経過後
    next_time+=1000; //次回のLoopが切れる時間(ミリ秒)を設定

    //記録時間(logging_time)をhh:mm:ssに変換
    // hh
    hhmmss_buff=(logging_time/3600)%100;
    hhmmss_array[1]+=hhmmss_buff%10;hhmmss_buff/=10;
    hhmmss_array[0]+=hhmmss_buff%10;
    
    // mm
    hhmmss_buff=(logging_time/60)%60;
    hhmmss_array[4]+=hhmmss_buff%10;hhmmss_buff/=10;
    hhmmss_array[3]+=hhmmss_buff%10;

    // ss
    hhmmss_buff=logging_time%60;
    hhmmss_array[7]+=hhmmss_buff%10;hhmmss_buff/=10;
    hhmmss_array[6]+=hhmmss_buff%10;

    
    // OLEDディスプレイ表示関連
    // 記録時間(hh:mm:ss)を表示
    display.clearDisplay();
    display.setCursor(0,0);
    display.print("Time");
    display.setCursor(48,0);
    display.print(hhmmss_array);
    
    // A0ピンの入力値を表示 0〜5V
    v0 = analogRead(a0);
    display.setCursor(0,16);    
    display.print("A0");
    display.setCursor(24,16); 
    display.print(String( (float)v0/1024*2.56) );
    display.setCursor(48,16);
    display.print("V");

    // A1ピンの入力値を表示 0〜10V
    v1 = analogRead(a1);
    display.setCursor(0,24);    
    display.print("A1");
    display.setCursor(24,24); 
    display.print(String( (float)v1/1024*2.56*4 ));
    display.setCursor(48,24);
    display.print("V");
    display.display();

    display.setCursor(0,40);

    // 記録スイッチ 状態確認
    logging_sw = digitalRead(input_pin) ;
    // 記録スイッチ"ON"のとき
    if(logging_sw ==HIGH){

      // 記録状態 true ⇒ "Logging"と表示  
      if(logging_state==true){
        display.print("Logging");
      //記録時間(logging_time)に1[秒]を追加
      logging_time+=1;
      
      // 記録状態 false ⇒ 初回のため"start"と表示
      }else{
        display.print("start");
        logging_state=true;
        logging_time=0;
      }
    // 記録スイッチ"OFF"のとき
    }else{
      // 記録状態をfalseに、さらに"stop"と表示
      logging_state=false;
      display.print("stop");
    }  
  }

  // SDカード記録関連
  // 記録スイッチ"ON"のとき
  if(logging_sw ==HIGH){
    loggingdatafile = SD.open("aaaa.txt", FILE_WRITE);
    //SDカードが読み込めたとき ⇒ SDカードへ書き込み開始
    if (loggingdatafile) {

      // hhmmss_array: 記録時間(logging_time)をhh:mm:ssに変換した配列
      loggingdatafile.print(hhmmss_array);
      loggingdatafile.print(",");
      // A0ピンの入力値を記録 0〜2.56V
      loggingdatafile.print(String( (float)v0/1024*2.56) );
      loggingdatafile.print(",");
      // A1ピンの入力値を記録 0〜10.24V(分圧抵抗で調整)
      loggingdatafile.print(String( (float)v1/1024*2.56*4) );
      
      // テスト用
      loggingdatafile.print(",");
      loggingdatafile.print(String(millis()));
      loggingdatafile.print(",");
      loggingdatafile.println(String(next_time));
      loggingdatafile.close();

      // 書き込み中にLED点滅
      digitalWrite(LED, LOW);
      delay(10);
      digitalWrite(LED, HIGH);

    //SetupでSDカードが読み込めなかったとき ⇒ "ERROR"と表示
    }else{
      display.setCursor(56,40);
      display.print("ERROR");
    }
  }
  // OLEDディスプレイ表示を指示
  display.display();
}

上記までをArduino IDEに貼り付けて、コンパイルします。

コンパイル

コンパイルが無事完了しました。メモリが少ないという警告が表示されていますが……実際の動作はどうなるでしょう。実機で動作確認してみます。

f:id:friedrice_mushroom:20181103122818p:plain

動作確認

ブレッドボード図と同じように配線しました。ただしリセット用のスイッチ、記録スイッチは省略しています。あくまで試作なので、必要に応じてジャンパ線をVCCやGNDにショートさせます。

SDカードはあらかじめFAT32でフォーマットしておきます。

f:id:friedrice_mushroom:20181103122838p:plain

電源オン(USBマイクロケーブル挿す)

待機画面が表示されました。

f:id:friedrice_mushroom:20181103122906p:plain

記録スイッチオン(Arduino D7をGNDからVCCにつなぎ変え)

“Logging”と表示され、記録が開始されました。

f:id:friedrice_mushroom:20181103122925p:plain

記録スイッチオフ(Arduino D7をVCC→GNDにつなぎ変え)

“Stop”と表示され、記録が終了しました。(消えてるところを撮ってしまいました)

f:id:friedrice_mushroom:20181103122942p:plain

SDカードに記録されたデータを確認

“aaaa.txt”というファイルが追加され、中身もきちんと記録できていました。

f:id:friedrice_mushroom:20181103192121p:plain

ひとまずこれで、うまく動作することが確認できました。

オマケ…記録時間(秒)をhh:mm:ss形式に変換(Arduino上で)

Youtubeの製作事例その他、ネットにある作例では経過時間は秒単位で表示するものがほとんどでした。例えば1分なら60[秒]、1時間なら3600[秒]、一日なら86400[秒]とカウントされます。 秒→hh:mm:ss形式に変換することは、いちどExcelに貼り付ければ容易ですが、個人的にはロガー本体に表示できるのが理想でした。そう思って挑戦してみたのですが、なかなかうまくいきませんでした。よく出くわしたのは測定値が文字化け、フリーズなど、どうやらSRAM不足に起因するものでした。そもそもchar変数に関する私の理解不足も妨げの一つでした。

最終的な解決方法として、任意のchar変数に任意の整数を加算すると、文字コードの順番に基づいて任意の整数分シフトしたcharとなることを応用しました。たとえば ’0’ を ’3’ に変えたいならば ‘0’から3つ目の文字コードを指定すれば’3’となります。 おそらく一般的なC言語の動作ではないと思います、苦肉の策でした。

同じことをProcessingで確認してみました。Arduino IDEのベースとなっている言語です。まず’0’の文字列を定義し、forループ毎に+1加算していったときの文字コード(Unicode)、それに対応する文字列を出力してみます。

char kato='0';
for(int i=0;i<10;i++){
  print(hex(kato));  //文字コード(Unicode)
  print(", ");
  println(kato);    //文字コードに対応する文字列
  kato+=1;
}

これを実行するとコンソールに以下のものが出力されます。 左側が文字コード(Unicode、16進数)、右側がそれに対応する文字列です。9の次は全く別の文字列となるため、あくまで0〜9の範囲で加算しなければなりません。

0030, 0
0031, 1
0032, 2
0033, 3
0034, 4
0035, 5
0036, 6
0037, 7
0038, 8
0039, 9

これを用いて実装するために、まずloopごとに”00:00:00”というchar配列を作製し、次に経過時間(秒)からhh,mm,ssを計算していき、その値にもとづいて一列ずつ加算していく方法としました。もっと良い方法があるかもしれませんが。
これのお陰かは不明ですが、記録時間をhh:mm:ss形式にこだわったことで直面していたメモリ不足問題は解決しました。

半田付け、配線

ブレッドボードと同じように各モジュールをハンダ付けして単一のモジュールに仕上げました。裏側の配線写真は作者の都合(とてもみっともない)のため公開することができません。

f:id:friedrice_mushroom:20181103123016p:plain

ついでにデータロガーの電圧とテスターの電圧を比較してみました。データロガー側は8.27Vとのことです。一方テスターの値は8.14V……ブレッドボードよりも精度がさがりました。誤差は約1.5%なのでもういいや、となりました(そもそもテスターが正しいかどうかも不明)。
ミニブレッドボードではArduinoのアナログ入力(A0, A1)を↓の2箇所から取り出しています。今後のためA2, A3も同じように出していますが、今回は未使用です。

f:id:friedrice_mushroom:20181103191551p:plain

実測…次回に続く

いよいよゲームボーイの電池残量を測定……といきたいのですが、あまりに長々とした文章になってしまったため後編に続きます。
後編はこちらデータロガーの作製 (後編) - チャーハンノートです。
ここまで読んでくださった方、ありがとうございました。