ArduinoでSDメモリカードを読み書きする
Posted by arms22 on 2013年02月20日

以前公開した「Arduinoで遊ぼう - SDメモリカードを読み書きする」の内容が古くなっていたので、新しく書き直しました。今回はArduino IDE標準のSDライブラリを使ってSDメモリカードを読み書きする方法を解説します。
最近のSDメモリカードは1GBを超える容量のものが非常に安価に発売されています。SDライブラリを使えばArduinoからも簡単に読み書きできるので、大規模なデータロギングに最適なストレージと言えます。
注意すべきこと
SDメモリカードの動作電圧は2.7~3.6Vです。SDカードにもよりますが書き込み時に電流は、100mAを超えて流れるものがあります。SDカードの電源は十分な容量を確保してください。
また5V動作のArduinoでSDメモリカードを使用する場合、Arduinoの出力電圧を5Vから3.3Vに変換する必要があります。今回、抵抗による分圧回路を使っていますが、信号がナマってしまってうまく通信できない場合があります。このような場合、電圧の変換には5Vトレラント機能が付いたバッファIC(74LVX245または74VHC125)や専用IC(2電源レベルシフタ)を使うことをお勧めします。
SDメモリカードには次の3種類の規格があります。
- SD - 最大2GB。FAT12・FAT16ファイルシステムを使用。
- SDHC - 2GB〜32GBまで。FAT32ファイルシステムを使用。
- SDXC - 32GB〜2TBまで。exFATファイルシステムを使用。
ピン配列
次の図はSDメモリカードとmicroSDメモリカードのピン配列を表しています。

SD端子 | microSD端子 | 機能 | 説明 |
9 | 1 | DAT2 | 未使用 |
1 | 2 | DAT3/CS | チップセレクト |
2 | 3 | CMD/DI | データ入力 |
3 | VSS | グランド | |
4 | 4 | VDD | 電源 |
5 | 5 | CLK | シリアルクロック |
6 | 6 | VSS | グランド |
7 | 7 | DAT0/D0 | データ出力 |
8 | 8 | DAT1 | 未使用 |
SDカードにはSDモードとSPIモードとよばれる2つのモードがあります。SDモードはSDカードの速度を最大限にいかした転送を行うモードです。SPIモードはCS/DI/CLK/DOの4つの端子を使った転送モードです。低速なマイコンとの通信に使われます。SDライブラリはSPIモードでSDカードにアクセスします。
Arduinoに接続する
SDメモリカードとの接続にはサンハヤトのmicroSD変換基板を使いました。変換基板が手に入らない場合は、SDカードアダプタ(micorSD-SD)にヘッダピンやワイヤを直接半田付けすると良いでしょう。電圧変換には抵抗分圧回路を使いました。抵抗値は1.8KΩと3.3KΩ。

Arduino vs SDメモリカード
ピン11 - DI (MOSI)
ピン12 - DO (MISO)
ピン13 - CLK (SCK)
ピン4 - CS
シールドを使ってArduinoに接続する
もっとお手軽・簡単に使いたい場合は、SDメモリカードスロットを搭載したシールドを使うと良いでしょう。例えば、イーサネットシールドやワイヤレスSDシールド、マイクロSDシールドなどです。これらのシールドには電圧変換用にバッファICなどが使われています。
シールドによってSDカードのCSに接続されているピンが異なるので注意すること。

Arduino イーサネットシールド R3
CS -> 4

ワイヤレスSDシールド
CS -> 4

SparkFun マイクロSDシールド
CS -> 8
SDライブラリのインポート
Arduino IDE標準のSDライブラリを使います。ファイルメニューから"スケッチ→ライブラリを使用→SD"を選ぶと自動的にインポートされます。
Arduino - SD Library
http://arduino.cc/en/Reference/SD
Arduino IDE 1.0.x以降では、複数ファイルの読み書きが同時に行えるようになりました。
SDライブラリにはいくつか制限があります。
- ファイル名は8文字+3文字までです。例えば「filename.txt」はOKですが、「filename_2013.text」はNGです。
- ファイルシステムはFAT16またはFAT32のみ対応しています。
- SDとSDHCのみ対応しています。SDXCは非対応です。
サンプルスケッチ(書き込み)
1秒間隔でアナログ入力0の値を読み取り、SDカードに書き込むサンプルです。
SD.open() の引数に FILE_WRITE を指定するとファイルを読み書きモードでオープンします。ファイルが存在しない場合は新しいファイルが作成され、すでにファイルが存在する場合は追記モードでオープンします。
print() や println() で数値や文字が書き込めます。バイナリデータを書き込む場合は、write() を使います。書き込み速度は一度に沢山のデータを書き込んだ方が速くなります。seek(0) でファイルの先頭からデータを書き込めます。
#include <SD.h>
// この値は使用しているシールドや基板に合わせて変更すること。たとえば、
// イーサーネットシールドは 4
// Adafruit のSDシールドは 10
// Sparkfun のSDシールドは 8
const int chipSelect = 4;
void setup()
{
// シリアルポート初期化
Serial.begin(9600);
while (!Serial) {
; // USBケーブルが接続されるのを待つ。この待ちループは Leonardo のみ必要。
}
Serial.print(F("Initializing SD card..."));
// SSピン(Unoは10番、Megaは53番)は使わない場合でも出力にする必要があります。
// そうしないと、SPIがスレーブモードに移行し、SDライブラリが動作しなくなります。
pinMode(SS, OUTPUT);
// SDライブラリを初期化
if (!SD.begin(chipSelect)) {
Serial.println(F("Card failed, or not present"));
// 失敗、何もしない
while(1);
}
Serial.println(F("ok."));
// 日付と時刻を返す関数を登録
SdFile::dateTimeCallback( &dateTime );
}
void loop()
{
// ファイルを開く
File dataFile = SD.open("datalog.txt", FILE_WRITE);
// もしファイルが開けたら値を書き込む
if (dataFile) {
int value = analogRead(0);
dataFile.println(value);
dataFile.close();
// シリアルポートにも出力
Serial.println(value);
}
// ファイルが開けなかったらエラーを出力
else {
Serial.println(F("error opening datalog.txt"));
}
// 一秒待つ
delay(1000);
}
void dateTime(uint16_t* date, uint16_t* time)
{
uint16_t year = 2013;
uint8_t month = 2, day = 3, hour = 9, minute = 0, second = 0;
// GPSやRTCから日付と時間を取得
// FAT_DATEマクロでフィールドを埋めて日付を返す
*date = FAT_DATE(year, month, day);
// FAT_TIMEマクロでフィールドを埋めて時間を返す
*time = FAT_TIME(hour, minute, second);
}
次のように日時を返す関数を登録することで、ファイルの作成日時や変更日時が記録できます。
// 日付と時刻を返す関数を登録
SdFile::dateTimeCallback( &dateTime );
サンプルスケッチ(読み込み)
ファイルに書き込まれている値をシリアルモニタに出力するサンプルスケッチです。
SD.open() の引数を省略すると読み込み専用モードでオープンします。引数を省略した場合は FILE_READ を指定したことと同じになります。
available() は現在の位置から何バイト読み込めるか返します。available() の戻り値は int 型で読み込めるバイト数が32767を超える場合、戻り値は常に32767になります。ファイルのサイズは size() で取得できます。
read() は1byte読み出します。read(buffer, length) とした場合、lengthバイト分のデータをbufferに読み込みます。
position() は現在の読み込み位置を返します。seek() で読み込み位置を移動できます。
#include <SD.h>
// この値は使用しているシールドや基板に合わせて変更すること。たとえば、
// イーサーネットシールドは 4
// Adafruit のSDシールドは 10
// Sparkfun のSDシールドは 8
const int chipSelect = 4;
void setup()
{
// シリアルポート初期化
Serial.begin(9600);
while (!Serial) {
; // USBケーブルが接続されるのを待つ。この待ちループは Leonardo のみ必要。
}
Serial.print(F("Initializing SD card..."));
// SSピン(Unoは10番、Megaは53番)は使わない場合でも出力にする必要があります。
// そうしないとSPIがスレーブモードに移行し、SDライブラリが動作しなくなります。
pinMode(SS, OUTPUT);
// SDライブラリを初期化
if (!SD.begin(chipSelect)) {
Serial.println(F("Card failed, or not present"));
// 失敗、何もしない
while(1);
}
Serial.println(F("ok."));
// ファイルを開く
File dataFile = SD.open("datalog.txt");
// もしファイルが開けたら値をシリアルポートに出力する
if (dataFile) {
// 64byte単位で読み出す
byte buffer[64];
while (dataFile.available()) {
int length = dataFile.available();
if(length > 64){
length = 64;
}
dataFile.read(buffer, length);
Serial.write(buffer, length);
}
// 1byte単位で読み出す
// while (dataFile.available()) {
// Serial.write(dataFile.read());
// }
dataFile.close();
}
// ファイルが開けなかったらエラーを出力する
else {
Serial.println(F("error opening datalog.txt"));
}
}
void loop()
{
}
参考リンク
SDメモリカード - Wikipedia
http://ja.wikipedia.org/wiki/SDSDメモリカード
ArduinoでマイクロSDカードを使う - ラジオペンチ
http://radiopench.blog96.fc2.com/blog-entry-332.html
東芝 無線LAN搭載 SDHC FlashAir Class10 32GB SD-R032GR7AL
posted with amazlet at 14.12.21
東芝
売り上げランキング: 552
売り上げランキング: 552
Adafruit データロギング シールド Data Logging shield for Arduino
posted with amazlet at 14.12.21
Adafruit
売り上げランキング: 477,896
売り上げランキング: 477,896
Ads by Google
28 Comments
今日は、リンク貼っていただいて、ありがとうございます。こちらはちょくちょく拝見してますです。
実は、RTCのライブラリ使わせていただいてます。ただ、コンバイルすると、ライブリのdelay(1000)の行でエラーが出ます。原因が判らないのでコメントアウトで回避しましてます。
http://radiopench.blog96.fc2.com/blog-entry-330.html
原因は何でしょう。
便乗質問ですみません。
実は、RTCのライブラリ使わせていただいてます。ただ、コンバイルすると、ライブリのdelay(1000)の行でエラーが出ます。原因が判らないのでコメントアウトで回避しましてます。
http://radiopench.blog96.fc2.com/blog-entry-330.html
原因は何でしょう。
便乗質問ですみません。
いえいえこちらこそSDの波形、参考になりました。
RTCライブラリ更新したいんだけど、なかなか手が回らなくて。。
すみません。。
delay(1000)でエラーってなんでしょうね。
その行の前後に日本語の空白とか入っていませんか?
エラーメッセージがあると何かわかるかも。
それとピン6と7でもピン変化割り込みが使えますよ。
Arduinoには関数はないけど、割り込みハンドラとレジスタの設定をすれば使えますよ。
RTCライブラリ更新したいんだけど、なかなか手が回らなくて。。
すみません。。
delay(1000)でエラーってなんでしょうね。
その行の前後に日本語の空白とか入っていませんか?
エラーメッセージがあると何かわかるかも。
それとピン6と7でもピン変化割り込みが使えますよ。
Arduinoには関数はないけど、割り込みハンドラとレジスタの設定をすれば使えますよ。
ライブラリ(RTC8564.cpp)の先頭付近に
#include "Arduino.h"
を追加したらコンパイラが通るようになりました。
経緯は
http://radiopench.blog96.fc2.com/blog-entry-330.html
のコメント欄にあります。
#include "Arduino.h"
を追加したらコンパイラが通るようになりました。
経緯は
http://radiopench.blog96.fc2.com/blog-entry-330.html
のコメント欄にあります。
お世話になっています。
SDカードライブラリーも
他のシールドとの併用している関係なのか
ファイル名の指定方法によって動作したりしなかったり
タイミングの関係か
正常動作させるのに苦労しています。
今回、ファイルの日付時刻指定を使わさせて頂きました。
SDカードライブラリーも
他のシールドとの併用している関係なのか
ファイル名の指定方法によって動作したりしなかったり
タイミングの関係か
正常動作させるのに苦労しています。
今回、ファイルの日付時刻指定を使わさせて頂きました。
このままでは、"error: 'SPI' was not declared in this scope"と言われてしまうのですがどうすればいいのでしょうか?
spi.hをインクルードしてみてください。
このコメントは管理人のみ閲覧できます
このコメントは管理人のみ閲覧できます
擬似コードだけどこんなかんじ?
val = digitalRead();
if(val != old_val)
{
File dataFile = SD.open("datalog.txt", FILE_WRITE);
dataFile.print(" ");
dataFile.print(hour);
dataFile.print(":");
dataFile.print(min);
dataFile.print(":");
dataFile.print(sec);
dataFile.println(value);
dataFile.close();
old_val = val;
}
delay(1000);
何をやりたいのか・何をやってどうなったのか、箇条書きにしてくれたらもう少しアドバイスしやすいと思う。
val = digitalRead();
if(val != old_val)
{
File dataFile = SD.open("datalog.txt", FILE_WRITE);
dataFile.print(" ");
dataFile.print(hour);
dataFile.print(":");
dataFile.print(min);
dataFile.print(":");
dataFile.print(sec);
dataFile.println(value);
dataFile.close();
old_val = val;
}
delay(1000);
何をやりたいのか・何をやってどうなったのか、箇条書きにしてくれたらもう少しアドバイスしやすいと思う。
このコメントは管理人のみ閲覧できます
Arduino単体では起動時からの時間をカウントする(これも精度はあまりよくない)ぐらいしかできません。
正確な時間が継続的に必要であるならRTCを組み込んだほうが良いでしょう。
処理の流れはおおよそ次のような形になるのではないでしょうか。
1.系統1〜16読み込み
2.現在時間取得
3.書き込み
4.n秒待つ
5.1〜4を繰り返す
正確な時間が継続的に必要であるならRTCを組み込んだほうが良いでしょう。
処理の流れはおおよそ次のような形になるのではないでしょうか。
1.系統1〜16読み込み
2.現在時間取得
3.書き込み
4.n秒待つ
5.1〜4を繰り返す
このコメントは管理人のみ閲覧できます
このコメントは管理人のみ閲覧できます
いつも拝見させていただいております。
RTC、SDカード等、本当に助けられております。
ライブラリやお知恵をお借りしまして、勉強して、やりたいことを少しずつ実現しております。
arms22様のおかげで、Arduinoが楽しくて、電子工作が楽しくて、本当に、大感謝です。
何か楽しい物ができたら(きっとarms22様には叶いませんが)、報告できますことを楽しみにしております。
これからも継続した更新楽しみにしております。
ご自愛下さいませ。
それでは。
RTC、SDカード等、本当に助けられております。
ライブラリやお知恵をお借りしまして、勉強して、やりたいことを少しずつ実現しております。
arms22様のおかげで、Arduinoが楽しくて、電子工作が楽しくて、本当に、大感謝です。
何か楽しい物ができたら(きっとarms22様には叶いませんが)、報告できますことを楽しみにしております。
これからも継続した更新楽しみにしております。
ご自愛下さいませ。
それでは。
digitalReadを1ピン分しか呼んでないみたいだけど、
全部のピンをチェックして変化があるか判断しないといけないのでは?
> void loop()
> {
> DateTime now = RTC.now();
> val = digitalRead();
> if(val != old_val)
> {
全部のピンをチェックして変化があるか判断しないといけないのでは?
> void loop()
> {
> DateTime now = RTC.now();
> val = digitalRead();
> if(val != old_val)
> {
arms22様
早速ありがとうございます。
鋭いですね・・・
全部のピンをチェックする方法が分からないのです。
まだまだ、勉強不足です。
本当に、ありがとうございます。
早速ありがとうございます。
鋭いですね・・・
全部のピンをチェックする方法が分からないのです。
まだまだ、勉強不足です。
本当に、ありがとうございます。
digitalReadの引数を読みたい端子の番号にかえればよいよ。
12本あるなら12回、digitalReadの引数をかえて読めばよい。
for文つかって12回まわしてもいい。
12本あるなら12回、digitalReadの引数をかえて読めばよい。
for文つかって12回まわしてもいい。
このコメントは管理人のみ閲覧できます
> val = digitalRead(0);
> val = digitalRead(1);
> val = digitalRead(2);
> val = digitalRead(3);
> val = digitalRead(4);
> val = digitalRead(5);
> val = digitalRead(6);
> val = digitalRead(7);
> val = digitalRead(8);
> val = digitalRead(9);
> val = digitalRead(14);
> val = digitalRead(15);
> val = digitalRead(16);
> val = digitalRead(17);
> val = digitalRead(18);
> val = digitalRead(19);
うん、val上書きしちゃってるね。
valの名前を変えるか配列にしないとだめだね。
16本だったらvalをuint16型にして1bitずつシフトして代入してもいいかもね。
> val = digitalRead(1);
> val = digitalRead(2);
> val = digitalRead(3);
> val = digitalRead(4);
> val = digitalRead(5);
> val = digitalRead(6);
> val = digitalRead(7);
> val = digitalRead(8);
> val = digitalRead(9);
> val = digitalRead(14);
> val = digitalRead(15);
> val = digitalRead(16);
> val = digitalRead(17);
> val = digitalRead(18);
> val = digitalRead(19);
うん、val上書きしちゃってるね。
valの名前を変えるか配列にしないとだめだね。
16本だったらvalをuint16型にして1bitずつシフトして代入してもいいかもね。
このコメントは管理人のみ閲覧できます
old_valが配列として宣言されてないとかかな?
>int old_val = 0;// valの前の値を保存しておく変数
↓
int old_val[DPINMAX];
>int old_val = 0;// valの前の値を保存しておく変数
↓
int old_val[DPINMAX];
このコメントは管理人のみ閲覧できます
何も反応がないというのはSDカードの初期化に失敗しているということかな?
とりあえずデバッグ用のprint処理をいれて何処まで処理が進んでいるのか確認するところからはじめたらよいのではないでしょうか。シリアルのボーレートがあっているかも確認したほうが良いでしょう。
とりあえずデバッグ用のprint処理をいれて何処まで処理が進んでいるのか確認するところからはじめたらよいのではないでしょうか。シリアルのボーレートがあっているかも確認したほうが良いでしょう。
arms22様
ありがとうござます。
「反応が無い」症状は原因が分かりました。
スケッチを書き込んだ後に一度USBを抜いて再起動してからシリアルモニターを表示させると動きました。
シリアルモニターには
card initialized.
Setup complete.
とでますが、ボタンを押してもDATA LOGGERとして反応が無い。
「デバッグ用のprint処理」とは、
Serial.println(val); // デバッグ用
これだとvalの状態なので進行状況とは違うと思うのですが・・・・
進行状況を確認するprint処理の場合は、各指示(時間やカード書き込み準備)の後に入れる形でしょうか。
このページの内容とずれてしまっていて申し訳ありません。
ありがとうござます。
「反応が無い」症状は原因が分かりました。
スケッチを書き込んだ後に一度USBを抜いて再起動してからシリアルモニターを表示させると動きました。
シリアルモニターには
card initialized.
Setup complete.
とでますが、ボタンを押してもDATA LOGGERとして反応が無い。
「デバッグ用のprint処理」とは、
Serial.println(val); // デバッグ用
これだとvalの状態なので進行状況とは違うと思うのですが・・・・
進行状況を確認するprint処理の場合は、各指示(時間やカード書き込み準備)の後に入れる形でしょうか。
このページの内容とずれてしまっていて申し訳ありません。
> 各指示(時間やカード書き込み準備)の後に入れる形でしょうか。
うん、そう。
反応がないってことは期待した処理が実行されていないわけだから、
どこかでとまってるか、その処理が実行されてない。
だからその実行されない条件は何か?
1つ1つ見ていく必要がある。
うん、そう。
反応がないってことは期待した処理が実行されていないわけだから、
どこかでとまってるか、その処理が実行されてない。
だからその実行されない条件は何か?
1つ1つ見ていく必要がある。
初めまして。
いつもとても参考にさせていただいております。
電子工作やArduino関連のブログを立ち上げたばかりですが、こちらのリンクを貼らせていただきました。
EthernetシールドのSDカードの読み込み不良が長い間続き、とても悩んだのですが、
結局電圧不足で、ACアダプターとUSB同時挿しで無事解決しました。
このページに「SDカードの電源は十分な容量を確保してください。」と書いてあり、とても参考になりました。
ありがとうございました。
いつもとても参考にさせていただいております。
電子工作やArduino関連のブログを立ち上げたばかりですが、こちらのリンクを貼らせていただきました。
EthernetシールドのSDカードの読み込み不良が長い間続き、とても悩んだのですが、
結局電圧不足で、ACアダプターとUSB同時挿しで無事解決しました。
このページに「SDカードの電源は十分な容量を確保してください。」と書いてあり、とても参考になりました。
ありがとうございました。
問題が解決して良かったです。
SDカードによって使用する電流がまちまちだったりするのではまりどころですね。
昨今のwifi機能を搭載したSDカードなどはかなり電流が必要そうです。
SDカードによって使用する電流がまちまちだったりするのではまりどころですね。
昨今のwifi機能を搭載したSDカードなどはかなり電流が必要そうです。
>昨今のwifi機能を搭載したSDカードなどはかなり電流が必要そうです。
なるほど、そうなんですね。 いずれWiFiも試そうと思っていたので、そこのところは気を付けたいと思います。
本当に勉強になりました。 ありがとうございました。
なるほど、そうなんですね。 いずれWiFiも試そうと思っていたので、そこのところは気を付けたいと思います。
本当に勉強になりました。 ありがとうございました。
Leave a reply
該当の記事は見つかりませんでした。