全体概要
Raspberry Pi 3BとESP-WROOM-02をベースとした自作の環境ログシステムです。特徴は次の通りです。
- 屋外に設置される計測センサは単三乾電池3本で長時間駆動可能
- 将来のデータ分析に備えてセンサデータをデータベースへ蓄積
- ファイルシステムのハードディスク実装によりRaspberry Piを長寿命化
システム構成図は次図の通りです。
環境ログシステムは、気温・湿度・気圧・土壌湿度を計測する測定クライアントと、測定クライアントから送信されたセンサデータをMySQLデータベースに記録するデータ蓄積サーバから構成されます。
測定クライアントはESP-WROOM-02をベースとしており、屋外のウォールボックスに設置して、屋内設置のデータ蓄積サーバとWiFi接続します。センサデータの通信には、MQTTを用いています。
一方、データ蓄積サーバはRaspberry Pi 3Bをベースとしており、測定クライアントからMQTTで受信したセンサデータをMySQLデータベースに記録します。MySQLのセンサデータはNode-REDによって可視化し、パソコンやスマホのWebブラウザに表示します。
データ蓄積サーバは常時起動状態となるため、このままインターネットに晒すと外部から攻撃を受ける可能性が高くなります。そこで、データ蓄積サーバはインターネットから切り離したクローズドなネットワーク(システム構成図の点線内)に配置することで、セキュリティリスクを軽減しています。このクローズドなネットワークにアクセスするためのアクセスポイントがデータアップロード用アクセスポイントです。測定クライアントがセンサーデータを送信する際は、このデータアップロード用アクセスポイントに接続し、データ蓄積サーバへセンサデータを送信します。
一方、インターネット用アクセスポイントは、測定クライアントがデータアップロード用アクセスポイントにWiFi接続できない場合など異常検知した時に用いられ、測定クライアントはインターネット上の異常監視スマホに対してLINE Notifyで異常を通知します。LINE Notifyが呼ばれると、スマホのLINEアプリに受信メッセージが表示されます。
インターネット用アクセスポイントは、測定クライアントの初期化時にインターネット上のNTPサーバと時刻同期する際にも用いられます。
測定クライアント
測定クライアントは、
- マイコンESP-WROOM-02
- 環境センサBME280
- 土壌湿度センサ
- リアルタイムクロックRTC-8564NB、
で構成します。BME-280は気温・湿度・気圧を測定するセンサであり、ESP-WROOM-02にI2Cで接続します。土壌湿度センサは土壌の湿り気を測定するセンサであり、ESP-WROOM-02のアナログ入力端子に接続します。リアルタイムクロックはESP-WROOM-02とI2Cで接続し、NTPサーバと同期が取れた時刻の保持と、ESP-WROOM-02の起動制御を行います。
測定クライアントの回路図と、ユニバーサル基板に実装した写真は次の通りです。
なお、回路図には記載していませんが、電源は単3乾電池3本で、3端子レギュレータで3.3Vまで降圧しています。ESP-WROOM-02はWiFi接続時に大きな電流を必要とするので、それに耐え得る3端子レギュレータを選定します。
写真では、測定クライアントのユニバーサル基板の下に、別記事
techrmc.hatenablog.com
で紹介したWiFiドアセンサのESP-WROOM-02が写っていますが、環境ログシステムとは無関係です。
電力消費量を削減するため、データ測定時以外は、ESP-WROOM-02をスリープ状態にしておきます。スリープ状態のESP-WROOM-02は非常に小さな電力消費となるため、乾電池駆動の長期化に役立ちます。
スリープ状態のESP-WROOM-02は、リアルタイムクロックRTC-8564NBのアラーム割り込みによって起動トリガをかけます。ESP-WROOM-02は測定が終わるとスリープ状態に入っていますが、所定のアラーム時刻にRTC-8564NBからトリガがかかると、ESP-WROOM-02はスリープ状態から復帰し、BME280と土壌湿度センサからデータを読み取ります。読み取ったセンサデータはMQTTでpublishし、次の起動時刻をRTC-8564NBのアラーム時刻としてセットしたのち再びスリープ状態に入ります。測定クライアントの初期化時には、RTC-8564NBをインターネット上のNTPサーバと時刻同期しておきます。
なお、土壌湿度センサをVCCとGNDに直接接続すると、土壌湿度測定時以外も常時、土壌湿度センサに電流が流れるため、乾電池の寿命が短くなリます。そこで、測定時以外は消費電流を抑制できるよう、土壌湿度センサへの電流供給をトランジスタ2SC1815によってスイッチングしています。
Arduinoで作成したESP-WROOM-02のプログラムを次に示します。なお、BME280用のプログラムは、スイッチサイエンスのサンプルプログラム
BME280 – スイッチサイエンス
をほぼそのまま利用させて頂きましたので、そちらをご覧ください。
メインプログラムmain.ino
/* * 測定クライアントのメインプログラム * * 動作モード:DIP-SW(SW1)で切り替え * キャリブレーションモード:WiFi接続テスト・NTP同期・土壌湿度センサを水に浸して半固定抵抗を調整 * 通常モード:定期的にBME280と土壌湿度センサで温度・湿度・気圧・土壌湿度を測定し、MQTTブローカへpublish * 異常時の挙動: * 動作モードがキャリブレーションモードの時: * WiFiエラー(インターネットアクセス用):キャリブレーション無限ループ * WiFiエラー(データアップロード用):キャリブレーション無限ループ * NTPエラー:キャリブレーション無限ループ * 動作モードが通常モードの時: * BME280エラー:ディープスリープ * WiFiエラー(インターネットアクセス用):ディープスリープ * WiFiエラー(データアップロード用):LINE通知&ディープスリープ * MQTTエラー:LINE通知&ディープスリープ * */ #include <Wire.h> #include <ESP8266WiFi.h> #include <WiFiClientSecure.h> #include <time.h> extern "C" { #include "user_interface.h" // ESP8266用の拡張IFライブラリ } #include <PubSubClient.h> #include <ArduinoJson.h> #define ESP8266_PLATFORM // IOピン番号 #define PIN_MODE 12 // 動作モード(HIGH:通常, LOW:キャリブレーション) #define PIN_LED 13 // LED点灯制御(HIGH:点灯, LOW:消灯) #define PIN_SPWR 14 // 土壌湿度センサ電源制御(HIGH:ON, LOW:OFF) // LED点灯回数 #define NOERR 3 // 1秒間隔で3回点灯:NTP同期した後(同期成功・不成功には無関係) #define WIFIERRM 5 // 1秒間隔で5回点灯:WiFi接続エラー(MQTTデータアップロード用アクセスポイント) #define NTPERR 7 // 1秒間隔で7回点灯:NTP同期エラー(年が2020未満) #define WIFIERRU 9 // 1秒間隔で9回点灯:WiFi接続エラー(インターネット用アクセスポイント) // 測定定数 #define SLEEP_P 0 // スリープ周期(0は無期限) #define CYCLE_M 60 // 測定周期(分単位) // キャリブレーションモードでは、土壌湿度センサを水に浸し、 // 上限値と下限値の間に設定する。 #define STH_H 930 // 土壌湿度センサの上限値 #define STH_M 900 // 土壌湿度センサの標的値 #define STH_L 870 // 土壌湿度センサの下限値 // 動作モード(HIGH:通常, LOW:キャリブレーション) int mode; // WiFi定数 const char ssid4mqtt[] = "xxxx"; // MQTTデータアップロード用アクセスポイントのSSID const char pass4mqtt[] = "xxxx"; // MQTTデータアップロード用アクセスポイントのパスワード const char ssid4util[] = "xxxx"; // インターネット用アクセスポイント(NTP同期、LINEエラー通知)のSSID const char pass4util[] = "xxxx"; // インターネット用アクセスポイント(NTP同期、LINEエラー通知)のパスワード // MQTT定数 #define MQTTCONNECTWAIT 10000 // MQTTブローカへの接続リトライ待ち時間(msec) #define MQTTMAXRETRY 2 // MQTT接続リトライの最大回数 const char mqttAddress[] = "xxx.xx.xx.xx"; const char mqttTopic[] = "xxxx"; const char mqttClientid[] = "xxxx"; const int mqttPort = 1883; // LINE const char* host = "notify-api.line.me"; const int httpPort = 443; const char* token = "xxxx"; // Use WiFiClient class for MQTT connections WiFiClient client; PubSubClient mqttClient(client); // Use WiFiClientSecure class for LINE connections WiFiClientSecure utilclient; // タイムスタンプ(ISO8601形式) #define ATLEN 26 // timestampの文字列長+1(末尾のNULLをカウント) 定義変更時はrtc8564nb.inoの定義も要変更 char at[ATLEN]; // timestamp ISO8601形式 // 測定値をMQTTブローカへpublishする際のJSONバッファ // https://arduinojson.org/v6/assistant/でのバッファサイズの計算式は、 // const size_t capacity = JSON_OBJECT_SIZE(5) + 78; StaticJsonDocument<200> jsondoc; char msg[200]; //////////////////////////////////////// //メインsetup //////////////////////////////////////// void setup() { //////////////////////////////////////// // モード共通初期処理 //////////////////////////////////////// Serial.begin(115200); //IO PIN 設定 pinMode(PIN_LED,OUTPUT); // PIN_LEDを出力用にセットして、LEDを消灯しておく digitalWrite(PIN_LED,LOW); pinMode(PIN_SPWR,OUTPUT); // PIN_SPWRを出力用にセットして、土壌湿度センサを電源OFFにしておく digitalWrite(PIN_SPWR,LOW); pinMode(PIN_MODE,INPUT_PULLUP); // PIN_MODEのデフォルトは、HIGH(動作モード:通常) Wire.begin(); // マスタとしてI2Cバスに接続 mode = digitalRead(PIN_MODE); // モードSWを読み取り if (mode==LOW) { // 土壌センサキャリブレーションモード(WiFi接続テスト、NTP時刻同期を兼ねる) if (connectWiFi(ssid4mqtt, pass4mqtt)==false) { // MQTTデータアップロード用アクセスポイントへの接続がエラーの場合 longLightMulti(WIFIERRM); digitalWrite(PIN_SPWR,HIGH); delay(5000); // 土壌センサの安定を待つ return; // setup()を抜けて、loop()へ入る // WiFiエラー時は、NTP同期せず、土壌センサのキャリブレーションのみ実行 } else if (connectWiFi(ssid4util, pass4util)==false) { // インターネット用アクセスポイントがエラーの場合 longLightMulti(WIFIERRU); digitalWrite(PIN_SPWR,HIGH); delay(5000); // 土壌センサの安定を待つ return; // setup()を抜けて、loop()へ入る // WiFiエラー時は、NTP同期せず、土壌センサのキャリブレーションのみ実行 } rtc_init(); //キャリブレーションモードでは、rtc_init()での低電圧検知に関係なくNTP同期に入る if (rtc_sync_ntp() < 2020) { // 現在の西暦2020より小さいとき、間違いなくNTP同期エラー longLightMulti(NTPERR); } else { // 2020以上の場合、NTP同期エラーの可能性は低い。ただし、NTP同期成功とは限らない。 longLightMulti(NOERR); } // 現在のRTCレジスタを取得 rtc_read_regtbl(); digitalWrite(PIN_SPWR,HIGH); delay(5000); // 土壌センサの安定を待つ } else { //キャリブレーションモードはここまで。ここから通常モード // RTC初期設定 if (rtc_init()==true) { // 低電圧検知時 if (connectWiFi(ssid4util, pass4util) == false) { // インターネット用アクセスポイントへの接続がエラーの場合 // 現在のRTCレジスタを取得 rtc_read_regtbl(); // WiFi接続エラー時はRTCアラームをCYCLE_M分後にセット // RTC低電圧が起きているので、不定時刻のCYCLE_M分後となる rtc_set_alarm_min(CYCLE_M); sleep(); } // WiFi接続成功 // NTP同期処理 rtc_sync_ntp(); // 低電圧検知時の処理終了 } // 現在のRTCレジスタを取得 rtc_read_regtbl(); // CYCLE_M分後に新アラームをセット // RTC低電圧が起きていると、不定時刻のCYCLE_M分後となる rtc_set_alarm_min(CYCLE_M); rtc_read_regtbl(); digitalWrite(PIN_SPWR,HIGH); delay(5000); // 土壌センサの安定を待つ bme280_Setup(); // 土壌センサ・BME280 読み取り・送信 soilbme280_mqtt(); sleep(); } } //////////////////////////////////////// //メインloop //////////////////////////////////////// void loop() { int value; if (mode==LOW) { value = analogRead(A0); if (value > STH_H) { // 上限閾値超えの場合 intLight(100); } else if (value > STH_M) { // 標的閾値と上限閾値の中間の場合 intLight(70); } else if (value > STH_L) { // 下限閾値と標的閾値の中間の場合 intLight(40); } else { // 下限閾値以下の場合 intLight(10); } } else { //ダミー。mode=HIGH(通常モード)の時はloop()は呼ばれないはず。 sleep(); } } //////////////////////////////////////// //ディープスリープに入る // ・bme280をスリープ // ・土壌センサを電源OFF // ・LEDを消灯 //////////////////////////////////////// void sleep(){ bme280_Sleep(); digitalWrite(PIN_SPWR,LOW); digitalWrite(PIN_LED,LOW); delay(100); ESP.deepSleep(SLEEP_P,WAKE_RF_DEFAULT); // スリープモードへ移行する delay(1000); // スリープモードに入るのに時間を要すためダミーのdelayを入れる } //////////////////////////////////////// //長く点灯して消灯 //////////////////////////////////////// void longLight(){ digitalWrite(PIN_LED,HIGH); delay(1000); // light on for 1 sec digitalWrite(PIN_LED,LOW); delay(100); // light off for 100 msec } //////////////////////////////////////// //長く点灯して消灯を、n回繰り返す //////////////////////////////////////// void longLightMulti(int n) { for(int i=0;i<n;i++) longLight(); } //////////////////////////////////////// //duty比率で点灯&消灯 // duty=100 1秒ずっと点灯 // duty=50 0.5秒点灯して0.5秒消灯 // duty=20 0.2秒点灯して0.8秒消灯 //////////////////////////////////////// void intLight(int duty){ digitalWrite(PIN_LED,HIGH); delay(10*duty); // light on digitalWrite(PIN_LED,LOW); delay(10*(100-duty)+10); // light off } //////////////////////////////////////// // WiFiアクセスポイントに接続 // 戻り値 // true:接続成功 // false:接続エラー //////////////////////////////////////// bool connectWiFi(const char *ssid, const char *pass){ int waiting=0; if (WiFi.status() == WL_CONNECTED) { // 接続済みの時、一旦、接続を切る WiFi.disconnect(); } digitalWrite(PIN_LED,LOW); WiFi.mode(WIFI_STA); // STA mode WiFi.begin(ssid, pass); while(WiFi.status() != WL_CONNECTED){ // wait until WiFi connected delay(100); // wait 100msec waiting++; // counter increment if(waiting > 300) { // max waiting counter. approx.300*100 msec= 30sec digitalWrite(PIN_LED,LOW); return false; } } digitalWrite(PIN_LED,LOW); return true; } //////////////////////////////////////// // LINEへメッセージ送信 // 戻り値 // true: LINEメッセージ送信完了 // false: WiFi接続エラー or LINE接続エラー or LINEレスポンス無し //////////////////////////////////////// bool send_to_LINE(char *value1, char *value2, char *value3) { utilclient.setInsecure(); if (connectWiFi(ssid4util, pass4util) == false) { // インターネットアクセス用アクセスポイントへの接続がエラーの場合 return false; } if (!utilclient.connect(host, httpPort)) { return false; } // LINEサーバへメッセージ送信 String message = String("value1=") + String(value1) + String(", value2=") + String(value2) + String(", value3=") + String(value3); String content = String("message=") + message; utilclient.print(String("POST /api/notify HTTP/1.1\r\n") + "Host: " + host + "\r\n" + "Authorization: Bearer " + token + "\r\n" + "Content-Length: " + String(content.length()) + "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n\r\n" + content + "\r\n"); unsigned long timeout = millis(); while (utilclient.available() == 0) { if (millis() - timeout > 5000) { utilclient.stop(); return false; } } // Read all the lines of the reply from server and print them to Serial while(utilclient.available()){ String line = utilclient.readStringUntil('\r'); } return true; } //////////////////////////////////////// //測定してmqtt送信 //測定値5回のうち最大・最小以外の3回の平均を取る //////////////////////////////////////// void soilbme280_mqtt(){ float temperature[5], max_temperature, min_temperature; float humidity[5], max_humidity, min_humidity; float pressure[5], max_pressure, min_pressure; int moisture[5], max_moisture, min_moisture; readData(); temperature[0] = readTemperature(); humidity[0] = readHumidity(); pressure[0] = readPressure(); moisture[0] = analogRead(A0); // Check if any reads failed and exit early (to try again). if (isnan(temperature[0]) || isnan(humidity[0]) || isnan(pressure[0])) { sleep(); } delay(100); max_temperature = temperature[0]; min_temperature = temperature[0]; max_humidity = humidity[0]; min_humidity = humidity[0]; max_pressure = pressure[0]; min_pressure = pressure[0]; max_moisture = moisture[0]; min_moisture = moisture[0]; for(int i=1;i<5;i++) { readData(); temperature[i] = readTemperature(); humidity[i] = readHumidity(); pressure[i] = readPressure(); moisture[i] = analogRead(A0); // Check if any reads failed and exit early (to try again). if (isnan(temperature[i]) || isnan(humidity[i]) || isnan(pressure[i])) { sleep(); } if (temperature[i] > max_temperature) max_temperature = temperature[i]; if (temperature[i] < min_temperature) min_temperature = temperature[i]; if (humidity[i] > max_humidity) max_humidity = humidity[i]; if (humidity[i] < min_humidity) min_humidity = humidity[i]; if (pressure[i] > max_pressure) max_pressure = pressure[i]; if (pressure[i] < min_pressure) min_pressure = pressure[i]; if (moisture[i] > max_moisture) max_moisture = moisture[i]; if (moisture[i] < min_moisture) min_moisture = moisture[i]; delay(100); } float ave_temperature=0; float ave_humidity=0; float ave_pressure=0; int ave_moisture=0; for(int j=0;j<5;j++) { ave_temperature += temperature[j]; ave_humidity += humidity[j]; ave_pressure += pressure[j]; ave_moisture += moisture[j]; } // remove abnormal (min, max) values ave_temperature = (ave_temperature-max_temperature-min_temperature)/3; ave_humidity = (ave_humidity-max_humidity-min_humidity)/3; ave_pressure = (ave_pressure-max_pressure-min_pressure)/3; ave_moisture = (ave_moisture-max_moisture-min_moisture)/3; // タイムスタンプ取得 rtc_get_timestamp(at); if (connectWiFi(ssid4mqtt, pass4mqtt) == false) { // データアップロード用アクセスポイントへの接続がエラーの場合 send_to_LINE((char *)mqttClientid,"error","WiFi_connection"); sleep(); // WiFi接続エラー時はsetup()でアラームセットした時刻(CYCLE_M分後)に再起動 } // データアップロード用アクセスポイントに接続成功するとここまで来る // //時刻・温度・湿度・大気圧・土壌湿度のMQTT送信 // mqttClient.setServer(mqttAddress, mqttPort); int cnt = 0; while (!mqttClient.connected()) { if (mqttClient.connect(mqttClientid)){ jsondoc["datetime"] = at; jsondoc["temperature"] = ave_temperature; jsondoc["humidity"] = ave_humidity; jsondoc["pressure"] = ave_pressure; jsondoc["soil_moisture"] = ave_moisture; serializeJson(jsondoc,msg); mqttClient.publish(mqttTopic, msg); jsondoc.clear(); // jsondocのメモリリーク防止のためclearする } else { cnt++; if (cnt <= MQTTMAXRETRY ) { // 最大リトライ回数以下のとき delay(MQTTCONNECTWAIT); // wait for MQTTCONNECTWAIT msec before retrying } else { // 最大リトライ回数を超えた時 send_to_LINE((char *)mqttClientid,"error", "MQTT_connection"); sleep(); } } } mqttClient.disconnect(); }
RTC-8564NB周りのプログラムrtc8564nb.ino
#include <Wire.h> #include <time.h> #include <TZ.h> /* * bool rtc_init() -- RTC初期化 * void rtc_sync_ntp() --- NTP時刻をRTCに設定 * void rtc_read_regtbl() --- RTC内のレジスタからレジスタテーブルRegTblに読み込み * void rtc_set_alarm_min(int m) --- RTC内のレジスタにm分後のアラーム時分を書き込み * void rtc_set_alarm(int h, int m) --- RTC内のレジスタにアラーム時分を書き込み * int rtc_get_hour() --- レジスタテーブルRegTblから現在のhourを返す * int rtc_get_min() --- レジスタテーブルRegTblから現在のminを返す * void rtc_get_timestamp(char *at) --- レジスタテーブルからtimestampを生成 * * byte BCDtoDec(byte value) --- BCDを10進数に変換 * byte DectoBCD(int value) --- 10進数をBCDに変換 */ #define RTC_ADDRESS 0x51 // RTCのI2Cアドレス #define NTP_SERVER1 "xxx.xxx.xxx" // NTPサーバー #define NTP_SERVER2 "xxx.xxx.xxx" // NTPサーバー #define NTP_SERVER3 "ntp.nict.jp" // NTPサーバー #define NTPMAXREPEAT 60 // NTP同期ウェイト(1000ms)の最大リピート数 #define RTCREGLEN 16 int RegTbl[RTCREGLEN]; // RTCのレジスタテーブル(16byte) static const char *pszWDay[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; // 曜日表現 #define ATLEN 26 // timestampの文字列長+1(末尾のNULLをカウント) /* * bool rtc_init() -- RTC初期化。 * 低電圧が起きたかチェック(check VL bit of Seconds register)し、アラームINTをリセット。 * アラームはダミー設定し(ダミー値:0時・0分・1日・日曜)、全てアラーム対象外ビットを立てる。 * * Return: * true: 低電圧検知 * false: 低電圧非検知 */ bool rtc_init(){ bool isVL; // 低電圧検知フラグ // VL bitのあるSeconds registerを取得 Wire.beginTransmission(RTC_ADDRESS); Wire.write(0x02); // [02]Seconds レジスタを指定 Wire.endTransmission(); Wire.requestFrom(RTC_ADDRESS,1); while (Wire.available() == 0 ){} RegTbl[2] = Wire.read(); // 低電圧検知ビットVLをチェック if((RegTbl[2] & 0x80) != 0){ //低電圧検知 isVL = true; } else { // 低電圧非検知 isVL = false; } // アラーム発生時に1となっているAFビット(bit3)をリセット Wire.beginTransmission(RTC_ADDRESS); Wire.write(0x01); // [01]Control2 レジスタを指定 Wire.write(0x02); // [01]Control2 0x02:アラーム割り込み時に3ピン[INT]をLOWにする&アラームリセット Wire.endTransmission(); // アラームはダミー値を設定。先頭bitに1がある場合(0x80)はアラーム対象外 Wire.beginTransmission(RTC_ADDRESS); Wire.write(0x09); // [09]Minutes Alarm レジスタを指定 Wire.write(0x00 | 0x80); // [09]Minutes Alarm, ダミーで0分 Wire.write(0x00 | 0x80); // [0A]Hours Alarm, ダミーで0時 Wire.write(0x01 | 0x80); // [0B]Days Alarm, ダミーで1日 Wire.write(0x00 | 0x80); // [0C]Weekdays Alarm, ダミーで日曜 // 0:日 1:月 2:火 3:水 4:木 5:金 6:土 // クロック出力レジスタ // [0D]CLKOUT Frequency // ・クロック出力機能を有効にする。 // ※有効:0x80 無効 :0x00 // ・クロック周波数は1Hz(1秒間に1回)とする // ※0x00:32768Hz, 0x01:1024Hz, 0x02:32Hz, 0x03:1Hz // 次の設定だと2ピン[CLKOUT]から1秒に1回、クロック出力される // Wire.write(0x80 | 0x03); Wire.write(0x00 | 0x03); // クロック周波数1Hz(0x03)だが、クロック出力無効(0x00) Wire.endTransmission(); return(isVL); } /* * int rtc_sync_ntp() --- NTP時刻をRTCに設定。 * 同期エラー時は不正値に設定される。 * * Return * 西暦年 */ int rtc_sync_ntp(){ time_t timeNow; struct tm* tmNow; int i=0; configTzTime(TZ_Asia_Tokyo, NTP_SERVER1, NTP_SERVER2, NTP_SERVER3); // NTP同期を待つ。同期されていないと1970となる様子。2020未満は未同期と判断。 do { delay(1000); // 1秒ごとに繰り返し timeNow = time(NULL); tmNow = localtime(&timeNow); } while(((tmNow->tm_year+1900) < 2020)&&(i<=NTPMAXREPEAT)); // 繰り返しがNTPMAXREPEATを超えたら同期を諦める // NTP取得情報をRTCにセット Wire.beginTransmission(RTC_ADDRESS); Wire.write(0x00); Wire.write(0x00); // [00]Control1 Wire.write(0x02); // [01]Control2 0x02:アラーム割り込み時に3ピン[INT]をLOWにする Wire.write(DectoBCD(tmNow->tm_sec)); // [02]Seconds Wire.write(DectoBCD(tmNow->tm_min)); // [03]Minutes Wire.write(DectoBCD(tmNow->tm_hour)); // [04]Hours Wire.write(DectoBCD(tmNow->tm_mday)); // [05]Days Wire.write(DectoBCD(tmNow->tm_wday)); // [06]Weekdays(月) Wire.write(DectoBCD(tmNow->tm_mon+1)|0x80); // [07]Month/Century(21世紀の12月) // ・Month(01-12) BCD形式 // ・Century(先頭bit 0:20世紀[19xx年代],1(0x80):21世紀[20xx年代]) Wire.write(DectoBCD(tmNow->tm_year-100)); // [08]Years // I2Cスレーブへの送信完了 Wire.endTransmission(); return(tmNow->tm_year+1900); } /* * void rtc_set_alarm_min(int m) --- RTC内のレジスタにm分後のアラーム時分を書き込み */ void rtc_set_alarm_min(int m) { int now_hour, now_min; //現在時分 int alarm_hour, alarm_min; //アラーム時分 int wh, wm; //作業用時分 // アラーム時分をm分後に設定 wh = m / 60; wm = m % 60; now_min = rtc_get_min(); now_hour = rtc_get_hour(); alarm_min = (now_min+wm)%60; if (((now_min+wm)/60)==0) { //分桁上がり無し alarm_hour = (now_hour + wh)%24; } else { //分桁上がり有り alarm_hour = (now_hour + wh + 1)%24; } rtc_set_alarm(alarm_hour, alarm_min); } /* * void rtc_set_alarm(int h, int m) --- RTC内のレジスタにアラーム時分を書き込み * DaysとWDaysはダミー値を書き込み、アラーム対象外に設定 */ void rtc_set_alarm(int h, int m){ Wire.beginTransmission(RTC_ADDRESS); Wire.write(0x09); Wire.write(DectoBCD(m)); // [09]Minutes Wire.write(DectoBCD(h)); // [0A]Hours Alarm Wire.write(0x01 | 0x80); // [0B]Days Alarm(1日), 先頭bitに1がある場合(0x80)はアラーム対象外 Wire.write(0x00 | 0x80); // [0C]Weekdays Alarm(日曜),先頭bitに1がある場合(0x80)はアラーム対象外 // 0:日 1:月 2:火 3:水 4:木 5:金 6:土 Wire.endTransmission(); } /* * int rtc_get_hour() --- レジスタテーブルRegTblから現在のhourを返す * RTCのレジスタから読み出すわけではないので要注意。 */ int rtc_get_hour(){ return(BCDtoDec(RegTbl[4] & 0x3F)); } /* * int rtc_get_min() --- レジスタテーブルRegTblから現在のminを返す * RTCのレジスタから読み出すわけではないので要注意。 */ int rtc_get_min(){ return(BCDtoDec(RegTbl[3] & 0x7F)); } /* * void rtc_get_timestamp(char *at) --- レジスタテーブルからtimestampを生成 */ void rtc_get_timestamp(char *at) { String wts = String(BCDtoDec(RegTbl[8] & 0xFF)+2000); // year wts += '-'; wts += ((RegTbl[7] >> 4) & 0x01); // 月の10の位 (0,1) wts += (RegTbl[7] & 0x0F); // 月の1の位 (0,,,,,9) wts += '-'; wts += ((RegTbl[5] >> 4) & 0x03); // 日の10の位 (0,,3) wts += (RegTbl[5] & 0x0F); // 日の1の位 (0,,,,,9) wts += 'T'; wts += ((RegTbl[4] >> 4) & 0x03); // 時の10の位 (0,,2) wts += (RegTbl[4] & 0x0F); // 時の1の位 (0,,,,,9) wts += ':'; wts += ((RegTbl[3] >> 4) & 0x07); // 分の10の位 (0,,,5) wts += (RegTbl[3] & 0x0F); // 分の1の位 (0,,,,,9) wts += ':'; wts += ((RegTbl[2] >> 4) & 0x07); // 秒の10の位 (0,,,5) wts += (RegTbl[2] & 0x0F); // 秒の1の位 (0,,,,,9) wts += "+09:00"; // JST //Serial.println(wts); wts.toCharArray(at, ATLEN); } /* * void rtc_read_regtbl() --- RTC内のレジスタからレジスタテーブルRegTblに読み込み */ void rtc_read_regtbl(){ // レジスタのアドレスを先頭にする Wire.beginTransmission(RTC_ADDRESS); Wire.write(0x00); Wire.endTransmission(); // I2Cスレーブに16byteのレジスタデータを要求する Wire.requestFrom(RTC_ADDRESS,16); // RTCREGLEN(16byte)のデータを取得する for (int i=0; i<RTCREGLEN; i++){ while (Wire.available() == 0 ){} RegTbl[i] = Wire.read(); } } //////////////////////////////////////// // 2進化10進数(BCD)を10進数に変換 //////////////////////////////////////// byte BCDtoDec(byte value){ return ((value >> 4) * 10) + (value & 0x0F) ; } //////////////////////////////////////// // 10進数をBCDに変換 //////////////////////////////////////// byte DectoBCD(int value) { return((value/10*0x10) | (value%10)); }
そろそろ記事が長くなってきたので、続きは別記事とします。
続きはこちらへ。
techrmc.hatenablog.com