diff --git a/README.md b/README.md index 99c4ee5..8b1b8c6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # JJY Receiver -JJY standard radio wave signal receiver library for arduino. +JJY standard radio wave signal receiver library for arduino. arudinoの日本標準時受信モジュール用ライブラリ # 機能 @@ -10,7 +10,7 @@ JJYの日本標準時刻データを受信します。C言語標準のtime_t型 電波時計モジュールをarduinoで利用しやすい形のJJY受信ライブラリ的なものがWebに見つけられなかったので作ってみました。 -電波時計やデータロガーの日時情報など、低電力やインターネット未接続環境での時刻情報の利用ができます +電波時計の制作やデータロガーの日時情報など、Wifiを利用せずとも電池駆動可能な低電力でインターネット未接続環境での時刻情報の利用ができます。 # ハードウェア要件 @@ -37,7 +37,7 @@ RTCなどを使用して時刻を維持し、マイコン側で時を刻まな ### JJY受信IC -写真には黄色のコンデンサがついていますが不要です。JJY受信モジュールはおおむねどれも似たようなインタフェースかと思いますが、負論理と正論理のものがあり手元にあるものは負論理モジュールでしたので、負論理と正論理の出力がある場合は負論理出力をつないでください。 +写真には黄色のコンデンサがついていますが不要です。JJY受信モジュールはおおむねどれも似たようなインタフェースかと思いますが、負論理と正論理のものがあり手元にあるものは負論理モジュールでしたので、負論理と正論理の出力がある場合は負論理出力をつないでください。正論理モジュールの場合はおそらく、トランジスタを使って反転させるか、TODOに書いてある変更を加えると動作すると思われます。 利用したモジュールの参考: @@ -89,14 +89,56 @@ https://ja.aliexpress.com/item/1005005254051736.html ## 基本的な使い方 ``` - jjy.begin(); // 受信開始 - while(jjy.getTime() == -1){ // 受信するまで待ち - delay(1000); - } - time_t now = get_time(); // 時間の利用 +#include + +#define DATA 39 +#define PON 33 +#define SEL 25 + +JJYReceiver jjy(DATA,SEL,PON); // JJYReceiver lib set up. +// JJYReceiver jjy(DATA); // 1pinにする場合 + +void setup() { + // 10msec Timer for clock ticktock (Mandatory) + タイマーの設定。ライブラリやレジスタ等を設定。 + ticktock()関数をタイマ呼び出しハンドラに登録 + + // DATA pin 入力変化割り込み (Mandatory) + attachInterrupt(digitalPinToInterrupt(DATA), isr_routine, CHANGE); + + // JJY Library + jjy.freq(40); // 受信周波数の設定 + jjy.begin(); // 受信の開始 + + while(jjy.getTime() == -1) delay(1000); // 受信が終わるまで次を実行させない場合に書く +} + +void isr_routine() { // 入力信号変化割り込みで呼び出すハンドラ + jjy.jjy_receive(); +} +void ticktock() { // 10 msecタイマで呼び出すハンドラ + jjy.delta_tick(); +} + +void loop() { + time_t now = get_time(); // 時間の利用。呼び出したときの現在時刻を取得 time_t receive_time = jjy.getTime(); // 最後に電波を受信した時点の時刻の取得 + delay(10000); +} + ``` +## サンプルスケッチ + +[サンプル](https://github.com/Blue-Crescent/JJYReceiver/tree/main/examples) + +- minimumsample + + - 最低限の記述の例です。MSTimer2ライブラリの部分はお使いのマイコンのアーキテクチャのタイマーを設定して10msecを作ってください。 +- lgt8f328pで確認しています。 + +- _esp32が付いている名称のものは、ESP32で確認しているサンプルです。 + ## 関数 ### JJYReceiver(ピン番号) @@ -151,12 +193,16 @@ JJY受信モジュールのデータ出力をマイコンの端子変化割り 受信が完了しているかの確認に使用します。受信時刻を取得します。時刻が受信できていない場合は-1を返します。 -getTime()が戻り値を返すには最低2つの内部の時刻受信データが一致する必要があります。JJYは1分かけて時刻情報を送信していますので、受信には最低2分かかります。良好な状態で3分程度、ノイズがある環境だと数分~受信不可となります。 +getTime()が戻り値を返すには最低2つの内部の時刻受信データが一致する必要があります(3分ぶんのバッファ)。JJYは1分かけて時刻情報を送信していますので、受信には最低2分かかります。良好な状態で3分程度、ノイズがある環境だと数分~受信不可となります。 v0.6.0より begin()により受信が成功後のgetTime()の初回呼び出し時にJJY受信データの変換が行われて時刻が校正され、内部管理時刻に反映されます。(esp32では割り込みハンドラ内でmktime計算が実行できないため、変換処理は外部から呼び出す必要がある。受信完了後、すぐ呼び出さないと内部管理時刻への反映遅れによりずれます) +v0.7.0より + +1回目の受信データのデータ長が一致していた場合、受信できた時刻をget_time()が返す時刻として仮反映する。この時の戻り値は-1。2回受信できた時点で確定し、最終受信時刻を返す。 + [Note] v0.6.0より動作変更 ### get_time() @@ -207,17 +253,7 @@ if(jjy.quality > 80){ - 60:60kHz -## サンプルスケッチ - -[サンプル](https://github.com/Blue-Crescent/JJYReceiver/tree/main/examples) -- minimumsample - - - 最低限の記述の例です。MSTimer2ライブラリの部分はお使いのマイコンのアーキテクチャのタイマーを設定して10msecを作ってください。 - -- lgt8f328pで確認しています。 - -- _esp32が付いている名称のものは、ESP32で確認しているサンプルです。 # デバッグモード @@ -228,23 +264,23 @@ SoftwareSerialなどのシリアル通信ライブラリを有効にすること データ判定結果が:の後にP,L,Hで表示され、それぞれマーカ、L、Hとなります。 そのあとにマーカの区間、受信状態を表示します。 -受信中の中間データはlocaltime[0],localtime[1],localtime[2]のデータに格納されます。*は更新対象の時刻です。 +受信中の中間データはjjydata配列に格納されます。jjypayload配列に各マーカー間のビット数を格納します。 Q:の手前の三つの数字はL,H,Pのマーカーとのハミング距離です。最大値96。サンプリングデータとCONST_L,CONST_H,CONST_PMそれぞれの配列との相関ですので、配列の内容を調整することで最適化できます。 Q:は受信品質を示します。ハミング距離から50%(ランダム一致を考慮)を差し引いた値からの一致具合の割合です。 ``` -0000000000000FFFFFFFFFFF:H MIN:RECEIVE 3 *Sat Feb 10 11:55:01 2024 Sun Feb 18 11:55:03 2024 Sat Feb 10 11:55:04 2024 =>Sat Jan 01 00:08:42 2000 68:92:72 Q:90 -00000000000000000000FFFF:L MIN:RECEIVE 4 *Sat Feb 10 11:55:02 2024 Sun Feb 18 11:55:04 2024 Sat Feb 10 11:55:05 2024 =>Sat Jan 01 00:08:43 2000 96:72:44 Q:100 -0000000000000000001FFFFF:L MIN:RECEIVE 5 *Sat Feb 10 11:55:03 2024 Sun Feb 18 11:55:05 2024 Sat Feb 10 11:55:06 2024 =>Sat Jan 01 00:08:44 2000 91:77:49 Q:88 -000000000003FFFFFFFFFFFF:H MIN:RECEIVE 6 *Sat Feb 10 11:55:04 2024 Sun Feb 18 11:55:06 2024 Sat Feb 10 11:55:07 2024 =>Sat Jan 01 00:08:45 2000 62:86:78 Q:78 -0000000000000000000FFFFF:L MIN:RECEIVE 7 *Sat Feb 10 11:55:05 2024 Sun Feb 18 11:55:07 2024 Sat Feb 10 11:55:08 2024 =>Sat Jan 01 00:08:46 2000 92:76:48 Q:90 -0000000000001FFFFFFFFFFF:H MIN:RECEIVE 8 *Sat Feb 10 11:55:06 2024 Sun Feb 18 11:55:08 2024 Sat Feb 10 11:55:09 2024 =>Sat Jan 01 00:08:47 2000 67:91:73 Q:88 -000007FFFFFFFFFFFFFFFFFF:P HOUR:RECEIVE 0 *Sat Feb 10 11:55:07 2024 Sun Feb 18 11:55:09 2024 Sat Feb 10 11:55:10 2024 =>Sat Jan 01 00:08:48 2000 37:61:89 Q:84 -000000000000000000001FFF:L HOUR:RECEIVE 1 *Sat Feb 10 11:55:08 2024 Sun Feb 18 11:55:10 2024 Sat Feb 10 11:55:11 2024 =>Sat Jan 01 00:08:49 2000 93:69:41 Q:92 -0000000000007FE000001FFF:L HOUR:RECEIVE 2 *Sat Feb 10 11:55:09 2024 Sun Feb 18 11:55:11 2024 Sat Feb 10 11:55:12 2024 =>Sat Jan 01 00:08:50 2000 83:65:51 Q:72 -000000000000000000003FFF:L HOUR:RECEIVE 3 *Sat Feb 10 11:55:10 2024 Sun Feb 18 11:55:12 2024 Sat Feb 10 11:55:13 2024 =>Sat Jan 01 00:08:51 2000 94:70:42 Q:94 +[2024-02-12 11:00:51.722] 00000000000001FFFC01FFFF:H MIN:RECEIVE 10 80:86:60 Q:78 +[2024-02-12 11:00:52.722] 00000000007FFFFFFFFFFFFF:P HOUR:RECEIVE 9 57:81:83 Q:72 +[2024-02-12 11:00:53.643] 0000000000000000000000FF:L HOUR:RECEIVE 10 88:64:36 Q:82 +[2024-02-12 11:00:54.831] 00000000000000003FFFFFFF:H HOUR:RECEIVE 11 82:86:58 Q:78 +[2024-02-12 11:00:55.832] 0000000000000000FFFFFFE0:H HOUR:RECEIVE 12 75:83:55 Q:72 +[2024-02-12 11:00:56.832] 0000000000FFFFF03FFFFF00:P DOYH:RECEIVE 6 54:66:70 Q:44 +[2024-02-12 11:00:57.832] 0000003FF800000000FFFF00:L DOYH:RECEIVE 7 69:61:47 Q:42 +[2024-02-12 11:00:58.832] 000000000000000003FFFFFF:L DOYH:RECEIVE 8 86:82:54 Q:78 +[2024-02-12 11:00:59.802] 00FFFFFFFFFFFFFFFFFFFFFF:P DOYL:RECEIVE 4 24:48:76 Q:58 +[2024-02-12 11:01:00.803] 0001FFFFFFFFFFFFFFFFFFE0:M MIN:RECEIVE 0 26:50:78 Q:62 ``` # アルゴリズム @@ -269,7 +305,7 @@ https://www.nict.go.jp/sts/jjy_signal.html 受信は3回分の受信データを保持しており、同一の時刻を2つ観測した段階で正式時刻として採用します。 2つデータが揃わない場合は2つ揃うまでデータを巡回して上書きしていきます。 -時刻データはtime.hのtimeinfo構造体を利用して、JJYデータからUTC時刻に変換しtime_t型で管理します +時刻データはtime.hのtm構造体を利用して、JJYデータからUTC時刻に変換しtime_t型で管理します 40KHzでの動作確認をしています。 @@ -283,6 +319,7 @@ https://www.nict.go.jp/sts/jjy_signal.html 未定 - 本家Arduinoボード動作確認。未所持 Uno R4欲しいな~ +- 正論理出力への対応。原理的にはjjy_receive()をposedgeにしてconst_L,const_H,const_PMの1と0を反対にすればいけるはず。モジュールが入手できない # 受信 diff --git a/src/JJYReceiver.cpp b/src/JJYReceiver.cpp index 0fc9a19..30fffdf 100644 --- a/src/JJYReceiver.cpp +++ b/src/JJYReceiver.cpp @@ -82,13 +82,9 @@ bool JJYReceiver::timeCheck(){ jjydata[compare[i][0]].bits.doyh == jjydata[compare[i][1]].bits.doyh && jjydata[compare[i][0]].bits.doyl == jjydata[compare[i][1]].bits.doyl && jjydata[compare[i][0]].bits.hour == jjydata[compare[i][1]].bits.hour && - min1 == min2) + abs(min1 - min2) <= 2) { - if(min1 > min2){ - last_jjydata = jjydata[compare[i][0]]; - }else{ - last_jjydata = jjydata[compare[i][1]]; - } + last_jjydata[0] = (min2 > min1) ? jjydata[compare[i][1]] : jjydata[compare[i][0]]; state = TIMEVALID; stop(); return true; @@ -100,17 +96,10 @@ bool JJYReceiver::timeCheck(){ time_t JJYReceiver::get_time() { return globaltime; } + time_t JJYReceiver::get_time(uint8_t index) { uint16_t year,yday; - time_t temptime; - year = (((jjydata[index].bits.year & 0xf0) >> 4) * 10 + (jjydata[index].bits.year & 0x0f)) + 2000; - timeinfo.tm_year = year - 1900; // 年 - yday = ((((jjydata[index].bits.doyh >> 5) & 0x0002)) * 100) + (((jjydata[index].bits.doyh & 0x000f)) * 10) + jjydata[index].bits.doyl; - calculateDate(year, yday ,(uint8_t*) &timeinfo.tm_mon,(uint8_t*) &timeinfo.tm_mday); - timeinfo.tm_hour = ((jjydata[index].bits.hour >> 5) & 0x3) * 10 + (jjydata[index].bits.hour & 0x0f) ; // 時 - timeinfo.tm_min = ((jjydata[index].bits.min >> 5) & 0x7) * 10 + (jjydata[index].bits.min & 0x0f) + 1; // 分 - temptime = mktime(&timeinfo); - return temptime; + return updateTimeInfo(jjydata,index,1); } time_t JJYReceiver::getTime() { uint16_t year,yday; @@ -119,24 +108,11 @@ time_t JJYReceiver::getTime() { return -1; case RECEIVE: // Intermediate update (1st receive update) if(timeavailable == -1) return -1; - year = (((jjydata[timeavailable].bits.year & 0xf0) >> 4) * 10 + (jjydata[timeavailable].bits.year & 0x0f)) + 2000; - timeinfo.tm_year = year - 1900; // 年 - yday = ((((jjydata[timeavailable].bits.doyh >> 5) & 0x0002)) * 100) + (((jjydata[timeavailable].bits.doyh & 0x000f)) * 10) + jjydata[timeavailable].bits.doyl; - calculateDate(year, yday ,(uint8_t*) &timeinfo.tm_mon,(uint8_t*) &timeinfo.tm_mday); - timeinfo.tm_hour = ((jjydata[timeavailable].bits.hour >> 5) & 0x3) * 10 + (jjydata[timeavailable].bits.hour & 0x0f) ; // 時 - timeinfo.tm_min = ((jjydata[timeavailable].bits.min >> 5) & 0x7) * 10 + (jjydata[timeavailable].bits.min & 0x0f) + 2; // 分 - globaltime = mktime(&timeinfo); + globaltime = updateTimeInfo(jjydata,timeavailable,1); timeavailable = -1; return -1; case TIMEVALID: - year = (((last_jjydata.bits.year & 0xf0) >> 4) * 10 + (last_jjydata.bits.year & 0x0f)) + 2000; - timeinfo.tm_year = year - 1900; // 年 - //timeinfo.tm_yday = // Day of the year is not implmented in Arduino time.h - yday = ((((last_jjydata.bits.doyh >> 5) & 0x0002)) * 100) + (((last_jjydata.bits.doyh & 0x000f)) * 10) + last_jjydata.bits.doyl; - calculateDate(year, yday ,(uint8_t*) &timeinfo.tm_mon,(uint8_t*) &timeinfo.tm_mday); - timeinfo.tm_hour = ((last_jjydata.bits.hour >> 5) & 0x3) * 10 + (last_jjydata.bits.hour & 0x0f) ; // 時 - timeinfo.tm_min = ((last_jjydata.bits.min >> 5) & 0x7) * 10 + (last_jjydata.bits.min & 0x0f) + 2; // 分 - globaltime = mktime(&timeinfo); + globaltime = updateTimeInfo(last_jjydata,0,1); state = TIMETICK; received_time = globaltime; } diff --git a/src/JJYReceiver.h b/src/JJYReceiver.h index 8b9a0ec..3381771 100644 --- a/src/JJYReceiver.h +++ b/src/JJYReceiver.h @@ -63,7 +63,7 @@ class JJYReceiver { public: volatile uint8_t jjypayloadlen[6] = {0,0,0,0,0,0}; JJYData jjydata[VERIFYLOOP]; - JJYData last_jjydata; + JJYData last_jjydata[1]; volatile enum STATE state = INIT; volatile enum JJYSTATE jjystate = JJY_INIT; volatile uint8_t rcvcnt = 0; @@ -111,6 +111,7 @@ class JJYReceiver { int max_of_three(uint8_t a, uint8_t b, uint8_t c); bool calculateParity(uint8_t value, uint8_t bitLength, uint8_t expectedParity); bool timeCheck(); + //time_t updateTimeInfo(JJYData jjydata*, int8_t index, int8_t offset); time_t getTime(); time_t get_time(); time_t get_time(uint8_t index); @@ -134,6 +135,17 @@ class JJYReceiver { timeinfo.tm_sec = 1; // 秒 return true; } + time_t updateTimeInfo(JJYData* jjydata, int8_t index, int8_t offset) { + int year, yday; + year = (((jjydata[index].bits.year & 0xf0) >> 4) * 10 + (jjydata[index].bits.year & 0x0f)) + 2000; + timeinfo.tm_year = year - 1900; // 年 + yday = ((((jjydata[index].bits.doyh >> 5) & 0x0002)) * 100) + (((jjydata[index].bits.doyh & 0x000f)) * 10) + jjydata[index].bits.doyl; + calculateDate(year, yday ,(uint8_t*) &timeinfo.tm_mon,(uint8_t*) &timeinfo.tm_mday); + timeinfo.tm_hour = ((jjydata[index].bits.hour >> 5) & 0x3) * 10 + (jjydata[index].bits.hour & 0x0f) ; // 時 + timeinfo.tm_min = ((jjydata[index].bits.min >> 5) & 0x7) * 10 + (jjydata[index].bits.min & 0x0f) + offset; // 分 + time_t temp = mktime(&timeinfo); + return temp; + } void init(){ state = RECEIVE; clear(sampling,N);