google-site-verification: google3bd66dd162ef54c7.html

SDカードを使ったデーターロガー

 ArduinoにSDカードを接続したので,次は簡単なデーターロガーを作ってみることにします。

 最終的には電力量計に接続して電力ロガーを作りたいと思います。ただそういうプログラムは、用途が限定されているので、他のことに使い廻すのが不便なものになる可能性が高いと思います。

 そんなことで、まずはある程度汎用的に使えるロガーを作ることにしました。

 ArduinoとSDカードを使ったデーターロガーは先人の方がいろいろ作られています。またArduino IDE の SDのサンプルプログラムにも SDを使ったデーターロガーのプログラムが公開されています。こういう事例がそのまま使えればいいのですが、個人的には特に以下の2点が気になります。

1) データー収集のタイミング精度が悪い
 ほとんどの事例はループの中でdelay(1000)とやって約1秒間隔のサンプリングを行っています。しかし、この方法は時間精度が悪いのであまりよろしくありません。電力量計では時間の精度は測定精度に直結するので、出来るだけ正確な時間間隔で測定する必要があります。ということで対策としては、delayではなく、タイマー割り込みに変更することにします。

2) ログの条件をもう少し簡単に設定したい
 公開されているロガーは、結果を記録するログファイルの名前が固定になっています(例えば logfile.csv )また、測定周期も固定です。こういう条件を変えるためには再コンパイルしてプログラムの書き換えが必要になりますが、これでは使い難いです。
 そこで設定ファイルを用意し、条件を書き込んでおく方式にします。ロガーのプログラムは最初に設定ファイルを読み、その内容に合わせてログの収集条件を設定するようにします。設定ファイルはエディタで修正出来るので、比較的簡単に内容の変更が可能になります。

 以上のような条件を満たすプログラムを作ることにしました。と言ってもまずは回路図から。

▼データーロガーの回路図 (クリックで別窓に拡大)
SDカードロガー回路図
 入力はアナログポートの A0~A3 までの4つを使うことにしました。A4,A5 はI2C用に空けておきます。

▼ブレッドボード
SDカードロガー
 回路図に無い部品も乗ってますが、気にしないでください。

 プログラムは以下の通りです。ちょっと長いですが説明のために全体を掲載します。

▼データロガーのプログラム(_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
Log00200.csv
5
N, sec, Battery(V), ch1, ch2, ch3


説明
これはSDロガーの設定ファイルです。
最初の4行でログ条件を指定しています。この範囲は半角英数文字で記入します。
以降は読み飛ばしているので何を書いてもOKです。つまり全角文字(漢字など)を書いても大丈夫です。
1行目:ここに書いたテキストはログデーターの最初に記録される
2行目:ログファイル名(8+3形式で指定)
3行目:記録間隔。秒単位で指定
4行目:列の名前
▼SDカードのファイルの状態
SDカードのファイルの内容

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

▼ログファイルの内容
ログファイルの内容X

 エディタでもいいですが、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カードは使用しないで下さい。この記事の内容を試すことで発生した損害などにつき、筆者は一切の責任を負いません。

アナレンマの作図状況 2017年秋分の日

 太陽電池パネルの電圧を監視することでアナレンマを描く試みを行ってます。始めたのは去年の冬至の頃で、昨日は秋分の日だったので4分の3のまで進んだことになります。

 なお、作図はAmbientのサービスを使わせて頂いてリアルタイムで公開しています。まあ、リアルタイムと言っても朝夕一回、つまり一日に2点しか新しいデーターは追加されません。

 現在のプロット状況を見てみます。

▼Ambientで公開中のグラフ(2017/9/24現在)
Ambientで公開中のアナレンマ作図状況
 このグラフは、Ambient で アナレンマを描く試み (チャネルID: 200) というページのスクリーンショットです。

 左上から順に、日の出時刻、日の入り時刻、昼の長さ、南中時刻で、左下がアナレンマのグラフです。日の出、日の入り時刻が変化していく様子がよく判ります。現在は秋分の日なので、振れ幅の中間までプロットが進んでいます。

◆秋の日はつるべおとし
 せっかくグラフがあるので一つ豆知識です。右上の日の入りのグラフを見ると、上りと下りの傾斜が違っていて、下りの方が傾斜がきつくなっています。こうなる理由の説明はさておき、この現象は「秋の日はつるべおとし」という言葉の説明によく使われます。秋は日があっという間に沈む、という意味だったと思いますが、日が沈む時刻がどんどん早くなっていくので、日が沈むのが速くなったように感じるということだそうです。

 話を戻します。同じデーターをローカルのPCの Excel にも保存しているので、そのデーターでグラフを書くと以下のようになっています。

▼ローカルPCの Excel で描いたグラフ
EXCELに書いた日の出日の入り時刻

▼Excelで描いたアナレンマ
EXCELで描いたアナレンマ

 青い点が実測値、赤い点は天文台の暦のページのデーターからプロットしたもので、いわば理論値です。実測値はかなりばらつきがあり、また理論値より右上にオフセットしています。でもプロットのばらつきの中心はほ、ぼ理論値通りに行われている感じです。

 秋分点なのでアナレンマの8の字の上のループを終わり、下の大きなループに入って少し進んだところです。上のループは、バラツキの影響でかろうじて存在が確認出来る程度になってしまいました。下側の大きなループは、たぶんはっきりと判るグラフが出来上がるのではないかと期待しています。

◆まとめ
 あと3ケ月でアナレンマが完成するはずです。もう一息なので、それまでプロット用のハードソフトなどが無事に動き続けることを祈っています。あとAmbientさん、よろしくお願い致します。
カレンダー
08 | 2017/09 | 10
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
プロフィール

ラジオペンチ

Author:ラジオペンチ
電子工作を中心としたブログです。たまに近所(東京都稲城市)の話題など。60過ぎて視力や器用さの衰えを感じつつ日々挑戦!
コメントを入れる時にメールアドレスの記入は不要です。なお、非公開コメントは受け付けていません。

記事が気に入ったらクリックを!
最新記事
カテゴリ
最新コメント
リンク
FC2カウンター
検索フォーム
月別アーカイブ
RSSリンクの表示
QRコード
QRコード