Top Page OCXO 較正自動化のための「Vcon I/F」の製作
自作・実験工程のメモとして
01.Feb.2021
(最終更新:2022/10/08) ハードソフトともに全体的な見直しと追記しました。


 2020年は、OCXO較正器(OCXO Calibrator以下、OCXO Calib.)を中心に取り組み、完成の目途が立った後も、デバッグを兼ねて手持ちのOCXO(EPSON TOYOCOM)を調整していましたが、最後の数mHzを10.0MHzまでに追い込むことに苦労していました。
そのため、OCXOのVC端子(Uinとも記載有り。以下、Vconと表記する)の電圧を小数点以下3桁が読めるデジタルテスターを購入して合わせていましたが、それでも苦戦していました。
そこで、今までテスターを見ながらマイナスドライバーで調整する手動での手順を、Vconの電圧をCPU側からコントロールして「放っておいても10.0MHzに合わせてくれる」ような自動化につながるツールとしてOCXO Calib.を発展させるため、本テーマに取り組んでみました。
私自身はそこまで正確な10.0MHzが必要な訳ではなく、「OCXOを持っているが、周波数は本当に(ある程度)正確な発振をしているか?」という不安からスタートしたテーマを元に、関連テーマを長々と引っ張っています。
 また、実現に当たってはテーマ名に有りますように、Net上の『10MHz GPSDOの作製』という自作情報を元に回路等を引用させていただき、先の「OCXO Calib.」と今回の付加基板「VconI/F」でGPSDO(ホールドオーバー機能搭載)を実現するとともに、OCXO単体でも高い調整精度で較正できるように考えてみました。

このページに興味のある方は、前提としている「OCXO Calib.」の製作過程を掲載している前編続編番外編完結編Part4をご確認ください。

2022/10/08:ハードウェアーの抵抗定数の見直し並びにソフトウェアーの見直しにより、画像等の入替ならびに動画の挿入を実施しました。





OCXO(CTS 1250019)を載せた基板をケース(タカチ:CU-12N)に入れて付加した「Vcon I/F」基板
VconI/F」の取付は秋月の2.6mm用六角スペーサー(16.5mmと11mm)を繋ぎ、2箇所だけ止めました。

Edit by bluegriffon 3.1

1.OCXO Vcon I/Fの回路

 文頭にも書きましたが、Net上の記事を参考にさせていただいた簡単な回路で、OCXO基板のVcon電圧をボリュームに代わってコントロールすることを目指します。
必要電圧を求めるための抵抗値はエクセル(私はLiberOfficeを使用)で可変範囲を想定しながら変更しています。


■.OCXO Vcon I/Fの回路

クリックでpdfファイルが開きます。(MCP4726の回路に変更済み)
当初、何も考えずマイクロチップのDAC MCP4726を想定していましたが、単体での電源ON時にEEPROMの設定値で出力しないことが分かり、MCP4725に変更しています。
上記の「MCP4726が電源ON時に設定値で出力しない・・・」は、私の勘違いで、データシートを読み間違ったと思います。
もう一度見ていると、『最初のEEPROMの初期値がオールZEROになっている』部分を読み違えて、注釈の『デバイスが顧客に出荷されたときのデフォルト構成。 POR / BOR値は、対応する不揮発性構成ビットを書き込むことによって変更できます。』部分を見落としていました。 Google翻訳使用 (2021/02/14)

また、回路上では、実測の電流や計算上の電圧をメモとしていれています。
(最終的にMCP4726を使用しています)


2.OCXO Vcon I/F PCBの設計と修正

 上記 1.項 に有りますように簡単な回路ですので、ブレッドボードや穴あき基板に組んでも良いようなものですが、小さくまとめたい為にPCBを発注してしまいました。
そのため、毎回のようにある修正が今回も発生したため、チップ上にジャンパー線を入れて対応しています。


■.PCBの3Dビューアー

1.U1は、実際には78L05(TO-92)を使用します。
2.ただし、OCXO Calib.側に5Vを供給する構成にした場合を考えて、200mAレギュレータのパターンも用意しています。
3.今回の修正箇所は、U3(シルク:MCP4726)をMCP4725に変更することにともなうパターン変更が有ります。
4.上面右側のP2(3ピンヘッダ)からOCXO基板の多回転ボリューム(取り外し)に接続します。
5.手前側のJ3(4ピン)は、OCXO Cailb.本体からくるI2C端子でこの端子を使ってコントロールします。
6.基板サイズは30mm x 40mmにまとめました。
(最終的にMCP4726を使用しています)

■.実PCB誤修正時の画像(現在は元回路に修復済み) 修正不要でした

1.OCXO Calib.本体側もですが、基板上のコネクタはピンヘッダを使うことにしました。
2.U1の部分は、先の説明に有りますようにTO-92のスルーホールパターンに78L05をハンダ付けしています。
3.入力の電圧はOCXO側から取り入れ、電流値は12Vで8.72mA(実測値)になります。

■.基板のパターンカット(USB簡易顕微鏡にて) 不要の工程でした

1.U3部の6ピンのパターンをカットします。
2.あとは、テスターで導通が無いことを確認して修正完了です。


■.基板修正とチップ素子上にジャンパー線(USB簡易顕微鏡にて) 不要の工程でした

1.MCP4725の6ピン(A0)を2ピン(G)に落とします。
2.はんだ付けの後は、フラックス除去剤を綿棒に含ませて清掃しました。
3.それでもソケット等を配線した後なのでフラックス除去スプレーが使用できず、チップ下のフラックスが取り切れません。

(最終的にMCP4726を使用しています)



3.新しく手配したOCXO

 昨年の夏に入手したOCXOは手放してしまったので、改めてAliExpressで手配してみました。
以前の品と違うものを試してみようとしましたが、手にしてみると私の勘違いで夏に手配したものと同じタイプ(navicom)や、少しでも安くと思いCTSのOCXOは基板付きの中古品を注文してしまい、取り外すのに苦労しました。

■.昨年の夏に入手した品と同じタイプのOCXO(navicom)
1.この評価基板は1枚だけなので、OCXO-navicomを外しましたが、スルーホールの穴が狭くOCXOの足が太いため、外すときにスルーホール穴(51x41用穴)が外れたように見えましたが、大丈夫でした。
2.もう一つのOCXOは51x51サイズのため、取付に支障は有りませんが自分でパターンを起こすときは注意したいと思います。
(このPCBは、OCXO取付用の端子穴が狭すぎて、OCXOユニットの入替には不向きです)

■.基板付き中古OCXO(CTS 1250019)
1.この基板付き中古品は、Net上ではMorion Double oven MV89Aの代替品との紹介が有ります。
2.Net上で主に新品が出回っているように思いますが、詳細は分かりません。
3.この品は、製造が2008年と読み取れます。

■.基板付き中古OCXO(CTS 1250019)の外した基板
1.この基板からの取り外し作業が有るため、(株)エンジニアのハンダ吸取器(SS-02)を購入して備えましたが、手こずりました。
2.ハンダごて5本と2人がかりで同時にやればよかったと思いますが、そんなに多くのハンダごてを持っているわけでも無く、次回注文することが有れば取り外し品を手配したいと思います。

■.中古OCXO(CTS 1250019)の実装
1.緑のOCXO評価基板はこの項最初にありますOCXO(navicom)が載っていたものです。
2.ケースは昨年の夏に入手したOCXOの時と同じCU-12Nを使いましたが、今回は電源コネクタ(DC-J)を別に取付けて基板の位置を変えています。
3.手前側に見えるのが、多回転ボリュームの後に付けたピンヘッダ・ソケットです。
4.ピンヘッダーは、ボリュームのスルーホールの穴が小さく、通常品が入らず細ピンヘッダーを無理やり入れたようなギリギリの状態です。
5.本品だけかどうかは分かりませんが、Vconの電圧は2.5V前後になります。(別のTOYOCOM品は4.9V前後)

■.OCXO(CTS 1250019)を横から拡大
1.navicomが載っていた基板からOCXOを外し、CTSをリード線(約2cm)を使って取付けています。
2.OCXOを入替(評価)するときは、1ピン毎にハンダ付けできるため基板のスルーホールを痛めることは無いと思います。




4.較正自動化の手順

 OCXO Calib.だけの較正の手順は、周波数を測定してその誤差を確認し、その誤差を少なくするようにOCXOの多回転ボリュームを回します。
当初はこの手順通りに、予め設定した固定の変化量を計測結果を元に加減算し、少しずつVconに加える電圧を変更していましたが、それでは時間がかかり過ぎるのでDAC(MCP4725)の、分解能(12ビット)の段階(1/4096)当りの周波数変化量を最初に求めるやり方に変更しています。(以下、変化量を mHz(1LSB) と表記します。)
この変化量mHz(1LSB)を当てはめてみると、事前の準備は必要ですが「mHz台の誤差」まで調整するのに1〜2時間で調整できる目途が立ちました。
更に良いやり方や、実際のGPSDOのソフト処理がどのようになっているかは、その手順を覗いていませんのでわかりませんが、一つのやり方として紹介しておきます。
ただし、既に『当たり前の手順』となっているかもしれないことを、予めお断りしておきます。


■.全体の流れ概要と新画面の追加

 1.OCXO Calib.とVconI/Fの接続 

 2.Vconの設定値確認とモードをManualに設定 

 3.DAC(MCP4726)のRegisterへDAC値(12bit)の中間値2048をセット 

 4.GATE時間1秒〜10秒で、周波数をカウントしながらVconI/Fのボリュームを調整 

 5.GATE時間1000秒に設定 

 6.DAC値に1000をセットして周波数をカウント(1回)し、f1をメモする 

 7.DAC値に3000をセットして周波数をカウント(1回)し、f2をメモする 

 8.f1 f2 からDAC値1ビット当りの周波数変化量(0.?????mHz)を求める 

 9.変化量を mHz(1LSB) へ入力してOCXO Calib.内に記憶 

10.モードをAutoに設定して、計測を開始([STRT]ボタン) 

11.数回の連続測定で10MHzの許容値内に収まったら停止して、DAC内のEEPROMに記憶する 


今回の追加で、画面に追加表示項目並びにMENUを追加しました。
▼メイン画面に、MODEDAC値を表示するようにしました。

MODE=AUTOは、2桁の安定数(stable)を表示するよう変更します。
MANUAL時は"M"表示のまま。

▼もしVconの接続がされていないときは、vcOFFと表示するようにしました。

▼Config MENUに「5.Vcon Setting」 の追加

▼「5.Vcon Setting の内容」

 1.OCXO Calib.とVconI/Fの接続 


電源用を除くI2Cの信号を本体から引き出した線でつなぎます。
また、OCXOの電源は事前に投入し、必要十分な時間をかけて温めておく。

OCXO Calib.本体側は、予め用意していたI2Cのコネクタに接続します。
また、I2Cの電源は、デフォルト(3.3V)から5Vにハンダジャンパーで変更する必要が有ります。

 2.Vconの設定値確認とモードをManualに設定 

(MENU番号50)
1.ここでVconの接続ができているかと各設定を一覧で確認します。


2.未接続の状態でも、本メニューを開くと接続確認をして状態を表示します。
3.確認が終わったら戻って、(MENU番号51)を開きます。


この画面では、以下の3項目について設定します。
1. MODE: Auto・Manualの切り替えは、下段のボタンでトグル動作で切り替えます。
2. ErrorTOLERANCE 10^4s: GATE=10^4s時の誤差許容範囲 ( 0 = 0.0mHz, 1 = 0.1mHz, 2 = 0.2mHz を下段のボタンで巡回切替)
個々のOCXOのバラツキにより、安定状態に入った後の変動にも違いが有り、その度にコンパイルをやり直してソフト側で合わせる訳にも行かないので、最小桁が0.1mHz台の時には誤差許容範囲を設定できるようにしました。
3. IGNORE Width 10^4s: GATE=10^4s 時に安定状態に入った時は、指定の変動幅までを1回無視する値(Fluctuation-ignoring width)
( 0=0.0mHz, 5=0.5mHz, 10=1.0mHz, 20=2.0mHz を下段のボタンで巡回切替)
一度安定状態に入った後は突発的な変動(カウンター値)が有っても、直ぐに補正して次の回に逆方向の変動として捉えることの無いように、無視する(stable値は減算)範囲を設定できるようにしました。
 

 3.DAC(MCP4726)のRegisterへDAC値(12bit)の中間値2048をセット 

(MENU番号52)
1.DAC値は0〜4095までの範囲に制限しています。
2.桁オーバー等では、0クリヤーされるようにしています。
3.今までの値を残したければ、「RET」で戻ることができます。
 

 4.GATE時間1秒〜10秒で、周波数をカウントしながらVconI/Fのボリュームを調整 

1.DACの変化量だけでは周波数の調整範囲が少なすぎるため、粗調整用ボリュームで±0.01Hz程度に調整する。
2.十分な通電後で無いとヒートアップ中の周波数変動が大きく、再調整が必要になる可能性が有ります。

 5.GATE時間1000秒に設定 

「GATE」ボタンで切換る。

 6.DAC値に1000をセットして周波数をカウント(1回)し、f1をメモする 

(MENU番号52)


 7.DAC値に3000をセットして周波数をカウント(1回)し、f2をメモする 

(MENU番号52)


 8.f1 f2 からDAC値1ビット当りの周波数変化量(0.?????mHz)を求める 

1.(MENU番号55)にも簡単な手順や計算式を入れています。
2.f1 (DAC=1000指定)、f2 (DAC=3000指定)の周波数の差 fd (絶対値)をmHz台で求めます。
3.fd をf1 f2 間のDACの差(2000)で割って、ビット当たりの変化量を求めます。

 9.変化量を mHz(1LSB) へ入力してOCXO Calib.内に記憶 

(MENU番号53)

1.求めた変化量を mHzの単位で mHz(1LSB) に入力します。
2.入力した数値が異常値で無ければ、その数値を OCXO Calib. 側のEEPROMにsaveします。
3.終了時は入力した数値を1秒ほど表示して戻ります。

4.もし、0(ZERO)を入力した場合は警告を出すようにしました。

5.0.1超の時には、同様に GATE=10^4s CANNOT と警告を出すように追加しました。

10.モードをAutoに設定して、計測を開始([STRT]ボタン) 

1.(MENU番号55)でAutoに設定
2.DAC値(ここでは3000)は変更する必要無し。
3.1回の計測で、ほぼ数mHz以内に収まると思います。
4.緑色で囲った部分は、最初はオレンジですが安定状態がある程度続くと、その時のDAC値を自動でDACROMに書込んでシアン(水色)に変わります。
  この時、DACROMへの書込みに失敗するとマゼンタ(ピンク色)に変えるようにしていますが、発生しない予定です。

11.数回の連続測定で自分で求める許容値内に収まったと思ったら停止して、DAC内のEEPROMに記憶する (こちらは手動の設定時)

(MENU番号54)
1.DAC(MCP4726)内のEEPROMに記憶します。
2.次回の立ち上げ(電源ON)から、OCXO Calib.を接続していなくても、VconI/Fを載せたOCXO単体でこの設定値を元に立ち上がります。(暖気運転時間は必要)


12.以上の設定をVconのデモ画面とともに設定操作マニュアルとしてビデオにまとめました (追加:2022/10/08)

約3分27秒(BGM付)


■.上記 4.の辺りから記録したLOG画面 (2022/10の変更前)

1.最初の3行は、DAC値に2048を入れた粗調整後の状態です。
2.LOGの右端に"Vcon"という列を追加してDAC値を記録します。
3.次にDAC値10003000をそれぞれ1回実行し、両周波数の差を求めます。
4.その差を mHz(1LSB) に変換し(今回は、0.222mHz)入力後に、モードをAutoに変えて通常の計測をします。
5.2回目の時点で、DAC値2342に書き換わった状態で1mHzの誤差まで合わせられたことに成ります。
6.ここでは夜中にそのまま走らせていますが、その後数回計測して安定していればEEPROMに書きこんで、較正は完了です。
7.最新のLOGには、許容値内を何回続けたかをカウントする"STABLE"と、指定したmHz(1LSB) を表す"(1LSB)"を追加しています。(追記:2022/06/20)

■.誤差検出における許容範囲の考え
1.上記のようにカウンタタイプの誤差検出では、9,999,999.999Hzから10,000,000.000Hzの間を許容値としています。
2.上記許容値の範囲外をカウントしたときに、補正の動作を実行します。


■.上記較正後にGATE時間を一万秒で2回走らせた後、再度千秒で約7時間経過のLOG (2022/10の変更前)
1.GATE時間一万秒では、最初の計測の誤差(0.0002Hz)からDAC値を+1。
2.DAC値の1LSB当りの周波数変化量が0.222mHz(今回使ったCTS 1250019の場合)なので、これ以上の補正は不可能となります。
3.その後、GATE時間千秒で7時間ほど計測を続けましたが、たまたまGPS衛星の乱れ?やノイズ?の影響も無く安定状態を維持していました。

■.計測中のOCXO Calib.の画面表示

画像は2022年10月時点で差換え。





5.VconI/F用ソフト

 ソフトの処理は今までのカウント周期の中で、周波数をカウントし終わるとその差によりDACに与える設定値を変更するやり方で進めます。
ここではまだ、10.000MHzOCXOの較正中なのか通常の周波数カウント中なのかの判定等は入れていなくて、操作で自動補正を実施『する・しない』設定するにとどめています。
 また、ノイズ等による一時的な周波数変化への誤対応回避も入れてみましたが、起こりうる事象全てに対応できるはずも無く、是非のほどは分かりません。しかし、何はともあれ前項の「誤差検出における許容範囲の考え」に基づきテストしてみました。

■.
補正のルールとGATE=1000Sec時の処理(例)
    // ■■■■ VconValueが上がると周波数は下がる(逆特性) ■■■■
    // 誤差が有り過ぎるときの処理
    // その他を追加する
    //
    // Vcon MODE= Auto の場合で且つ GATE=1000Sec, 10^4Secの場合のみ自動補正する。
    // 許容値内(例)10--0.000Hz〜9--9.999Hz の場合は、補正はせずSTABLE++をする。
    // 許容値±0.002 までの小誤差は、STABLEが9まで加算していれば見送り、
    //      それ以下の場合は値を縮小して補正する。ともに、STABLEを-2する。
    // 許容値±0.003 以上の誤差は、STABLEが10まで加算していれば見送り、
    //      それ以下の場合は計算値もって補正する。ともに、STABLEを-3する。
    // STABLEは、0から10の間の値とする。


    B4VconValue = VconValue;
    if (VconAUTO == 1) {      // Vcon AUTO MODE の時に実行

      // ゲート時間により、
      switch (gatePRM) {
<<省略>>
        case 3:
          gosa = dFrqReg - 10000000.000;
          tmpdac = fabs(gosa) * 1000 / UNI.dmHz;    // fabs(gosa) 絶対値を求める

          if (gosa >= 0.003) {          // プラス方向に、0.003以上の誤差

            if (stable < 10) VconValue += tmpdac;

            // 大幅な許容値外なので安定状態のカウントを-3する
            if (stable > 1) stable -= 3; else stable = 0;

          } else if (gosa >= 0.001) {   // プラス方向に、0.002から0.001の誤差

            // 安定状態が9未満なら補正する(9&10なら補正を一回見送る:実験中)
            if (stable < 9) VconValue += (tmpdac / 2);   // 大幅な変化を抑制

            // 許容値外なので安定状態のカウントを-2する
            if (stable > 1) stable -= 2; else stable = 0;

          } else if (gosa <= -0.004) {  // マイナス方向に、0.004以上の誤差
            if (stable < 10) VconValue -= tmpdac;

            // 許容値外なので安定状態のカウントを-3する
            if (stable > 1) stable -= 3; else stable = 0;

          } else if (gosa <= -0.002) {  // マイナス方向に、0.003から0.002の誤差

            // 安定状態が9未満なら補正する(9&10なら補正を一回見送る:実験中)
            if (stable < 9)  VconValue -= (tmpdac / 3);  // 大幅な変化を抑制

            // 大幅な許容値外なので安定状態のカウントを-2する
            if (stable > 1) stable -= 2; else stable = 0;

          } else {                      // +0.000から-0.001までの誤差(許容値内)
            if (stable < 10) {          // 10未満なら安定状態を加算
              stable++;
            }
          }
          if (VconValue > 4095) VconValue = 4095;
          break;

<<省略>>

        default:    // 0Hz台の測定中は自動加減しない。(I/F多回転VRで合わせる)
          //        gosa = 10000000 - dFrqReg;
          //        if (gosa >= 2) VconValue += 200;
          //        if (gosa <= -2) VconValue -= 200;
          break;
      }
      if (VconI2C == 0) {     // Vcon 接続中なら
        vconFastSet(VconValue);   // ■■■■■ VconへDAC値をセット■■■■■■■■
      }
    }


急激な補正を掛けない方が良い安定状態の長さが10で適切かどうかが不明です。


■.OCXO(CTS)のVconによる制御の実測値
1.いずれも、3時間電源OFFにしていたOCXOを、電源ON5分後より計測スタートしています。
2.青い線は、補正有の周波数変動で、オレンジ色のVcon(DAC値)により制御された出力結果です。
3.緑色の線は、別の日に同じ条件で制御せずに(Manual)周波数変化を約11時間計測したグラフです。
4.補正有は、3回目の計測より安定(GPSDOのような)状態にあり、補正無はこの後もじっくり10.0MHzに近づいていきます。


■.OCXO(CTS)のVconによる制御の実測値(補正有)  (2021/02/10)
一つ前のグラフに有る緑色の線(補正無)後に約2日間連続で記録したグラフになります。
雰囲気だけは補正有で±2mHz以内に収まっているようです。
X軸ラベル151の辺りは、短時間の間にマイナス・プラスの両方向に変動しており、補正ルールの影響かも知れません。
補正値を見れば、2353で始まり2353に戻っていますので、神経質にならずにこの値を固定しておいても十分使えるのではと思います。

■.ソフト
2021/02/08:OCXO Calib.のソースファイル  のV50_60eより公開中です。





6.Vcon用I2Cインターフェース(Arduino)の追加  (2021/05/17)

 上記5項の更新後も、VconI/FによりOCXOの評価を続けていますが、OCXO Calib.を使わなくても個別にDACの値を設定できるようインターフェースを追加してみました。
内容は、Arduino(UNOでもNANOでも可)のI2C端子をPC側から操作するもので、特に工夫ということも有りませんが記録として追記しておきます。


■.Vcon用I2Cインターフェース(Arduino NANO)

NANOのI2C端子からVcon用のI2C端子に行くピンヘッダーを出しているだけで、プルアップもしていません(笑)


■.Vcon用I2Cインターフェースのスケッチ (2023/01/25:軽微な変更)
/*
   FileName NANO_V3_MCP4726_I2Ccom.ino

   2021/05/04
   Arduino NANO V3     SDA=A4 SCL=A5
   I2Cインターフェース
   I2CデバイスをPCから制御する
   I2Cデバイス:MCP4726  ADDRESS 0x60
   コマンド操作は、MCP4726 データシートの「6.0章〜」を参照のこと

   私的メモ(コンパイル時の設定等)
   ボード:"Arduino Nano"
   プロセッサ:"ATmegs168P" ・・・・・赤い基盤の"nano"は16KB品
   シリアルポート:"COM4"
   115200bps  CR/LF

 個別OCXOのメモ(1)
  navicom NOX038663XS   S/N:21419
  EFC 2.887V辺りに Set,
  ADCvalueは、大きくすると周波数が下がり、小さくすると周波数が上がる。
  その1ビット当たりの周波数変化量は、約 @0.09945mV/1LSB となる。
 個別OCXOのメモ(2)
  TOYOCOM S/N:01991
  EFC 5.195V 辺りに Set,
  ADCvalueは、大きくすると周波数が下がり、小さくすると周波数が上がる。
  その1ビット当たりの周波数変化量は、約 @@0.04545mHz/1LSB となる。

  2022/03/28:V0.3
   通信速度を9600に変更。
   CMD= 状態で[RET]を押すとヘルプと同じ表示をするよう変更。
  2022/03/29:V0.4
   コマンド + - を削除、処理の見直しと微調整。
  2022/03/30:V0.41
   各操作時にリード動作を挿入した。

  2022/08/29:V0.5
    NANO ATmegs168 へ対応:グローバル変数のRAM使用量を減らすための修正
  2023/01/25:V0.51
    BSキーが使えるように追加。ヘルプの中を追記

   (c)jlb.jp
*/

#include <Wire.h>
#include   <stdio.h>
#include   <stdlib.h>

#define Vcon_ADDRESS 0x60   //I2C 12bit DAC MCP4726A0T-E/CH

// シリアル受信処理関係
uint16_t RXposi = 0;
uint8_t RCVok = 0;

byte RXchar;
char RXbuff[16];
char tmptmp[16];


void Opening_Text()
{
  Serial.println("=== for OCXO Vcon(MCP4726) I2C Commander V0.51  2023/01/25 ===");
  Serial.println(" R; DACvalue 読み出し,  Sxxxx; DACvalue(0 to 4095) セット, ");
  Serial.println(" W; DACvalue EEPROMへ書込み,  [BS]; 一文字戻る,");
  Serial.println(" [Enter]; ヘルプ表示 ");
}
void Cmd_PRMPT()
{
  Serial.println("============================");
  Serial.print("CMD=");
}

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

// Vcon
//
// ■□□□■□□□□□□□□□□□□□□□□□□□
// ■□□□■□□□□□□□□□□□□□□□□□□□
// ■□□□■□□■■■□□□■■■□□■■■■□□
// ■□□□■□■□□□■□■□□□■□■□□□■□
// ■□□□■□■□□□□□■□□□■□■□□□■□
// □■□■□□■□□□■□■□□□■□■□□□■□
// □□■□□□□■■■□□□■■■□□■□□□■□

// MCP4725 & MCP4726
uint8_t VconI2C = 1;          // 0 = Vcon I2C 接続中、 4 = 未接続
uint16_t VconValue = 4096;    //  2048
uint16_t VconValueSub = 4096;
uint8_t getMODE;              // MCP4725 & MCP4726
uint16_t getDAC;              // MCP4725 & MCP4726
uint16_t getEPROM;            // MCP4725 & MCP4726

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// DACvalueのセットに使用する
// ■■変更有り■■
// Write Volatile(DAC) Memory: (C2, C1, C0) = (0,1,0)
// Vref1, Vref2 = 11 = VREF pin 2.50V(Buffered), 10 unBufferd
// PD1, PD2 = 0, 0
// Gain  = 1    1 = 2x (gain of 2)
// 0101 1001 0x59(固定)

void vconDACset(uint16_t SetVal) {
  uint8_t value1 = 0x59;
  if (SetVal > 4095) SetVal = 4095;
  Wire.beginTransmission(Vcon_ADDRESS);
  Wire.write(value1);
  Wire.write(SetVal >> 4);
  Wire.write(SetVal << 4);
  Wire.endTransmission();
  delay(10);
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
/*
  // MCP4726 の場合は、このコマンドで動作モードをセットする。
  // このデバイス(MCP4726)を初めて使うときは、本コマンドを最初に
  // 実行し、デバイスを再起動すること。
  // また、他の箇所で以下のモードを変更する記述をしては成らない。
  // Write All Memory: (C2, C1, C0) = (0,1,1)
  // Vref1, Vref2 = 11 = VREF pin 2.50V(Buffered), 10 unBufferd
  // PD1, PD2 = 0, 0
  // Gain  = 1    1 = 2x (gain of 2)
  // 0111 1001 0x79(固定)
*/
void vconALLMEMset(uint16_t SetVal) {    // MCP4726

  uint8_t value1 = 0x79;
  if (SetVal > 4095) SetVal = 4095;
  Wire.beginTransmission(Vcon_ADDRESS);
  Wire.write(value1);
  Wire.write(SetVal >> 4);
  Wire.write(SetVal << 4);
  Wire.endTransmission();
  delay(200);
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
uint8_t vconALLread() {        // MCP4726
  uint8_t ret;
  if (Wire.requestFrom(Vcon_ADDRESS, 6) == 6) {
    getMODE = Wire.read();
    getDAC = Wire.read();
    getDAC <<= 4;
    getDAC |= Wire.read() >> 4;
    getMODE = Wire.read();    // 2回目
    getEPROM = Wire.read();
    getEPROM <<= 4;
    getEPROM |= Wire.read() >> 4;
    ret = 0;
  } else {
    ret = 1;
  }
  return ret;   // 0 = OK
}


// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
uint8_t vconStartCHK() {
  uint8_t ret = 0;
  //  Wire.begin();    // (仮)ストール対策
  //  delay(20);

  // Vcon のI2C接続(有無)確認 if (ret == 0) 接続中なら
  // VconのEEPROMからDACvalueの初期値を読み出してセット
  // 返り値=0なら、接続中
  // 以後のI2Cコマンドの発出許可にも使用
  Wire.beginTransmission(Vcon_ADDRESS);
  ret = Wire.endTransmission();

  if (ret == 0) {
    if (vconALLread() == 0) {

      // if (VconValue == 4096) VconValue = getDAC; // 今まで未接続だったらROM読み込み
      VconValue = getDAC;
    } else {
      // VconのEEPROMが読めなかったらエラー表示(接続OKの時、在りえないかも?)
      Serial.println("#ERROR_00 vconStartCHK ");
    }
  }
  return ret;   // 0 = Vcon 接続中
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


///////////////////////////////////////////////////////////////////////
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//
// □■■■□□■■■■■□■■■■■□■□□□■□■■■■□
// ■□□□■□■□□□□□□□■□□□■□□□■□■□□□■
// ■□□□□□■□□□□□□□■□□□■□□□■□■□□□■
// □■■■□□■■■■□□□□■□□□■□□□■□■■■■□
// □□□□■□■□□□□□□□■□□□■□□□■□■□□□□
// ■□□□■□■□□□□□□□■□□□■□□□■□■□□□□
// □■■■□□■■■■■□□□■□□□□■■■□□■□□□□
//
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

void setup() {
  // put your setup code here, to run once:
  Wire.begin();
  Serial.begin(9600);
  Opening_Text();
  cmd_read();
  Cmd_PRMPT();    // コマンド・プロンプト
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
//
// ■□□□□□□■■■□□□■■■□□■■■■□
// ■□□□□□■□□□■□■□□□■□■□□□■
// ■□□□□□■□□□■□■□□□■□■□□□■
// ■□□□□□■□□□■□■□□□■□■■■■□
// ■□□□□□■□□□■□■□□□■□■□□□□
// ■□□□□□■□□□■□■□□□■□■□□□□
// ■■■■■□□■■■□□□■■■□□■□□□□
//
void loop() {
  // put your main code here, to run repeatedly:

  if (Serial.available()) { // (PC側)■

    RXchar = Serial.read();
    if (islower(RXchar)) {
      RXchar = toupper(RXchar);
    }
    switch (RXposi) {
      case 0:
        switch (RXchar) {
          case 'R':
          case 'W':
          case 'S':
            RXbuff[RXposi] = RXchar;
            RXposi++;
            Serial.write(RXchar);
            break;
          case 0x0d:        // CR
            Serial.println("");
            Opening_Text();
            cmd_read();
            Cmd_PRMPT();
            break;
          default:
            RXposi = 0;
            RCVok = 0;
            break;
        }
        break;
      case 16:                 // バッファ数 16 一杯なら吐き捨てる
        RXposi = 0;
        RCVok = 0;
        break;
      default:

        if (RXchar == 0x0d || RXchar == 0x0a) // 復帰<\r>0x0d 又は 改行<\n>0x0a
        {
          RXbuff[RXposi] = 0x00;      // 文字配列に0x00を書き込む
          RCVok = 1;
          RXposi = 0;
          Serial.println("");         // 改行
        } else if (RXchar == 0x08)     // [BS]Key
        {
          RXposi--;
          Serial.write(0x08);         // 画面上でバックスペース動作
        }else{
          if (isdigit(RXchar)) {
            RXbuff[RXposi] = RXchar;    // 数字なら RXbuff[] に取り込む
            RXposi++;
            Serial.write(RXchar);
          }
        }
    }

  }
  // --------------------------------------
  if (RCVok == 1)
  {
    //   Serial.println(RXbuff);

    if (strncmp(RXbuff, "R", 1) == 0) {
      cmd_read();
    }
    if (strncmp(RXbuff, "W", 1) == 0) {
      cmd_ROMwrite();
    }

    if (strncmp(RXbuff, "S", 1) == 0) {
     // strcpy(RXbuff2, RXbuff);
      VconValueSub = prmGET(RXbuff);        // 数値パラメータを抽出
      //////////////////////////////////////////////////////
      //      sprintf(tmptmp, "[%d]", VconValue);
      //////////////////////////////////////////////////////
      if (VconValueSub >= 0  && VconValueSub <= 4095 ) {
        Serial.println("DACvalueセット");
        cmd_DACset();
      } else {

        Serial.println("DACvalue 無効");
      }
    }
    Cmd_PRMPT();    // コマンド・プロンプト
    RCVok = 0;
  }
}
// ■■■■■■■■■ループエンド■■■■■■■■■■

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void prm_disp() {

  Serial.print("Status&Config = ");
  sprintf(tmptmp, "%2X", getMODE);
  Serial.print(tmptmp);

  Serial.print("   DACvalue = ");
  sprintf(tmptmp, "%4d", VconValue);
  Serial.print(tmptmp);

  Serial.print("    EEPROMvalue = ");
  sprintf(tmptmp, "%4d", getEPROM);
  Serial.println(tmptmp);

}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void cmd_read() {

  Serial.println("DACvalue 読み出し");

  VconI2C = vconStartCHK();       // Vcon の接続チェック

  if (VconI2C == 0) {

    //    Serial.println("CONNECT");

    prm_disp();   // 各パラメータ表示

  } else {
    Serial.println("vcOFF ");
  }
}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void cmd_ROMwrite() {

  Serial.println("DACvalue EEPROMへ書込み");
  if (VconI2C == 0) {

    vconALLMEMset(VconValue);   // ■■■DAC Value to EEPROM 書込み

    VconI2C = vconStartCHK();       // Vcon の接続チェック

    prm_disp();   // 各パラメータ表示

  } else {
    Serial.println("vcOFF ");
  }
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void cmd_DACset() {

  Serial.print("DACvalue 入力");
  sprintf(tmptmp, "[%d]", VconValueSub);
  Serial.println(tmptmp);

  vconDACset(VconValueSub);

  VconI2C = vconStartCHK();       // Vcon の接続チェック

  if (VconI2C == 0) {

    //    Serial.println("CONNECT");

    prm_disp();   // 各パラメータ表示

  } else {
    Serial.println("vcOFF ");
  }
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// RXbuffにある数値を抽出する
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
int16_t prmGET(char *str) {
  uint8_t i, c, ch;
  int16_t dacval = 0;

  c = strlen(str);
  if (c <= 5) {
    for (i = 1; i < c; i++) {
      if (str[i] >= '0' && str[i] <= '9') {
        dacval *= 10;
        ch = str[i] & 0x0f;
        dacval += ch;
      }

    }
    return dacval;
  } else {

    return -1;
  }

}
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void cmd_ROMwrite() {

  Serial.println("DACvalue EEPROMへ書込み");
  if (VconI2C == 0) {

    vconALLMEMset(VconValue);   // ■■■DAC Value to EEPROM 書込み

    VconI2C = vconStartCHK();       // Vcon の接続チェック

    prm_disp();   // 各パラメータ表示

  } else {
    Serial.println("vcOFF ");
  }
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
void cmd_DACset() {

  Serial.print("DACvalue 入力");
  sprintf(tmptmp, "[%d]", VconValueSub);
  Serial.println(tmptmp);

  vconDACset(VconValueSub);

  VconI2C = vconStartCHK();       // Vcon の接続チェック

  if (VconI2C == 0) {

    //    Serial.println("CONNECT");

    prm_disp();   // 各パラメータ表示

  } else {
    Serial.println("vcOFF ");
  }
}

// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
// RXbuffにある数値を抽出する
// ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
int16_t prmGET(char *str) {
  uint8_t i, c, ch;
  int16_t dacval = 0;

  c = strlen(str);
  if (c <= 5) {
    for (i = 1; i < c; i++) {
      if (str[i] >= '0' && str[i] <= '9') {
        dacval *= 10;
        ch = str[i] & 0x0f;
        dacval += ch;
      }

    }
    return dacval;
  } else {

    return -1;
  }

}




■.Vcon用I2CインターフェースのPC操作イメージ例(TeraTerm)(2023/01/25:差替え)

主にMCP4726のDAC値・EEPROM
値の読み出しと、DAC値への値セット、DAC値のEEPROMへの書込みをしています。
最終的にはEEPROMへの書込みをして完了で、書き込んだ値が次回の電源ON時に反映されます。



7.OCXO Vcon I/F PCB 拡張版の設計と修正  (2022/09/19)(2022/10/08)

  ずいぶん前の話になりますが、上記の小基板の作成の後にOCXOを載せる基板とともにコネクタで接続できる Vcon I/F PCB を作成しておりました。
OCXO 搭載基板につきましては、基板の設計でOCXO出力にアンプを入れて出力を分割したときのレベルダウンをカバーしようとして、Net上の回路を参考にしながら「LTspice XVII」を使って事前調整したつもりですが、結論としては、出力波形が歪む結果となりアンプ部はコンデンサでジャンプし、エミッタフォロワ部分のみ使用しています。
あまり時間が経つとわからなくなるので、記録として載せておきます。


■.OCXO Vcon I/Fの回路(拡張版:V3.5)

クリックでpdfファイルが開きます。
オペアンプ回路の電圧値(抵抗値で分圧)をOCXO個体に合わせてLTspice XVII」でシミュレーションして手持ちのE24系列抵抗の組合せで概算値を算出しました。(次項へ)
OCXO側のElectronic Frequency Control Input (EFC)の負荷により計算どおりとはいきませんが、ある程度の範囲に収まっていると思います。



■.PCBの3Dビューアー(拡張版:V3.5)



■.OCXO 搭載基板の回路(拡張版:V3.5)

クリックでpdfファイルが開きます。

出力増幅部のアンプは、不採用としています。(プリントパターンは残ります)

■.OCXO 搭載基板の3Dビューアー(拡張版:V3.5)
 
一応専用の箱に合うようにサイズを決め、フロント・リアパネルともPCBで作成しましたが一点物となります。
上記のVcon I/Fの小基板が載るように合わせています。



8.「LTspice XVII」による Vcon I/F(回路定数)最適値の算出(2022/10/08)

上記7項の回路図に、今まで扱ってきたOCXOのElectronic Frequency Control Input (EFC)値(リファレンスPCB上にある調整用の多回転ポテンショメータの2番端子とGNDの電圧値で代用)に合わせた計算値をロジック上にテキストメモで入れていますが、それを算出した「LTspice XVII」の使い方も直ぐに忘れてしまいそうなのでメモとして残しておきます。
尚、オペアンプのLM358をSPICEモデルとして登録する必要が有り、その登録に当たっては「LTspiceでオペアンプにSPICEモデルを適用する方法」というタイトルのブログを参考にさせていただきました。

■.LTspiceの画面イメージ

ダウンロードした「lmx58_lm2904.lib」ファイルは、「Simulate」==>「Control Panel」 ==> 「Sym. & Lib. Search Paths」 ==> 「Library Search Path」画面で保管場所のパスを追記して保管しています。(上記タイトルのブログでは「LMx58_LM2904.CIR」ファイルを .include 記述して使用すると有りましたが見つからず、「lmx58_lm2904.lib」というファイル名で使ってみたら動いたので .lib ファイルを使用しています。)
値の算出は、手動で抵抗値を変更(特にVR1は都度R21とR22の値変更)してそれなりの手間がかかります。
また、接続するOCXOの入力インピーダンス相当の抵抗値を[OUT3]部分に接続して、実測値に近づけていますが計算どおりには行きません。目安です。
上手く動くかどうかは分かりませんが、今回のファイル(lmx58_lm2904.libファイルを除く)を添付しておきます。
注:抵抗のリファレンス番号は実PCBに合わせています。コンデンサ等は、他回路を流用したため飛び番となっています。

動画

約2分(BGM付)


■.LTspiceを使った試算値の一覧
Target OCXO (Trial calculation at LTspice)  R99 @LORD navicom=150k, TOYOCOM=300K
Vin=5.2V, R3=1.2Kohm, VR1=2Kohm(DACcenter5.14 to 5.29V), R2=68k    VR1canterDAC_Adj. 5.09 to 5.34V   (@R4=9.1K)
Vin=4.9V, R3=1.2Kohm, VR1=2Kohm(DACcenter4.81 to 5.00V), R2=51k    VR1canterDAC_Adj. 4.78 to 5.02V   (@R4=10K)
Vin=4.4V, R3=6.8Kohm, VR1=2Kohm(DACcenter4.29 to 4.48V), R2=47k    VR1canterDAC_Adj. 4.26 to 4.51V   (@R4=10K)
Vin=2.9V, R3=33K+690Kohm, VR1=2Kohm(DACcenter2.82 to 2.95V), R2=43k    VR1canterDAC_Adj. 2.77 to 3.01V   (@R4=10K)
Vin=2.5V, R3=27Kohm, VR1=2Kohm(DACcenter2.41 to 2.59V), R2=27K    VR1canterDAC_Adj. 2.37 to 2.62V   (@R4=10K)
R99はシミュレーション上のダミー的な抵抗値で、実在しません。(Vin=2.9V時:navicom、Vin=5.2V時:TOYOCOMの二つについては実験済み)
R3・VR1・R2 の変更によりVR1による電圧変更幅が少なくなり、合わせやすくなりました。
また、2段目のオペアンプの抵抗(R8)を以前の10KΩから約半分の5.1KΩにしたことで、DACの1LSB相当の変化量が約半分になり、分解能が向上しました。
OCXOへのEFCが5Vを超えるときは、高い方向に出力が出やすいようにR4を10Kから9.1Kに変更しています。





9.Vcon(GPSDOもどき)の動作例 (2022/10/13)

上記7・8項の変更追加でVconの動きもそれらしくなったようなので、改めて変動への自動追随のテストを実施してみました。


■.OCXO Calib.とVcon基板付きのOCXOの連結(I2C)

OCXOはnavicomを使用しています。
この基板の出力は1チャンネルしか無く、Vconに使用するときは10MHz出力の分割が必要になります。


■.GPSDOもどきとして連動中
縦置きまたは横置きで


■.出力されるログの一例

使用したOCXO(navicom)は約1か月通電を続け、7日間電源OFFの後に「GATE=1000s」で粗調整を実施し、「GATE=10^4s」で15回連動させたイメージです。
下部に白線を引いた部分(Freq, Vcon, STABLE値)を抜き出して次にグラフ化しています。


■.出力されたログのグラフ化

それぞれのOCXOの特性に合わせてコンパイルをしなくて良いように、許容幅と安定期の無視幅を加えましたが、全部をカバーできるかどうかは分かりません。
実験のために試しましたが、0.1mHz台を意識した一万秒(GATE=10^4s)で連動させる必要性は無いと思いますし、1mHz台(GATE=1000s)で連動させるだけでも十分すぎると感じています。
何より安定度を求めるときは、少なくとも一カ月程度通電を続けることが大切で、立ち上げ後短時間で安定状態に入ることは期待せず、十分な暖気期間を経て周波数を合わせることの方が必要だと思います。


■.ソフト
2022/10/16:OCXO Calib.のソースファイル  のV50_63 より公開中です。





19.その他気づいたこと

(1)その他



99.追記用

大幅な改定・追記用