google-site-verification: google3bd66dd162ef54c7.html
FC2ブログ

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

コメントの投稿

管理者にだけ表示を許可する

No title

すみません、安易に質問してしまいます。
SDに記録されないのです。

エラーなくコンパイル→書込できました。
SWオンでちょっと点灯、その後1秒に1回滅

ですが、SWをオフにしてSDを見るとLogConf.txt しかありません。

配線は間違いないと思います。
LogConf.txtの説明の2行目と3行目が違っていて、入れ替えてみましたが同じです。

SWオフで消灯します。

すみません、よろしくお願いいたします。

jh3gpnさん、今晩は

すみません、LogConf.txtの例の、2行目と3行目が入れ替わっていたので記事を修正しました。

あと、お尋ねのような現象になる原因はたくさんあるのでちょっと簡単には判りません、すみません。

LogConf.Txtの最初の4行に全角文字が入っていたりしないでしょうか。

記録中はLEDの消灯は、短いのと長いのがありますがそのあたりはどうでしょう。

シリアルモニタに情報を流していますが、そちらはも表示は大丈夫でしょうか。

No title

早速ありがとうございます。
SDのLogConf.txtを読めていないようです。?違いますかね。

Reading SD card...initialization done.

Log settings (LogConf.txt)
Data title =
File name =
Rec.interval = 0sec
Data lavel =



1, 0, 2.35, 2.11, 1.86, 1.72

データは1行目のみでその後進みません。

すみません、よろしくお願いいたします。


No title

解決しました
その後SDを替えてみたところ動作しました。

余り物のマイクロSDをアダプタにさして使っていたのですが新品のSDを使ったところすんなりと動きました。

私みたいな素人はちょっとつまずくとそこで挫折してしまいます。

よろしくお願いいたします。

jh3gpnさん、おはようございます

なるほど、ファイルのオープンでエラーが無くても読めないことがあるのですね。

そうやって記事の内容を検証していただけると助かります。

使用出来るSDには制限があるので、そのあたりの話を、記事の末尾にでも追加しておきます。

教えて下さい。

サイト”SDカードを使ったデーターロガー”の内容を拝見させて頂きました。  実験で使用する為に、使わせて頂きました。
最初のテストでは、うまく動いたのですが、  数回目から、"Reading SD card...initialization done. の表示後、LEDが点灯したまま、進みません。  大変厚かましいのですが、考えられる解決策について、ご教示の程、宜しくお願い致します。
 

re:教えて下さい。

モスさん、今晩は。

このハードはばらしてしまったので、実際に確認は出来ませんが、お尋ねの症状から想像すると、LogConf.txt の内容がおかしくなっているような気がします。

LogConf.txtの4行目までが使われます、(以降は無視されます)が、その範囲のカンマの数や変な文字、(特に全角の空白)が入ると、動作がおかしくなるはずです。あと、OSによって行末コードが違うので、そのあたりが影響しているのかも知れません。

No title

ご教示有難うございました。
エディターを替えて、LF(0x0A)を行末にすべて、変更した結果正常にデータログされました。
お陰様で、更なる一歩を踏み出すことが出来ます。
有難うございました。
以上

モスさん、おはようございます

了解です、解決してよかったです。

このプログラムを作る時に、行末コードの問題でうまく動かなかったことがあったので、134行目から始まるlineRead() 関数のコメントにその辺の話を詳しくコメントを入れてます。
というか、そういうことがあったのを今思い出しました。
カレンダー
11 | 2018/12 | 01
- - - - - - 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 31 - - - - -
プロフィール

ラジオペンチ

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

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