SDカードを利用するにはカードリーダーが必要で、今回はサンハヤトのCK-40を利用した。秋月のキット(K-05488)も入手したが、カードの幅が狭いCK-40にした(カード検出回路は不使用)。SDカードスロットの動作電圧が3.3Vとなっているので、Arduinoで使う場合はI2Cの信号電圧を変換する必要がある。電圧変換法には種々の方法があるが、今回は抵抗(2.0kΩと3.9kΩ)を利用する分圧法を使った。スケッチはサンプルにある「SDカード/Datalogger」を参考にした。
さらに、最終的にデータロガーとしての利用を考慮し、時刻の修正機能と、時計機能の電池バックアップ回路(CR2032(3V)、電池ホルダーは両面テープで固定)も追加した。
【時刻修正】 @「赤ボタン」を長押し(約3秒以上)する A 時刻設定モードとなり「年」が表示される B「黄ボタン」は「−」、「緑ボタン」は「+」 C「赤ボタン」押すと次の「月」・・「分」へ D BとCを繰り返す E「分」の設定後、赤ボタンを押すと設定終了 ※(秒は常に0にリセットしています) ※(月の日数が正しいかチェックしていない) ※(初期時刻セット:赤ボタンを押して起動) 【通常動作時】 <モード変更> ・黄ボタンを押すと「時刻」/「温湿度」に変わる。 <時刻表示時> ・緑ボタンを押すと「秒」/「曜日」に変わる。 <温湿度表示時> ・緑ボタンを押すと「記録中」/「記録停止」に変わる(SD記録中はSDアクセスLEDが点灯)。 <SD記録> ・SDへの記録はCSV形式で記録しています。 ・ファイル名はdatalog.txt(固定)で、データの記録はアペンドモードで行います。 ・最小計測・記録間隔は1秒です。 ・記録モード中にはSDカードを抜かないこと。
![]() |
![]() |
![]() |
| ブレッドボード配線 (右下はGNDベタ) |
データを取込み記録中 (LEDが点灯) |
ボタン電池(CR2032)を取付 |
![]() |
![]() |
| 温湿度測定SD記録回路図 | 記録したデータをパソコンで確認 |
// 温度・湿度測定+RTC+LCD( RTC設定 )+SD記録 #include |
int subZeller(int y, int m, int d ){
if(m <3) { y--; m += 12; }
return (y + y/4 - y/100 + y/400 + (13*m + 8 )/5 + d )%7;
}
//***** 液晶表示 ********(使用電源によりどちらか選択)
//#define BOOST 0x04 // 3.3V用 AQM0802
#define BOOST 0x00 // 5.0V用 AQM0802
int contrast = 0x20; // コントラスト初期値
void lcd_cmd(byte cmd){
Wire.beginTransmission(LCD_Adr);
Wire.write(0x00); // コマンド指定
Wire.write(cmd); // コマンド出力
Wire.endTransmission(); // ストップ
if((cmd == 0x01)||(cmd == 0x02)) // ClearかHomeか
delay(2); // 2msec待ち
else
delayMicroseconds(30); // 30μsec待ち
}
void lcd_data(byte data){
Wire.beginTransmission(LCD_Adr); // スタート
Wire.write(0x40); // 表示データ指定
Wire.write(data); // 表示データ出力
Wire.endTransmission(); // ストップ
delayMicroseconds(30); // 遅延
}
void lcd_setCursor(byte clm,byte row){
if(row==0) lcd_cmd(0x80+clm); // カーソル位置
if(row==1) lcd_cmd(0xc0+clm);
}
void lcd_begin(void){ // LCDの初期化
Serial.println("LCD_begin");
delay(10);
lcd_cmd(0x38); // 8ビット2行 標準モード
lcd_cmd(0x39); // 8ビット2行 拡張モード
lcd_cmd(0x14); // OSC 183Hz BIAS 1/5
lcd_cmd((byte)(0x70 + (contrast & 0x0F)));
lcd_cmd((byte)(0x58 + BOOST + (contrast >> 4)));
lcd_cmd((byte)0x6B); // Follwer for 3.3V
delay(1);
lcd_cmd((byte)0x38); // 標準モードへ
lcd_cmd((byte)0x0C); // 表示開始
lcd_cmd((byte)0x01); // 画面消去
delay(1);
}
void lcd_clear(void){
lcd_cmd(0x01); // 全消去コマンド出力
delay(2);
}
void lcd_print(char* ptr){ // 文字列表示関数
while(*ptr != 0) // 文字取り出し
lcd_data(*ptr++); // 文字表示
}
void lcd_print(int num){ // 整数表示関数
sprintf(s_buf,"%d",num);
lcd_print(s_buf);
}
//***** HDC1050 ********
#define HDC_TEMP 0 // HDCレジスタアドレス
#define HDC_HUME 1
#define HDC_CONFIG 2
void hdc_write(byte adr, word data){
Wire.beginTransmission(HDC_Adr);
Wire.write(adr); // アドレス指定
Wire.write((byte)(data >> 8)); // データ書込み(MSD8)
Wire.write((byte)(data % 0xff)); // データ書込み(LSD8)
Wire.endTransmission();
delay(1);
}
unsigned int hdc_read(byte adr){
word rd_dat;
Wire.beginTransmission(HDC_Adr);
Wire.write(adr); // アドレス指定
Wire.endTransmission();
Wire.requestFrom(HDC_Adr,2); // データ1バイト送信要求
while ( Wire.available()== 0 );
rd_dat = Wire.read() << 8 ; // データ取得(MSD8)
rd_dat = rd_dat | Wire.read(); // データ取得(LSD8)
return rd_dat;
}
void hdc_trigger(byte adr){
Wire.beginTransmission(HDC_Adr);
Wire.write(adr); // アドレス指定
Wire.endTransmission();
delay(1);
}
|
unsigned int hdc_read_data(){
word rd_dat;
Wire.requestFrom(HDC_Adr,2); // データ1バイト送信要求
rd_dat = Wire.read() << 8 ; // データ取得(MSD8)
rd_dat = rd_dat | Wire.read(); // データ取得(LSD8)
return rd_dat;
}
void hdc_begin(){ //****** 条件により選択
Serial.println("HDC_begin");
//hdc_write(HDC_CONFIG, 0x0000); // TH単独、14bit、ヒートoff
//hdc_write(HDC_CONFIG, 0x1000); // TH同時、14bit、ヒートoff
hdc_write(HDC_CONFIG, 0x2000); // TH単独、14bit、ヒートon
//hdc_write(HDC_CONFIG, 0x3000); // TH同時、14bit、ヒートon
delay(1);
sprintf(s_buf,"HDC device: %x", hdc_read(0xff) );
Serial.println(s_buf);
}
//********** interrupt routin *****
void rtc_intr(){ // 割込処理ルーチン
rtc_int_flg = true; // 割込フラグをセット
// Serial.println("interrupt!"); // 周期割込確認用
}
//********** Time Set Mode ******
void TimSet(){
lcd_setCursor(0,0); // カーソルを0行目の先頭に
lcd_print("Time Set"); // LCDに年月日を表示
sprintf( s_buf,"Yer:20%02d",years );
lcd_setCursor(0,1); // カーソルを1行目の先頭に
lcd_print(s_buf); // LCDに年を表示
while( digitalRead(StBtn_Pin) == LOW ); // highまで待つ
RedCnt = 0;
for ( int i=0; i<5; i++ ){
sprintf(s_buf,"set %d", i );
Serial.println(s_buf);
do{
if ( digitalRead(MdBtn_Pin) == LOW ){
switch(i){
case 0: years++; if (years>99) years=0; break;
case 1: months++; if (months>12) months=1; break;
case 2: days++; if (days>31) days=1; break;
case 3: hours++; if (hours>23) hours=0; break;
case 4: minutes++; if (minutes>59) minutes=0; break;
}
while( digitalRead(MdBtn_Pin) == LOW ); // highまで待つ
}
if ( digitalRead(THBtn_Pin) == LOW ){
switch(i){
case 0: years--; if (years<0) years=99; break;
case 1: months--; if (months<1) months=12; break;
case 2: days--; if (days<1) days=31; break;
case 3: hours--; if (hours<0) hours=23; break;
case 4: minutes--; if (minutes<0) minutes=59; break;
}
while( digitalRead(THBtn_Pin) == LOW ); // highまで待つ
}
switch(i){
case 0: sprintf( s_buf,"Yer:20%02d",years ); break;
case 1: sprintf( s_buf,"Month:%02d",months ); break;
case 2: sprintf( s_buf,"Day: %02d",days ); break;
case 3: sprintf( s_buf,"Hour: %02d",hours ); break;
case 4: sprintf( s_buf,"Minut:%02d",minutes ); break;
}
lcd_setCursor(0,1); // カーソルを1行目の先頭に
lcd_print(s_buf); // LCDに変更データを表示
delay(100); // チャタリング対策(不完全)
} while( digitalRead(StBtn_Pin) == HIGH );
}
seconds = 0;
weeks = subZeller( 2000 + years, months, days );
rtc_write_data();
while( digitalRead(StBtn_Pin) == LOW ); // highまで待つ
}
//********* setup ***********
void setup(){
Serial.begin(9600); // シリアルモニタON
pinMode(MdBtn_Pin, INPUT_PULLUP); // モード選択ボタン
pinMode(THBtn_Pin, INPUT_PULLUP); // 時間・温度選択ボタン
pinMode(StBtn_Pin, INPUT_PULLUP); // 時間セットボタン
pinMode(SDLed_Pin, OUTPUT);
pinMode(SS_PIN, OUTPUT); // これは必須
// digitalWrite(SS_PIN, HIGH); // なくても?
Wire.begin(); // I2C通信開始
delay(1);
if ( digitalRead(StBtn_Pin) == LOW ) rtc_begin();
// 起動時に赤ボタンが押されていたら、RTCを初期化
lcd_begin(); // LCDの初期化
hdc_begin();
attachInterrupt( 0, rtc_intr, FALLING ); //割込設定
if (!SD.begin( chipSelect )) {
Serial.println("Card failed, or not present");
// 特にSDエラー処理をしていないので注意
}else{
Serial.println("SD Card Ready");
}
Serial.println("-- setup end");
}
|
//********* main loop ***********
unsigned int temp; // 温度データ取得用
unsigned int hum; // 湿度データ取得用
float f_temp; // 浮動小数点温度データ
float f_hum; // 浮動小数点湿度データ
char buf[10] = ""; // 一時文字変換用
boolean sd_act = false; // SD書き込みモード
File dataFile; // SD用FILE構造体
void loop(){
if ( digitalRead(StBtn_Pin) == LOW ){
if ( RedCnt > 100 ) TimSet(); // 長押しで時刻設定モードへ
else RedCnt++; // 設定時までカウンタを+1
}
if ( digitalRead(MdBtn_Pin) == LOW ){
dsp_md = dsp_md ^ 0x01; // ビット0を反転
if (dsp_md == 3){
sd_act = true;
dataFile = SD.open( "datalog.txt",FILE_WRITE );
digitalWrite(SDLed_Pin, HIGH); // SDに書込準備完了
}else if (dsp_md == 2){
sd_act = false;
dataFile.close(); // SDに書込終了
digitalWrite(SDLed_Pin, LOW);
}
while( digitalRead(MdBtn_Pin) == LOW ); // highまで待つ
}
if ( digitalRead(THBtn_Pin) == LOW ){
dsp_md = dsp_md ^ 0x02; // ビット1を反転
while( digitalRead(THBtn_Pin) == LOW ); // highまで待つ
}
hdc_trigger( HDC_TEMP ); // 温度測定開始
delay(10);
temp = hdc_read_data();
//temp = 0xffff; // 表示テスト用
f_temp = (float)temp / 65536.0 * 165.0 - 40.0;
hdc_trigger( HDC_HUME ); // 湿度測定開始
delay(10);
hum = hdc_read_data();
f_hum = (float)hum / 65536.0 * 100.0;
if ( rtc_int_flg == true ){ // 割込フラグが立っているか?
rtc_read_data(); // RTCからデータを読む
switch ( dsp_md ){
case 0:
case 1:
sprintf( s_buf,"%02d/%02d/%02d,%3s,",years,months,days,wk_dt[weeks] );
break;
case 2:
case 3:
if (( f_temp > 99.9 )||( f_temp < 0.0 )){
dtostrf( (double)f_temp,5,1,buf); // "%f"の代わり
sprintf( s_buf,"T%5s\337C,",buf );
}else{
dtostrf( (double)f_temp,4,1,buf); // "%f"の代わり
sprintf( s_buf,"T:%4s\337C,",buf );
}
break;
}
lcd_setCursor(0,0); // カーソルを0行目の先頭に
lcd_print(s_buf); // LCDに年月日を表示
Serial.print( s_buf ); // 表示テスト・確認用
switch ( dsp_md ){
case 0:
sprintf( s_buf,"%02d:%02d:%02d",hours,minutes,seconds );
break;
case 1:
sprintf( s_buf,"%02d:%02d%3s",hours,minutes,wk_dt[weeks] );
break;
case 2:
case 3:
dtostrf( (double)f_hum,4,1,buf); // "%f"の代わり
sprintf( s_buf,"H:%4s %%",buf );
break;
}
lcd_setCursor(0,1); // カーソルを1行目の先頭に
lcd_print(s_buf); // LCDに時分秒を表示
Serial.println( s_buf ); // 表示テスト・確認用
if (sd_act==true){ // SDにデータを書き込み
sprintf( s_buf,"20%02d/%02d/%02d, %3s,",years,months,days,wk_dt[weeks] );
dataFile.print(s_buf);
sprintf( s_buf," %02d:%02d:%02d,",hours,minutes,seconds );
dataFile.print(s_buf);
dtostrf( (double)f_temp,5,1,buf); // "%f"の代わり
sprintf( s_buf," %5s,",buf );
dataFile.print(s_buf);
dtostrf( (double)f_hum,4,1,buf); // "%f"の代わり
sprintf( s_buf," %4s",buf );
dataFile.println(s_buf);
}
rtc_int_flg = false; // 割込フラグ消去
}
delay(10);
}
|
| 温度・湿度−SD記録スケッチ (hdc1050_logger.ino), (hdc1050_logger.zip) |
フリーの電子回路設計ツールとして"Eagle"を使っていた。Eagleは自動配線が出来るなど便利だが、パターン図が必要なくてもパターンデータを作成しなければならず面倒になってきた。そこで、もっと簡単な電子回路図のみの作成ツールとして、水魚堂の"bsch3v.exe"を使ってみた。実際に利用するには部品ライブラリが必要で、ライブラリ作製・編集ツールであるLCoV.exeで部品を作成し、bsch3の「設定/ライブラリ」でライブラリの設定を行うと、作成したパーツが使えるようになります。
使い方は簡単で、画面の上にアイコンがいくつも並んでいますが、そのうち「セレクタ」は個々の部品を選択するツールで、配線は接続を維持したまま移動しませんが、ネーム名や配置変更などが自由に行えます。「ドラッグ」はパーツを移動するとワイヤーも繋がったまま一緒に移動します。この機能を使い分けることでほとんど思い通りの配置にすることが出来ます。
詳細は水魚堂のオンラインマニュアルを参照。
![]() |
![]() |
![]() |
| 回路設計画面 | パーツ選択画面 | ライブラリ編集画面 |
![]() |
| VoiceTraの利用画面 |
![]() |
![]() |
![]() |