NTP時刻・温度・湿度・気圧データのSDカードロガー制作。日本語フォントもSDカードから読み込む ( ESP-WROOM-02 使用 )

記事公開日:2016年8月17日
最終修正日:2017年1月26日

スポンサーリンク

※OLED_SSD1351ライブラリのbeta 1.53 では動きません。beta 1.40をご使用ください。
こちらのページに再アップしました。
今までうまく動作しなかった方は大変失礼いたしました。
ライブラリをアンインストールする際は以前のライブラリのフォルダごと削除してから、新たにインストールしてください。(2016/10/21)

こんばんは。

前回の記事では、BME280 の温度・湿度・気圧のデータを 一時的に ESP-WROOM-02 ( ESP8266 ) のSRAMに記憶して、有機EL ( OLED ) SSD1351 に表示させる簡易ロガーを作りましたが、今回はOLED SSD1351 に付属しているmicro SD カードにデータを保存してみました。
そして、ESP-WROOM-02 ( ESP8266 ) のWi-Fi 機能の生かして、NTPサーバーから定時に時刻を自動取得してディスプレイに表示させて、その時刻もmicro SD カードに記録してみました。
下図のディスプレイ右下にある時刻がNTPサーバーから取得して時刻補正したものです。
SD_ntp_bme280_ssd1351_01

更に、フリーの日本語漢字フォント、東雲フォントをmicro SDカードから読み込んでみました。
今まで、東雲フォントはSPIFFSファイルシステムを使って読み込んでいましたが、今回実験した結果、micro SD カードから読み込んだ方が表示が速いことに気付きました。
こちらの比較動画をご覧ください。

いかがでしょうか。
左側のmicro SD カードからフォントを読み込んだ方の表示が速いですね。
こうなると、わざわざSPIFFSの面倒なアップロードや、ファイルサイズ気にしないでも存分にストレス無くフォントやデータファイルを使用できますね。

ESP-WROOM-02 ( ESP8266 )ではSPI通信デバイスを複数連結する場合はいろいろと注意が必要で、特にSDカードを使って、Arduino IDE の SD.h ライブラリを使う場合にはSCLKピンやMOSIピン、MISOピン、CSピンをプログラム中で工夫して使わなければなりません。
SD.h ライブラリではCSピンしか指定できませんので、Arduino core for ESP8266 Wi-Fi chip ボードのSD.h ライブラリの中で MOSI, MISO, SCLK ピンを予め定義しているのです。
ですから、SD.h ライブラリの関数を使うと、他のライブラリのピン設定が変わるようで、毎回ピン設定をしなければいけないようです。

因みに、この記事を書いている最中に台風が通過しました。
気圧の変化が面白かったので報告しておきます。
こんな感じになりました。
下の写真は5時間ロガー設定で、台風が近づいているときに計測したものです。
気圧がだんだん下がっていることがわかり、台風が近づいていることがよくわかりますね。
SD_ntp_bme280_ssd1351_01

湿度がギザギザしているのはエアコンが入っているためです。
エアコンが定期的に動くと、それに伴って湿気を奪っていることが良く分かりますね。ちゃんと働いてくれています。

次の写真は台風が遠ざかるときに計測したものです。
SD_ntp_bme280_ssd1351_02

台風が遠ざかるにしたがって気圧が上がっているのが分かると思います。
10時間ロガーにするともっと顕著に気圧の谷が分かりやすかったかもしれません。こうやって視覚的に見るとオモシロイですね・・・。

そんな感じで、いろいろと実験した結果、何とか複数のSPIデバイス中でSDカードを使うことが出来ましたので、その解説をしていこうと思います。

1.準備するもの

ESPr Developer ( ESP-WROOM-02 開発ボード) スイッチサイエンス
Amazon.co.jp
当ブログで何度も紹介しているものです。
2.4GHzのWi-FiボードのESP8266を日本の電波法をクリアして技適認証取得したESP-WROOM-02 にUSBシリアル変換、電源レギュレーターなどをパッケージにしたボードです。
動作がとても安定していて、使いやすいボードでおすすめです。
これの使い方は以下のページをご覧ください。
ESPr Developer ( ESP-WROOM-02 開発ボード )の使い方をザッと紹介

Adafruit OLED Breakout Board – 16-bit Color 1.5″ w/microSD holder
Amazon.co.jp
microSDカードスロット付きの有機EL ( OLED ) ボードです。チップはSSD1351です。
microSDカードは別売りです。
SPI接続で制御します。
16bitでカラー指定できるので、微妙な色設定が可能です。
電子工作用ディスプレイとしては高価ですが、microSDカードスロット付きということと、フルカラーというこで、これ一つあればかなりいろいろなことができますので、おすすめです。

BME280搭載 温湿度・気圧センサモジュール ( スイッチサイエンス )
Amazon.co.jp
真ん中の小さい四角いシルバーの箱の中に温度・湿度・気圧センサーが入っているモジュールです。
電動工具で有名なBOSCH ( ボッシュ )製です。
SPI通信とI2C通信両対応ですが、ここではSPI通信を使用します。
これはピンヘッダが付属しているので、ハンダ付けしておいてください。
これについての詳細は以下のページをご覧ください。
BME280 搭載、温度・湿度・気圧センサーを SPI で動かしてみた( ESP-WROOM-02 ( ESP8266 )使用)

●microSDカード
Amazon.co.jp
温度・湿度・気圧データ記録用のmicroSDカードで、当方で動作確認したものはTDK のmicroSDHC 4GB です。
この他のものでも動作するとは思いますが、場合によっては動作しないものもありますのでご注意ください。特にメモリが大きいものや大手メーカー製以外のものは要注意です。

●インターネットに接続してあるWi-Fiルーター環境
今回はインターネット上のNTPサーバーに時刻を定時取得して、ESP-WROOM-02 ( ESP8266 )の内部時計を補正しますので、インターネットに接続できる2.4GHz帯のWi-Fi環境が必要です。

●その他、ブレッドボード、ジャンパーワイヤー、USBケーブル、パソコン等

2.接続する

接続は前回の記事とは異なりますのでご注意ください
なぜかというと、OLEDに付属しているmicroSDカードスロットを使うときには、ライブラリのSD.hを使用しますが、それはSPI通信のピン配置が予め決められているために、それに合わせなくてはいけなくなったためです。

ESP-WROOM-02 のデータシートを見てみると、Table7 に下図の様にSPI通信のデフォルトのピン配置が掲載されていました。
SD_ntp_bme280_ssd1351_07

これを見ると、上図のHSPIの段にピン配置があり、MISO、MOSI、CLK は前回の記事と同じですが、CS ( Chip Select )ピンがGPIO #15 でした。
ESP8266ボードのライブラリ SD.h を使用する場合、これに習った方が良いと思いましたので、SD の CS ピンは #15 にしました。
結果、下図の様に変えました。

SD_ntp_bme280_ssd1351_06

Adafruit の OLED ボードでは、micro SD カードと OLED の MOSI、MISO、SCK ピンは共通ですが、CS ( Chip Select )ピンは別々です。
OLED のCSピンは OC 、SDカードのCSピンはSC になります。
つまり、OLEDデバイスと通信するときにはOCをLOWレベルにして他のデバイスのCSピンをHIGHレベルにし、SDカードと通信するときにはSCをLOWレベルにして、他のデバイスをHIGHレベルにすれば、MOSI, MISO, SCKピンは共通でも通信できるわけです。

3.micro SD カードにフォントファイルやデータファイルをセットする

micro SD カードには予めフォントファイルをパソコンからコピーしておかねばなりません。micro SD カードはTDKのものを購入すると、下図のように標準サイズのSDカードアダプターが付属しているので、それに下図の向きで差し込み、パソコンのSDカードスロットに挿入します。

SD_ntp_bme280_ssd1351_08SD_ntp_bme280_ssd1351_09

パソコンにSDカードスロットが無い場合は、別途SDカードリーダーを購入してください。

パソコンがSDカードを認識するまで少々待ち、認識したら念のためプロパティでFATでフォーマットされているか確認してください。されていなければ、SDHCカードならばFAT32でフォーマットします。

次に、そのSDカードのルートに font という名前のフォルダを新規に作成します。
そのfontフォルダの中に以下の3つのファイルをコピーします。

Utf8Sjis.tbl  (UTF-8文字コードをShift_JISに変換するテーブルファイル)
shnmk16.bdf  (フリーの日本語漢字の全角フォント、東雲フォント)
shnm8x16r.bdf  (半角の東雲フォント)

Utf8Sjis ファイルは当方自作のテーブルファイルです。
このファイルについては以下のページを参照してダウンロードしてください。
WROOMでUTF-8文字コードをShift_JIS変換して日本語漢字表示(半角カナ対応)してみました

東雲フォントについては以下のページを参照してダウンロードしてください。
OLED ( 有機EL ) SSD1306 に16×16ドットのフリーの日本語漢字、東雲フォントを表示させてみました

上記の3つのファイルをSDカードのfontフォルダにコピーしたら、忘れがちな大事なことをしなければいけません。
Arduino IDE のESP8266ボードのSD.hライブラリでは、SDカード内のファイル名(拡張子を除く)は半角8文字以内でなければ動作しません
ですから、以下のファイル名を変更してください。

shnm8x16r.bdf → shnm8x16.bdf

“r” という文字だけ削除して名前を変更すればOKです。
これを忘れてしまうと、永遠とエラーが続き、ハマリにハマって、とんでもない時間を過ごすことになります。(私はそうなりました・・・)

4.micro SD カードをOLEDモジュールのスロットにセットする

以上の接続ができたら、電源を入れる前(USBケーブルを挿す前)に、microSD カードをOLEDモジュールのスロットに挿入してください。
下図の様な向きで、カチッと音がするまで挿入してください。
SD_ntp_bme280_ssd1351_03

キッチリ挿入するとこうなります。
SD_ntp_bme280_ssd1351_04

5.Arduino IDE を設定する

2016/8/17現在では、Arduino IDE のバージョンは1.6.10 で問題なく動作しています。
このArduino IDE に ESP8266 ボードをインストールしておきます。
ESP8266ボードは現在は2.3.0です。
このボードのインストールとIDEの設定は以下の記事を参照してください。
Arduino IDE に Staging(Stable)版ESP8266 ボードをインストールする方法

6.Arduino IDE にTimeLibライブラリをインストールする

まず、Arduino IDE 標準のTimeLibライブラリをインストールします。

Arduino.ccページのこちらにあります。→http://playground.arduino.cc/code/time
このページを開いて下図のようなところをクリックすると
ws_Yahoo_message05

下図のように表示されますので、Githubリンクをクリックしてください。

ws_Yahoo_message06

そうしたら下図のようなところをクリックするとZIPファイルのダウンロードが開始されます。

WS_Shinonome01

次にダウンロードされたZIPファイルをArduino IDE にインストールする方法は以下のページを参照してください。
GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )

7.Arduino IDE に自作ライブラリをインストールする

当方で自作した、以下の4つのライブラリをインストールします。

SD_UTF8toSJIS  (Beta ver1.0.1)
UTF-8 から Shift_JISへ変換するライブラリ。
今回新たに作りました。
ダウンロードはGitHubのこちらのページ

SD_ShinonomeFONTread  (Beta ver1.0.1)
日本語漢字の東雲フォントをSDカードから読み込むライブラリ。
今回新たに作りました。
ダウンロードはGitHubのこちらのページ

OLED_SSD1351  (Beta ver1.40)
Adafruit OLED SSD1351 用の自作ライブラリ
ダウンロードはGitHubのこちらのページ

※beta 1.53 では動きませんのでご注意ください。1.40は再アップしました

BME280_SPI  (Beta ver1.0)
スイッチサイエンス BME280 モジュールをSPI制御する自作ライブラリ
ダウンロードはGitHubのこちらのページ
ダウンロードしたZIPファイルのIDEへのインストール方法は以下のページを参照してください。
GitHubにある ZIP形式ライブラリ のインストール方法 ( Arduino IDE )

8.スケッチを入力

では、IDEに以下のスケッチを入力してみてください。

#include <SD_UTF8toSJIS.h>
#include <SD_ShinonomeFONTread.h>
#include <OLED_SSD1351.h>
#include <BME280_SPI.h>
#include <SD.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <TimeLib.h> //timeライブラリver1.4の場合

const uint8_t sclk = 14; //OLED & SDcard & BME280 SCLK
const uint8_t mosi =13; //Master Output Slave Input ESP8266=Master, BME280 & OLED & SD = slave 
const uint8_t miso =12; //Master Input Slave Output
const uint8_t cs_bme = 2; //BME280 CS(ChipSelect)
const uint8_t cs_OLED = 16; //OLED CS(ChipSelect)
const uint8_t cs_SD = 15; //SDcard CS(ChipSelect)
const uint8_t DCpin =  5; //OLED D/C (Data/Command)
const uint8_t RSTpin =  4; //OLED Reset

SD_UTF8toSJIS SDu8ts; //ライブラリのクラス名定義
SD_ShinonomeFONTread SDsfr; //ライブラリのクラス名定義
OLED_SSD1351 ssd1351; //ライブラリのクラス名定義
BME280_SPI bme280spi(sclk, mosi, miso, cs_bme); //BME280ライブラリクラス定義。ここでSPI通信のピン設定をする

const char* UTF8SJIS_file = "font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* ZenkakuFontFile = "font/shnmk16.bdf"; //全角フォントファイル名を宣言
const char* HalfFontFile = "font/shnm8x16.bdf"; //半角フォントファイル名を宣言
const char* DataFile = "DATALOG.TXT"; //気温・湿度・気圧データログファイル名を宣言

uint8_t get_sjis[3][16];
uint16_t sj_length[8]; //Shift_JISコードの長さ
uint8_t font_buf[16][16];
uint8_t SnnmDotOut[16][16];

uint16_t ppp[128], ttt[128], hhh[128]; //グラフプロットの値

uint32_t M_time1, M_time2;

uint32_t Graph_plot_interval = 143000; //143秒毎にグラフプロット及びdatalogファイル書き込み。Log全体は5時間になる。
boolean FirstPlot = false; //初回にファイルに書き込むためのフラグ

File dataFile; //ログデータFileクラス宣言
File UtoS; //UTF8 to Shift_JIS変換Fileクラス宣言
File SinoZ; //全角東雲フォントFileクラス宣言
File SinoH; //半角東雲フォントFileクラス宣言

//-------NTPサーバー定義----------------
IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov; //(129, 6, 15, 28)time.nist.gov NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
const int timeZone = 9;     // Tokyo
WiFiUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
uint32_t NTPlastTime = 0;

//-------時刻表示定義-------------------
String time_str, ymd_str;
const char* week_jp[7] = {"sun","mon","tue","wed","thu","fri","sat"};
uint8_t MinuteTime;

//*********************セットアップ********************************
void setup() {
  uint8_t t_sb = 5; //stanby 1000ms
  uint8_t filter = 0; //filter O = off
  uint8_t osrs_t = 4; //OverSampling Temperature x4
  uint8_t osrs_p = 4; //OverSampling Pressure x4
  uint8_t osrs_h = 4; //OverSampling Humidity x4
  uint8_t Mode = 3; //Normal mode
 
  Udp.begin(localPort);
  Serial.begin(115200);
   
  pinMode(sclk, OUTPUT);
  pinMode(mosi, OUTPUT);
  pinMode(miso, INPUT); //ここはINPUTなので注意
  pinMode(cs_OLED, OUTPUT);
  pinMode(cs_SD, OUTPUT);
  pinMode(cs_bme, OUTPUT);
  pinMode(DCpin, OUTPUT);
  pinMode(RSTpin, OUTPUT);
  
  bme280spi.BME280_SPI_Init(t_sb, filter, osrs_t, osrs_p, osrs_h, Mode);

  ssd1351.SSD1351_Init(sclk, mosi, cs_OLED, DCpin, RSTpin);
  delay(300);
  ssd1351.SSD1351_BlackOut(); //黒画面出力

  for(int i=0; i<16; i++) { //初期化しておく
    for(int j=0; j<16; j++){
      font_buf[i][j] = 0;
      SnnmDotOut[i][j] = 0;
    }
  }

  uint8_t Red, Green, Blue; //Red(Max=31),Green(Max=63),Blue(Max=31)

  Red = 3; Green = 8; Blue = 4;
  ssd1351.SSD1351_RectFill(0, 48, 127, 111, Red, Green, Blue);
  Red = 31; Green = 63; Blue = 31;
  ssd1351.SSD1351_RectLine(0, 48, 127, 111, Red, Green, Blue);

  Serial.println(); Serial.print("Initializing SD card...");
  if (!SD.begin(cs_SD,SPI_FULL_SPEED)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");
/*
  if(SD.remove("DATALOG.TXT")){ //removeクラスは変数のconst char* DataFileを使うとエラーになるので注意
    Serial.println("Removed datalog.txt");
  }else{
    Serial.println("Not removed datalog.txt");
  }
*/  
  UtoS = SD.open(UTF8SJIS_file, FILE_READ);
  if (UtoS == NULL) {
    Serial.print("Utf8Sjis.tbl File not found");
    return;
  }else{
    Serial.println("Utf8Sjis.tbl File OK!");
  }
  SinoH = SD.open(HalfFontFile, FILE_READ);
  if (SinoH == NULL) {
    Serial.print("shnm8x16.bdf File not found");
    return;
  }else{
    Serial.println("shnm8x16.bdf File OK!");
  }
  SinoZ = SD.open(ZenkakuFontFile, FILE_READ);
  if (SinoZ == NULL) {
    Serial.print("shnmk16.bdf File not found");
    return;
  }else{
    Serial.println("shnmk16.bdf File OK!");
  }
  
  //NTPサーバーから時刻を取得
  delay(2000); //ここの秒数が少ないとESP8266のWi-Fiが起動していないので十分余裕を持つこと
  setSyncProvider(getNtpTime);
  delay(1000);

  NTPlastTime = millis();
  MinuteTime = now();

  for(int i=0; i<128; i++){
    ppp[i] = 127; ttt[i] = 127; hhh[i] = 127;
  }

}
//******************メインループ************************************
void loop() {
  double temperature = 0.0, pressure = 0.0, humidity = 0.0;
  uint8_t T_Red = 0, T_Green = 63, T_Blue = 0; //気温表示色。Red(Max=31),Green(Max=63),Blue(Max=31)
  uint8_t H_Red = 0, H_Green = 0, H_Blue = 31; //湿度表示色。Red(Max=31),Green(Max=63),Blue(Max=31)
  uint8_t P_Red = 31, P_Green = 10, P_Blue = 10; //気圧表示色。Red(Max=31),Green(Max=63),Blue(Max=31)
  String oled_str;
  uint16_t Len; //Shift_JISコードのバイトの長さ
  char month_chr[3], day_chr[3], h_chr[3], m_chr[3], s_chr[3];
  char index_chr[3];

  if(millis() - NTPlastTime > 600000){ //10分毎にNTP時刻補正
    setSyncProvider(getNtpTime);
    Serial.print("Get NTP Time ----------------");
    Serial.println(String(year()) + "/" + String(month()) + "/" + String(day()) + "/" + String(hour()) + ":" + String(minute()) + ":" + String(second()));
    NTPlastTime = millis();
  }

  if(minute() != MinuteTime){
    sprintf(h_chr, "%2d", hour());//ゼロを空白で埋める場合は%2dとすれば良い
    sprintf(m_chr, "%02d", minute());
    sprintf(index_chr,"%03d",round((Graph_plot_interval*126)/60000)); //グラフプロット間隔からログ全体時間の計算
    
    SD.begin(cs_SD,SPI_FULL_SPEED); //フォントファイルを呼び込む前に必ずSD.beginクラスが必要
    SDu8ts.UTF8_to_SJIS_str_cnv(UtoS, "5時間前    " + String(h_chr) + ":" + String(m_chr), get_sjis[0], &Len);
    SDsfr.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, get_sjis[0], Len, font_buf);
  
    pinMode(sclk, OUTPUT);
    pinMode(mosi, OUTPUT);
    ssd1351.SSD1351_8x16_DisplayOut_1col_LtoR(0, 112, 31, 63, 0, Len, font_buf);
    MinuteTime = minute();
  }
  
  if(millis()-M_time1 > 2000){ //2秒毎にディスプレイにデータ表示
    pinMode(miso, INPUT); //bme280を複数デバイスのうちで使う場合はこれがないと計測できない
    temperature = bme280spi.Read_Temperature();
    pressure = bme280spi.Read_Pressure();
    humidity = bme280spi.Read_Humidity();

    sprintf(h_chr, "%02d", hour());//ゼロを空白で埋める場合は%2dとすれば良い
    sprintf(m_chr, "%02d", minute());
    sprintf(s_chr, "%02d", second());
    sprintf(month_chr, "%02d", month());
    sprintf(day_chr, "%02d", day());
    time_str = String(h_chr) + ':' + String(m_chr) + ':' + String(s_chr);
    ymd_str = String(year()) + '/' + String(month_chr) + '/' + String(day_chr) + "(" + String(week_jp[weekday()-1]) + ")";
    
    Serial.println("-----------------------");
    Serial.print("Temperature = "); Serial.print(temperature); Serial.println(" *C");
    Serial.print("Humidity = "); Serial.print(humidity); Serial.println(" %");
    Serial.print("Pressure = "); Serial.print(pressure); Serial.println(" hPa");
  
    oled_str = "気温= "+String(temperature)+" ℃ ";
    SD.begin(cs_SD,SPI_FULL_SPEED); //フォントファイルを呼び込む前に必ずSD.beginクラスが必要
    SDu8ts.UTF8_to_SJIS_str_cnv(UtoS, oled_str, get_sjis[0], &Len);
    SDsfr.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, get_sjis[0], Len, font_buf);
    pinMode(sclk, OUTPUT);
    pinMode(mosi, OUTPUT);
    ssd1351.SSD1351_8x16_DisplayOut_1col_LtoR(0, 0, T_Red, T_Green, T_Blue, Len, font_buf);
  
    oled_str = "湿度= "+String(humidity)+" %";
    SD.begin(cs_SD,SPI_FULL_SPEED); //フォントファイルを呼び込む前に必ずSD.beginクラスが必要
    SDu8ts.UTF8_to_SJIS_str_cnv(UtoS, oled_str, get_sjis[1], &Len);
    SDsfr.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, get_sjis[1], Len, font_buf);
    pinMode(sclk, OUTPUT);
    pinMode(mosi, OUTPUT);
    ssd1351.SSD1351_8x16_DisplayOut_1col_LtoR(0, 16, H_Red, H_Green, H_Blue, Len, font_buf);
  
    oled_str = "気圧= "+String(pressure)+"hPa";
    SD.begin(cs_SD,SPI_FULL_SPEED); //フォントファイルを呼び込む前に必ずSD.beginクラスが必要
    SDu8ts.UTF8_to_SJIS_str_cnv(UtoS, oled_str, get_sjis[2], &Len);
    SDsfr.SjisToShinonome16FontRead_ALL(SinoZ, SinoH, 0, 0, get_sjis[2], Len, font_buf);
    pinMode(sclk, OUTPUT);
    pinMode(mosi, OUTPUT);
    ssd1351.SSD1351_8x16_DisplayOut_1col_LtoR(0, 32, P_Red, P_Green, P_Blue, Len, font_buf);

    if(millis()-M_time2 > Graph_plot_interval || FirstPlot == false){
      for(int i=126; i>0; i--){
        ttt[i] = ttt[i-1];
        hhh[i] = hhh[i-1];
        ppp[i] = ppp[i-1];
      }
      ttt[0] = round(temperature);
      hhh[0] = round(humidity*0.64);
      ppp[0] = round(pressure)-956;
      
      SD.begin(cs_SD,SPI_FULL_SPEED); //ファイルを呼び込む前に必ずSD.beginクラスが必要
      dataFile = SD.open(DataFile, FILE_WRITE);
      if (dataFile == NULL) {
        Serial.print("datalog.txt File not found");
        return;
      }else{
        Serial.println("datalog.txt File OK!");
      }
      if (dataFile) {
        dataFile.println(ymd_str + "," + time_str + "," +  String(temperature) + "," + String(humidity) + "," + String(pressure));
        dataFile.close();
        Serial.println("---------------datalog.txt writed");
      }
      // if the file isn't open, pop up an error:
      else {
        Serial.println("error opening datalog.txt");
      }
    
      pinMode(sclk, OUTPUT);
      pinMode(mosi, OUTPUT);
      for(int i=0; i<126; i++){
        for(int j=0; j<62; j++){
          if(ttt[i] == j){
            ssd1351.SSD1351_1pixel_DisplayOut((127-1)-i, (111-1)-ttt[i], T_Red, T_Green, T_Blue);
          }else if(hhh[i] == j){
            ssd1351.SSD1351_1pixel_DisplayOut((127-1)-i, (111-1)-hhh[i], H_Red, H_Green, H_Blue);
          }else if(ppp[i] == j){
            ssd1351.SSD1351_1pixel_DisplayOut((127-1)-i, (111-1)-ppp[i], P_Red, P_Green, P_Blue);
          }else{
            ssd1351.SSD1351_1pixel_DisplayOut((127-1)-i, (111-1)-j, 3, 8, 4);
          }
        }
      }
      M_time2 = millis();
      FirstPlot = true;
    }
    M_time1 = millis();
  }
}
//*************************NTP Time 関数**************************************
time_t getNtpTime()
{
  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  sendNTPpacket(timeServer);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}
//*************************NTP Time 関数**************************************
void sendNTPpacket(IPAddress &address)
{// send an NTP request to the time server at the given address
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:                 
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

●1-4行:
自作ライブラリのインクルードです。

●5行目:
Arduino IDE の標準のSDカードライブラリのインクルードです。
ESP8266 ボードにも同じライブラリがありますが、自動でESP8266用のSD.hに切り替わるようです。

●6-7行:
Arduino core for ESP8266 Wi-Fi chip にある標準ライブラリです。

●8行目:
Arduino 標準のTimeライブラリのインクルードです。

●10-17行:
ESP-WROOM-02 ( ESP8266 ) のGPIOのSPI通信用ピン設定です。

●19-22行:
自作ライブラリのクラス名宣言です。好きな名前に設定できます。

●24-27行:
micro SD カード内にあるフォントファイルやログデータファイルを宣言します。
注意しなければならないのは、拡張子を除くファイル名は8文字以下です。SDカード内のファイル名と合わせて下さい。
フォントファイルはファイル名の前に先に説明したフォルダ名を入れてください。
ログデータファイルはルート最上位としてます。

●38行:
グラフをプロットする間隔、および、SDカードに記録する間隔を定義してます。
グラフは枠線を除くと126ドットなので、143秒ごとにプロットするとグラフ全体が5時間丁度となります。

●41-44行:
ファイルクラス名を宣言してます。グローバル変数領域で宣言することが大事です。

●46-53行:
NTPサーバーに関する定義ですが、これはArduino IDEサンプルスケッチの流用です。

●62-67行目:
これは前回の記事前々回の記事を参照してください。

●72-79行目:
ESP-WROOM-02 ( ESP8266 ) のGPIOピンのモードを設定します。特に74行目だけがINPUTとなるので注意してください。

●81-99行目:
これは前回の記事を参照してください。

●102行目:
SD.hライブラリではCS ( Chip Select )ピンだけ指定します。ということは、ESP8266を自動判断して、SCLK や MOSIピンを設定してしまうということです。ですから、GPIO を正しく設定していないと、SDカードと他のSPIデバイスと連結する場合は正しく動作しませんので要注意です。
SPI_FULL_SPEED という変数はArduino15フォルダにある、ESP8266のSDライブラリで宣言されている変数で、値は 8000000 です。これが最大値のようです。

●107-113行:
ここはコメントアウトしていますが、既にあるDATALOGファイルを消去して、新たに作り直す場合はコメントを消してください。
ここで注意してほしいのは、ファイル名をDataFileとしてはいけません。これをやるとエラーになります
なぜかは分かりませんが、”DATALOG.TXT” としないと動作しません。
また、removeしない場合は、DATALOGファイルにデータを追記します。

●114-134行:
フォントファイルや文字コード変換テーブルファイルを読み取りモードで開きます。

●137-139行:
ここで、NTPサーバーにアクセスして、時刻を取得して、ESP-WROOM-02 ( ESP8266 )の内部時刻を補正します。
起動して直ぐだと、ESP-WROOM-02 のWi-Fiが起動していない場合があるので、2秒待ちます。

●160-165行:
10分毎にNTPサーバーから時刻を取得して、補正します。

●167-180行:
時刻の分数が変化したら、OLEDディスプレイの時刻を更新します。
ここでとても重要なのが、172行目のSD.begin です。
SDカードからフォントを読み込む場合に、この関数を直前に呼び込んでおかないとうまく動作しません。
恐らく、その前にOLED やBME280などのデバイスでGPIOピンを設定したために、SDカードのGPIOピン設定を初期化しないと動作しないようです。SDカードも自由にピン設定できればいいのですが・・・。
そして、もう一つ重要なのが、176行と177行です。
先にSD.beginでGPIOピンの初期化が行われたために、ここでSCLKとMOSIピンのモードを設定しなおさないとディスプレイに上手く表示されません。SD.hライブラリはかなり厄介で、いろいろと実験した結果、こうなりました。
以降、フォントファイルを読み込む時と、ディスプレイに表示させる時はこんな指定が繰り返されます。

●183-186行:
2秒毎にBME280の温度・湿度・気圧データを読み込みます。
183行目が重要で、先に述べたように、SD.beginでピン設定が変えられた場合はこれが無いとBME280は正しく動作しません。

●188-192行:
SDカードにログを記録する場合、読み出すことを考えてsprintf関数を使って時刻の桁数を揃えます。

●201-223行:
OLEDディスプレイに温度・湿度・気圧データを表示させてます。
SDカードからフォントを読み込む直前にSD.beginを呼び出し、OLEDディスプレイに表示させる直前にpinMode設定をしているのがポイントです。

●225-270行:
これは、前回の記事とほぼ同じですが、235-251行でSDカードにデータを書き込んでいるところが違います。
データはカンマで区切ってます。
書き込み終わったらかならずファイルをclose()します。

●274-319行:
これはArduino IDE のNTPのサンプルスケッチそのままです。

9.コンパイル、実行する

では、ご自分のWi-Fiルーター(アクセスポイント)を立ち上げて、しばらく待ってから、スケッチをコンパイルして実行させてみましょう。
NTPサーバーアクセスはUDPなので、SSIDやパスワード不要でスルーしてインターネットにアクセスします。
シリアルモニターではこう表示されればOKです。
SD_ntp_bme280_ssd1351_10

最初の文字化けは例のように無視してOKです。
datalog.txt writed
と表示されたときにSDカードにデータを書き込んでいます。

しばらく動作させた後、SDカードに記録されたデータをパソコンのメモ帳などのテキストエディタでDATALOG.TXTファイルを見てみましょう。
下図の様になっていればOKです。
SD_ntp_bme280_ssd1351_11

いかがでしたでしょうか?
結構イイ感じでデータロガーを手作りすることができました。
台風が来る時には気圧データの変化が良く分かっておもしろいです。
これをうまく生かせば、気圧の変化で関節痛が起こる人達に役立つ何かが出来そうな気がしますね。

今回は以上です。
次回は、これをスマホに送信して表示させることに挑戦してみようと思います。

ではまた・・・。

スポンサーリンク

広告 と mgo-tec電子工作 関連コンテンツ
Amazon.co.jp広告



投稿者:

mgo-tec

Arduino, ESP8266, ESP-WROOM-02 等を使って、主にスマホと連携した電子工作やプログラミング記事を書いてます。ライブラリも作ったりしてます。趣味、独学でやってますので、動作保証はしません。 電子回路やプログラミングの専門家ではありません。 畑違いの仕事をしてます。 でも、少しだけ電気の知識が必要な仕事なので、電気工事士や工事担任者等の資格は持ってます。

コメントを残す

メールアドレスが公開されることはありません。

*