Top Page STM32F103でOCXO Calibratorを作る(2)
自作・実験工程のメモとして
05.Apr.2020


 一つ前の「STM32F103でOCXO Calibratorを作る(1)」で、10MHz OCXOの校正器を作り始めて失敗した掲載をしましたが、ケース加工も済ませていることから諦めることなく、計測周波数を伸ばす安直な方法ですが前置カウンター(FrontCounter)を補強することにしました。

 ここでは、「STM32F103でOCXO Calibratorを作る(1)」(以下、前編)を前提に進めますので興味のある方は、前編と併せてご覧ください。
本続編の結論としては、約27MHzまでは計測できるようになりました。
ただし、前置カウンター(FrontCounter)の関係か、規則性の有る特定周波数で問題が発生しました。(3項
本掲載で後編にしようと思っていましたが、終われそうに有りません。

番外編「PIC24FV32KA302でバイナリカウンターを試す」を掲載しました。(2020/04/09)




約27MHzを計測中のOCXO Calibrator(以下、OCXO 校正器)
 

Edit by bluegriffon3.1

1.見直し後:OCXO 校正器のブロックダイヤグラム(BDG)と回路

 前編との変更は、前置カウンタ(FrontCounter)を4ビットから8ビットに変更し、最高計測周波数を前編比16倍(計256倍)にするということで進めます。
また、既にこのテーマで3枚目のPCB発注になることから、他のテストもできるように回路に冗長度を持たせています。


■.OCXO 校正器のBDG
1.回路のシルクには、IC(74シリーズ)の型番を74HC…としていますが、実際は74ACシリーズを使用しています。
2.ただし、今回使った8ビットバイナリカウンタは74AC590が入手できず、74HC590を使っています。
3.他のICも74ACシリーズが入手できたため使っていますが、74HCシリーズで十分と思われます。
4.他は、8ビットバイナリカウンタ採用による回路変更が主な違いになります。

■.OCXO 校正器の回路
クリックでpdfファイルが開きます。(2020/04/06:pdfファイル差し替えました)
1.8ビットバイナリカウンタ(74AC590)関連で8ビットのカウンタ情報を取り出すために8ポートを使っていますが、STM32F103の周辺回路が5Vのためトレラント(5V tolerant)ポートが不足しており、不足分(2ポート分)はダイオードで保護しています。
2.また、今回の8ビットバイナリカウンタが上手くいかないときを考え、ICのピンに並行してピンソケットを植えられるようにしました。
3.もし次に変更が必要になった時には、そのピンソケット部に子亀基板を載せて回路追加ができるように冗長度を持たせています。
操作スイッチの回路は、変更なしです。

2.見直し後:PCBの設計

 前編のレイアウトを踏襲した構成にしています。当然、ケース加工をやり直す考えは有りませんのでネジ穴やコネクタの位置は変更していません。

■.PCBの3Dビューアー(表面)
1.前編との違いは74シリーズの配置を変更し、回路図のところで書きましたように74HC590の両側にピンソケットのパターンを付けています。
2.その他、STM32の使えそうなポートは、ピンソケットが挿せるようにパターンを引いています。

■.PCBの3Dビューアー(裏面)

大きな変更は無く、一部表面が手狭になったことから裏面にパターンを移しています。





3.計測時の問題点

 文頭に書きましたが最高周波数は、8bit前置カウンタのおかげで約27MHzまでなりました。
この27MHzの制限は、アナログアンプの周波数特性(調整も含む)のようで、4bit(2.8MHz)の16倍にはなりませんでしたが、OCXO(10MHz)の倍以上の周波数に届いて完成かと思いましたが、特定の周波数で大幅に測定値の変動?が発生します。
この現象は、4bit前置カウンタの時も時々発生しており、前置カウンタを交換すれば解消することを期待していました。
その為、プログラム上のLOG機能に次々と情報を加えてソフト上の回避策を前編(V2.1)から試しており、続編でも念のため確認して再現しています。
何か間違っていると思いますが、原因不明のまま公開しておきます。

発生現象の概略
前置カウンタのビットが、0b11111111 から 0b00000000 にオーバーフロー(Carry)する前後で発生し、Carryアップした後も前置カウンタの内容がゼロにならない」ような現象が発生します。
その為、発生しやすい入力周波数を別掲載の自作「FRAT」で発振させて再現実験を続けています。

発生パターン周波数
発生周波数は、エクセルを使ってどの周波数で下8ビットがオール1に成るかを確認します。

10,006,015Hzの辺りが再現しやすい周波数と分かります。
この周波数を「FRAT」で発振させます。

ログに周波数の変動とビット列を表示して変動を確認
ログは本器の設定と、PC側で「teraterm」を起動して記録します。

左からカンマ区切りで、「連番,年月日,時分秒,GPS品質,衛星数,計測周波数,一つ前との差,GPSロス回数,PPSロス回数,周波数のカウント値をHEXで表示,同下2バイトをビット列で表示,ゲートが閉まった後のキャリーアップ数」を入れています。(2020/03/29現在)


発生の例と推察
ここでは、前置カウンタのCarryを処理するSTM32F103の割り込み処理部分( void intFrq() )を変更しながら例として書きだしておきます。
// TEST 1
// Carry割込みが有ったら、前置カウンタの出力ラッチをして
// MSB側(bit7)がLOWなら周波数としてカウント、以外は過剰キャリーカウント(デバッグ)
void intFrq() {
  digitalWrite(coRCK, HIGH);
  digitalWrite(coRCK, LOW);
  if (digitalRead(ctQ7) == LOW)  ui64Reg++; else excessCarry++;
}

FRATの出力は、26006015.0000Hz前後
Num,YYMMDD,HHMMSS,GpsQ,Sat,Freq,(+-),gpsLoss,ppsLoss,HEX,b15-b0,excessCarry
1,2020/03/29,00:55:24,2,19,26006015.0000,  +0.000,0,0,D1FF,1101 0001 1111 1111,0
2,2020/03/29,00:55:26,2,19,26006271.0000,+256.000,0,0,D2FF,1101 0010 1111 1111,0
3,2020/03/29,00:55:28,2,18,26006270.0000,  -1.000,0,0,D2FE,1101 0010 1111 1110,0
4,2020/03/29,00:55:30,2,19,26006015.0000,-255.000,0,0,D1FF,1101 0001 1111 1111,0
5,2020/03/29,00:55:32,2,18,26006270.0000,+255.000,0,0,D2FE,1101 0010 1111 1110,0
6,2020/03/29,00:55:34,2,18,26006014.0000,-256.000,0,0,D1FE,1101 0001 1111 1110,0
7,2020/03/29,00:55:36,2,18,26006015.0000,  +1.000,0,0,D1FF,1101 0001 1111 1111,0
8,2020/03/29,00:55:38,2,18,26006271.0000,+256.000,0,0,D2FF,1101 0010 1111 1111,0
9,2020/03/29,00:55:40,2,18,26006271.0000,  +0.000,0,0,D2FF,1101 0010 1111 1111,0
10,2020/03/29,00:55:42,2,17,26006271.0000,  +0.000,0,0,D2FF,1101 0010 1111 1111,0
11,2020/03/29,00:55:44,2,18,26006271.0000,  +0.000,0,0,D2FF,1101 0010 1111 1111,0
12,2020/03/29,00:55:46,2,18,26006272.0000,  +1.000,0,0,D300,1101 0011 0000 0000,0

13,2020/03/29,00:55:48,2,18,26006016.0000,-256.000,0,0,D200,1101 0010 0000 0000,0
14,2020/03/29,00:55:50,2,18,26006015.0000,  -1.000,0,0,D1FF,1101 0001 1111 1111,0

過剰キャリーカウントが多発(+256Hz)

==========================================================
// TEST 2
// ゲートONなら周波数としてカウント、以外は過剰キャリーカウント(デバッグ)
void intFrq() {
   if (gateON == 1) ui64Reg++; else excessCarry++;
}

FRATの出力は、26006015.0000Hz前後
69,2020/03/29,01:10:50,2,20,26006029.0000,  +4.000,0,0,D20D,1101 0010 0000 1101,3
70,2020/03/29,01:10:52,2,21,26006019.0000, -10.000,0,0,D203,1101 0010 0000 0011,3
71,2020/03/29,01:10:54,2,21,26006016.0000,  -3.000,0,0,D200,1101 0010 0000 0000,3
72,2020/03/29,01:10:56,2,21,26006017.0000,  +1.000,0,0,D201,1101 0010 0000 0001,3
73,2020/03/29,01:10:58,2,21,26006017.0000,  +0.000,0,0,D201,1101 0010 0000 0001,3
74,2020/03/29,01:11:00,2,21,26006269.0000,+252.000,0,0,D2FD,1101 0010 1111 1101,3
75,2020/03/29,01:11:02,2,21,26006016.0000,-253.000,0,0,D200,1101 0010 0000 0000,3
76,2020/03/29,01:11:04,2,21,26006016.0000,  +0.000,0,0,D200,1101 0010 0000 0000,3
77,2020/03/29,01:11:06,2,20,26006017.0000,  +1.000,0,0,D201,1101 0010 0000 0001,3
78,2020/03/29,01:11:08,2,21,26006016.0000,  -1.000,0,0,D200,1101 0010 0000 0000,3
79,2020/03/29,01:11:10,2,21,26006015.0000,  -1.000,0,0,D1FF,1101 0001 1111 1111,4
80,2020/03/29,01:11:12,2,20,26006269.0000,+254.000,0,0,D2FD,1101 0010 1111 1101,4
81,2020/03/29,01:11:14,2,21,26006016.0000,-253.000,0,0,D200,1101 0010 0000 0000,4
82,2020/03/29,01:11:16,2,21,26006015.0000,  -1.000,0,0,D1FF,1101 0001 1111 1111,5
83,2020/03/29,01:11:18,2,21,26006018.0000,  +3.000,0,0,D202,1101 0010 0000 0010,5
84,2020/03/29,01:11:20,2,20,26006016.0000,  -2.000,0,0,D200,1101 0010 0000 0000,5
85,2020/03/29,01:11:22,2,20,26006016.0000,  +0.000,0,0,D200,1101 0010 0000 0000,5

下8ビットが完全な形の"11111111"ではないが、キャリーの(Carry)過加算のような現象

==========================================================
// TEST 3
// ゲート(D-F/F(74AC74))の出力がON(負論理)なら周波数としてカウント
void intFrq() {
 if (digitalRead(ciGONn) == LOW) ui64Reg++;
}

FRATの出力は、26006015.0000Hz前後(実周波数は、多分 26006032Hz)
1,2020/03/29,01:21:16,2,17,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
2,2020/03/29,01:21:18,2,18,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
3,2020/03/29,01:21:20,2,17,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
4,2020/03/29,01:21:22,2,17,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
5,2020/03/29,01:21:24,2,18,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
6,2020/03/29,01:21:26,2,18,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
7,2020/03/29,01:21:28,2,18,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
8,2020/03/29,01:21:30,2,19,26005777.0000,  +1.000,0,0,D111,1101 0001 0001 0001,0
9,2020/03/29,01:21:32,2,18,26005776.0000,  -1.000,0,0,D110,1101 0001 0001 0000,0
10,2020/03/29,01:21:34,2,18,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
11,2020/03/29,01:21:36,2,18,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0
12,2020/03/29,01:21:38,2,19,26005776.0000,  +0.000,0,0,D110,1101 0001 0001 0000,0


全ての計測で256カウント前後のマイナス

10MHzOCXOを入れてみる。
Num,YYMMDD,HHMMSS,GpsQ,Sat,Freq,(+-),gpsLoss,ppsLoss,HEX,b15-b0,excessCarry
1,2020/03/29,01:24:26,2,18, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
2,2020/03/29,01:24:37,2,19, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
3,2020/03/29,01:24:48,2,17, 9999974.5000,  +0.100,0,0,E001,1110 0000 0000 0001,0
4,2020/03/29,01:24:59,2,17, 9999974.4000,  -0.100,0,0,E000,1110 0000 0000 0000,0
5,2020/03/29,01:25:10,2,18, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
6,2020/03/29,01:25:21,2,18, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
7,2020/03/29,01:25:32,2,20, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
8,2020/03/29,01:25:43,2,20, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
9,2020/03/29,01:25:54,2,18, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0
10,2020/03/29,01:26:05,2,16, 9999974.5000,  +0.100,0,0,E001,1110 0000 0000 0001,0
11,2020/03/29,01:26:16,2,18, 9999974.4000,  -0.100,0,0,E000,1110 0000 0000 0000,0
12,2020/03/29,01:26:27,2,19, 9999974.4000,  +0.000,0,0,E000,1110 0000 0000 0000,0


256カウント(キャリー)分が欠落している。

=========================================================
// TEST 4
// ゲートONなら周波数としてカウント、以外は過剰キャリーカウント(デバッグ)
void intFrq() {
   if (gateON == 1) ui64Reg++; else excessCarry++;
}

10MHzOCXOを入れてみる。10,000,000.02Hzで安定発振(ゲート10S)
Num,YYMMDD,HHMMSS,GpsQ,Sat,Freq,(+-),gpsLoss,ppsLoss,HEX,b15-b0,excessCarry
1,2020/03/29,01:33:15,2,17,10000000.1000,  +0.000,0,0,E101,1110 0001 0000 0001,0
2,2020/03/29,01:33:26,2,18,10000000.0000,  -0.100,0,0,E100,1110 0001 0000 0000,0
3,2020/03/29,01:33:37,2,19,10000000.0000,  +0.000,0,0,E100,1110 0001 0000 0000,0
4,2020/03/29,01:33:48,2,19,10000000.0000,  +0.000,0,0,E100,1110 0001 0000 0000,0
5,2020/03/29,01:33:59,2,19,10000000.1000,  +0.100,0,0,E101,1110 0001 0000 0001,0
6,2020/03/29,01:34:10,2,19,10000000.0000,  -0.100,0,0,E100,1110 0001 0000 0000,0
7,2020/03/29,01:34:21,2,18,10000000.0000,  +0.000,0,0,E100,1110 0001 0000 0000,0
8,2020/03/29,01:34:32,2,19,10000000.0000,  +0.000,0,0,E100,1110 0001 0000 0000,0

下8ビットが11111111にならない。(変動が無いので別の手で試す)

===========================================================
// TEST 5
// ゲートONなら周波数としてカウント、以外は過剰キャリーカウント(デバッグ)
void intFrq() {
   if (gateON == 1) ui64Reg++; else excessCarry++;
}

FRAT10,006,015Hzを入れる。(1S)

106,2020/03/29,01:42:48,2,20,10006016.0000,  +1.000,0,0,AE00,1010 1110 0000 0000,19
107,2020/03/29,01:42:50,2,20,10006016.0000,  +0.000,0,0,AE00,1010 1110 0000 0000,19
108,2020/03/29,01:42:52,2,21,10006033.0000, +17.000,0,0,AE11,1010 1110 0001 0001,19

ここはFRATの周波数変動と見ても良いかも知れない。

116,2020/03/29,01:43:08,2,20,10006015.0000,  +0.000,0,0,ADFF,1010 1101 1111 1111,26
117,2020/03/29,01:43:10,2,20,10006015.0000,  +0.000,0,0,ADFF,1010 1101 1111 1111,27
118,2020/03/29,01:43:12,2,20,10006015.0000,  +0.000,0,0,ADFF,1010 1101 1111 1111,28
119,2020/03/29,01:43:14,2,20,10006017.0000,  +2.000,0,0,AE01,1010 1110 0000 0001,28
120,2020/03/29,01:43:16,2,20,10006261.0000,+244.000,0,0,AEF5,1010 1110 1111 0101,28
121,2020/03/29,01:43:18,2,20,10006016.0000,-245.000,0,0,AE00,1010 1110 0000 0000,28
122,2020/03/29,01:43:20,2,20,10006016.0000,  +0.000,0,0,AE00,1010 1110 0000 0000,28
123,2020/03/29,01:43:22,2,20,10006016.0000,  +0.000,0,0,AE00,1010 1110 0000 0000,28
124,2020/03/29,01:43:24,2,20,10006016.0000,  +0.000,0,0,AE00,1010 1110 0000 0000,28
125,2020/03/29,01:43:26,2,20,10006016.0000,  +0.000,0,0,AE00,1010 1110 0000 0000,28
126,2020/03/29,01:43:28,2,19,10006015.0000,  -1.000,0,0,ADFF,1010 1101 1111 1111,29
127,2020/03/29,01:43:30,2,20,10006016.0000,  +1.000,0,0,AE00,1010 1110 0000 0000,29

下8ビットが完全な形の"11111111"ではないが、キャリーの不具合か?
または、過剰加算か、ビット抜けb4 b2 が発生。

============================================================
現象に対する推察を紫字で入れています。
多分ゲートがOFFになる前後の前置カウンタのCarry信号に関連すると思いますが、真の原因は分かっていません。

ということで、これも失敗として諦め、別の方式を考えることにしました。


4.プログラム(デバッグ中)

 まだデバッグ中で、スケッチの中にコメントにしたコードや試したことを記述していますので間違いや分かりにくいところが有ると思いますが、参考として公開しておきます。
このスケッチは、差し替えることが有ります。(又は削除)


V30(74HC590使用)のソースコード(参考) 

/************************************************
 * STM32F103 によるOCXO Calibrator
 * 2020/03/28より
 * V3.0の基板用
 *
 * (c)2020 jlb.jp
 ************************************************/


#include <Wire.h>
// I2C EEPROM アドレス
#define DEVICE_ADDRESS 0x50  //I2C EEPROM スレーブアドレス 24C256

#include <U8g2lib.h>
#include <SPI.h>
#include <wirish_time.h>          // STM32
// #include <EEPROM.h>            // STM32
#include "libmaple/timer.h"        // STM32 GitHub より入手、
/*
   timer.h ファイルは、
   \Documents\ArduinoIDE1.8\hardware\Arduino_STM32\STM32F1\system\libmaple\stm32f1\include\libmaple
   (\libmaple フォルダは追加作成)
   に保存した。
*/
// OLED SSD1309 Board Pin definitions                       [b4 AD9959 Signal]
#define SSD_SDA  PA7      // Serial DATA Pin(out) SPI:MOSI  [AD9959=IO0]
#define SSD_MISO PA6      // Serial DATA Pin(in)  SPI:MISO  [AD9959=IO2] not use
#define SSD_SCL  PA5      // Serial DATA Pin(out) Low       [AD9959=SCK]
#define SSD_CS   PA4      // Chip Select Active Low(out)    [AD9959=CSB]
#define SSD_RST  PA3      // Reset(out)                     [AD9959=P3]
#define SSD_DC   PA2      // Data/Command(out)              [AD9959=P2]
//
U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, SSD_CS, SSD_DC, SSD_RST);
//
#define ADC_In  PA0     // アナログ入力ピンの定義(予備)          ■V2.2:V3.0■
#define SWa_In  PA1     // コントロールSW アナログ入力ピンの定義 ■V2.2:V3.0■

#define coGSET  PA8     // Counter Gate Active High(out)Low edge
#define ciGONn  PA15    // Counter Gate ON                          ■V2.2:V3.0■
#define coRCK   PB3     // Counter reset Active High(out)
#define coRSET  PB4     // Counter reset Active High(out)
#define ciCOUNT PB8     // Counter Input 1/16 Active Low edge(increment)
// #define ciCOUNT_T PA1       // Counter Input Timer2(TIM2_CH2)      ■V2.2:追加■
#define ciPPS   PB9     // Counter Input 1sec Active High edge(increment)

#define ctQ0  PB1       // PreCounter b0  pull-up
#define ctQ1  PB0       // PreCounter b1  pull-up
#define ctQ2  PB10      // PreCounter b2
#define ctQ3  PB11      // PreCounter b3
#define ctQ4  PB15      // PreCounter b4 V3.0
#define ctQ5  PB14      // PreCounter b5 V3.0
#define ctQ6  PB13      // PreCounter b6 V3.0
#define ctQ7  PB12      // PreCounter b7 V3.0

#define LED_PIN PC13    // PC13 or 32 or D32
// ==============================================
//  自作関数のプロトタイプ宣言
void handle_timer1(void);
void handle_timer3(void);
void Opening_Text(void);
void func_print(uint8_t, const char*);
void Text_setCursor(uint8_t, uint8_t);
void drawTxline(uint8_t);

int mid(char, char, int, int);
int cmmSTRcmm(char, char, int);
int8_t extractGPGGA(char*);
int8_t extractGNRMC(char*);
int8_t extractGPZDA(char*);
uint8_t jobSelect(uint8_t);        // F0キーにより、設定等の処理選択
int8_t buttonSW(void);            // M ボタンの状態を返す
uint8_t confPRMset(uint8_t, uint8_t); // PRM set

void intFrq(void);
void dFRQtoStr(char, double);       // 周波数のフォーマット
uint8_t write(uint16_t, uint8_t);
uint8_t read(uint16_t);

void RegZero(void);               // カウンター用レジスタのゼロクリヤー
void PortReset(void);             // ポート初期セット&ゲート・前置カウンタのリセット
void PpsSet(void);                // スタート・リスタート時の PPS セット

// ==============================================

// ==============================================
// タイマー割り込み関係
int32_t tIntCount;          // Timer1:割込み回数
volatile int32_t tIntCount3;  // Timer3:割込み回数(int内で使用=volatile を付ける)
// ==============================================
// シリアル受信処理関係
uint8_t RXposi = 0;
uint8_t RCVok = 0;
byte RXchar, PCchar;
char RXbuff[256];
char RXbuff2[256];
char tmpbuff[64];
char useGPS[5];
char nowTime[22];
char nowDate[22];
char logdata[256];
char tmplog[32];

// uint16_t SSt;                 // キー操作用変数
int8_t Keycnt = 0;
uint8_t FuncEnable = 0;       // Function キー有効ビット F0:bit4, F1:bit3, F2:bit2, F3:bit1,
uint8_t jobPRM = 0;           // 編集画面の処理No
uint8_t bpsPRM = 0;           // BPSの指数
uint8_t hostPRM = 0;          // HOSTへ送出するデータ選択 0:GPS Data, 1:LOG Data, 2:None
uint8_t gatePRM = 0;          // GATE時間の指数
uint8_t dispNo = 0;           // 表示番号 0:初期画面ならびに計測中, 1:各パラメータ表示中
uint8_t Counting = 0;         // 計数中 = 1;
uint16_t gpsLoss = 0;         // GPSエラー回数
uint16_t ppsLoss = 0;         // PPS信号間隔のエラー回数
uint32_t CountTime = 0;       // 計測回数
uint32_t excessCarry = 0;        // 過剰キャリー回数

// GPSとの交信速度変更用(チェックSUM(27:2桁)は、実測値) \r\n は、Serial.println で付加する。
const char bps96set[] = "$PMTK251,9600*17";     // (1秒中のデータ占有率 65.1%)(default)
const char bps192set[] = "$PMTK251,19200*22";   // (1秒中のデータ占有率 32.6%)
const char bps384set[] = "$PMTK251,38400*27";   // (1秒中のデータ占有率 16.3%)
const char bps576set[] = "$PMTK251,57600*2C";   // (1秒中のデータ占有率 10.9%)20cmで時々崩れ?
const char bps1152set[] = "$PMTK251,115200*1F"; // (1秒中のデータ占有率 5.4%)*使用しない*
const char hotStart[] = "$PMTK101*32";          // コールドスタート ANS="$PMTK809*33"
const char warmStart[] = "$PMTK102*31";         // ワームスタート ANS="$PMTK809*33"
const char coldStart[] = "$PMTK103*30";         // コールドスタート ANS="$PMTK605*31"

// カウンター用レジスタ?
volatile uint64_t ui64Reg = 0;       // 符号無し(64bit) 周波数カウント用

static union {            // 共用体
  uint64_t    ui64sub;    // 64bit
  byte        inByte[6];  // 6Byte
  uint16_t     twoByte;
} uniD;


double dFrqReg = 0;         // 浮動小数点(64bit) 周波数計算用
double dAvgReg = 0;         // 浮動小数点(64bit) アベレージ計算用
double dMaxReg = 0;         // 浮動小数点(64bit) 最高値保持
double dMinReg = 0;         // 浮動小数点(64bit) 最小値保持
double dTmpReg = 0;         // 浮動小数点(64bit) 汎用
double dP_PReg = 0;         // 浮動小数点(64bit) p-p 振れ幅
int16_t AddCnt = 0;         // 加算回数
char fmtFrq[22];
double B4dFrqReg = 0;       // 浮動小数点(64bit) 周波数計算用
float IncDec = 0;           // 増減計算用

// (int内で使用=volatile を付ける)
volatile int16_t ppsCnt = 0;  // ゲートON時のPPSカウント用
uint8_t countDone = 0;        // カウント完了
volatile uint8_t gateON = 0;  // ゲートの状態
uint8_t Running = 0;          // カウント中
volatile uint8_t reStart = 0; // PPS信号が欠落したときの再開フラグ

char tmpsec[3] = "00";  // ■臨時■ 秒単位でのLOG送信に使用する。
char b4sec[3] = "XX";   // ■臨時■ 秒単位でのLOG送信に使用する。

// CSVのヘッダー(1行目)のデータ列(2020/03/20 17:00 現在)
// Num=連番, YYMMDD=年月日,HHMMSS=時分秒 ,GpsQ=QPS品質, Sat=使用衛星数,
// Freq=周波数, (+-)=前差, gpsLoss=GPS信号ロス回数, ppsLoss=PPS信号ロス回数
const char logheader[] = "Num,YYMMDD,HHMMSS,GpsQ,Sat,Freq,(+-),gpsLoss,ppsLoss,HEX,b15-b0,excessCarry";


// カウンター用レジスタのゼロクリヤー
void RegZero() {
  ui64Reg = 0;
  dFrqReg = 0;
  dAvgReg = 0;
  dMaxReg = 0;
  dMinReg = 0;
  dTmpReg = 0;
  dP_PReg = 0;
  AddCnt = 0;
  ppsCnt = 0;
  gateON = 0;
  gpsLoss = 0;
  ppsLoss = 0;
  tIntCount3 = 0;
  Running = 0;  // 未使用?
  B4dFrqReg = 0;       // 浮動小数点(64bit) 周波数計算用
  IncDec = 0;           // 増減計算用
  excessCarry = 0;      // 過剰キャリー(2020/03/28)

}
// 前置カウンタのリセット
void PortReset() {
  digitalWrite(coRSET, HIGH);
  delay(1);                         // 1mS
  digitalWrite(coRSET, LOW);
  delay(1);                         // 1mS
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ◆◆◆◆◆電源投入時の初期画面◆◆◆◆◆
//
// □■■■□□■■■■□□■■■■■□■□□□■
// ■□□□■□■□□□■□■□□□□□■■□□■
// ■□□□■□■□□□■□■□□□□□■■□□■
// ■□□□■□■■■■□□■■■■□□■□■□■
// ■□□□■□■□□□□□■□□□□□■□□■■
// ■□□□■□■□□□□□■□□□□□■□□■■
// □■■■□□■□□□□□■■■■■□■□□□■
void Opening_Text()
{
  u8g2.clearBuffer();          // clear

  Text_setCursor(0, 1);     // 桁・行
  u8g2.print("OCXO Calibrator (GPS)");
  Text_setCursor(1, 2);   // 桁・行
  u8g2.print("V30 & Ver.0.03.27");

  Text_setCursor(0, 3);     // 桁・行
  u8g2.print(" (c) 2020, JE3JLB    ");
  FuncEnable = B00001111;
  func_print(0, "MENU");
  func_print(1, "GATE");
  func_print(2, "STRT");
  func_print(3, "CngD");

  u8g2.sendBuffer();          // OLEDに送信(描画する)
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// 周波数割り込みのカウント
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void intFrq() {     // 周波数カウント割込み
  // D F/Fのゲートが切れるとき1割込み分を余分にカウントアップするため、
  // gateON で代用判定した。(2020/03/13)
  // 例)1,100,000Hz ==> 1,099,999Hz/1,100,015Hz/1,100,016Hz になってしまう。
  //            bit表示  1101 1111   1110 1111   1111 0000
  //            本来 ==> 1,099,000Hz/1,100,000Hz/1,100,001Hz
  //            bit表示  1101 1111   1110 0000   1110 0001digitalRead(ciGON)
   if (gateON == 1) ui64Reg++; else excessCarry++;      // 過剰キャリーカウント(デバッグ)
  // if (digitalRead(ciGONn) == LOW) ui64Reg++;   // 欠落する。
  // ui64Reg++;
  //digitalWrite(coRCK, HIGH);
  //digitalWrite(coRCK, LOW);
  //if (digitalRead(ctQ7) == LOW)  ui64Reg++; else excessCarry++;  // 過剰キャリーカウント
 
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// PPS 割り込みのカウント
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void intPps() {     // PPS 割込み

  if (tIntCount3 >= 12) {        // 前回のPPS信号から1.2秒以上経過している。

    digitalWrite(coGSET, LOW);
    gateON = 0;                 // GATEの状態OFF
    reStart = 1;                // PPS信号が欠落した後の再開フラグ SET

  } else {
    // 正常処理
    if (digitalRead(coGSET) == HIGH) {
      gateON = 1;     // GATEの状態ON

      // 最終手前のPPS(次のPPSでカウント完了)なら、GSETをOFFする。
      if (ppsCnt <= 1) digitalWrite(coGSET, LOW);

      ppsCnt--;
    } else {
      gateON = 0;     // GATEの状態OFF

      if (Counting != 0) countDone = 1;  // カウント完了
      ppsCnt = 0;     // PPS Count Zero
      // delay(1);     // delay() は機能しない。

    }
  }
  tIntCount3 = 0;
}

void PpsSet() {           // PPS パラメータセット <<共通使用>>
  switch (gatePRM) {
    case 1:
      ppsCnt = 10;
      break;
    case 2:
      ppsCnt = 100;     // 約1分40秒
      break;
    case 3:
      ppsCnt = 1000;     // 約16分40秒
      break;
    case 4:
      ppsCnt = 10000;     // 約2時間46.7分
      break;
    default:
      ppsCnt = 1;
      break;
  }
}


// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// double の数値を周波数表記に変換する
//
//      01234567890123456
//      98765432.1234       ----sprintf 後の状態
//      98 765 432.1234Hz   ----書式変換後のイメージ
//                              10MHz台まで表示する。桁の先頭がzeroの場合は空白。
//                              3桁カンマは付けずに空白。少数以下4桁まで。
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void dFRQtoStr(char* fmttmp, double freqIN) {
  double dtmp;
  char strtmp[22];
  int8_t i, j, ln;

  dtmp = freqIN;

  sprintf(strtmp, "%13.4lf", dtmp);
  ln = strlen(strtmp) + 2;
  j = 0;
  for (i = 0; i < ln; i++) {
    switch (i) {
      case 2:
      case 5:
        fmttmp[j] = ' ';
        j++;
        fmttmp[j] = strtmp[i];
        j++;
        break;
      default:
        fmttmp[j] = strtmp[i];
        j++;
        break;
    }
  }
  //  strcat(fmttmp, "Hz");     // Hzは、ここでは付けないことにした。(2020/03/18)
  //  fmttmp[17] = 0x0;
  fmttmp[15] = 0x0;
}

////////////////////////////////////////////////////////////
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ◆◆◆◆◆タイマー割込み◆◆◆◆◆
// 0.1秒ごとに数を増分する  
// OLED の表示サイクル用に使用する        0.01Sec
// PPS信号が欠落したことの検出に使用する。 0.1Sec
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void handle_timer1() {
  tIntCount++;
  if (tIntCount > 500) tIntCount = 0;    // 500 x 0.01S (5 Sec)以上はクリアー
}
void handle_timer3() {
  if (gateON == 1)  tIntCount3++;        // GATEがONの間、0.1Sec毎にカウント
}
////////////////////////////////////////////////////////////
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//
//  ■■■  ■■■■■ ■■■■■ ■   ■ ■■■■  
// ■   ■ ■       ■   ■   ■ ■   ■ 
// ■     ■       ■   ■   ■ ■   ■ 
//  ■■■  ■■■■    ■   ■   ■ ■■■■  
//     ■ ■       ■   ■   ■ ■     
// ■   ■ ■       ■   ■   ■ ■     
//  ■■■  ■■■■■   ■    ■■■  ■     
//
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

void setup(void) {
  disableDebugPorts();      // JTAG_Debug を使わないことで、PA15,PB3,PB4を使えるようにする。
  pinMode(ADC_In, INPUT_ANALOG);
  pinMode(SWa_In, INPUT_ANALOG);


  // pinMode(ciCOUNT_T, INPUT_PULLUP);           //  ■V2.2:追加■


  pinMode(coGSET, OUTPUT);
  pinMode(ciGONn, INPUT);
  pinMode(coRCK, OUTPUT);
  pinMode(coRSET, OUTPUT);
  pinMode(ciCOUNT, INPUT);
  // #define ciCOUNT_T PA1       // Counter Input Timer2(TIM2_CH2)      ■V2.2:追加■
  pinMode(ciPPS, INPUT);

  pinMode(ctQ0, INPUT_PULLUP);
  pinMode(ctQ1, INPUT_PULLUP);
  pinMode(ctQ2, INPUT);
  pinMode(ctQ3, INPUT);
  pinMode(ctQ4, INPUT);
  pinMode(ctQ5, INPUT);
  pinMode(ctQ6, INPUT);
  pinMode(ctQ7, INPUT);

  pinMode(LED_PIN, OUTPUT);

  digitalWrite(LED_PIN, HIGH);

  digitalWrite(coGSET, LOW);

  PortReset();     // 前置カウンタの初期化

  attachInterrupt(ciCOUNT, intFrq, RISING);    // カウンター割り込み関数の指定。FALLING
  attachInterrupt(ciPPS, intPps, RISING);    // PPS 割り込み関数の指定。RISING

  RegZero();      // レジスターの初期化

  SPI.begin();

  u8g2.begin();
  //  u8g2.setFont(u8g2_font_6x12_tr);  // 7bit 等幅、21文字、5行(行12dot)、●●これが良さそう●●
  u8g2.setFont(u8g2_font_6x12_t_symbols); // ▶記号を使用するために、こちらのフォントを使う。
  u8g2.setFontMode(1);  /* activate transparent font mode */

  Wire.begin();           // I2C EEPROM用、他

  Opening_Text();         // 初期画面を表示

  // ■■■■■■■■■■■■■■■
  // EEPROM読み出し・初期化
  // ■■■■■■■■■■■■■■■
  uint8_t tmp;
  tmp = read(0); // ROMからの読み出し Address = 0
  if (tmp >= 0 && tmp <= 2) {   // パラメータが範囲内   0 〜 2
    bpsPRM = tmp;               //  = PRMへ読み出し値セット
  } else {                      // パラメータが範囲外 = ROM新品または、故障??
    write(0, 0);               // 0 = (default) を書込み
  }
  tmp = read(1); // ROMからの読み出し Address = 1
  if (tmp >= 0 && tmp <= 2) {
    hostPRM = tmp;
  } else {
    write(1, 0);
  }
  tmp = read(2); // ROMからの読み出し Address = 2
  if (tmp >= 0 && tmp <= 2) {
    gatePRM = tmp;
  } else {
    write(2, 0);
  }
  // ■■■■■■■■■■■■■■■

  // ★★★タイマー割込み 1 の初期設定 START★★★▼▼▼▼▼▼
  Timer1.pause();                   // タイマー停止
  Timer1.setPrescaleFactor(7200);   // 分周値set 72MHz ÷ 7200 = 10KHz
  Timer1.setOverflow(100);          // 10KHz x 100 = 0.01秒でオーバーフォロー割込み
  // ------
  Timer1.attachInterrupt(           // 割り込みハンドラの登録
    TIMER_UPDATE_INTERRUPT,         // 条件は、カウンターオーバーフロー更新時
    handle_timer1);                  // 呼び出す関数
  // ------
  Timer1.setCount(0);               // カウンタを0に設定
  Timer1.refresh();                 // タイマ更新
  Timer1.resume();                  // タイマースタート

  // --------------------------------------
  // ★★★タイマー割込み 3 の初期設定 START★★★▼▼▼▼▼▼
  Timer3.pause();                   // タイマー停止
  Timer3.setPrescaleFactor(7200);   // 分周値set 72MHz ÷ 7200 = 10KHz (uint32)
  Timer3.setOverflow(1000);          // 10KHz x 1000 = 0.1秒でオーバーフォロー割込み (uint16)
  // ------
  Timer3.attachInterrupt(           // 割り込みハンドラの登録
    TIMER_UPDATE_INTERRUPT,         // 条件は、カウンターオーバーフロー更新時
    handle_timer3);                  // 呼び出す関数
  // ------
  Timer3.setCount(0);               // カウンタを0に設定
  Timer3.refresh();                 // タイマ更新
  Timer3.resume();                  // タイマー2スタート
  // ★★★タイマー割込みの初期設定 END  ★★★▲▲▲▲▲▲
  // --------------------------------------


  Serial.begin(9600);               // USBシリアル(PC側)
  //while (!Serial);                 // Arduino UNO 書込み後のUSBシリアルの解放を待つ。
  // while (!Serial.isConnected()) delay(10);  // STM32 USB-シリアル用 isConnected()を使う。

  // ここでROMからリードしたbpsをセット

  switch (bpsPRM) {
    case 1:
      Serial1.begin(19200);              // Serial1(PA9 PA10)
      break;
    case 2:
      Serial1.begin(38400);              // Serial1(PA9 PA10)
      break;
    default:
      Serial1.begin(9600);               // Serial1(PA9 PA10)
      break;
  }

  delay(3000);

}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//
//  ■      ■■■   ■■■  ■■■■ 
//  ■     ■   ■ ■   ■ ■   ■
//  ■     ■   ■ ■   ■ ■   ■
//  ■     ■   ■ ■   ■ ■■■■ 
//  ■     ■   ■ ■   ■ ■    
//  ■     ■   ■ ■   ■ ■    
//  ■■■■■  ■■■   ■■■  ■    
//
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

void loop(void) {

  switch (buttonSW()) {     // キーが押されているか確認。
    // パラメータ編集メニューへ
    case 20:          // 長押しF0
      if ((FuncEnable & 0x08) > 0) {    // 許可フラグ
        jobPRM = jobSelect(jobPRM);        // ★★★設定処理等の選択画面に行く
        u8g2.clearBuffer();   // ★★★設定処理等の選択画面からの帰りにクリアー
        Text_setCursor(1, 1);   // 桁・行
        u8g2.print(" Config MENU END ");
        u8g2.sendBuffer();            // OLEDに送信(描画する)
        delay(500);
      }
      break;
    // GATE time切換
    case 11:          // 短押しF1、長押しは無効として処理。
      if ((FuncEnable & 0x04) > 0) {    // 許可フラグ
        if (dispNo == 0) {
          gatePRM++;
          if (gatePRM == 4) gatePRM = 0;    // GATE time を変更
        } else {
          // CLAR
          gpsLoss = 0;
          ppsLoss = 0;
        }

      }
      break;
    // 計測スタート・ストップ
    case 12:          // 短押しF2、長押しは無効として処理。
      if ((FuncEnable & 0x02) > 0) {    // 許可フラグ
        if (Counting == 0) {
          Counting = 1;     // カウントスタート
          RegZero();

          // ゲート時間により、
          PpsSet();                       // gatePRMからppsCntへ時間をセット
          //     gpsLoss = 0;                    // GPS見失った回数をゼロ
          //     ppsLoss = 0;
          PortReset();
          digitalWrite(coGSET, HIGH);
        } else {
          digitalWrite(coGSET, LOW);
          Counting = 0;     // カウントストップ
          CountTime = 0;
          //  gateON = 0;
          //  ppsCnt = 0;     // PPS Count Zero
        }
      }
      break;
    // 表示画面の切替
    case 13:          // 短押しF3、長押しは無効として処理。
      if ((FuncEnable & 0x01) > 0) {    // 許可フラグ
        if (dispNo == 0) {
          dispNo = 1;     // 2画面へ変更
        } else {
          dispNo = 0;     // 1画面へ変更
        }
      }
      break;
  } // switch() end

  // ■■■もし、PPS信号が欠落したときの再開フラグが立ったら。■■■
  if (reStart != 0) {

    ppsLoss++;      // PPSのロス回数を+1

    PortReset();    // 前置カウンタのリセット

    PpsSet();                       // gatePRMからppsCntへ時間をセット
    ui64Reg = 0;                    // クリヤー
    reStart = 0;
    digitalWrite(coGSET, HIGH);

  }

  // ■■■PPSが長時間停止したときのカウント処理停止■■■
  if (tIntCount3 > 200000) { // タイマー割り込み 100mS * 200000 = 20000S(約5.5時間) で
    // カウント処理を停止する。(PPS信号が5.5時間到達しなかった)

    digitalWrite(coGSET, LOW);
    Counting = 0;     // カウントストップ
    gateON = 0;
    tIntCount3 = 0;
  }

  // --------------------------------------
  if (Serial1.available()) { // ■Serial1(PA9 PA10)からUSBシリアル(PC側)■

    RXchar = Serial1.read();
    switch (RXposi) {
      case 0:
        if (RXchar == '$')
        {
          RXbuff[RXposi] = RXchar;
          RXposi++;
        } else {
          RXposi = 0;
          RCVok = 0;
        }
        break;
      case 255:                 // バッファ数 255 一杯なら吐き捨てる
        RXposi = 0;
        RCVok = 0;
        break;
      default:
        if (RXchar == 0x0a)      // エンドマークなら '*'又は <lf>0x0a
        {
          RXbuff[RXposi] = 0x00;      // 文字配列に0x00を書き込む
          RCVok = 1;
          RXposi = 0;
        } else {
          RXbuff[RXposi] = RXchar;    // それ以外なら RXbuff[] に取り込む
          RXposi++;
        }
    }
    if (hostPRM == 0) {         // 0:GPSデータを受け付ける状態
      Serial.write(RXchar);
    }
  }
  // --------------------------------------
  if (RCVok == 1)
  {


    Text_setCursor(1, 3);     // 桁・行
    u8g2.print(RXbuff);


    if (strncmp(RXbuff, "$GPGGA", 6) == 0) {
      strcpy(RXbuff2, RXbuff);
      extractGPGGA(RXbuff2);        // GPS数を抽出
    }
    if (strncmp(RXbuff, "$GPZDA", 6) == 0) {
      strcpy(RXbuff2, RXbuff);
      extractGPZDA(RXbuff2);        // UTCと年月日を抽出してJSTに変換する
    }
    RCVok = 0;
  }
  // --------------------------------------
  if (Serial.available()) {   // ■USBシリアル(PC側)からSerial1(PA9 PA10)■

    PCchar = Serial.read();
    Serial1.write(PCchar);
  }
  // --------------------------------------
  // ■■■■■■周波数カウントが完了した時の処理■■■■■■
  if (countDone == 1)  {
    CountTime++;          // 計測回数
    digitalWrite(coRCK, HIGH);
    delay(1);
    digitalWrite(coRCK, LOW);     // FrontCounter の出力をセット
    delay(1);             // 1mS   ■■■■2020/03/23 1mS 処置==> 関係なし■■■■
    ui64Reg = ui64Reg << 8;       // 8bit左へシフト

    ui64Reg &= 0xFFFFFFFFFFFFFF00;    // 念のため最下位の8bitをクリヤー
    if (digitalRead(ctQ0) == HIGH) ui64Reg += 0x1;    // 74HC590の0bit目の状態を加算
    if (digitalRead(ctQ1) == HIGH) ui64Reg += 0x2;    // 74HC590の1bit目の状態を加算
    if (digitalRead(ctQ2) == HIGH) ui64Reg += 0x4;    // 74HC590の2bit目の状態を加算
    if (digitalRead(ctQ3) == HIGH) ui64Reg += 0x8;    // 74HC590の3bit目の状態を加算
    if (digitalRead(ctQ4) == HIGH) ui64Reg += 0x10;   // 74HC590の4bit目の状態を加算
    if (digitalRead(ctQ5) == HIGH) ui64Reg += 0x20;   // 74HC590の5bit目の状態を加算
    if (digitalRead(ctQ6) == HIGH) ui64Reg += 0x40;   // 74HC590の6bit目の状態を加算
    if (digitalRead(ctQ7) == HIGH) ui64Reg += 0x80;   // 74HC590の7bit目の状態を加算


    dFrqReg = (double)ui64Reg;      // double に変換
    uniD.ui64sub = ui64Reg;         // 最下8ビットの抽出用
    ui64Reg = 0;                    // クリヤー

    // ゲート時間により、
    switch (gatePRM) {
      case 1:
        dFrqReg = dFrqReg / 10;
        break;
      case 2:
        dFrqReg = dFrqReg / 100;
        break;
      case 3:
        dFrqReg = dFrqReg / 1000;
        break;
      case 4:
        dFrqReg = dFrqReg / 10000;
        break;
      default:
        break;
    }

    // アベレージに加算
    dAvgReg += dFrqReg;
    AddCnt++;

    // 最高値の更新
    if (dMaxReg < dFrqReg) dMaxReg = dFrqReg;
    // 最低値の更新
    if (dMinReg != 0) {
      if (dMinReg > dFrqReg) dMinReg = dFrqReg;
    } else {
      dMinReg = dFrqReg;
    }
    // 振れ幅 p-p の更新
    dP_PReg = dMaxReg - dMinReg;

    // ■■■■■■■■■■■■■■■■■■■■■■■■
    // 一つ前の測定結果との誤差を求める。
    // ■■■■■■■■■■■■■■■■■■■■■■■■
    if (B4dFrqReg == 0) {
      B4dFrqReg = dFrqReg;
      IncDec = 0;
    } else {
      IncDec = dFrqReg - B4dFrqReg;
      B4dFrqReg = dFrqReg;
    }




    countDone = 0;       // 周波数カウントが完了した時の処理■■完了■■

    if (Counting == 1) {    // カウント継続中なら

      // ゲート時間により、
      PpsSet();                       // gatePRMからppsCntへ時間をセット

      PortReset();    // 前置カウンタのリセット

      digitalWrite(coGSET, HIGH);   // カウントの継続セット
    }

    // ■■■■■■■■■■■■■■■■■■■■■■■■
    // カウント完了ごとに、(LOG指定なら)LOG出力
    // ■■■■■■■■■■■■■■■■■■■■■■■■
    //  if (strncmp(b4sec, tmpsec, 2) != 0) {   // 以前と秒の値が違う時にLOG送信。
    //    strcpy(b4sec, tmpsec);              // 現在値を以前の値にコピー

    if (hostPRM == 1) {         // !0:LOGデータを受け付ける状態■テスト■

      if (CountTime == 1 ) {
        Serial.println(logheader);       // <cr><lf>を付けてヘッダー出力
      }

      sprintf(tmplog, "%d", CountTime);
      strcpy(logdata, tmplog);
      strcat(logdata, ",");
      strcat(logdata, nowDate);
      strcat(logdata, ",");
      strcat(logdata, nowTime);
      strcat(logdata, ",");
      strcat(logdata, useGPS);
      //    strcat(logdata, ",Freq=>,");
      strcat(logdata, ",");
      sprintf(tmplog, "%13.4lf", dFrqReg); // 周波数データを表示形式に変換
      strcat(logdata, tmplog);
      //    strcat(logdata, ",+-_=>,");
      strcat(logdata, ",");
      sprintf(tmplog, "%+8.3f", IncDec);    // + - 記号付き、少数以下3桁、計8桁
      strcat(logdata, tmplog);
      //    strcat(logdata, ",gpsLoss=>,");
      strcat(logdata, ",");
      sprintf(tmplog, "%d", gpsLoss);
      strcat(logdata, tmplog);
      //    strcat(logdata, ",ppsLoss=>,");
      strcat(logdata, ",");
      sprintf(tmplog, "%d", ppsLoss);
      strcat(logdata, tmplog);
      strcat(logdata, ",");

      sprintf(tmplog, "%4X", uniD.twoByte);   // カウントLSB側16ビットをHEXで出力
      strcat(logdata, tmplog);
      strcat(logdata, ",");

      for (int i = 16; i > 0; i--) {          // カウントLSB側16ビットをBinalyで出力
        if (uniD.twoByte & 0x8000) {
          strcat(logdata, "1");
        } else {
          strcat(logdata, "0");
        }
        if (i == 13 || i == 9 || i == 5)  strcat(logdata, " ");
        uniD.twoByte <<= 1;
      }
      strcat(logdata, ",");
      sprintf(tmplog, "%d", excessCarry);     // 過剰キャリーを追加(デバッグ)
      strcat(logdata, tmplog);

      Serial.println(logdata);       // <cr><lf>を付けて出力
    }

  }   // ■■■■■■周波数カウントが完了処理 終了 ■■■■■■

  // --------------------------------------

  if (tIntCount > 20) { // タイマー割り込み 10mS * 20 = 200mS 毎に表示を更新する。■■■■
    // *100mS 毎でも遅れは無い模様。 memo->( digitalRead(ciGON) == LOW && )
    tIntCount = 0;

    u8g2.clearBuffer();          // clear the internal memory

    // ■■■■■■■■■■■■■■■■■■■■■■■■
    if (dispNo == 0) {                // 基本画面
      Text_setCursor(0, 0);   // 桁・行
      u8g2.print("Frq:");
      dFRQtoStr(fmtFrq, dFrqReg); // 周波数データを表示形式に変換
      Text_setCursor(4, 0);     // 桁・行
      u8g2.print(fmtFrq);
      Text_setCursor(19, 0);   // 桁・行
      u8g2.print("Hz");
      Text_setCursor(1, 1);     // 桁・行
      u8g2.print(useGPS);
      u8g2.print("_");

      Text_setCursor(6, 1);     // 桁・行
      u8g2.print("e");
      Text_setCursor(7, 1);     // 桁・行
      u8g2.print(gpsLoss);

      Text_setCursor(12, 1);    // 桁・行
      u8g2.print(nowTime);
      Text_setCursor(1, 2);     // 桁・行
      u8g2.print("SEND=");
      Text_setCursor(6, 2);     // 桁・行
      switch (hostPRM) {
        case 0:
          u8g2.print("GPS");
          break;
        case 1:
          u8g2.print("LOG");
          break;
        case 2:
          u8g2.print("NON");
          break;
      }
      Text_setCursor(15, 2);     // 桁・行

      u8g2.print(ppsCnt);
      /*
        if (digitalRead(ciGON) == HIGH) {
             u8g2.print("G");
           } else {
             u8g2.print(" ");
           }
      */

      Text_setCursor(1, 3);     // 桁・行
      u8g2.print("GATE=");
      Text_setCursor(6, 3);     // 桁・行
      switch (gatePRM) {
        case 0:
          u8g2.print("1s");
          break;
        case 1:
          u8g2.print("10s");
          break;
        case 2:
          u8g2.print("100s");
          break;
        case 3:
          u8g2.print("1000s");
          break;
      }

      Text_setCursor(12, 3);     // 桁・行
      u8g2.print(CountTime);    // 計測回数

      Text_setCursor(18, 3);     // 桁・行
      //     u8g2.print(tIntCount3);   // PPS信号の間隔(0.1Sec単位)デバッグ用
      u8g2.print(ppsLoss);      // PS信号が欠落したとき   デバッグ用
      //u8g2.print(excessCarry);      // 過剰キャリーカウント   デバッグ用

      if (Counting == 0) {        // 停止中
        FuncEnable = B00001111;
        func_print(0, "MENU");
        func_print(1, "GATE");
        func_print(2, "STRT");
        func_print(3, "CngD");
      } else {                    // カウント中
        FuncEnable = B00000011;
        func_print(2, "STOP");
        func_print(3, "CngD");
      }

      // 仮■■ 変更したパラメータを表示する。デバッグ用
      char aa = 0x30 | jobPRM;
      char bb = 0x30 | bpsPRM;
      char cc = 0x30 | hostPRM;
      char dd = 0x30 | gatePRM;
      Text_setCursor(10, 2);    // 桁・行
      u8g2.print(aa);
      u8g2.print(bb);
      u8g2.print(cc);
      u8g2.print(dd);

    } else {                      // 拡張画面■■■■■■■■
      //
      Text_setCursor(0, 0);     // 桁・行
      u8g2.print("Ave:");
      if (AddCnt > 0) {
        dTmpReg = dAvgReg / (double)AddCnt;
        dFRQtoStr(fmtFrq, dTmpReg); // 周波数データを表示形式に変換
        Text_setCursor(4, 0);     // 桁・行
        u8g2.print(fmtFrq);
      }
      Text_setCursor(19, 0);   // 桁・行
      u8g2.print("Hz");
      //
      Text_setCursor(0, 1);     // 桁・行
      u8g2.print("Max:");
      dFRQtoStr(fmtFrq, dMaxReg); // 周波数データを表示形式に変換
      Text_setCursor(4, 1);     // 桁・行
      u8g2.print(fmtFrq);
      Text_setCursor(19, 1);   // 桁・行
      u8g2.print("Hz");
      //
      Text_setCursor(0, 2);     // 桁・行
      u8g2.print("Min:");
      dFRQtoStr(fmtFrq, dMinReg); // 周波数データを表示形式に変換
      Text_setCursor(4, 2);     // 桁・行
      u8g2.print(fmtFrq);
      Text_setCursor(19, 2);   // 桁・行
      u8g2.print("Hz");
      //
      Text_setCursor(0, 3);     // 桁・行
      u8g2.print("P-P:");
      dFRQtoStr(fmtFrq, dP_PReg); // 周波数データを表示形式に変換
      Text_setCursor(4, 3);     // 桁・行
      u8g2.print(fmtFrq);
      Text_setCursor(19, 3);   // 桁・行
      u8g2.print("Hz");

      if (Counting == 0) {        // 停止中
        FuncEnable = B00000101;
        func_print(1, "CLAR");
        func_print(3, "CngD");
      } else {                    // カウント中
        FuncEnable = B00000001;
        func_print(3, "CngD");
      }
    }
    // ■■■■■■■■■■■■■■■■■■■■■■■■
    u8g2.sendBuffer();          // OLEDに送信(描画する)
    /////////////////////////////////////
  }



} // Loop End
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//   「ファンクション」表示関数
//   表示器の最下行はファンクション表示エリアとして、4つのボタンに割り当てる。
//   一つのボタンは4文字で、左1文字分を空けてBox描画で反転表示する。
//   fnmb : ボタン番号
//   str  : 表示文字(4文字以内)
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void func_print(uint8_t fnmb, const char* str)
{
  int8_t boxORframe = 0;    // 0 = Box描画,  1 = フレーム描画 切替予定
  int8_t len;
  len = strlen(str);
  if ((len == 0) || (len > 4)) return;

  switch (fnmb) {
    case 0:
      u8g2.setDrawColor(1);          // 反転用の処理のため
      //    u8g2.drawBox(5, 50, 27, 13);   // Box描画
      u8g2.drawFrame(5, 50, 27, 13);   // フレーム描画
      u8g2.setDrawColor(2);          //  color 2 set
      Text_setCursor(1, 4);   // 桁・行
      u8g2.print(str);
      //      u8g2.drawLine(6, 63, 31, 63);   // (x1, y1, x2, y2)
      break;
    case 1:
      u8g2.setDrawColor(1);          // 反転用の処理のため
      //   u8g2.drawBox(35, 50, 27, 13);  // Box描画
      u8g2.drawFrame(35, 50, 27, 13);
      u8g2.setDrawColor(2);          //  color 2 set
      Text_setCursor(6, 4);   // 桁・行
      u8g2.print(str);
      //      u8g2.drawLine(36, 63, 61, 63);   // (x1, y1, x2, y2)
      break;
    case 2:
      u8g2.setDrawColor(1);          // 反転用の処理のため
      //   u8g2.drawBox(65, 50, 27, 13);  // Box描画
      u8g2.drawFrame(65, 50, 27, 13);
      u8g2.setDrawColor(2);          //  color 2 set
      Text_setCursor(11, 4);   // 桁・行
      u8g2.print(str);
      //      u8g2.drawLine(66, 63, 91, 63);   // (x1, y1, x2, y2)
      break;
    case 3:
      u8g2.setDrawColor(1);           // 反転用の処理のため
      //    u8g2.drawBox(95, 50, 27, 13);   // Box描画
      u8g2.drawFrame(95, 50, 27, 13);
      u8g2.setDrawColor(2);           //  color 2 set
      Text_setCursor(16, 4);   // 桁・行
      u8g2.print(str);
      //      u8g2.drawLine(96, 63, 121, 63);   // (x1, y1, x2, y2)
      break;
  }
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//  文字出力の位置を、文字単位で指定する。
//  左上が0カラムの0行(合計:21文字x5行)
//  対象とするフォント:   u8g2.setFont(u8g2_font_6x12_mf);
//  *1文字目の左1ドットは、空ける。
//   cc   : 桁 0から4
//   ll   : 行 0から20
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void Text_setCursor(uint8_t cc, uint8_t ll) // cc:0 to 20, ll:0 to 4  Use
{
  uint8_t cp, lp;
  cp = cc * 6 + 1;
  lp = (ll + 1) * 12 - 1;

  u8g2.setCursor(cp, lp);
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//  上記同様にテキストの行を指定して反転させる。
//  行は、0から4の5行として指定する。
//  対象とするフォント:   u8g2.setFont(u8g2_font_6x12_mf);
//  *1文字目の左1ドットは、空ける。
//   cc   : 桁 0から4
//   ll   : 行 0から20
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void drawTxline(uint8_t ll)
{
  uint8_t l1;
  l1 = ll * 12 + 1;
  u8g2.drawBox(0, l1, 127, 12);
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// バイト単位で文字列を抽出するMid関数
// 128文字以内を制限しないため、int8_t ではなく int 型を使用する。
// buf[]:抽出先
//  str[]:抽出される文字列
//  pos  :抽出開始位置
//  len  :文字数
// return は、抽出した文字数を返す。
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
int mid(char *buf, char *str, int pos, int len)
{
  int i;
  int j = 0;
  for (i = 0; str[i] != '\0'; i++) {  // 対象の文字列の文字数分を末尾まで順に
    if (i >= pos && i < pos + len) {  // 指定位置と長さ分を
      buf[j] = str[i];                // buf[j]に格納
      j++;
    }
  }
  buf[j] = '\0'; //bufの末尾にNULL文字を格納
  return strlen(buf);
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// int cmmSTRcmm(char buf[], char str[], int cnt)
// 128文字以内を制限しないため、int8_t ではなく int 型を使用する。
//
// buf[] :抽出先文字列
// str[] :検索対象文字列
// cnt   :何個目の ',' 以後の文字列以降を抽出するのか指定
//        注)指定個数+1以上の ',' が発見できない「不成立」
// return
// 0〜n:抽出した文字数
// -1  :不成立
//
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
int cmmSTRcmm(char *buf, char *str, int cnt)
{
  int c, i, h = 0, p1 = 0, p2 = 0;
  int j = 0;
  c = strlen(str);
  if (cnt >= c) return -1;   // 文字列が探したい位置より短い (-1)
  for (i = 0; c > i; i++)
  {
    if (str[i] == ',') {
      h++;
      if (h == cnt) p1 = i;       // p1に',' cnt個目の位置セット
      if (h == cnt + 1) p2 = i;   // p2に',' cnt+1個目の位置セット
    }
  }
  if (h < cnt + 1)  return -1;    // 指定個数+1以上の ',' が発見できない (-1)
  if (p1 + 1 == p2) {             // ','間に文字列ゼロ
    buf[0] = 0x00;
    return 0;
  }
  for (i = 1; (p1 + i) < p2 ; i++)
  {
    buf[j] = str[p1 + i];
    j++;
  }
  buf[j] = 0x00;
  return p2 - p1 - 1;           // 変換した文字数を返す
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// GPSデータから時分秒と使用中のGPS数を抽出して
// useGPS,nowTime にセットする。
// 対象データ:$GPGGA
// $GPGGA,001251.000,3445.9102,N,13527.9952,E,1,13,0.72,36.5,M,34.2,M,,*50<CR><LF>
// 時分秒.000, 北緯, 東経,「GPSのクオリティ0=No,1=OK,2=DGPS」, 使用衛星数, ]ほか
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

int8_t extractGPGGA(char *str) {
  int8_t ci = 0;
  char fn[2] = "0";
  char tmp[16];
  char tmps[16];

  /*
    // 時分秒を抽出
    if (cmmSTRcmm(tmp, str, 1) >= 10) {  //  HHMMDD.000 十文字以上なら
      mid(tmps, tmp, 0, 2);    // 2桁時
      int8_t jhh = atoi(tmps);        // UTC時間を数字に
      jhh = jhh + 9;                  // JST +9H
      if (jhh >= 24) jhh = jhh - 24;  // 24H以上なら24Hを引く
      sprintf(tmps, "%02d", jhh);     // 2文字に変換
      strcpy(nowTime, tmps);          // JST出力
      strcat(nowTime, ":");
      mid(tmps, tmp, 2, 2);    // 2桁分
      strcat(nowTime, tmps);
      strcat(nowTime, ":");
      mid(tmps, tmp, 4, 2);    // 2桁秒
      strcat(nowTime, tmps);
      strcpy(tmpsec, tmps);     // 秒を記憶する■■
    } else {
      return 0;
    }
  */
  // 使用GPS数を抽出
  if (cmmSTRcmm(tmp, str, 6) > 0) {
    switch (tmp[0]) {
      case '2':         // 0:受信不能 1:単独測位 2:DGPS
        fn[0]++;
      case '1':
        fn[0]++;
        strcpy(useGPS, fn);
        ci = cmmSTRcmm(tmp, str, 7);

        if (ci == 0) {
          strcat(useGPS, ",00");        // ゼロ文字 "00"表記
        }
        if (ci == 1) {
          strcat(useGPS, ",0");                    // 1文字 先頭に"0”を付加
          strncat(useGPS, tmp, 1);
        }
        if (ci >= 2) {
          strcat(useGPS, ",");
          strncat(useGPS, tmp, 2);    // 先頭二桁のみ抽出
        }
        break;
      default:
        gpsLoss++;
        strcpy(useGPS, "0,00");        // ゼロ文字 "00"表記
        break;
    }
  }
  return ci;                                     // 使用GPSの桁数を返す。0-1-2
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// GPSデータから時分秒と年月日を抽出する。
// useGPS,nowTime nowDateにセットする。    使わない予定 ==> GPZDA
// 対象データ:$GNRMC
// $GNRMC,001252.000,A,3445.9100,N,13527.9957,E,0.11,275.89,040120,,,A*7C<CR><LF>
// 時分秒.000,ステータスA有効:V無効,緯度,経度,速度,方向,日月年,地磁気,,モード*チェックSUM
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

int8_t extractGNRMC(char *str) {
  int8_t jhh, jdd, jmm, cdd, cmm, leapY;
  int16_t jyy;
  char fn[2] = "0";
  char tmp[16];
  char tmps[16];

  cdd = 0;

  // 時分秒を抽出
  if (cmmSTRcmm(tmp, str, 1) >= 10) {  //  HHMMDD.000 十文字以上なら
    mid(tmps, tmp, 0, 2);    // 2桁時
    jhh = atoi(tmps);        // UTC時間を数字に
    jhh = jhh + 9;                  // UTC +9H = JST
    if (jhh >= 24) {
      jhh = jhh - 24;  // 24H以上なら24Hを引く
      cdd = 1;
    }
    sprintf(tmps, "%02d", jhh);     // 2文字に変換
    strcpy(nowTime, tmps);          // JST出力
    strcat(nowTime, ":");
    mid(tmps, tmp, 2, 2);    // 2桁分
    strcat(nowTime, tmps);
    strcat(nowTime, ":");
    mid(tmps, tmp, 4, 2);    // 2桁秒
    strcat(nowTime, tmps);
    strcpy(tmpsec, tmps);     // 秒を記憶する■■
  } else {
    return 0;
  }

  if (cmmSTRcmm(tmp, str, 9) == 6) {  //  DDMMYY 6文字なら
    mid(tmps, tmp, 4, 2);   // 2桁・年
    jyy = atoi(tmps);       // UTC年を数字に
    jyy = jyy + 2000;
    //////////// 閏年の判定スタート
    if (jyy % 4 == 0) {
      if (jyy % 100 == 0) {
        if (jyy % 400 == 0) {
          leapY = 1;
        } else {
          leapY = 0;
        }
      } else {
        leapY = 1;
      }
    } else {
      leapY = 0;
    }
    //////////// 閏年の判定エンド
    mid(tmps, tmp, 0, 2);   // 2桁・日
    jdd = atoi(tmps);       // UTC日を数字に
    jdd = jdd + cdd;        // 24Hのオーバーフロー加算
    mid(tmps, tmp, 2, 2);   // 2桁・月
    jmm = atoi(tmps);       // UTC日を数字に
    //////////// 月の最終日の判定
    switch (jmm) {
      case 4:
      case 6:
      case 9:
      case 11:
        if (jdd > 30) {
          jdd = 1;
          cmm = 1;
        } else {
          cmm = 0;
        }
        break;
      case 2:
        if (leapY == 0) {
          if (jdd > 28) {
            jdd = 1;
            cmm = 1;
          } else {
            cmm = 0;
          }
        } else {
          if (jdd > 29) {
            jdd = 1;
            cmm = 1;
          } else {
            cmm = 0;
          }
        }
        break;
      default:            // 1 3 5 7 8 10 12
        if (jdd > 31) {
          jdd = 1;
          cmm = 1;
        } else {
          cmm = 0;
        }
        break;
    }
    //////////// 月の最終日の判定

    jmm = jmm + cmm;
    if (jmm > 12) {
      jmm = 1;
      jyy++;              // 年を進める
    }
    sprintf(tmps, "%04d", jyy);     // 4文字
    strcpy(nowDate, tmps);          // JST出力
    strcat(nowDate, "/");
    sprintf(tmps, "%02d", jmm);     // 2文字
    strcat(nowDate, tmps);          // JST出力
    strcat(nowDate, "/");
    sprintf(tmps, "%02d", jdd);     // 2文字
    strcat(nowDate, tmps);          // JST出力
    return 1;                      // 正常(時分秒と年月日の両方)抽出時は、1を返す。
  } else {
    return 0;
  }
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// GPSデータから時分秒と年月日を抽出する。
// useGPS,nowTime nowDateにセットする。
// 対象データ:$GPZDA
// $GPZDA,001251.000,04,01,2020,,*54<CR><LF>
// 協定世界時(UTC)時分秒.000, DD,MM,YYYY,,*チェックSUM
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

int8_t extractGPZDA(char *str) {
  int8_t jhh, jdd, jmm, cdd, leapY;
  int16_t jyy;
  char fn[2] = "0";
  char tmp[16];
  char tmps[16];

  cdd = 0;

  // 時分秒を抽出
  if (cmmSTRcmm(tmp, str, 1) >= 10) {  //  HHMMDD.000 十文字以上なら
    mid(tmps, tmp, 0, 2);    // 2桁時
    jhh = atoi(tmps);        // UTC時間を数字に
    jhh = jhh + 9;                  // UTC +9H = JST
    if (jhh >= 24) {
      jhh = jhh - 24;  // 24H以上なら24Hを引く
      cdd = 1;
    }
    sprintf(tmps, "%02d", jhh);     // 2文字に変換
    strcpy(nowTime, tmps);          // JST出力
    strcat(nowTime, ":");
    mid(tmps, tmp, 2, 2);    // 2桁分
    strcat(nowTime, tmps);
    strcat(nowTime, ":");
    mid(tmps, tmp, 4, 2);    // 2桁秒
    strcat(nowTime, tmps);
    strcpy(tmpsec, tmps);     // 秒を記憶する■■
  } else {
    return 0;
  }

  if (cmmSTRcmm(tmp, str, 4) == 4) {  //  DDMMYY 6文字なら
    jyy = atoi(tmp);       // UTC年を数字に

    //////////// 閏年の判定スタート
    if (jyy % 4 == 0) {
      if (jyy % 100 == 0) {
        if (jyy % 400 == 0) {
          leapY = 1;
        } else {
          leapY = 0;
        }
      } else {
        leapY = 1;
      }
    } else {
      leapY = 0;
    }
    //////////// 閏年の判定エンド

    if (cmmSTRcmm(tmp, str, 2) == 2) {   // 2桁・日
      jdd = atoi(tmp);       // UTC日を数字に
      jdd = jdd + cdd;        // 24Hのオーバーフロー加算
    } else {
      return 0;
    }
    if (cmmSTRcmm(tmp, str, 3) == 2) {   // 2桁・月
      jmm = atoi(tmp);       // UTC月を数字に

    } else {
      return 0;
    }

    //////////// 月の最終日の判定
    switch (jmm) {
      case 4:
      case 6:
      case 9:
      case 11:
        if (jdd > 30) {
          jdd = 1;
          jmm++;
        }
        break;
      case 2:
        if (leapY == 0) {     // 閏年ではない
          if (jdd > 28) {
            jdd = 1;
            jmm++;
          }
        } else {              // 閏年である
          if (jdd > 29) {
            jdd = 1;
            jmm++;
          }
        }
        break;
      default:            // 1 3 5 7 8 10 12
        if (jdd > 31) {
          jdd = 1;
          jmm++;
        }
        break;
    }
    //////////// 最終月の判定

    if (jmm > 12) {
      jmm = 1;
      jyy++;              // 年を進める
    }
    sprintf(tmps, "%04d", jyy);     // 4文字
    strcpy(nowDate, tmps);          // JST出力
    strcat(nowDate, "/");
    sprintf(tmps, "%02d", jmm);     // 2文字
    strcat(nowDate, tmps);          // JST出力
    strcat(nowDate, "/");
    sprintf(tmps, "%02d", jdd);     // 2文字
    strcat(nowDate, tmps);          // JST出力
    return 1;                      // 正常(時分秒と年月日の両方)抽出時は、1を返す。
  } else {
    return 0;
  }
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// 4つのキーによるパラメータ設定・変更操作
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

// ◆◆◆◆◆F0キーによる設定等の処理選択◆◆◆◆◆
uint8_t jobSelect(uint8_t def)        // F0キーにより、設定等の処理選択
{
  int8_t i, j, p, xl;
  int8_t stateRet = 0;
  const char titlMsg[] = "Config MENU";
  const char* selMsg[] = {  "0.UART bps Set",
                            "1.SEND to HOST",
                            "2.First GATE time"
                         };   // このアイテム(item)の有効数-1(行数-1)を次に記述。
  const uint8_t item = 2;
  uint8_t tmpprm;
  j = def;
  p = 0;

  while (1)
  {
    u8g2.clearBuffer();        // OLEDクリヤー

    u8g2.setDrawColor(1);         // 反転用の処理のため
    u8g2.drawBox(0, 2, 128, 12);  // Box描画
    u8g2.setDrawColor(2);         //  color 2 set

    Text_setCursor(0, 0);       // 桁・行
    u8g2.print(titlMsg);       // タイトルの行を表示

    for (i = 0; i <= item; i++)
    {
      Text_setCursor(2, i + 1);   // 桁・行
      u8g2.print(selMsg[i]);
    }

    xl = (j + 2) * 12;                // マーカー位置指定
    u8g2.drawGlyph(5, xl , 0x25b6);   //   0x21d2⇒  0x25b6▶

    FuncEnable = B00001111;
    func_print(0, "RETN");
    func_print(1, " UP ");
    func_print(2, "DOWN");
    func_print(3, "ENTR");
    u8g2.sendBuffer();          // OLEDに送信(描画する)


    switch (buttonSW()) {
      case 10:
        stateRet = 1;
        break;
      case 11:
        //     case 21:
        j--;
        if (j < 0) j = item;
        break;
      case 12:
        //     case 22:
        j++;
        if (j > item) j = 0;
        break;
      case 13:
        //     case 23:
        switch (j) {
          case 0:                     // UART BPS set
            tmpprm = bpsPRM;
            bpsPRM = confPRMset(j, tmpprm);
            switch (bpsPRM) {    // 以前の状態に関係なく速度切換を実行する。(2020/01/28)
              case 1:
                Serial1.println(bps192set);      // <cr><lf>を付けて速度をGPSに指示
                delay(50);
                Serial1.println(bps192set);      // <cr><lf>を付けて速度をGPSに指示(2回目)
                delay(50);
                Serial1.end();                    // シリアルを一度止める
                delay(50);
                Serial1.begin(19200);             // Serial1(PA9 PA10)
                break;
              case 2:
                Serial1.println(bps384set);      // <cr><lf>を付けて速度をGPSに指示
                delay(50);
                Serial1.println(bps384set);      // <cr><lf>を付けて速度をGPSに指示(2回目)
                delay(50);
                Serial1.end();                    // シリアルを一度止める
                delay(50);
                Serial1.begin(38400);             // Serial1(PA9 PA10)
                break;
              case 0:
              default:
                Serial1.println(bps96set);       // <cr><lf>を付けて速度をGPSに指示
                delay(50);
                Serial1.println(bps96set);       // <cr><lf>を付けて速度をGPSに指示(2回目)
                delay(50);
                Serial1.end();                    // シリアルを一度止める
                delay(50);
                Serial1.begin(9600);              // Serial1(PA9 PA10)
                break;
            }

            if (bpsPRM != tmpprm) {        // 書き替えが有った
              write(0, bpsPRM);            // ここにROM書込み
            }
            break;
          case 1:                     // SEND to HOST
            tmpprm = hostPRM;
            hostPRM = confPRMset(j, tmpprm);          // 新しい方向をセット

            if (hostPRM != tmpprm) {        // 書き替えが有った
              write(1, hostPRM);           // ここにROM書込み
            }
            break;
          case 2:                     // GATE time
            tmpprm = gatePRM;
            gatePRM = confPRMset(j, tmpprm);

            if (gatePRM != tmpprm) {       // 書き替えが有った
              write(2, gatePRM);           // ここにROM書込み
            }
            break;
        }   // switch (j) end
        break;

    }   // switch (buttonSW()) end

    if (stateRet == 1) {     // jobSelect(uint8_t)を抜ける
      return j;
    }
  }  // while (1) end
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// ◆◆◆◆◆M ボタンの状態を返す◆◆
// 何れかのボタンが押されて放された(F0=10,F1=11,F2=12,F3=13)を待つ。
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
int8_t buttonSW() {
  int8_t retVal = 0;      // = 0 の記述は必須。
  int8_t repeat10 = 0;
  int8_t count = 0;
  uint16_t asw;

  // while (1) {
  asw = analogRead(SWa_In);
  if (asw < 80) {  // F0ボタンなら return 10
    while (asw < 80) { // F0ボタンを放すまで待つ
      delay(50);
      asw = analogRead(SWa_In);
      count++;
      if (count >= 20) {        // 1000mS 以上押されたら10プラス
        repeat10 = 10;
      }
    }
    delay(50);
    return 10 + repeat10;
  }

  if ((asw > 300) && (asw < 430)) { // F1ボタンなら Plus

    while ((asw > 300) && (asw < 430)) { // F1ボタンを放すまで待つ
      delay(50);
      asw = analogRead(SWa_In);
      count++;
      if (count >= 20) {        // 1000mS 以上押されたら10プラス
        repeat10 = 10;
      }
    }
    delay(50);
    return 11 + repeat10;
  }

  if ((asw > 580) && (asw < 790)) {   // F2ボタンなら Minus
    while ((asw > 580) && (asw < 790)) { // F2ボタンを放すまで待つ
      delay(50);
      asw = analogRead(SWa_In);
      count++;
      if (count >= 20) {        // 1000mS 以上押されたら10プラス
        repeat10 = 10;
      }
    }
    delay(50);
    return 12 + repeat10;
  }

  if ((asw > 800) && (asw < 1100)) {  // F3ボタンなら return 13
    while ((asw > 800) && (asw < 1100)) { // F3ボタンを放すまで待つ
      delay(50);
      asw = analogRead(SWa_In);
      count++;
      if (count >= 20) {        // 1000mS 以上押されたら10プラス
        repeat10 = 10;
      }
    }
    delay(50);
    return 13 + repeat10;
  }

  // } // while end
}
///////////////////////////////////////////////////////////////////////
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// 処理選択画面を展開し、選択された値を返す。
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
uint8_t confPRMset(uint8_t jobNo, uint8_t prm)
{
  int8_t i, j, p, xl;
  int8_t stateRet = 0;
  const char* titlMsg[] = {
    "0.UART BPS Set",
    "1.SEND to HOST",
    "2.First GATE time"
  };
  const char selMsg[3][3][22] = {
    { "9,600 bps(default)",   // jobNo = 0
      "19,200 bps(32.6%)",
      "38,400 bps(16.3%)"
    },
    { "GPS data(default)",    // jobNo = 1
      "LOG data",
      "None"
    },
    { "1 sec(default)",        // jobNo = 2
      "10 sec",
      "100 sec"
    }
  };
  int8_t item;
  uint8_t tmpprm = 0;

  switch (jobNo) {    // 選択する項目(selMsg)の数を処理No毎にセットする。012
    case 0:
      item = 2;
      break;
    case 1:
      item = 2;
      break;
    case 2:
      item = 2;
      break;
  };
  j = prm;
  p = 0;

  while (1)
  {
    u8g2.clearBuffer();        // OLEDクリヤー

    u8g2.setDrawColor(1);         // 反転用の処理のため
    u8g2.drawBox(0, 2, 128, 12);  // Box描画
    u8g2.setDrawColor(2);         //  color 2 set
    Text_setCursor(0, 0);       // 桁・行
    u8g2.print(titlMsg[jobNo]);       // タイトルの行を表示

    for (i = 0; i <= item; i++)
    {
      Text_setCursor(2, i + 1);   // 桁・行
      u8g2.print(selMsg[jobNo][i]);
    }

    xl = (j + 2) * 12;                // マーカー位置指定
    u8g2.drawGlyph(5, xl , 0x25b6);   //   0x21d2⇒  0x25b6▶

    func_print(0, "RETN");
    func_print(1, " UP ");
    func_print(2, "Down");
    func_print(3, "ENTR");
    u8g2.sendBuffer();          // OLEDに送信(描画する)

    switch (buttonSW()) {
      case 10:
        tmpprm = prm;            // 初期値で戻る
        stateRet = 1;
        break;
      case 11:
      case 21:
        j--;
        if (j < 0) j = item;
        break;
      case 12:
      case 22:
        j++;
        if (j > item) j = 0;
        break;
      case 13:
        switch (j) {
          case 0:
          case 1:
          case 2:
            tmpprm = j;
            stateRet = 1;       // 終了処理をセット
            break;
        }   // switch (j) end
        break;

    }   // switch (buttonSW()) end

    if (stateRet == 1) {     // jobSelect(int8_t)を抜ける
      return tmpprm;
    }
  }  // while (1) end
}
///////////////////////////////////////////////////////////////////////
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// EEPROM 24C256 等の外部EEPROMのI2C読み書き関数。
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// 1バイト書込
uint8_t write(uint16_t address, uint8_t data)  {
  uint8_t rc;
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write((byte)(address >> 8));   // アドレス上位
  Wire.write((byte)(address & 0xFF)); // アドレス下位
  Wire.write(data);
  rc = Wire.endTransmission();
  delay(6);
  return rc;
}

// 1バイト読込
uint8_t read(uint16_t address)  {
  Wire.beginTransmission(DEVICE_ADDRESS);
  Wire.write((byte)(address >> 8));   // アドレス上位
  Wire.write((byte)(address & 0xFF)); // アドレス下位
  Wire.endTransmission();
  Wire.requestFrom(DEVICE_ADDRESS, 1);
  return  Wire.read();
}



9.その他気づいたこと

(1)その他





99.追記用

大幅な改定・追記用