google-site-verification: google3bd66dd162ef54c7.html

Arduinoで作るSDカードを使ったデーターロガー

 前の記事で簡単なデーターロガーを作りましたが、やはり液晶表示があった方が使い易そうなので、改良版を作ってみました。

▼液晶表示を付けたデーターロガー
ロガーのブレッドボード
 ご覧のように16x2のキャラクタ液晶を付けました。この写真では見づらいですが、液晶の接続基板は本来の向きとは裏返しに使いました。こうした方が構造的に安定するし、フットプリントも少し小さくなります。

▼回路図 (クリックで別窓に拡大)
ロガーの回路図
 I2C液晶なので接続は4本だけで済みます。この手軽さは嬉しいです。

 右下の明るさセンサーは周囲の明るさをモニターするためのものですが、これは無くてもOKです。

 ロガーのスケッチはこちら (拡張子をinoに要変更。S-JISでエンコードしています)

 ログの記録条件はLogConf.txtで指定しますが、下記はその設定例です
Data Logger test 2017/10/3
Log00450.csv
60
N, time(s), ch0, ch1, ch2, brightness, cpuTemp(C), cpuVcc(V)

説明
これはSDロガーの設定ファイルです。最初の4行だけ有効で、
以降はメモ領域(何を書いても読み飛ばされる)
1行目:ここに書いたテキストはログデーターの最初に記録される
2行目:ログファイル名(8+3形式で指定)
3行目:記録間隔。秒単位で指定
4行目:列の名前
 以下は実行時の液晶画面です

▼ログ記録開始前
開始時の液晶表示
 ログの名称(LogConf.txtの1行目の最初の16文字)とログファイル名、ログ記録間隔(秒)を表示します。

▼記録中
記録中の液晶表示
 1行目は左から、記録インターバル残り時間、記録した行数、CPUの温度。2行目は、各チャンネルの電圧で、表示の内容は1秒周期で更新されます。

▼ログの例
出力例
 brightnessはフォトトランジスタで検出した周囲の明るさです。このロガーはRTCが無いので正確な時刻は判りませんが、明るさセンサーがあれば、昼夜の区別くらいはつけることが出来ます。なおbrightnessの値は、夜間は5V近くになり、昼間は1V以下の値になります。この感度を調整したい場合は、フォトトラのコレクタ抵抗(Rp)をCPU内蔵のプルアップ抵抗から外部抵抗に変更すればいけると思います。

 cpuTempはCPUに内蔵された温度センサーの値、cpuVccはCPUの電源電圧です。このあたりの測定原理は以前の記事をご覧ください。なおCPUに内蔵されている温度センサの分解能はとても粗いので、何度も測定してアベレージングすることで無理やり 0.1 度単位の値にしています。また絶対精度も怪しいので、無いよりがマシくらいに考えて下さい。それにCPUチップの発熱の影響もモロに受けますし、、

 CPU電源電圧(cpuVcc)はADCの内部基準電圧(1.1V)を使って測定しているので、そこそこ正確な値が得られるはずです。なお、ch0~3 の測定結果は、フルスケール(電源電圧)が5Vという前提で換算した値なので、絶対値が問題になる場合は注意が必要です。

◆まとめ
 Arduinoで作るデーターロガーとしては結構高機能なものが出来たと思います。これをベースに改造でもなんでもやって試していただけば、と思います。

 次はこれを電力ロガーとして仕上げていきたいと思います。電力量計を買ったのが7月の終わりなので、あれから2ケ月経ったわけですが、納期など無いので自分のペースでのんびりとやっていきたいと思います。

 ところで、今回のプログラムは液晶に固定フォーマットで数値を表示させていますが、そのプログラムが何とも美しくないのが気になっています。lcd.printf( )とやって書式指定が出来ればいいのですが、液晶のライブラリにはそんな機能はサポートされていないようです。ということで、他の手を探す必要があります。
 String形式を操作する関数を使うか、sprintf()を使えば良さそうですが、うまくいったら記事にしたいと思います。

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

ArduinoにSDメモリーカードを接続する実験

 最近は無線LANでデーターを飛ばして適当なサーバーに保存するのが流行っています。確かに便利ですが、SDメモリーカードにデーターを保存して、自己完結型にするのも悪くない方法だと思います。

 実はこの間から取り組んでいる電力量計の測定結果をロギングする方法について検討していますが、SDメモリーカードにデーターを保存するのが使い易いかな、と思っています。実は以前気圧ロガーを作った時にSDメモリーカードを使いましたが、この時はとにかく動かすことを優先したので、作り込みがちょっと甘いところがあって気になっていました。

 そんなことで、インターフェイスレベルからもう一度確認していくことにします。

 SDカードの接続アダプタはいろいろありますが、今回は秋月で売っているSDカードスロットDIP化モジュールを使うことにしました。

▼秋月のSDカードアダプタ
秋月のSDカードアダプタ
 標準サイズのSDカード用のアダプタです。なおこの写真は東芝のSDメモリーカードが挿さった状態です。

▼SDカードアダプタの回路図 (秋月の資料から抜粋)
秋月のSDカード接続アダプタ
 ボード上に3.3Vのレギュレータが実装されているので使い易いです。当然J1を接続して使います。すべての信号線が10kΩで+3.3Vにプルアップされています。

 Arduino UNOは5Vで動いているので信号を3.3Vに変換する必要があります。10kΩの抵抗はそのままにして数百Ωの抵抗で分圧する方法が判り易いですが、ネットを調べると抵抗はそのままでダイオードを使った事例がありました。ちなみに以前私が分圧抵抗方式で動かした時の記事はこちら。

▼ダーオードを使ったレベル変換回路
5V to 3.3Vレベル変換回路
 ダイオードを直列に入れるだけの簡単な回路です。プルアップ抵抗(Rp)はアダプタ内蔵の物を使うと10kΩになります。これでSDのサンプルプログラムは動いたのですが、いくらなんでも10kΩは高すぎると思うので波形を見てみました。

▼SDの信号波形調査中
ArduinoへSDカード接続テスト

▼Rp=10kΩの場合の波形
10kΩ
 写真が見づらいですが、上がSDA、下がクロックで、縦軸は1V/Div.です。

 案の定立ち上がりがものすごくなまっていて十分なレベルに達していません。プログラムを走らせながらプロービングすると、ハングアップすることがあるので、全くマージンが無い状態です。これではダメです。

▼Rp=750Ωの場合 (正確には750//10k)
750Ω
 10kΩに並列に750Ωを入れた状態です。縦軸は2V/Div.です。
 やや立ち上がりが遅いのとダイオードの影響でLowレベルが少し持ち上がっていますが、これくらいなら大丈夫でしょう。クロック 周波数4MHzで、クロックの立ち上がりでデーターを読んでいるようです。

▼追加のプルアップ抵抗の実装方法
プルアップ抵抗を750Ωに改造
 チップ抵抗を持っていないのでラジアル部品をこの位置に取り付けて、内部の10kΩとパラにしました。

▼全体回路図
ArduinoにSDカード接続する回路
 Arduinoと接続する場合の全体回路図です。750Ωのプルアップ抵抗はCS, SDI, CLKに入れています。右下は使い勝手を良くするためのスイッチやLEDで、この後の記事で使います。

 この回路図の状態で、Arduino IDE に入っているSDのサンプルプログラムは全て動かすことが出来ました。

◆まとめ
 ということでインターフェイス回路は大丈夫そうなので、次はSDメモリーカードロガーのプログラムを作っていきます。

 なお、いきなり電力ロガー用のプログラムを作ると汎用性が無くなってしまうので、ある程度一般的なロガープログラムを先に作りたいと思います。こうしておくとプログラムの改造が楽になるので、どなたかがが応用プログラムを書いてくださるかも知れません。
カレンダー
10 | 2017/11 | 12
- - - 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コード