SDカードを使ったデーターロガー
ArduinoにSDカードを接続したので,次は簡単なデーターロガーを作ってみることにします。
最終的には電力量計に接続して電力ロガーを作りたいと思います。ただそういうプログラムは、用途が限定されているので、他のことに使い廻すのが不便なものになる可能性が高いと思います。
そんなことで、まずはある程度汎用的に使えるロガーを作ることにしました。
ArduinoとSDカードを使ったデーターロガーは先人の方がいろいろ作られています。またArduino IDE の SDのサンプルプログラムにも SDを使ったデーターロガーのプログラムが公開されています。こういう事例がそのまま使えればいいのですが、個人的には特に以下の2点が気になります。
1) データー収集のタイミング精度が悪い
ほとんどの事例はループの中でdelay(1000)とやって約1秒間隔のサンプリングを行っています。しかし、この方法は時間精度が悪いのであまりよろしくありません。電力量計では時間の精度は測定精度に直結するので、出来るだけ正確な時間間隔で測定する必要があります。ということで対策としては、delayではなく、タイマー割り込みに変更することにします。
2) ログの条件をもう少し簡単に設定したい
公開されているロガーは、結果を記録するログファイルの名前が固定になっています(例えば logfile.csv )また、測定周期も固定です。こういう条件を変えるためには再コンパイルしてプログラムの書き換えが必要になりますが、これでは使い難いです。
そこで設定ファイルを用意し、条件を書き込んでおく方式にします。ロガーのプログラムは最初に設定ファイルを読み、その内容に合わせてログの収集条件を設定するようにします。設定ファイルはエディタで修正出来るので、比較的簡単に内容の変更が可能になります。
以上のような条件を満たすプログラムを作ることにしました。と言ってもまずは回路図から。
▼データーロガーの回路図 (クリックで別窓に拡大)

入力はアナログポートの A0~A3 までの4つを使うことにしました。A4,A5 はI2C用に空けておきます。
▼ブレッドボード

回路図に無い部品も乗ってますが、気にしないでください。
プログラムは以下の通りです。ちょっと長いですが説明のために全体を掲載します。
▼データロガーのプログラム(_20170925DataLogger.ino)
・53行目の readConfig(); で設定ファイル (LogConf.txt) の内容を読んでログの条件を設定しています。
・65行目で記録スイッチを読んでログを開始しています。スイッチがONの間は記録が行われます。記録を止めるにはスイッチOFFにします。こうしておけば安全にSDカードを抜くことが出来ます。世の中のSDカードロガーの事例を見ると記録スイッチなど無く、いきなりログを開始し、停止させる方法が無いものが多いです。こういう場合にログを止めるには、SDカードを抜くか電源を切るしか無いと思いますが、そんなことをするとファイルを壊しそうで怖いです。なお、スイッチを再度入れれば新しいログとして記録を開始します。
・84,85行目でタイマー割り込み(MsTimer2)の動作条件を設定しています。
・106と113行のdelay()は動作表示のLEDを短時間消灯させるために入れています。消灯時間を変えることでデーターの書き込みがあったことが判るようになっています。なお、データ取得のタイミングはタイマー割り込みで決まるので、このようにdelayの値が変わっても影響はありません。
・134行目はファイルからデーターを 1行読む関数です。こういう機能が最初からライブラリに入っていると助かるのですが、無いのは何か事情があるのでしょう。ちなみにこの関数ではLF(0x0A)を行末とみなしています。(MACの人は要注意)
・151行目のvoid sensData() でデーターを読んで出力用の文字列を生成しています。データーの取得内容を変えたい時は、ここを修正すればOKです。なお、このプログラムではアナログポートの値を電圧に換算して出力しています。
・167行目はエラー表示ルーチンで、無限ループでLEDを高速点滅させることでエラーがあったことを知らせます。
ログの条件を指定する LogConf.txt の設定例は以下の通りです。
◆ LogConf.txt の例

SDのファイルのルートに LogConf.txt を置いておきます。プログラムを動かすと、同じ場所にログファイルが生成されます。なお、同じ名前のファイルが既にあれば、既存ファイルの末尾にログを追加します。ファイルが無ければ新しいファイルが作成されます。(Log00200.xlsは私が作成したものです)
▼ログファイルの内容

エディタでもいいですが、csv形式なので excel で開いた方が判り易いです。
2行目、3行目は設定ファイルで指定した内容がそのまま出力されています。
4行目以降は測定データーで、左から、行番号、経過時間(秒)、ch0電圧、ch1電圧、ch2電圧、ch3電圧です。測定間隔を5秒に設定しているので、経過時間は5秒間隔になっています。
実際に使った事例として、電池の放電カーブを測定してみました。
▼電池の放電カーブ測定

中古の単四アルカリ電池を10Ωのセメント抵抗で放電させました。
▼測定結果グラフ

4.75時間あたりで電圧が急降下しているので、ここで電池寿命になったことが判ります。
こういう測定はPCとそれなりの計測器を使えば簡単に出来ます。でも Arduino でやればずっとコンパクトかつ省エネで記録出来るので、使い道はいろいろあると思います。
◆まとめ
ということで、ある程度汎用性のあるデーターロガーが出来たと思います。SDメモリーカードの容量は巨大なので、ほぼ無制限にデーターを記録できると思います。
この続きは電力量計を使ったロガー作りです。こっちを作るのは、少し時間が必要になるかも知れません。
【追記】
コンパイラの警告を「全て」に設定すると、ワーニングが出ます。内容としては pathidx;の使い方がおかしいという感じのメッセージです。コンパイラの警告を「初期値」にするとこのワーニングは出なくなります。ちなみに、SDの example の listfiles でも同じワーニングが出るのでライブラリ側の問題のような気がします。
ということで、このワーニングは気にしないことにしています。
【ご注意】
使用できるSDは FAT16 か FAT32 でフォーマットされた物です。詳しくはSDライブラリのリファレンスをお読みください。garretlabさんのページが読み易いです。
・https://garretlab.web.fc2.com/arduino_reference/libraries/standard_libraries/SD/index.html
・https://garretlab.web.fc2.com/arduino_reference/libraries/standard_libraries/SD/card_notes.html
FAT16でフォーマットしてあり、windowsで正常に読み書き出来るのにかかわらず、この記事のデーターロガーでは使えないSDがありました。SD内のファームとの相性のようなものがあるのかも知れません。
ファイルを全部失う可能性があるので、重要なデーターが入ったSDカードは使用しないで下さい。この記事の内容を試すことで発生した損害などにつき、筆者は一切の責任を負いません。
最終的には電力量計に接続して電力ロガーを作りたいと思います。ただそういうプログラムは、用途が限定されているので、他のことに使い廻すのが不便なものになる可能性が高いと思います。
そんなことで、まずはある程度汎用的に使えるロガーを作ることにしました。
ArduinoとSDカードを使ったデーターロガーは先人の方がいろいろ作られています。またArduino IDE の SDのサンプルプログラムにも SDを使ったデーターロガーのプログラムが公開されています。こういう事例がそのまま使えればいいのですが、個人的には特に以下の2点が気になります。
1) データー収集のタイミング精度が悪い
ほとんどの事例はループの中でdelay(1000)とやって約1秒間隔のサンプリングを行っています。しかし、この方法は時間精度が悪いのであまりよろしくありません。電力量計では時間の精度は測定精度に直結するので、出来るだけ正確な時間間隔で測定する必要があります。ということで対策としては、delayではなく、タイマー割り込みに変更することにします。
2) ログの条件をもう少し簡単に設定したい
公開されているロガーは、結果を記録するログファイルの名前が固定になっています(例えば logfile.csv )また、測定周期も固定です。こういう条件を変えるためには再コンパイルしてプログラムの書き換えが必要になりますが、これでは使い難いです。
そこで設定ファイルを用意し、条件を書き込んでおく方式にします。ロガーのプログラムは最初に設定ファイルを読み、その内容に合わせてログの収集条件を設定するようにします。設定ファイルはエディタで修正出来るので、比較的簡単に内容の変更が可能になります。
以上のような条件を満たすプログラムを作ることにしました。と言ってもまずは回路図から。
▼データーロガーの回路図 (クリックで別窓に拡大)

入力はアナログポートの A0~A3 までの4つを使うことにしました。A4,A5 はI2C用に空けておきます。
▼ブレッドボード

回路図に無い部品も乗ってますが、気にしないでください。
プログラムは以下の通りです。ちょっと長いですが説明のために全体を掲載します。
▼データロガーのプログラム(_20170925DataLogger.ino)
/*◆プログラムのポイント
SDカードロガー
アナログポート(A0-3)の電圧を読んでSDカードに保存。
記録条件は LogConf.txt の先頭4行で タイトル、ログファイル名、記録間隔、データラベル
の順で指定する。下記は設定例。5行目以降は無視されるのでメモなどを記入してもよい)
SD logger test 2017/09/23
Log00010.csv
3
N, sec, ch0, ch1, ch2, ch3
2018/9/23 ラジオペンチ http://radiopench.blog96.fc2.com/
*/
#include <SPI.h>
#include <SD.h>
#include <MsTimer2.h>
const int chipSelect = 4; // SDカードのChip Selectピン
const int logSW = 5; // ログ記録スイッチ
const int ledPin = 6; // LEDピン
unsigned long IC = 0; // インターバルカウンタ
unsigned long LC = 0; // 行カウンタ(LineCounter)
unsigned long TC = 0; // 秒カウンタ(TimeCounter)
volatile int sampleFlag = 0;
File myFile; // SD読み書き用
String readBuff; // ファイルのラインリードバッファ
String logTitle; // ログのタイトル情報
String logFileName; // ログファイル名
String colName; // データー名(先頭行に出力される)
String logData;
unsigned int logInterval;
void setup() {
pinMode(logSW, INPUT_PULLUP); // 記録スイッチ
pinMode(ledPin, OUTPUT); // 動作表示LED
pinMode(chipSelect, OUTPUT); // SDのchip selectピン
Serial.begin(115200);
Serial.print("Reading SD card...");
if (!SD.begin(chipSelect)) {
Serial.println("SD failed!");
errorStop(); // エラー表示して停止
}
digitalWrite(ledPin, HIGH);
Serial.println("initialization done."); Serial.println();
readConfig(); // ログ条件設定ファイル読み出し
Serial.println("Log settings (LogConf.txt)");
Serial.print("Data title = "); Serial.println(logTitle);
Serial.print("File name = "); Serial.println(logFileName);
Serial.print("Rec.interval = "); Serial.print(logInterval); Serial.println("sec");
Serial.print("Data lavel = "); Serial.println(colName);
digitalWrite(ledPin, LOW);
}
void loop() {
digitalWrite(ledPin, LOW);
while (digitalRead(logSW) == HIGH) { // 記録スイッチがONになるまで待つ
}
myFile = SD.open(logFileName, FILE_WRITE); // ログファイルを開く
if (myFile == 0) { // エラー処理
Serial.println("Cant open log file.");
errorStop(); // LED点滅
}
Serial.println(); myFile.println();
Serial.println(logTitle);
myFile.println(logTitle); // タイトル出力
Serial.println(colName);
myFile.println(colName); // データーラベル出力
myFile.close();
LC = 0; // 行数カウンタ初期化
TC = 0; // タイムカウンタ初期化
IC = 0; // インターバルカウンタ初期化
MsTimer2::set(1000, sensInterval); // 1000ms間隔でMsTimer2割込み
MsTimer2::start();
while (digitalRead(logSW) == LOW) { // 記録スイッチがONの間は以下を繰り返し実行
digitalWrite(ledPin, HIGH);
while (sampleFlag == 0) { // MsTimer2割込みを待つ
}
sampleFlag = 0;
digitalWrite(ledPin, LOW);
if (IC == 0) { // インターバルカウンタがゼロだったら、
LC++;
File myFile = SD.open(logFileName, FILE_WRITE); // ログファイルを開く
if (myFile == 0) {
Serial.println("Cant open log file.");
errorStop();
}
sensData(); // データー取得して記録文字列に変換
Serial.println(logData);
myFile.println(logData); // ファイルにログデーター書き込み
myFile.close();
delay(150); // LED表示のために待つ(長めに減光)
}
TC++;
IC++;
if (IC == logInterval) {
IC = 0;
}
delay(20); // LED表示のために待つ(短く減光)
digitalWrite(ledPin, HIGH);
}
MsTimer2::stop(); // 記録スイッチがOFFになったので割込みタイマー停止
}
void readConfig() { // ログ条件ファイルを読む
myFile = SD.open("LogConf.txt", FILE_READ); // 設定ファイルを開く
if (myFile) {
lineRead(); // 1行目:ログタイトル
logTitle = readBuff;
lineRead(); // 2行目:ファイル名
logFileName = readBuff;
lineRead(); // 3行目:ログ周期(秒)
logInterval = readBuff.toInt(); // ログ取得周期 値は整数に変換
lineRead(); // 4行目
colName = readBuff;
myFile.close();
}
}
void lineRead() { // ファイルから1行読んで readBuffに格納する
// 行末コード win:0x0D,0x0A unix:0x0A mac:0x0D
char s;
int x = 0;
readBuff = ""; // String型
for (;;) {
s = myFile.read(); // ファイルから1文字読む
if (s == 0x0A) { // LF(0x0A)なら行末とみなして
break; // ループを抜ける
}
if (s != 0x0D) { // CR(0x0D)以外だったら、つまり0x0Dは無視
readBuff += s; // 文字追加。Stringなので末尾には0x00が自動挿入される
x++;
}
}
}
void sensData() { // 測定と記録用文字列の作成
int x;
float voltage;
logData = "";
logData += String(LC, DEC); // 1列目は行番号
logData += ", ";
logData += String(TC, DEC); // 2列目は経過時刻(秒)
for (int n = 0; n <= 3; n++) { // 0から3の範囲の
logData += ", "; // (csvデリミッタ)
x = analogRead(n); // アナログポートの値を読んで
voltage = 5.0 * x / 1023.0; // 浮動小数点形式の電圧に換算して
logData += String(voltage, 2); // 文字列に変換して追加(小数点以下2桁まで)
}
}
void errorStop() { // エラー表示して停止
for (;;) { // 無限ループでLED点滅
digitalWrite(ledPin, HIGH); delay(20);
digitalWrite(ledPin, LOW); delay(150);
}
}
void sensInterval() { // Mstimet2の割り込み処理
sampleFlag = 1; // サンプリングフラグ立てる
}
・53行目の readConfig(); で設定ファイル (LogConf.txt) の内容を読んでログの条件を設定しています。
・65行目で記録スイッチを読んでログを開始しています。スイッチがONの間は記録が行われます。記録を止めるにはスイッチOFFにします。こうしておけば安全にSDカードを抜くことが出来ます。世の中のSDカードロガーの事例を見ると記録スイッチなど無く、いきなりログを開始し、停止させる方法が無いものが多いです。こういう場合にログを止めるには、SDカードを抜くか電源を切るしか無いと思いますが、そんなことをするとファイルを壊しそうで怖いです。なお、スイッチを再度入れれば新しいログとして記録を開始します。
・84,85行目でタイマー割り込み(MsTimer2)の動作条件を設定しています。
・106と113行のdelay()は動作表示のLEDを短時間消灯させるために入れています。消灯時間を変えることでデーターの書き込みがあったことが判るようになっています。なお、データ取得のタイミングはタイマー割り込みで決まるので、このようにdelayの値が変わっても影響はありません。
・134行目はファイルからデーターを 1行読む関数です。こういう機能が最初からライブラリに入っていると助かるのですが、無いのは何か事情があるのでしょう。ちなみにこの関数ではLF(0x0A)を行末とみなしています。(MACの人は要注意)
・151行目のvoid sensData() でデーターを読んで出力用の文字列を生成しています。データーの取得内容を変えたい時は、ここを修正すればOKです。なお、このプログラムではアナログポートの値を電圧に換算して出力しています。
・167行目はエラー表示ルーチンで、無限ループでLEDを高速点滅させることでエラーがあったことを知らせます。
ログの条件を指定する LogConf.txt の設定例は以下の通りです。
◆ LogConf.txt の例
Battry discharge test 2017/09/22▼SDカードのファイルの状態
Log00200.csv
5
N, sec, Battery(V), ch1, ch2, ch3
説明
これはSDロガーの設定ファイルです。
最初の4行でログ条件を指定しています。この範囲は半角英数文字で記入します。
以降は読み飛ばしているので何を書いてもOKです。つまり全角文字(漢字など)を書いても大丈夫です。
1行目:ここに書いたテキストはログデーターの最初に記録される
2行目:ログファイル名(8+3形式で指定)
3行目:記録間隔。秒単位で指定
4行目:列の名前

SDのファイルのルートに LogConf.txt を置いておきます。プログラムを動かすと、同じ場所にログファイルが生成されます。なお、同じ名前のファイルが既にあれば、既存ファイルの末尾にログを追加します。ファイルが無ければ新しいファイルが作成されます。(Log00200.xlsは私が作成したものです)
▼ログファイルの内容

エディタでもいいですが、csv形式なので excel で開いた方が判り易いです。
2行目、3行目は設定ファイルで指定した内容がそのまま出力されています。
4行目以降は測定データーで、左から、行番号、経過時間(秒)、ch0電圧、ch1電圧、ch2電圧、ch3電圧です。測定間隔を5秒に設定しているので、経過時間は5秒間隔になっています。
実際に使った事例として、電池の放電カーブを測定してみました。
▼電池の放電カーブ測定

中古の単四アルカリ電池を10Ωのセメント抵抗で放電させました。
▼測定結果グラフ

4.75時間あたりで電圧が急降下しているので、ここで電池寿命になったことが判ります。
こういう測定はPCとそれなりの計測器を使えば簡単に出来ます。でも Arduino でやればずっとコンパクトかつ省エネで記録出来るので、使い道はいろいろあると思います。
◆まとめ
ということで、ある程度汎用性のあるデーターロガーが出来たと思います。SDメモリーカードの容量は巨大なので、ほぼ無制限にデーターを記録できると思います。
この続きは電力量計を使ったロガー作りです。こっちを作るのは、少し時間が必要になるかも知れません。
【追記】
コンパイラの警告を「全て」に設定すると、ワーニングが出ます。内容としては pathidx;の使い方がおかしいという感じのメッセージです。コンパイラの警告を「初期値」にするとこのワーニングは出なくなります。ちなみに、SDの example の listfiles でも同じワーニングが出るのでライブラリ側の問題のような気がします。
ということで、このワーニングは気にしないことにしています。
【ご注意】
使用できるSDは FAT16 か FAT32 でフォーマットされた物です。詳しくはSDライブラリのリファレンスをお読みください。garretlabさんのページが読み易いです。
・https://garretlab.web.fc2.com/arduino_reference/libraries/standard_libraries/SD/index.html
・https://garretlab.web.fc2.com/arduino_reference/libraries/standard_libraries/SD/card_notes.html
FAT16でフォーマットしてあり、windowsで正常に読み書き出来るのにかかわらず、この記事のデーターロガーでは使えないSDがありました。SD内のファームとの相性のようなものがあるのかも知れません。
ファイルを全部失う可能性があるので、重要なデーターが入ったSDカードは使用しないで下さい。この記事の内容を試すことで発生した損害などにつき、筆者は一切の責任を負いません。