アーカイブ: ブログ

昔のアナログチューナーをプチ改造、ワイドFM対応に

地元のAM放送をワイドFMで聞きたい

パソコンやスマホでAM放送を聞くこともできるのですが、アナログチューナーで聞くほうが柔らかく心地よくリスニングできるように感じます。

そこで愛用のチューナーでワイドFMが聞けるようにプチ改造することにしました。

改造内容

チューナーの方式によって多少違うようですが、基本的にはフロントエンド(アナログチューナーの場合、バリコン周り)で中間周波数にダウンクロックしているので、フロントエンドで少し受信周波数をずらしてやるだけです。

ただ、これによって、受信可能な周波数がずれてしまいますので、76MHzあたりに受信したいFM局がある場合は、この方法だけでは都合が悪いですが、私の場合、幸い受信したいFM曲で一番低い周波数が79.7MHzですので、単純に受信周波数を高い方へ2MHzほど高い方にずらしました。

アナログチューナーのバリコンを調整してワイドFMを受信

Lo-D FT-3500の場合、受信したい位置にチューニング(表示位置合わせだけ)しておいて、ブルーのコイルを慎重に回します。

シビアなので、大体合わせて、チューニング位置を合わせるくらいが合わせやすいかと思います。

ちなみに工具は高周波対応したものを使いましょう。たとえば

ホーザン コアドライバーセット D-16

新品価格
¥502から
(2019/10/4 13:52時点)

感度が低くなるかもしれません。その場合は、前段も少々さわります。右端の裸の黒いコイルを微妙に広げる感じです。

ワイドFM 南海放送91.7MHzが受信できた

無事、受信できました。

ワイドFM 南海放送91.7MHz受信できましたしっかり、ステレオになっています。ん、音もBGMとしては十分

もちろん、

FM愛媛 79.7MHz受信FM愛媛 79.7MHzも受信できます。

スケールと受信周波数がずれているのは、ご愛嬌で、許容範囲でしょう。

 

ネット小説は、目が疲れる(汗)

最近、ネット小説(主に、小説家になろう/小説を読もう)が面白いことを知って、PC画面、タブレットやKindle Fire HDX 7で、読むことが増えましたが、どうも目が疲れる。

以前からKindle Unlimitedの漫画は読んでいて、お風呂で読みたくって、防水のKindle Paperwhiteをセールで購入していたのですが、どうも漫画を読むには微妙に画面が小さく(Kindle Fire HDX 7だと吹き出しも拡大なしに読めるんだけど….)吹き出し文字がまともに読めなくってほとんど放置されていました。小説を読むならやっぱりKindle Paperwhiteかなということで、ネット小説を読めるように設定してみました。

Narou.rbで、ネット小説をKindle向けに変換、送信する。

なければプログラムの作成も考えていましたが、大変便利で完成度も高いプログラムが公開されていましたので利用させて頂きました。

Narou.rbはMITライセンスで公開されている小説家になろうなどで公開されている小説の管理、 及び電子書籍データへの変換を支援するRubyで記述されたアプリです。

Windowsでのインストールの解説は、作者の説明やwikiサイトもあるのでそちらを見ていただくと、一見めんどそうですが、以外に簡単に便利な環境が構築できると思います。ここではUbuntu(Linux)のインストール(設定)を簡単に

1.必要なプログラムを事前に入手

  • Ruby 2.5 以上(Linuxだと標準でインストール済み)
  • AozoraEpub3 – 青空文庫ePub3変換
  • Java8以上(Linuxだと標準でインストール済み)
  • KindleGen for Linux
  • Narou.rbは後ほどRubyのgemコマンドでインストールするので、事前に入手は不要

2.インストール

AozoraEpub3を適当なユーザーフォルダーに展開(解凍)

そのフォルダーにKindleGenを展開して得られるkindlegen(これ以外は消去してOK)を入れる。

narou.rbをインストール

sudo gem install narou

 

Narou.rbのインストールでエラーが出る場合

sudo gem install rubygems-update --source http://rubygems.org/
sudo update_rubygems
sudo apt install gem-dev
sudo gem install narou

3.設定

小説を管理・保管するフォルダーを作成(ユーザー権限でリード/ライトできる場所)して、narou.rbの設定をします。

mkdir MyNovel
cd MyNovel
narou init

AozoraEpub3 のフォルダの入力を求められるので、AozoraEpub3を置いたフォルダーの絶対パスを入力

5.Kindle Paperwhiteに変換した小説を転送の事前準備

転送する手段は、2つ。

USB接続で直接転送する

mtp接続できるようにする。(環境によっては、標準で対応済かも)

sudo apt-get install mtp-tools
sudo apt-get install libmtp-common
sudo apt-get install libmtp-dev
sudo apt-get install libmtp-runtime
sudo apt-get install libmtp9

メールで転送する

大きな小説は転送できない場合がありますが、設定しておくと便利と思います。

5.で作成した小説を管理するフォルダーにあるmail_setting.yamlを設定します。

設定方法は、mail_setting.yamlをテキストエディタで開くと書かれています。

Amazon側のコンテンツと端末の管理 > パーソナル・ドキュメント設定は必須ですので忘れないように

6.使ってみよう!

cd MyNovel
narou web

小説家になろうのダウンローダ&縦書き整形&管理アプリNarou.rb WEB UI

うまく動作すれば、このようにデフォルトのブラウザで表示されます。

Downloadをクリックすると「ダウンロードする小説のURL、もしくはNコードを入力(複数可)」が開くので、読みたい小説のNコードを入力します。半角スペースで区切ることで複数登録できます。「Nコードは小説家になろう/小説を読もう」の読みたい小説の目次ページのurlの最後の「nxxxxxx」の部分です。

「ダウンロード終了後にメールで送信する」にチェックを入れておくとKindleへのメール転送もしてくれます。

管理している小説をkindleへ登録するのは、USB接続の場合は、KindleをUSB接続後、右上の歯車(設定アイコン)にあるサーバーの再起動をして(接続後ファイルマネージャでKindleを開くだけでOKかも)から個別「・・・」で送信とするか、全体を送る場合は「Send」をクリックする。

メールで送る場合は、個別「・・・」で「メールで送信」をします。

終了は、narouを起動したターミナルコンソールでctrl+cとするか、右上の歯車(設定アイコン)にあるサーバーをシャットダウンをします。後者だとブラウザが閉じられますので、他のタブを開いている場合は注意が必要よなります。

環境設定で、deviceを変更することで、koboや手持ちのkindle以外の他のデバイスでも使えるようです。

使ってみて

販売されている電子書籍と同じに扱えて、辞書やハイライトもOK!、目も疲れない。

バッテリもまる1日読みぱなしでもなんとか持ちます(以外に連続使用だと持たない印象ですが、丸一日読書は疲れるので十分かな(汗))

V.M kindle / Paperwhite / Voyage / Oasis スリーブ ケース レザー [高品質高性能] 軽 薄 皮 革 キンドル ペーパーホワイト スリーブケース 純正 スリップイン スリップ カバー ペーパー ホワイト Paperwhiteケース ダーク ブラウン KindlePaperwhite 濃茶

最後に

読む小説は、必ず「小説家になろう」のマイページのブックマークに登録して、評価も返しましょう。

できれば、書籍化された作品は購入してあげてください。

Cuku クークー 封筒型 Amazon Kindle Paperwhite NEW-Kindle kindle voyage用レザーカバー 電子書保護カバー PUレザーケース 防水保護袋,グリーン

小説家になろう/小説を読もう」の最新xx部の横書きを縦書きにしてみた

読んでみるとなかなかおもしろい作品が結構ある(転生ものが多すぎるのは残念。転生の下りだけなくせば良いのにと思うものも多々)ので、気分転換に「小説家になろう/小説を読もう」で読書すること(読むだけでは悪いので、気がつけば誤字脱字はして着るようにしています。)がありますが、更新部分だけを読む場合、横書きのテキストなので、少々読みにくいと感じていたので、簡単なプログラムを作ってみました。
小説家になろうの更新を縦書きに

いつもの如く、pythonで、結果は、ブラウザで表示させる。かなり手抜きですが、日本語の小説はやっぱり縦書きが読みやすい。

ブックマークの最新xxxx部分のところのリンク先のURLをコピーして貼り付けて、Openボタンを押すと横書きを縦書きに変換してブラウザで表示します。

私の環境は、通常xubuntu環境になってしまっているので、Chromiumで表示します。Windowsの場合はブラウザコマンド部分を変更すれば同様に動作すると思います。

本当は、PDFに変換しようとか思いましたが、なかなか縦書きPDF化は面倒な様子でしたのでやめました。更新部分だけなら短いので、ブラウザの横スクロールで問題ないですしね。

ちなみに縦書きPDFをダウンロードできますが、パケットサイズを小さくするためでしょうか、これフォントを含んでいないので、環境によっては中身が表示されません。ちなみに、linuxの場合は、「Atril Document Viewer」で読むと表示崩れもなく(文字の向きがほぼ期待通りになり)快適に読めます。

pdf2ps %1 temp.ps
mv %1 %1.bk
ps2pdf14 temp.ps %1
rm temp.ps

こんな感じで、一度フォントを含んだps(ポストスクリプトファイル)に変換して、再度pdf化でフォントを含んだpdfが出来上がるのでどこでも同じように表示ができるようになります。(フォントを含む分サイズが大きくなりますが)

縦書きHTMLをpdf化するのも同様にgs(ゴーストスクリプト)を使ってpsに変換してからpdf化すると比較的簡単にできそうですが…..

 

さて、作ったプログラムは以下(pyとcssの2個からなります。)

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#

import wx
import requests
from bs4 import BeautifulSoup
import subprocess
import os

html_path = os.path.join(os.path.abspath(os.path.dirname(
    __file__)), 'tategaki.html')
otPrint = 'chromium-browser '
u_agent = {"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0"}


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 120))
        self.text_ctrl_1 = wx.TextCtrl(self, wx.ID_ANY, "")
        self.button_1 = wx.Button(self, wx.ID_OPEN, "")

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.evtURL, self.button_1)
        # end wxGlade
        # close event
        self.Bind(wx.EVT_CLOSE, self.frame_close)
        # end

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle("Tategaki")
        self.text_ctrl_1.SetToolTip("Input URL   https://xxxxx.xxxx/xxx/xxx")
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_1.Add(self.text_ctrl_1, 0, wx.ALL | wx.EXPAND, 4)
        sizer_1.Add(self.button_1, 0, wx.ALIGN_RIGHT | wx.ALL, 9)
        self.SetSizer(sizer_1)
        self.Layout()
        # end wxGlade

    def evtURL(self, event):  # wxGlade: MyFrame.<event_handler>
        url = self.text_ctrl_1.GetValue()
        res = requests.get(url, headers=u_agent)
        #print(res.encoding)
        #res.raise_for_status()
        content_type = 'UTF-8'
        soup = BeautifulSoup(res.content, 'html.parser', from_encoding=content_type)
        soup_novel_subtitle = soup.find_all('p', {'class': 'novel_subtitle'})
        soup_content0 = soup.find_all('div', {'id': 'novel_p'})
        soup_contents = soup.find_all('div', {'id': 'novel_honbun'})

        content = ''
        for l in soup_novel_subtitle:
            content += f'<p class="title">{l.get_text()}</p>'
        content += '<br><br>'
        for l in soup_content0:
            soup_content = l.find_all('p')
            for lm in soup_content:
                #content += f'<p class="honbun_p">{lm.get_text()}</p>'
                content += f'{lm}'

            content += '<br><br>'
        for l in soup_contents:
            soup_content = l.find_all('p')
            for lm in soup_content:
                #content += f'<p class="honbun">{lm.get_text()}</p>'
                content += f'{lm}'
        html = f"""<!DOCTYPE html>
<html lang="jp">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="css/tate.css">
<title>Tategaki</title>
</head>
<body>
<section class="sheet">
{content}
</section>
</body>
</html>
        """
        with open(html_path, 'wb') as fo:
            fo.write(html.encode('UTF-8'))
        cmd = otPrint + html_path
        proc = subprocess.Popen(cmd.split())
        event.Skip()

    def frame_close(self, event):  # main_MyFrame. close<event_handler>
        DialogMessage = '終了しますか?'
        dialog = wx.MessageDialog(None, DialogMessage, '確認', style=wx.YES_NO |
                                  wx.ICON_EXCLAMATION | wx.STAY_ON_TOP | wx.NO_DEFAULT)
        res = dialog.ShowModal()
        if res == wx.ID_YES:
            self.Destroy()
        else:
            dialog.Destroy()



# end of class MyFrame


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

# end of class MyApp


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

何故か文字化けが起きることがあるので、BeautifulSoupでUTF-8エンコードするように変更

ルビ表示できるように少し修正。(本文を生のHTMLで表示するようにした content += f'{lm}’)

/*
*  0.01 2019.04.20 draft
*/

* {
  margin: 0;
  padding: 0;
  color: #1f1f1f;
  font-family: IPAexMincho, TakaoExMicho, serif, helvetica, arial, sans-serif;
  font-size: 14pt;
  font-weight: normal;
  -webkit-print-color-adjust: exact;
  line-height: 2em;
}

body{
writing-mode: vertical-rl;
writing-mode: tb-rl;
-moz-writing-mode: vertical-rl;
-ms-writing-mode: tb-rl;
-ms-writing-mode: vertical-rl;
line-break: normal;
text-indent: 1ex;
text-orientation: mixed;
-epub-writing-mode: vertical-rl;
-webkit-writing-mode: vertical-rl;
-epub-line-break: normal;
-webkit-line-break: normal;
}
body title span{
  text-combine-upright: all;
  -ms-text-combine-horizontal: all;
  -webkit-text-combine: all;
}

ruby rt{
  font-size: 25%;
}
a {
  text-decoration: overline;
}
p.newpage{ page-break-before: always; }

.sheet {
  overflow: hidden;
  position: relative;
  box-sizing: border-box;
  page-break-after: always;

  height: 100vh;/*80ex;297mm;*/
  width: auto;/*100vw;40em;210mm;*/

  padding-top: 8mm;
  padding-bottom: 8mm;
  padding-left: 2ex;
  padding-right: 2ex;
}

@media screen {
  body {
    background: white;
  }
  .sheet {
    background: white;
    margin: 0mm auto;
  }
}

 

余分なコードや、全く持って洗練されていませんが、まぁ、おまけ便利ツール?ということでご容赦^_^

 

追記:(2019.04.26)

Chrome/Chromiumの拡張機能に「Nehan Reader」なるものを入れるとページ機能(広告・サイド表示を除く)がそのままで縦書き表示にできました。

こういったものまでに発展させようと思っていたんですが….

 

Xubuntu 18.04.1LTSで青空文庫などTextを縦表示する

一言でいうと

aobookをインストールします。

こちらのaobookプロジェクトページのリンクからダウンロードします。

こちらにaobookのマニュアルがあります。

ダウンロードしたファイルを適当なフォルダーに解凍して、「make」「make install」できますが、今後のUpdateを考えると

sudo add-apt-repository -y -n ppa:sicklylife/ppa 
sudo apt update 
sudo apt install aobook 

これで使用できるようになりますが、デフォルトのフォントが縦表示に対応できてないので、フォント設定をTakao明朝など縦表示に対応しているものにします。

Xubuntu 18.04.1LTSの古いPCの有線LANが遅いので無線LAN子機に変更

たまたま昔入手した「PLANEX GW-USMicroN」があったので、これを接続するとかなり実用レベルになってきたので、さらに欲張って5G帯300Mbpsの無線子機も試してみたいと物色しているところにAmazonでタイムセールを発見。

Linuxでの動作実績も確認せずに思わずポッチっと(-_-;)

Dootoper WIFI 無線LAN子機 wifi 子機 1200Mbps 無線lan アダプタ Mac/Windows対応

「PLANEX GW-USMicroN」は昔から有名で、Linuxでも刺すだけで認識して、且つ安定動作するのですが、果たしてこれは....

流石に刺しただけでは認識せず

lsusbで見ると

.....ID 0bda:b812 Realtek Semiconductor Corp.

これは何者なのか、便利な世の中です。検索するとサックと見つかりました。(英語ですが....)

「RTL8812」だそうで、こちらに有志のドライバーがありました。

これをダウンロードして、「/usr/src/rtl8822bu-4.15」に解凍します。

で、準備として、

$ sudo apt install dpkg-dev $ sudo apt install -y libelf-dev $ sudo apt install dkms

次にいよいよドライバーをビルドしてインストールです。

dkms add     rtl8822bu/4.15
dkms build   rtl8822bu/4.15
dkms install rtl8822bu/4.15

 

これでインストール完了。 USBにデバイスを挿してみるとちゃんと認識しています。

接続も期待通り、5G 11n 300Mbpsで接続できています。

ローカルネットワーク間のファイル転送速度は期待通り、これまでの2倍になりました。

ただ、CPUが遅すぎで、CPU処理速度で頭打ちされているようで、ローカルネットワークファイルの操作は快適にはなりましたが、ストリーミング配信はさほど改善は見られませんでした。

まあ、150Mbpsでの接続でも十分ストリーミングに必要な性能を十分満たしていて他のアプリを動かさなければ実用レベルで鑑賞できるので、良しとしましょう。

AD9833 DDS信号発生器 を追加

先に作成したSi5351Aクロックジェネレータを載せたシールドにAD9833 DDS信号発生器を追加しました。

これ

KKmoon DDS 信号発生器 信号発生器モジュール 0~12.5MHz 正方形/三角形/正弦波

オーディオ帯域のポテンショメータが使用されていてるので、実用周波数は1MHz+α程度ですが、簡単に方形波、三角波、正弦波が得られ、max5Vp-p(実力はせいぜい3V程度)ですが出力レベルの調整もできます。

但し、Si5351Aクロックジェネレータと同じく普通の水晶発振子が搭載されていますので、温度に対する周波数変動があります。

そこで、ヒータを搭載して温度を一定に保てるようにしました。

小型ヒータの作成

消費電力の大きい抵抗があれば、Heater代わりになるのですが、残念ながら手持ちに丁度良いものがありませんでしたので、不要となった水晶発振子を改造して小型のHeaterを作成しました。

使うのはケースだけなので、でっぱり部分をニッパーで削除して、必要に応じてやすりで削って、中身を取り出します。

発熱体となる抵抗を取り付けます。

熱伝導性に優れたグリースを詰め込んで封印します。使用したのは、ホームセンターなどで売っている極圧グラファイトグリースです。絶縁性があり熱伝導性が高い優れもので、耐熱性も200℃と高く粘度も低めで密着度も良いので、やたら高いヒートシンク取り付け用のグリースの代わりにも使用できます。

このヒーターと温度センサーを水晶発振子を挟むように取り付け、Arduinoで温度制御します。

Arduinoのレギュレータの強化

消費電力を抑えめに作ってはいますが、Heaterを駆動するとなるとArduino標準のレギュレーターにはかなりの負担になり発熱も半端でなくなります。

かなりいい加減ですが、簡易的な放熱強化をしました。

接触したらまずいところに耐熱性のあるテープを貼って、クリップでつまんだだけです。

あと整流ダイオード(逆差し防止ダイオード)も発熱するので、その裏側に小型のヒートシンク

かなり手抜きですが、これでもそれなりに機能をはたしてくれています。

外気温の影響を少なくする

ヒーターと温度計測用センサーを水晶発振子に貼り付けただけでは、外気温の影響がかなり大きいので、オシレータが乗っているシールドを保温材でくるみます。

Si5351Aクロックジェネレータ + AD9833 DDS信号発生器 制御用Arduinoスケッチ

パネルの5つのスイッチで必要な操作はすべてできるように作ってあります。

/**************************************************************************/
//v.0.1 10.10.2018 Frequency generator 1.5k...200MHz .  JrDrg
//v.0.1c 10.14.2018 + OSC calibration
//v.0.2c 10.15.2018 PLL clock Improvement + new Algorithm
//v.0.3  10.16.2018 ch2 10MHz fix
//v.0.3d 10.24.2018 Tuning、 bugFix
//v.0.4  10.24.2018 +AD9833 WaveGen (DDS)1-1.2MHz Practical use:-800kHz
//v.0.88 11.03.2018 High precision(8bits->16bits) temperature control
//v.0.89 11.03.2018 Change WG Panel+Button,+ Step operation
//v.0.91 11.05.2018 add Sirial cntrol
//v.0.92 11.06.2018 freq High precision : float -> long
//v.0.94 11.08.2018 キャリブレーション方法の変更
//v.0.95 11.08.2018 si5351ライブラリの変更 PG:4k-225MHz
/**************************************************************************/
#include <Wire.h>
#include <SPI.h>
#include <LiquidCrystal.h>
#include <avr/io.h>
#include <avr/wdt.h>
/*
   si5351.h - Si5351 library for Arduino
   https://github.com/etherkit/Si5351Arduino
*/
#include <si5351.h>
Si5351 si5351;
//◆ Siral Debug 0:off 1:on 1<:manual check
byte debug = 0;
int SGmenu = 0;//0:Top 1:Pulse 2:Wave
// select SW long Push > 5s => Top menu
const int loopwait = 100;
int chrcnt = 0; //Sirial command I/F Chra count
uint8_t input[33];
int cmdphase = 0; //Sirial command phase
long Num;
int64_t NumL;
// heater setup
#define ht_pin 9  // pin 3 -> 9(16bits PB1)
// Temp ctrl start time
int initwait = 3000; // about 5minits [3000*loopwait]
// Temp Transient characteristics
const int invtimerlimit_base = 5000; // about 5s Temp ctrl interval
int invtimerlimit = invtimerlimit_base;
int invtimer = 0;
int temploop = 0;
int anlg1;
int anlg2; //WaveGen OSC Temp
float temp;
float temp2;
float temp2_old = 0;
float dtemp;
//◆ Target oven temp
const int terget_temp_base = 52; //simple oven temp
int terget_temp = terget_temp_base;
int heater = 160;  //heater init set 150-205 <0-1023>
/* Key setup */
int lcd_key     = 0;
int adc_key_in  = 0;
int adc_key_in2  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
#define waveSELECT 6
const int lcdLED = 10;
LiquidCrystal lcd(8, 3, 4, 5, 6, 7);//9->3
/*AD9833 WaveGen (DDS) Setup*/
const int SINE = 0x2000;                    // Define AD9833's waveform register value.
const int SQUARE = 0x2028;                  // When we update the frequency, we need to
const int TRIANGLE = 0x2002;                // define the waveform when we end writing.
int wave = 0;
int waveType = SINE;
//◆ Set wave generator initial frequency.
unsigned long  freq10 = 10000000; //freq * 10
const long freqTRmax = 1200000;  // max 1.2MHz(Practical 1MHz)
const long freqSQmax = 1200000;  // max 1.2MHz(Practical 500kHz)
const long freqSImax = 6000000;  // max 5MHz(Practical 5MHz)
unsigned long freqMAX = freqSImax;
//◆ Wave generator OSC clock
const long refFreq_base = 250003334;        // On-board crystal reference frequency 25000318.4*10
unsigned long refFreq10 = refFreq_base;
const int FSYNC = 2;                        // Standard SPI pins for the AD9833 waveform generator. 10=>2
const int CLK = 13;                         // CLK and DATA pins are shared with the TFT display.
const int DATA = 11;
const int CS = A3;                          //MCP41010 potentiometer 9=>A3(17)
// digital pot values for differant wave forms to maintain constant output level(1-255)
const int TRPOTmax = 220;
const int SQPOTmax = 29;
const int  SIPOTmax = 220;
int TRPOT = 126;
int SQPOT = 15;
int SIPOT = 126;
int exPOTmax = SIPOTmax;
int exPOT = SIPOT;
// potmode for determining digipot setting
byte potVal = exPOT;
int adc_key_wait = 40;
int read_LCD_buttons()
{
  // take measures to chattering
  adc_key_in = analogRead(0);
  delay(adc_key_wait);
  adc_key_in2 = analogRead(0);
  if (abs(adc_key_in - adc_key_in2) > 2) return btnNONE;
  // For V1.1 us this threshold
  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
  // For V1.1 us this threshold
  if (adc_key_in < 50)   return btnRIGHT;
  if (adc_key_in < 250)  return btnUP;
  if (adc_key_in < 350)  return btnDOWN;
  if (adc_key_in < 550)  return btnLEFT;
  if (adc_key_in < 850)  return btnSELECT;
  return btnNONE;
}
void wait_btnNONE()
{
  lcd_key = read_LCD_buttons();  // read the buttons
  while (lcd_key != btnNONE) {
    lcd_key = read_LCD_buttons();  // read the buttons
  }
}
//◆ Pulse Gen OSC Normal: oscf=25000000
const long oscf_base = 25000000 ;  //base OSC frequency
const long osc_devi_stnd = 56990 ;            // (偏差/10MHz) ppm
long pcal_devi = osc_devi_stnd ;
//Ref Volt
float Vref;
//◆ Pulse Generator Initial Output Clock Freq
uint64_t outF = 25000000 ;// initial frequency in Hz
const long outF2 = 10000000; //ch2: Fix frequency
uint32_t stpx; //freq freq step size
uint8_t changed_f = 1;
char msg10[10];
char s8[8];
/**************************************/
/* Calc Pram & Displays the frequency             */
/**************************************/
void count_frequency()
{

  /**************************************/
  /* Displays the PG frequency          */
  /**************************************/
  lcd.clear();
  lcd.setCursor(0, 0);

  char msg[12];
  char s[9];
  char msg4[5];
  char s4[2];
  sprintf(msg, "PG %s", dtostrf((outF / 10), 8, 0, s));
  sprintf(msg4, "%s Hz", dtostrf((outF % 10), 1, 0, s4));
  lcd.print(msg);
  lcd.print(msg4);

  display_stpx();
}
/**************************************/
/* Display the frequency change step */
/**************************************/
void display_stpx()
{
  lcd.setCursor(0, 1);
  lcd.print("Stp:");
  switch (stpx)
  {
    case 10:
      lcd.print("  10");
      break;
    case 100:
      lcd.print(" 100");
      break;
    case 1000:
      lcd.print("  1k");
      break;
    case 10000:
      lcd.print(" 10k");
      break;
    case 100000:
      lcd.print("100k");
      break;
    case 1000000:
      lcd.print("  1M");
      break;
    case 10000000:
      lcd.print(" 10M");
      break;
  }
  lcd.print("Hz ");
}
void setup_PGfreq0()
{
  //◆ display setup param
  if (changed_f > 0) {
    count_frequency();
    uint64_t outF_100 = outF;
    outF_100 = outF_100 * 100;
    si5351.output_enable(SI5351_CLK0, 0);
    delay(10);
    si5351.set_correction(pcal_devi, SI5351_PLL_INPUT_XO);
    si5351.set_ms_source(SI5351_CLK0, SI5351_PLLA);
    si5351.set_freq(outF_100, SI5351_CLK0);
    si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
    si5351.output_enable(SI5351_CLK0, 1);
    si5351.update_status();
  }
}
void setup_PGfreq2()
{
  uint64_t outF2_100 = outF2;
  outF2_100 = outF2_100 * 100;
  si5351.output_enable(SI5351_CLK2, 0);
  delay(10);
  si5351.set_correction(pcal_devi, SI5351_PLL_INPUT_XO);
  si5351.set_ms_source(SI5351_CLK2, SI5351_PLLB);
  si5351.set_freq(outF2_100, SI5351_CLK2);
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
  si5351.output_enable(SI5351_CLK2, 1);
  si5351.update_status();
}
/**************************************/
/* Display the WG frequency           */
/**************************************/
void display_WGfreq()
{
  lcd.setCursor(0, 0);

  char msg[19];
  char s[13];
  //  sprintf(msg, "WG %s Hz", dtostrf(freq, 9, 1, s));
  switch (waveType)
  {
    case SINE: {
        sprintf(msg, "Sin %s Hz", dtostrf((freq10 / 10.0), 9, 1, s));
        break;
      }
    case TRIANGLE: {
        sprintf(msg, "Tri %s Hz", dtostrf((freq10 / 10.0), 9, 1, s));
        break;
      }
    case SQUARE: {
        sprintf(msg, "Squ %s Hz", dtostrf((freq10 / 10.0), 9, 1, s));
        break;
      }
  }
  lcd.print(msg);
}
void display_WG()
{

  lcd.clear();
  display_WGfreq();
  char msg5[5];
  char ss[4];

  lcd.setCursor(0, 1);
  sprintf(msg5, "x%s ", dtostrf(exPOT, 3, 0, ss));
  lcd.print(msg5);
  lcd.setCursor(5, 1);
  switch (stpx)  //WG stpx=step*10
  {
    case 1:
      lcd.print(" 0.1");
      break;
    case 100:
      lcd.print("10Hz");
      break;
    case 10000:
      lcd.print("1kHz");
      break;
    case 1000000:
      lcd.print("100k");
  }
}
// AD9833 documentation advises a 'Reset' on first applying power.
void AD9833reset() {
  WriteRegister(0x100);   // Write '1' to AD9833 Control register bit D8.
  delay(10);
}
// Set the frequency and waveform registers in the AD9833.
void AD9833setFrequency(unsigned long frequency10, int Waveform) {

  unsigned long FreqWord  = ((frequency10 * pow(2, 28)) / refFreq10 );

  int MSB = (int)((FreqWord & 0xFFFC000) >> 14);    //Only lower 14 bits are used for data
  int LSB = (int)(FreqWord & 0x3FFF);

  //Set control bits 15 ande 14 to 0 and 1, respectively, for frequency register 0
  LSB |= 0x4000;
  MSB |= 0x4000;

  WriteRegister(0x2100);
  WriteRegister(LSB);                  // Write lower 16 bits to AD9833 registers
  WriteRegister(MSB);                  // Write upper 16 bits to AD9833 registers.
  WriteRegister(0xC000);               // Phase register
  WriteRegister(Waveform);             // Exit & Reset to SINE, SQUARE or TRIANGLE
}
void WriteRegister(int dat) {

  // Display and AD9833 use different SPI MODES so it has to be set for the AD9833 here.
  SPI.setDataMode(SPI_MODE2);

  digitalWrite(FSYNC, LOW);           // Set FSYNC low before writing to AD9833 registers
  delayMicroseconds(10);              // Give AD9833 time to get ready to receive data.

  SPI.transfer(highByte(dat));        // Each AD9833 register is 32 bits wide and each 16
  SPI.transfer(lowByte(dat));         // bits has to be transferred as 2 x 8-bit bytes.

  digitalWrite(FSYNC, HIGH);          //Write done. Set FSYNC high
}
void MCP41010Write(byte value)
{
  // Note that the integer vale passed to this subroutine
  // is cast to a byte

  digitalWrite(CS, LOW);
  delayMicroseconds(10);
  SPI.transfer(B00010001); // This tells the chip to set the pot
  SPI.transfer(value);     // This tells it the pot position
  delay (1);
  digitalWrite(CS, HIGH);
}
/*
  電源電圧の読出
  2014/7/19 ラジオペンチ
  http://radiopench.blog96.fc2.com/
*/
float cpuVcc() {                     // 電源電圧(AVCC)測定関数
  long sum = 0;
  adcSetup(0x4E);                    // Vref=AVcc, input=internal1.1V
  for (int n = 0; n < 10; n++) {
    sum = sum + adc();               // adcの値を読んで積分
  }
  return (1.1 * 10240.0) / sum;      // 電圧を計算して戻り値にする
}
void adcSetup(byte data) {           // ADコンバーターの設定
  ADMUX = data;                      // ADC Multiplexer Select Reg.
  ADCSRA |= ( 1 << ADEN);            // ADC イネーブル
  ADCSRA |= 0x07;                    // AD変換クロック CK/128
  delay(10);                         // 安定するまで待つ
}
unsigned int adc() {                 // ADCの値を読む
  unsigned int dL, dH;
  ADCSRA |= ( 1 << ADSC);            // AD変換開始
  while (ADCSRA & ( 1 << ADSC) ) {   // 変換完了待ち
  }
  dL = ADCL;                         // LSB側読み出し
  dH = ADCH;                         // MSB側
  return dL | (dH << 8);             // 10ビットに合成した値を返す
}
// Array to Numeric conversion
int64_t covNum() {                      // 入力値の10倍を返す。小数点以下は1桁以内限定
  uint8_t  asc;
  uint32_t ascN;
  int64_t NumL = 0;

  for (int8_t i = 0 ; i < chrcnt ; i++)
  {
    asc = input[i];
    if ((asc != '.') && (asc != '-') && (asc != ' ')) {
      if ((asc >= 0x30) && (asc < 0x3A)) {
        ascN = (uint32_t)(asc - 0x30);
        NumL = (uint32_t)(NumL * 10ULL);
        NumL = (uint32_t)(NumL + ascN);
      } else NumL = 0;
    }
  }

  if (input[chrcnt - 2] != '.') NumL = (uint32_t)(NumL * 10UL);
  if (input[0] == '-') NumL = -1 * NumL;
  return (int64_t)NumL;
}
// Software reset
void software_reset() {
  wdt_disable();
  wdt_enable(WDTO_15MS);
  while (1) {}
}
void setup(void)
{
  lcd.begin(16, 2);
  // PG
  Wire.begin();
  // WG AD9833
  SPI.begin();
  Serial.begin(115200);
  delay(1000);
  Serial.println("******************");
  Serial.println("*    1-1.2MHz WG *");
  Serial.println("*   4k-220MHz PG *");
  Serial.println("******************");
  Serial.println("hello");
  if (debug) {
    Serial.print("*  Debug mode :");
    Serial.print(debug);
    Serial.println(" *");
    Serial.print("*  SGmenu :");
    Serial.print(SGmenu);
    Serial.println("     *");
    Serial.println("");
  }

  /*
    電源電圧の読出
    2014/7/19 ラジオペンチ
    http://radiopench.blog96.fc2.com/
  */
  Vref  = cpuVcc();                   // 電源電圧測定

  // Heater pin
  pinMode(ht_pin, OUTPUT);
  //10bit高速PWM
  TCCR1A = 0b10000011;
  TCCR1B = 0b00001001;
  OCR1A = (unsigned int)( 1023 * (heater / 1023.0));
  //  PORTB = 0b00000010; //pul up


  pinMode(lcdLED, OUTPUT);

  // Startting Tittle
  lcd.setCursor(0, 0);
  //  lcd.print("* WG:  0.1-1.2MHz*");
  lcd.print("WG-1.2M//PG-200M");
  lcd.setCursor(0, 1);
  //  lcd.print("* PG:  4k-225MHz *");
  lcd.print("18.1189 by JrDrg");


  // pulse gen setup init clock
  // initialize the Si5351
  bool i2c_found;
  i2c_found = si5351.init(SI5351_CRYSTAL_LOAD_10PF, 0 , 0 );
  if (!i2c_found)
  {
    lcd.setCursor(0, 1);
    lcd.print(" PG clockgen error");
    while (1);
  }
  si5351.reset();
  delay(500);
  si5351.init(SI5351_CRYSTAL_LOAD_10PF, 0 , 0 );

  /*
     set_correction(int32_t corr, enum si5351_pll_input ref_osc)
     corr - Correction factor in ppb
     ref_osc - Desired reference oscillator
         (use the si5351_pll_input enum)
     ref_osc - Desired reference oscillator
         0: crystal oscillator (XO)
         1: external clock input (CLKIN)
  */
  changed_f = 1;
  /* Enable the clocks & Set Freq */
  setup_PGfreq0();
  setup_PGfreq2();

  // Wave generator Startup
  pinMode (CS, OUTPUT);
  pinMode (FSYNC, OUTPUT);


  AD9833reset();                                   // Reset AD9833 module after power-up.
  delay(500);
  AD9833setFrequency(freq10, waveType);                  // Set the frequency and wave type
  delay(5);
  MCP41010Write (potVal);             //Set Potentionmeter

  if (debug) {
    Serial.print(" potVal :");
    Serial.println(potVal);
  }


  // temp dummy read
  anlg1 = analogRead(1) ;
  anlg2 = analogRead(2) ;
  delay(3000);  //3seconds
  anlg2 = 0;
  for (int i = 1; i <= 20; i++) {
    anlg2 = anlg2 + analogRead(2) ;
    delay(5);
  }
  anlg2 = anlg2 / 20;

  temp2_old = ((Vref * anlg2) / 1024 / 2.907) * 100 - 60 ;
}
void loop(void)
{

  switch (SGmenu) {
    case 0: {
        //★★ Top Menu
        // SGmenu = 1;//0:Top 1:Pulse 2:Wave
        // select SW long Push > 5s => Top menu
        //        lcd.clear();
        lcd.setCursor(0, 0);

        char msg[19];
        char s[13];
        switch (waveType)
        {
          case SINE: {
              sprintf(msg, "SI   %s  ", dtostrf(freq10 / 10.0, 9, 1, s));
              break;
            }
          case TRIANGLE: {
              sprintf(msg, "TR   %s  ", dtostrf(freq10 / 10.0, 9, 1, s));
              break;
            }
          case SQUARE: {
              sprintf(msg, "SQ   %s  ", dtostrf(freq10 / 10.0, 9, 1, s));
              break;
            }
        }
        lcd.print(msg);

        lcd.setCursor(0, 1);

  char msg4[5];
  char s4[2];
  sprintf(msg, "PG %s", dtostrf((outF / 10), 8, 0, s));
  sprintf(msg4, "%s Hz", dtostrf((outF % 10), 1, 0, s4));
  lcd.print(msg);
  lcd.print(msg4);
//        sprintf(msg, "PG %s  Hz", dtostrf(outF, 9, 0, s));
//        lcd.print(msg);

        lcd_key = read_LCD_buttons();  // read the buttons
        switch (lcd_key)               // depending on which button was pushed, we perform an action
        {
          case btnUP:
            {
              SGmenu = 2;
              lcd.setCursor(0, 1);
              changed_f = 1;
              stpx = 1;
              lcd.print("                  ");
              lcd.setCursor(14, 0);
              lcd.print("Hz");
              wait_btnNONE();
              break;
            }
          case btnDOWN:
            {
              SGmenu = 1;
              stpx = 10;
              lcd.setCursor(0, 0);
              lcd.print("                  ");
              changed_f = 1;
              wait_btnNONE();
              break;
            }
        }

      }
    case 1:
      {
        //★★ PULSE GENERATER
        /**************************************/
        /* Change the frequency              */
        /* btnUP     Increment               */
        /* btnDOWN   Decrement               */
        /* btnRIGHT  Increment Tweak         */
        /* btnLEFT   Decrement Tweak         */
        /**************************************/

        lcd_key = read_LCD_buttons();  // read the buttons

        switch (lcd_key)               // depending on which button was pushed, we perform an action
        {
          case btnUP:
            {
              outF += stpx;
              changed_f = 1;
              break;
            }
          case btnDOWN:
            {
              outF -= stpx;
              changed_f = 1;
              break;
            }
          case btnRIGHT:
            {
              outF += 1;
              changed_f = 1;
              break;
            }
          case btnLEFT:
            {
              outF -= 1;
              changed_f = 1;
              break;
            }
        }

        // btnSELECT frequency step Renge

        if (lcd_key == btnSELECT) // check long push (Return SGmenu)
        {
          digitalWrite(lcdLED, HIGH);
          // check btnSELECT > 5s
          for (int i = 1; i <= int(2500 / adc_key_wait); i++) {
            lcd_key = read_LCD_buttons();  // read the buttons
            if (lcd_key != btnSELECT) break;
            delay(adc_key_wait);
          }
          if (lcd_key != btnSELECT) lcd_key = btnSELECT;
          else SGmenu = 0;
        }
        if (SGmenu != 1) break;

        if (lcd_key == btnSELECT)
        {
          switch (stpx)
          {
            case 10:
              stpx = 100;
              break;
            case 100:
              stpx = 1000;
              break;
            case 1000:
              stpx = 10000;
              break;
            case 10000:
              stpx = 100000;
              break;
            case 100000:
              stpx = 1000000;
              break;
            case 1000000:
              stpx = 10000000;
              break;
            case 10000000:
              stpx = 10;
              break;
          }
          display_stpx();
        }

        if ((changed_f > 0) && !(debug > 1))
        {
          // check over renge
          if (outF > 225000000)
            outF = 4000;
          if (outF < 4000)
            outF = 225000000;
          if (outF < 0)
            outF = 225000000;

          setup_PGfreq0();
          changed_f = 0;

        }

        break;

      }
    case 2: {
        //★★ Wave Generator


        /*************************************/
        /* Change the frequency & potention  */
        /* btnUP     freq Increment          */
        /* btnDOWN   freq Decrement          */
        /* btnRIGHT  pot Increment           */
        /* btnLEFT   pot Decrement           */
        /* btnSELECT Change Wave             */
        /*************************************/
        lcd_key = read_LCD_buttons();  // read the buttons
        while ((lcd_key == btnUP) || (lcd_key == btnDOWN)) {
          switch (lcd_key)               // depending on which button was pushed, we perform an action
          {
            case btnUP:
              {
                freq10 += stpx;
                break;
              }
            case btnDOWN:
              {
                freq10 -= stpx;
                break;
              }
          }
          changed_f = 1;
          // check over renge
          if (freq10 > freqMAX)freq10 = 1; else if (freq10 <= 0)freq10 = freqMAX * 10;
          //          if (freq > 1000000) freq = long(freq / 10) * 10;

          AD9833setFrequency(freq10, waveType);                  // Set the frequency and wave type
          display_WGfreq();
          changed_f = 0;
          delay(adc_key_wait);
          lcd_key = read_LCD_buttons();  // read the buttons
        }
        switch (lcd_key)               // depending on which button was pushed, we perform an action
        {
          case btnRIGHT:
            {
              exPOT += 1;
              changed_f = 1;
              break;
            }
          case btnLEFT:
            {
              exPOT -= 1;
              changed_f = 1;
              break;
            }
        }
        // check over renge

        if (changed_f > 0){
          if (freq10 > freqMAX * 10)freq10 = 1; else if (freq10 <= 0)freq10 = freqMAX * 10;
          if (exPOT > exPOTmax)exPOT = 0 ; else if (exPOT < 0)exPOT = exPOTmax;
          
          potVal = exPOT;
          MCP41010Write (potVal);             //Set Potentionmeter
          display_WG();
          changed_f = 0;
        }
        // btnSELECT WaveType

        if (lcd_key == btnSELECT)  // check long push (Return SGmenu)
        {
          uint32_t t_cont;
          digitalWrite(lcdLED, HIGH);
          // check btnSELECT > 5s
          for (t_cont = 1; t_cont <= int(2500 / adc_key_wait); t_cont++) {
            lcd_key = read_LCD_buttons();  // read the buttons
            if (lcd_key != btnSELECT) break;
            if (t_cont > int(2500 / adc_key_wait / 2)) {
              lcd.setCursor(0, 0);
              lcd.print("***");
            }
            delay(adc_key_wait);
          }
          if (debug) {
            Serial.println(t_cont);
          }
          if (lcd_key != btnSELECT) {
            if (t_cont > int(2500 / adc_key_wait / 2))lcd_key = waveSELECT; else lcd_key = btnSELECT;
          }
          else {
            SGmenu = 0;
            break;
          }
          if (SGmenu !=0){
          if (lcd_key == btnSELECT)
          {
            changed_f = 1;
            switch (stpx)
            {
              case 1:
                stpx = 100;
                break;
              case 100:
                stpx = 10000;
                break;
              case 10000:
                stpx = 1000000;
                break;
              case 1000000:
                stpx = 1;
            }
          }
          if ((lcd_key == waveSELECT) && (freq10 <= freqSQmax * 10)) { //freqSQmax >> other max
            changed_f = 1;
            switch (waveType)
            {
              case SINE: {
                  SIPOT = exPOT;
                  waveType = TRIANGLE;
                  freqMAX = freqTRmax;
                  exPOTmax = TRPOTmax;
                  exPOT = TRPOT;
                  break;
                }
              case TRIANGLE: {
                  TRPOT = exPOT;
                  waveType = SQUARE;
                  freqMAX = freqSQmax;
                  exPOTmax = SQPOTmax;
                  exPOT = SQPOT;
                  break;
                }
              case SQUARE: {
                  SQPOT = exPOT;
                  waveType = SINE;
                  freqMAX = freqSImax;
                  exPOTmax = SIPOTmax;
                  exPOT = SIPOT;
                  break;
                }
            }
            AD9833setFrequency(freq10, waveType);                  // Set the frequency and wave type
          }
          }
        }
        if (SGmenu != 2) break;

        if (changed_f)
        {
          display_WG();
          changed_f = 0;
        }
        break;

      }

  }
  /* heater control
  */
  // get PG OSC temp
  anlg1 = 0;
  for (int i = 1; i <= 20; i++) {
    anlg1 = anlg1 + analogRead(1) ;
    delay(5);
  }
  anlg1 = anlg1 / 20;
  temp = ((Vref * anlg1) / 1024) * 100 - 60 ;


  // get WG OSC temp2
  anlg2 = 0;
  for (int i = 1; i <= 20; i++) {
    anlg2 = anlg2 + analogRead(2) ;
    delay(5);
  }
  anlg2 = anlg2 / 20;

  temp2 = ((Vref * anlg2) / 1024 / 2.907) * 100 - 60 ; // OpAmp gain: x2.907

  if (abs(terget_temp - temp2 ) < 1)initwait = 0; // 設定温度に近い場合、温度制御を強制スタート

  if (initwait > 0) initwait -= 1; // Temp ctrl start waiting
  else {

    if ((terget_temp - temp2) < -0.8)invtimerlimit = invtimerlimit_base / 4; else invtimerlimit = invtimerlimit_base;
    if ((invtimer * loopwait) > invtimerlimit) {
      invtimer = 0;

      if (abs(terget_temp - temp2 ) <= 0.4) heater += 0;
      else if (((terget_temp - temp2 ) > 4) && (heater < 984 ) && (temp2 <= temp2_old)) {
        heater += 40;
        temploop = 0;
      }
      else if (((terget_temp - temp2 ) < -3) && (heater > 19 ) && (temp2 >= temp2_old)) {
        heater -= 20;
        temploop = 0;
      }
      else if (abs(temp2 - temp2_old) <= 0.4) {
        heater += 0;
        temploop += 1;
        if (temploop > 5) {
          temploop = 0;
          if ((terget_temp - temp2) > 0) heater += 1; else {
            if ((terget_temp - temp2) <= 2)heater -= 1; else heater -= 2;
          }
        }
      }
      else if (((terget_temp - temp2) > 0) && (heater < 1023) && (temp2 <= temp2_old)) {
        heater = heater += 1;
        temploop = 0;
      }
      else if (((terget_temp - temp2) < 0) && (heater > 0) && (temp2 >= temp2_old)) {
        heater -= 1;
        temploop = 0;
      }
      temp2_old = temp2;

      //     set heater PWM Duty(heater/1023.0)
      OCR1A = (unsigned int)( 1023 * (heater / 1023.0));

    }
    invtimer += 1;

  }
  if (debug) {
    Serial.print("** ");
    Serial.print(Vref);
    Serial.print("V ");
    Serial.print(initwait);
    Serial.print("  ");
    Serial.print(invtimer);
    Serial.print("  ");
    Serial.print(temploop);
    Serial.print("  ");
    Serial.print(heater);
    Serial.print("  ");
    Serial.print(temp2_old);
    Serial.print("℃ ");
    Serial.print(temp2);
    Serial.print("℃ ");
    Serial.print(temp);
    Serial.println("℃ ");
  }

  /*
     温度が設定値に近づくとバックライトが点灯
     温度が異常だとバックライトが点滅
     温度がほぼ設定値になるまで、温度差を点滅表示
  */
  dtemp = temp2 - terget_temp ;
  if (abs(dtemp) < 0.8)  digitalWrite(lcdLED, HIGH); else {
    if (dtemp < 5) {
      if ((initwait % 2) > 0) {
        digitalWrite(lcdLED, LOW);
      } else {
        digitalWrite(lcdLED, HIGH);
      }
    } else {
      //     set heater PWM Duty 0% = Power down
      OCR1A = (unsigned int)( 1023 * (0 / 1023.0));
      digitalWrite(lcdLED, LOW);
    }
  }

  if (SGmenu > 0) {
    temp = (temp2 + temp) / 2;
    char msgT[5];
    char sT[5];
    lcd.setCursor(11, 1);
    if (initwait > 0) {
      if ((initwait % 2) > 0) {
        lcd.print("     ");

      } else {
        sprintf(msgT, " %s0", dtostrf(dtemp, 2, 2, sT));
        lcd.print(msgT);
      }
    } else {
      if (((invtimer % 2) > 0) && (abs(dtemp) > 0.4)) {
        lcd.print("     ");

      } else {
        sprintf(msgT, "%s", dtostrf(int(heater / 1023.0 * 100.0), 3, 0, sT));
        lcd.print(msgT); lcd.print('%');
      }
    }
  }

  //Sirial command I/F chrcnt
  /*
     cmdphase
     1:w(WG)      5:<freq:0.1-1200000.0>    6:<POT:0-127>  7:i(sin)
                                                            :r(tri)
                                                            :q(squ)
     2:p(PG)      10:<freq:1-205000000>
     3:t(temp) 11:<Target_temp:40-55>  12:<heater:0-1023>
     4:g(get)  return status
        PG freq,WG freq,WG waveform,WG pot,temp2,temp,heater,target temp
     13:c(WG calibration)      16:<+-freq/10:-500~+500>
     14:C(PG calibration)      17:<+-freq/10:-500~+500>
     18:r(reboot)
     19:D(set Direct PG parameter) ....opt.
     20:d(Dump Direct PG parameter)
     21:h(hello)  for Sirial Command Sync
     0:No command
     [\]:end mark
  */
  if (Serial.available()) {
    input[chrcnt] = Serial.read();
    if (chrcnt > 32 || input[chrcnt] == '\\') {
      //      chrcnt=chrcnt-1;                            // ignore last'Y'
      switch (cmdphase) {
        case 0: {
            switch (input[0]) {
              case 'h': {
                  Serial.println("Hi! my Master (^^) ");
                  cmdphase = 0;
                  break;
                }
              case 'w': {
                  Serial.println("WG");
                  cmdphase = 5;
                  break;
                }
              case 'p': {
                  Serial.println("PG");
                  cmdphase = 10;
                  break;
                }
              case 't': {
                  Serial.println("TEMP");
                  cmdphase = 11;
                  break;
                }
              case 'g': {
                  Serial.print("Status: ");
                  Serial.print((uint32_t)outF);
                  Serial.print(" ");
                  Serial.print(freq10 / 10, DEC); Serial.print("."); Serial.print((freq10 % 10), DEC);
                  Serial.print(" ");
                  Serial.print(waveType);
                  Serial.print(" ");
                  Serial.print(exPOT);
                  Serial.print(" ");
                  Serial.print(temp2, 1);
                  Serial.print(" ");
                  Serial.print(temp, 1);
                  Serial.print(" ");
                  Serial.print(heater);
                  Serial.print(" ");
                  Serial.println(terget_temp,1);
                  //Serial.print(" ");
                  //Serial.print(refFreq10 / 10, DEC); Serial.print("."); Serial.print((refFreq10 % 10), DEC);
                  //Serial.print(" ");
                  //Serial.println( pcal_devi );
                  cmdphase = 0;
                  break;
                }
              case 'c': {
                  Serial.println("WG");
                  cmdphase = 16;
                  break;
                }
              case 'C': {
                  Serial.println("PG");
                  cmdphase = 17;
                  break;
                }
              case 'r': {
                  Serial.println("Rboot");
                  software_reset();
                  cmdphase = 0;
                  break;
                }
              case 'D': {
                  Serial.println("OPT");
                  cmdphase = 0; //19
                  break;
                }
              case 'd': {
                  Serial.print("PG: ");
                  si5351.update_status();
                  Serial.print("SYS_INIT: ");
                  Serial.print(si5351.dev_status.SYS_INIT);
                  Serial.print("  LOL_A: ");
                  Serial.print(si5351.dev_status.LOL_A);
                  Serial.print("  LOL_B: ");
                  Serial.print(si5351.dev_status.LOL_B);
                  Serial.print("  LOS: ");
                  Serial.print(si5351.dev_status.LOS);
                  Serial.print("  REVID: ");
                  Serial.println(si5351.dev_status.REVID);
                  cmdphase = 0;
                  break;
                }
              default: {
                  Serial.println("What? ");
                  cmdphase = 0;
                  break;
                }
            }
            break;
          }
        case 5: {
            Num = covNum();
            Serial.println("WG2");
            //Serial.print(Num / 10, DEC); Serial.print("."); Serial.println((Num % 10), DEC);
            if ((Num > 0) && (Num < 120000001)) {
              freq10 = Num;
              cmdphase = 6;
              changed_f = 0;
            } else {
              cmdphase = 0;
            }
            break;
          }
        case 6: {
            Num = covNum();
            //Serial.print(Num / 10, DEC);
            Serial.println("WG3");
            if ((Num >= 0) && (Num < 2550)) {
              exPOT = Num / 10;
              //Serial.println(exPOT);
              switch (waveType)
              {
                case SINE: {
                    SIPOT = exPOT;
                    break;
                  }
                case TRIANGLE: {
                    TRPOT = exPOT;
                    break;
                  }
                case SQUARE: {
                    SQPOT = exPOT;
                    break;
                  }
              }
            }
            changed_f = 0;
            cmdphase = 7;
            break;
          }
        case 7: {
            Serial.println("WG4");
            switch (input[0]) {
              case 'i': {
                  waveType = SINE;
                  changed_f = 1;
                  break;
                }
              case 'r': {
                  waveType = TRIANGLE;
                  changed_f = 1;
                  break;
                }
              case 'q': {
                  waveType = SQUARE;
                  changed_f = 1;
                  break;
                }
              default: {
                  changed_f = 0;
                  break;
                }
            }
            if (changed_f = 1) {
              switch (waveType)
              {
                case TRIANGLE: {
                    freqMAX = freqTRmax;
                    exPOTmax = TRPOTmax;
                    break;
                  }
                case SQUARE: {
                    freqMAX = freqSQmax;
                    exPOTmax = SQPOTmax;
                    break;
                  }
                case SINE: {
                    freqMAX = freqSImax;
                    exPOTmax = SIPOTmax;
                    break;
                  }
              }
              AD9833setFrequency(freq10, waveType);  // Set the frequency and wave type
              potVal = exPOT;
              //Serial.println(exPOT);
              MCP41010Write (potVal);             //Set Potentionmeter
              display_WG();
              changed_f = 0;
              cmdphase = 0;
            }
            break;
          }
        case 10: {
            NumL = (int64_t)covNum();
            if ((NumL >= 40000) && (NumL <= 2250000000)) {
              outF = (int64_t)(NumL / 10);
              //Serial.print((uint32_t)outF);
              Serial.println("PG");
              changed_f = 1;
              setup_PGfreq0();
              changed_f = 0;
            }
            cmdphase = 0;
            break;
          }
        case 11: {
            Num = covNum();
            if ((Num > 399) && (Num < 551)) {
              //Serial.print(Num / 10, DEC);
              Serial.println("TG");
              terget_temp = Num / 10;
              cmdphase = 12;
            } else {
              cmdphase = 0;
            }
            break;
          }
        case 12: {
            Num = covNum();
            if ((Num >= 0) && (Num < 10231)) {
              //Serial.print(Num / 10, DEC);
              Serial.println("HT");
              heater = Num / 10;
              OCR1A = (unsigned int)( 1023 * (heater / 1023.0));
            }
            cmdphase = 0;
            break;
          }
        case 16: {
            Num = covNum();
            if ((Num > -2000001) && (Num < 2000001)) {
              Serial.println("WG");
              //Serial.print(Num / 10, DEC); Serial.print("."); Serial.println(abs(Num % 10), DEC);
              refFreq10 = refFreq_base + Num;
              changed_f = 1;
              AD9833setFrequency(freq10, waveType);  // Set the frequency and wave type
              display_WG();
              changed_f = 0;
            }
            cmdphase = 0;
            break;
          }
        case 17: {
            Num = covNum();
            if ((Num > -2000001) && (Num < 2000001)) {
              Serial.println("PG");
              //Serial.print(Num / 10, DEC); Serial.print("."); Serial.println(abs(Num % 10), DEC);
              //    クロック偏差をppmで入力。100MHzを偏差0.1まで計測、差分を設定
              pcal_devi = Num;
              changed_f = 1;
              setup_PGfreq0();
              setup_PGfreq2();
              changed_f = 0;
            }
            cmdphase = 0;
            break;
          }
        default: {
          }
          cmdphase = 0;
          break;
      }
      chrcnt = 0;
    }
    else {
      chrcnt++;
    }
  }

  //loop delay
  delay(loopwait);
}
// END

シリアルからのコマンドでも操作できるように作っていますので、WindowsからGUIで制御できるツールもついでに作成しちゃいました。
言語は、python
開発環境は、Visual Studio Code
pythonは、本来は本格的なWeb用のプログラムなどを作成するものと思いますが、昔のBASIC的な位置付けでお手軽なプログラム言語としても使えます。
最近のPCはパワーが余っていますから、高速処理が苦手なインタプリタですが、中間言語で実行するのもあり、そこそこリアルタイムな処理も可能です。
また、マルチプラットホーム対応なのもGood、必要に応じて実行形式も作成可能です。
import wx
import time
import os
import serial
#from serial.tools import list_ports
import argparse

# シリアル設定,本来は引数か、自動検出等があれば汎用的だが、現状専用機仕様なので…
comport = ‘COM5’
combps = 115200
# OSCキャリブレーション値
# このキャリブレーション値と後出のHeater値は起動時に自動設定が良いかもしれない。
pgcal_d = 5683.0 # by GPS
wgcal_d = 3334
pgcal = pgcal_d
wgcal = wgcal_d
# ボードからの戻り値変換用
SINE = 0x2000
SQUARE = 0x2028
TRIANGLE = 0x2002
# 変数の宣言と、てきとうな初期設定
wgwavetype = 0
pg_freq = 25000000
pgfreq = pg_freq
wg_freq = 1200000
wgfreq=wg_freq
wg_type = ‘SINE’
wg_potn = 105
wgpot = wg_potn
# 変数宣言と、将来Target温度・Heaterの設定機能追加時の為。現状値はダミー
wg_temp = 52.5
pg_temp = 47.2
tg_temp = 52.0
osc_heater = 194
# 温度異常時にカラー表示を変えたい為、現時点機能なし
over_temp = tg_temp + 4
wg_temp_color = ‘#ff99b3’
# ボードに起動時(reset含む)おーぷにおんぐメッセージを読み飛ばすフラグ
hello_flag = 0
# wavetypeのシリアルコマンド、数字->1文字コマンドへ変換用文字列
wave_irq = ‘irq’
# シリアルのセマフォフラグ
ser_busy = 0

class MainFrame(wx.Frame):
#トップレベルウィンドウクラス
 
def __init__(self):
 
super().__init__(None, wx.ID_ANY, ‘WG/PC Control Panel’,style=(wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX))
# Windowサイズは、アプリに合わして、固定表示
# 表示ポジションは、前回の位置か基準位置
if os.path.exists(“WPGCtrl.exe”):
icon = wx.Icon(‘WPGCtrl.exe’, wx.BITMAP_TYPE_ICO)
self.SetIcon(icon)
else:
icon_path_plus = os.path.dirname(os.path.abspath(__file__))+’\\favicon.ico’
# 実行ファイルのパスにあるアイコンの絶対パス
#print (icon_path_plus)
if os.path.exists(icon_path_plus):
icon = wx.Icon(icon_path_plus, wx.BITMAP_TYPE_ICO)
# jpeg/pngの場合、 BITMAP_TYPE_JPEG/BITMAP_TYPE_PNG
self.SetIcon(icon)
self.CreateStatusBar()
self.SetStatusText(‘ホームページ/ネットショップ/海人ぬ宝 https://www.himeport.co.jp/’)

self.state_panel = STATEPanel(self)
self.state_panel.SetBackgroundColour(‘#ffffbf’)
self.ctrl_panel = CTRLPanel(self)
# ctrl_panel.SetBackgroundColour(‘#ff7396’)
layout = layout = wx.BoxSizer(wx.HORIZONTAL)
layout.Add(self.state_panel, flag=wx.GROW)
layout.Add(self.ctrl_panel, flag=wx.GROW)
self.SetSizer(layout)
self.Fit()

#print (self.ctrl_panel.wg_panel.freq.GetValue())
#print (self.ctrl_panel.wg_panel.wg2_panel.wavetype.GetSelection())
#print (self.ctrl_panel.wg_panel.wg2_panel.wgpot_panel.pot.GetValue())

 
 

class STATEPanel(wx.Panel):
# STATEパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
 
self.wg_lobel = wx.StaticText(self, wx.ID_ANY, ‘WAVE Generator’)
self.wg_freq_d = wx.TextCtrl(self, wx.ID_ANY, ‘WG周波数’, style=wx.TE_RIGHT)
self.wg_type_d = wx.TextCtrl(self, wx.ID_ANY, ‘WG波形タイプ’, style=wx.TE_CENTER)
self.wg_potn_d = wx.TextCtrl(self, wx.ID_ANY, ‘WGレベル’, style=wx.TE_RIGHT)
self.pg_lobel = wx.StaticText(self, wx.ID_ANY, ‘PULSE Generator’)
self.pg_freq_d = wx.TextCtrl(self, wx.ID_ANY, ‘PG周波数’, style=wx.TE_RIGHT)
self.tmp_lobel = wx.StaticText(self, wx.ID_ANY, ‘監視温度’)
self.wg_temp_d = wx.TextCtrl(self, wx.ID_ANY, ‘WG温度’, style=wx.TE_RIGHT)
self.pg_temp_d = wx.TextCtrl(self, wx.ID_ANY, ‘PG温度’, style=wx.TE_RIGHT)
self.tg_temp_d = wx.TextCtrl(self, wx.ID_ANY, ‘設定温度’, style=wx.TE_RIGHT)
self.osc_heater_d = wx.TextCtrl(self, wx.ID_ANY, ‘Heater’, style=wx.TE_RIGHT)
dummy_text1 = wx.StaticText(self, wx.ID_ANY, ”)
dummy_text2 = wx.StaticText(self, wx.ID_ANY, ”)
 
box = wx.StaticBox(self, wx.ID_ANY, ‘STATE’)
 
# text_1.SetValue(‘text_1’)
#self.wg_temp_d.SetForegroundColour( wg_temp_color )
self.wg_freq_d.SetForegroundColour(‘#0000FF’)
self.wg_type_d.SetForegroundColour(‘#0000FF’)
self.wg_potn_d.SetForegroundColour(‘#0000FF’)
self.pg_freq_d.SetForegroundColour(‘#0000FF’)
self.wg_temp_d.SetForegroundColour(‘#0000FF’)
self.pg_temp_d.SetForegroundColour(‘#0000FF’)
self.tg_temp_d.SetForegroundColour(‘#0000FF’)
self.osc_heater_d.SetForegroundColour(‘#0000FF’)
#self.wg_temp_d.SetBackgroundColour(‘#FFFFFF’)

#self.wg_freq_d.Disable()
#self.wg_type_d.Disable()
#self.wg_potn_d.Disable()
#self.pg_freq_d.Disable()
#self.wg_temp_d.Disable()
#self.pg_temp_d.Disable()
#self.tg_temp_d.Disable()
#self.osc_heater_d.Disable()
 
layout = wx.StaticBoxSizer(box, wx.VERTICAL)
layout.Add(self.wg_lobel, flag=wx.GROW)
layout.Add(self.wg_freq_d)
layout.Add(self.wg_type_d)
layout.Add(self.wg_potn_d)
layout.Add(dummy_text1, flag=wx.GROW)
layout.Add(self.pg_lobel, flag=wx.GROW)
layout.Add(self.pg_freq_d)
layout.Add(dummy_text2, flag=wx.GROW)
layout.Add(self.tmp_lobel, flag=wx.GROW)
layout.Add(self.wg_temp_d)
layout.Add(self.pg_temp_d)
layout.Add(self.tg_temp_d)
layout.Add(self.osc_heater_d)
 
self.SetSizer(layout)

global ser
ser = serial.Serial(str(comport),int(combps),timeout=2)
# 必要に応じて引数でポートとレートを渡すようにする。

# タイマーイベント 定期的にイベントを発生させる
self.getSTATE_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.getSTATE)
# 2秒間隔でイベント発生
self.getSTATE_timer.Start(2000)

# 定期的にステータスを引き取って表示を更新する。
def getSTATE(self,event):
global hello_flag,ser_busy,over_temp,tg_temp
# シリアル使用中なら何もしない
if (not ser_busy):
ser_busy = 1
# シリアルバッファーを空にする。
if (not hello_flag):
while ser.in_waiting > 0:
ser.reset_input_buffer()
time.sleep(0.01)
hello_flag = 1
if ser.out_waiting > 0:
ser.reset_output_buffer()
# コマンド発行、バイナリー文字列にencodeして発行
ser.reset_input_buffer()
ser.write(bytes(‘g\\’,’utf-8′))

i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if (line_arg[0].decode() == ‘Status:’ ):
self.wg_freq_d.SetValue( line_arg[2].decode() +’ Hz’)
wg_type = ‘NONE’
if(int(line_arg[3].decode()) == SINE):
wg_type = ‘SINE’
elif(int(line_arg[3].decode()) == SQUARE):
wg_type = ‘SQUARE’
elif(int(line_arg[3].decode()) == TRIANGLE):
wg_type = ‘TRIANGLE’
self.wg_type_d.SetValue( ‘出力波形: ‘+ wg_type)
self.wg_potn_d.SetValue( ‘POT: ‘+ line_arg[4].decode()+’/255’)
self.pg_freq_d.SetValue( line_arg[1].decode()+’ Hz’)
self.wg_temp_d.SetValue( ‘WG温度: ‘+ line_arg[5].decode()+’ deg’)
self.pg_temp_d.SetValue( ‘PG温度: ‘+ line_arg[6].decode()+’ deg’)
self.tg_temp_d.SetValue( ‘設定温度: ‘+ line_arg[8].decode()+’ deg’)
self.osc_heater_d.SetValue( ‘ヒータ出力: ‘+ line_arg[7].decode()+’/1024’ )
# 異常温度の場合red、適正温度になるとgreen
if float(line_arg[5].decode()) > over_temp:
self.wg_temp_d.SetBackgroundColour(‘#FF0000’)
elif abs(float(line_arg[5].decode()) – tg_temp) < 0.8:
self.wg_temp_d.SetBackgroundColour(‘#008000’)
else:
self.wg_temp_d.SetBackgroundColour(‘#FFFFFF’)
# Heaterのパワーが50%を超えた場合red…故障など異常の兆候
if int(line_arg[7].decode()) > 512:
self.wg_temp_d.SetBackgroundColour(‘#FF0000’)
else:
self.wg_temp_d.SetBackgroundColour(‘#FFFFFF’)
else:
ser.reset_input_buffer()

ser_busy = 0
 



class CTRLPanel(wx.Panel):
# CTRL コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
 
layout = wx.FlexGridSizer(rows=2, cols=1, gap=(0, 0))
self.wg_panel = WGPanel(self)
self.wg_panel.SetBackgroundColour(‘#c5edff’)
self.pg_panel = PGPanel(self)
self.pg_panel.SetBackgroundColour(‘#9bff9b’)
# ctrl_layout.AddGrowableRow(0)
layout.Add(self.wg_panel, flag=wx.GROW)
layout.Add(self.pg_panel, flag=wx.GROW)

self.SetSizer(layout)

class WGPanel(wx.Panel):
# WG コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
 
 
self.wgpot_panel = WPOTPanel(self)
self.wgcal_panel = WCALPanel(self)
 
self.box = wx.StaticBox( self, wx.ID_ANY, ‘WAVE Generator CONTROL’)
 
layout = wx.StaticBoxSizer(self.box, wx.VERTICAL)
layout.Add(self.wgpot_panel)
layout.Add(self.wgcal_panel)

self.SetSizer(layout)



class WPOTPanel(wx.Panel):
# WG POTコントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
self.pot_label = wx.StaticText(self, wx.ID_ANY, ‘ Pot. 0 – 255’)
self.pot = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_RIGHT)
# pot.GetValue() でテキストを取得できる。
self.pot.SetMaxLength(5)
global wgpot
self.pot.SetValue(str(wgpot))
# 現在の値を表示する
self.button_wgset = wx.Button( self, wx.ID_ANY, ‘Set’)
self.button_wgset.SetBackgroundColour(‘#00ffff’)
dummy_text = wx.StaticText(self, wx.ID_ANY, ”)
self.button_wgset.Bind(wx.EVT_BUTTON, self.click_wgset)


self.wg_freqpanel = WGFreqPanel(self)
self.wg_wavepanel = WGWavePanel(self)

layout = wx.GridBagSizer()
layout.Add(self.wg_freqpanel, (0,0), (1,2), flag=wx.EXPAND)
layout.Add(self.wg_wavepanel, (1,0), (4,1), flag=wx.EXPAND)
layout.Add(self.pot_label, (1,1), (1,1), flag=wx.EXPAND)
layout.Add(self.pot, (2,1), (1,1), flag=wx.EXPAND)
layout.Add(self.button_wgset, (4,1), (1,1), flag=wx.EXPAND)
self.SetSizer(layout)

def click_wgset(self,event):
global wgfreq,wgwavetype,wgpot,ser_busy
if (not ser_busy):
ser_busy = 1
get_wgfreq= self.wg_freqpanel.freq.GetValue()
get_wgwavetype= self.wg_wavepanel.wavetype.GetSelection()
get_wgpot= self.pot.GetValue()
 
# 処理の都合、一時的に初期値を設定。
self.wg_freqpanel.freq.SetValue(str(wgfreq))
self.wg_wavepanel.wavetype.SetSelection(int(wgwavetype))
self.pot.SetValue(str(wgpot))
# 全ての文字が十進数字か、チェック
if not(str.isdecimal(get_wgfreq.replace(‘.’, ”))):
# 必要ならエラー通知を入れる。
event.Skip()
else:
if (float(get_wgfreq)<0) or (1200000 < float(get_wgfreq)):
# 必要ならエラー通知を入れる。
event.Skip()
else:
if not(str.isdecimal(get_wgpot)):
# 必要ならエラー通知を入れる。
event.Skip()
else:
if (int(get_wgpot)<0) or (255 < int(get_wgpot)):
# 必要ならエラー通知を入れる。
event.Skip()
else:
self.wg_freqpanel.freq.SetValue(str(get_wgfreq))
self.wg_wavepanel.wavetype.SetSelection(int(get_wgwavetype))
self.pot.SetValue(str(get_wgpot))
wgfreq = str(get_wgfreq)
wgwavetype = int(get_wgwavetype)
wgpot = str(get_wgpot)
# シリアルでコマンドを送るサブルーチン追加!
# シリアルバッファーを空にする。
global hello_flag
if (not hello_flag):
while ser.in_waiting > 0:
ser.reset_input_buffer()
time.sleep(0.01)
hello_flag = 1
if ser.out_waiting > 0:
ser.reset_output_buffer()

# コマンド発行、バイナリー文字列にencodeして発行
ser.reset_input_buffer()
ser.write(bytes(‘w\\’,’utf-8′))

i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if (line_arg[0].decode() == ‘WG’ ):
ser.reset_input_buffer()
ser.write(wgfreq.encode())
ser.write(bytes(‘\\’,’utf-8′))

i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

line = ser.readline()
line_arg=line.split()
if (line_arg[0].decode() == ‘WG2’ ):
ser.reset_input_buffer()
ser.write(wgpot.encode())
ser.write(bytes(‘\\’,’utf-8′))

i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

line = ser.readline()
line_arg=line.split()
if (line_arg[0].decode() == ‘WG3’ ):
ser.reset_input_buffer()
b_wgwavetype = wave_irq[wgwavetype]
ser.write(b_wgwavetype.encode())
ser.write(bytes(‘\\’,’utf-8′))

i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

line = ser.readline()
line_arg=line.split()
if (line_arg[0].decode() == ‘WG4’ ):
self.button_wgset.SetBackgroundColour(‘#00ffff’)
# NG受信データの処理内容
else:
self.button_wgset.SetBackgroundColour(‘#ffff00’)
else:
self.button_wgset.SetBackgroundColour(‘#ff0000’)
else:
self.button_wgset.SetBackgroundColour(‘#ff00ff’)
else:
self.button_wgset.SetBackgroundColour(‘#000000’)
ser_busy = 0



class WGWavePanel(wx.Panel):
# WG WaveType コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
self.wavetype_array = (‘正弦波’, ‘三角波’, ‘方形波’)
self.wavetype = wx.RadioBox(self, wx.ID_ANY, ‘Wave type’,
choices=self.wavetype_array, style=wx.RA_VERTICAL)
# wavetype.GetSelection()で選択値、wavetype.GetStringSelection()要素名で取得
global wgwavetype
self.wavetype.SetSelection(int(wgwavetype))
# 仮に初期値を三角波に、要素名で指定はSetStringSelection()

layout = wx.BoxSizer(wx.VERTICAL)
layout.Add(self.wavetype, flag=wx.SHAPED | wx.ALIGN_LEFT)
 
self.SetSizer(layout)


class WGFreqPanel(wx.Panel):
# WG Freqコントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
 
self.freq_label = wx.StaticText(self, wx.ID_ANY, ‘ Frequency 0.1-1200000Hz’)
self.freq = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_RIGHT)

# freq.GetValue() でテキストを取得できる。
# print(freq.GetValue())
self.freq.SetMaxLength(10)
global wgfreq
self.freq.SetValue(str(wgfreq))
 
layout = wx.BoxSizer(wx.VERTICAL)
layout.Add(self.freq_label)
layout.Add(self.freq, flag=wx.GROW)

self.SetSizer(layout)


class WCALPanel(wx.Panel):
# WG Cal コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
self.cal_label = wx.StaticText(self, wx.ID_ANY, ‘ OSC cal. -200000.0 – +200000.0 (Hz)’)
self.cal_value = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_RIGHT)

# cal.GetValue() でテキストを取得できる。
self.cal_value.SetMaxLength(9)
self.cal_value.SetValue(str(wgcal))
# 現在のキャリブレーション値を表示する
self.button_wgcal = wx.Button( self, wx.ID_ANY, ‘Cal’)
self.button_wgcal.SetBackgroundColour(‘#00ffff’)

self.button_wgcal.Bind(wx.EVT_BUTTON, self.click_wgcal)

layout = wx.GridBagSizer()
layout.Add(self.cal_label, (0,0), (1,2), flag=wx.EXPAND)
layout.Add(self.cal_value, (1,0), (1,1), flag=wx.EXPAND)
layout.Add(self.button_wgcal, (1,1), (1,1), flag=wx.EXPAND)
self.SetSizer(layout)

def click_wgcal(self, event):
global wgcal,ser_busy
if (not ser_busy):
get_wgcal= self.cal_value.GetValue()
# 全ての文字が十進数字か、「-」「.」を除いてチェック
if not(str.isdecimal(get_wgcal.replace(‘-‘,”).replace(‘.’, ”))):
self.cal_value.SetValue(str(wgcal))
event.Skip()
else:
if (abs(int(get_wgcal)) > 200000):
self.cal_value.SetValue(str(wgcal))
event.Skip()
else:
if (int(get_wgcal) >= 200000 ):
get_wgcal=wgcal_d
self.cal_value.SetValue(str(wgcal_d))
ser_busy = 1
self.button_wgcal.SetBackgroundColour(‘#ffff00’)
wgcal = str(get_wgcal)
# シリアルバッファーを空にする。
global hello_flag
if (not hello_flag):
while ser.in_waiting > 0:
ser.reset_input_buffer()
time.sleep(0.01)
hello_flag = 1
if ser.out_waiting > 0:
ser.reset_output_buffer()
# コマンド発行、バイナリー文字列にencodeして発行
ser.reset_input_buffer()
ser.write(bytes(‘c\\’,’utf-8′))
 
i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if len(line_arg)>0:
if (line_arg[0].decode() == ‘WG’ ):
ser.reset_input_buffer()
ser.write(wgcal.encode())
ser.write(bytes(‘\\’,’utf-8′))
 
i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if (len(line_arg)>0):
if (line_arg[0].decode() == ‘WG’ ):
self.button_wgcal.SetBackgroundColour(‘#00ffff’)
# 処理内容
ser_busy = 0

class PGPanel(wx.Panel):
# PG コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)

self.pgfreq_panel = PFREQPanel(self)
self.pgcal_panel = PCALPanel(self)
 
self.box = wx.StaticBox( self, wx.ID_ANY, ‘PULSE Generator CONTROL’)
 
layout = wx.StaticBoxSizer(self.box, wx.VERTICAL)
layout.Add(self.pgfreq_panel)
layout.Add(self.pgcal_panel)
 
self.SetSizer(layout)

class PFREQPanel(wx.Panel):
# PG Freq コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
self.freq_label = wx.StaticText(self, wx.ID_ANY, ‘ Frequency 4000-225000000Hz’)
self.freq = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_RIGHT)
# freq.GetValue() でテキストを取得できる。
# print(freq.GetValue())
self.freq.SetMaxLength(10)
self.freq.SetValue(str(pgfreq))
self.button_pgset = wx.Button( self, wx.ID_ANY, ‘Set’)
self.button_pgset.SetBackgroundColour(‘#00ffff’)

self.button_pgset.Bind(wx.EVT_BUTTON, self.click_pgset)

layout = wx.GridBagSizer()
layout.Add(self.freq_label, (0,0), (1,2), flag=wx.EXPAND)
layout.Add(self.freq, (1,0), (1,1), flag=wx.EXPAND)
layout.Add(self.button_pgset, (1,1), (1,1), flag=wx.EXPAND)
self.SetSizer(layout)

def click_pgset(self, event):
global pgfreq,ser_busy
if (not ser_busy):
get_pgfreq= self.freq.GetValue()
# 全ての文字が十進数字か、チェック
if not(str.isdecimal(get_pgfreq)):
self.freq.SetValue(str(pgfreq))
event.Skip()
else:
if ((int(get_pgfreq)<4000) or (int(get_pgfreq)>225000000)):
self.freq.SetValue(str(pgfreq))
event.Skip()
else:
# シリアルでコマンドを送るサブルーチン追加!
ser_busy = 1
self.button_pgset.SetBackgroundColour(‘#ffff00’)
pgfreq = str(get_pgfreq)
# シリアルバッファーを空にする。
global hello_flag
if (not hello_flag):
while ser.in_waiting > 0:
ser.reset_input_buffer()
time.sleep(0.01)
hello_flag = 1
if ser.out_waiting > 0:
ser.reset_output_buffer()
# コマンド発行、バイナリー文字列にencodeして発行
ser.reset_input_buffer()
ser.write(bytes(‘p\\’,’utf-8′))
 
i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if len(line_arg)>0:
if (line_arg[0].decode() == ‘PG’ ):
ser.reset_input_buffer()
ser.write(pgfreq.encode())
ser.write(bytes(‘\\’,’utf-8′))
 
i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if (len(line_arg)>0):
if (line_arg[0].decode() == ‘PG’ ):
self.button_pgset.SetBackgroundColour(‘#00ffff’)
# 処理内容
ser_busy = 0

class PCALPanel(wx.Panel):
# PG Cal コントロールパネル内レイアウト

def __init__(self, parent):
 
super().__init__(parent, wx.ID_ANY)
self.cal_label = wx.StaticText(self, wx.ID_ANY, ‘ calibration -200000.0 – +200000.0ppm’)
self.cal_value = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_RIGHT)
# cal.GetValue() でテキストを取得できる。
self.cal_value.SetMaxLength(9)
self.cal_value.SetValue(str(pgcal))
# 現在のキャリブレーション値を表示する
self.button_pgcal = wx.Button( self, wx.ID_ANY, ‘Cal’)
self.button_pgcal.SetBackgroundColour(‘#00ffff’)

self.button_pgcal.Bind(wx.EVT_BUTTON, self.click_pgcal)

layout = wx.GridBagSizer()
layout.Add(self.cal_label, (0,0), (1,2), flag=wx.EXPAND)
layout.Add(self.cal_value, (1,0), (1,1), flag=wx.EXPAND)
layout.Add(self.button_pgcal,(1,1), (1,1), flag=wx.EXPAND)
self.SetSizer(layout)

def click_pgcal(self, event):
global pgcal,ser_busy
if (not ser_busy):
get_pgcal= self.cal_value.GetValue()
# 全ての文字が十進数字か「-」「.」符号を予めreplaceしてから、チェック
if not(str.isdecimal(get_pgcal.replace(‘-‘,”).replace(‘.’, ”))):
self.cal_value.SetValue(str(pgcal))
event.Skip()
else:
if (abs(float(get_pgcal)) > 200000):
self.cal_value.SetValue(str(pgcal))
event.Skip()
else:
if (float(get_pgcal) >= 200000 ):
get_pgcal=pgcal_d
self.cal_value.SetValue(str(pgcal_d))
# シリアルでコマンドを送るサブルーチン追加!
ser_busy = 1
self.button_pgcal.SetBackgroundColour(‘#ffff00’)
pgcal = str(get_pgcal)
# シリアルバッファーを空にする。
global hello_flag
if (not hello_flag):
while ser.in_waiting > 0:
ser.reset_input_buffer()
time.sleep(0.01)
hello_flag = 1
if ser.out_waiting > 0:
ser.reset_output_buffer()
# コマンド発行、バイナリー文字列にencodeして発行
ser.reset_input_buffer()
ser.write(bytes(‘C\\’,’utf-8′))
 
i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if len(line_arg)>0:
if (line_arg[0].decode() == ‘PG’ ):
ser.reset_input_buffer()
ser.write(pgcal.encode())
ser.write(bytes(‘\\’,’utf-8′))
 
i=0
while (ser.in_waiting<1 and i<200):
i=i+1
time.sleep(0.01)

# 結果の受け取りと分析、表示
line = ser.readline()
line_arg=line.split()
# 受信したデーターはバイナリー文字列b’n’なのでdecode()して文字に
if (len(line_arg)>0):
if (line_arg[0].decode() == ‘PG’ ):
self.button_pgcal.SetBackgroundColour(‘#00ffff’)
# 処理内容
ser_busy = 0


if __name__ == ‘__main__’:
 
# カスタムフレームを初期化してアプリケーションを開始
application = wx.App()
 
frame = MainFrame()
frame.Show()
 
application.MainLoop()
 

その他

温度制御にTC1を使用する為にLCDシールドの一部接続を変更しています。

居ないとは思いますが、もし参考にして作成される場合は掲載のスケッチのピン定義を事前に確認してください。

HW作成がいい加減なせいかもしれませんが、たまに波形が出ていない場合が見られ、ライブラリの使用方法か何かしら問題が潜んでいる可能性があります。(-_-;)

流用される場合は、注意してご利用ください。

GPS周波数標準器 完成!

GPS周波数標準器 (SATELLITE CONTROLLED FREQUENCY STANDARD)作成

1/10分周回路も追加して、1MHZも出力。

Arduinoを16MHz化して5MHZを計測することで、初期より周波数変動への応答性を向上(途中で24MHZ化しましたが、10MHz直接計測には波形整形にLS14を使用していたことでマージンが確保できず、LS14をHC14など高速なものにするか直接クロックを入力すればOKなんだが....手抜きで断念。)

割り込み処理ルーチンをリアルタイム性の低い部分を切り離し、縮小することで、GPSモジュールとのソフトウェアシリアルでのエラー発生を低減

最終的なスケッチは、以下。

電源ON後15分後以降、GPSから1ppsを得られていれば、周波数補正開始、電源ON後、約50分で実用レベル(±0.007Hz以下)に安定します。

/*
   GPS base 10MHz with MCP4725
   カウンタ変数をULL化
   CPUを8MHzから16MHzに、それに伴いダイレクト計測周波数も2.5MHzから5MHzへ
  ・割り込み処理ルーチンを最低限の処理にする。
  ・周波数測定方法を100s間隔で周波数を監視しながら、最適な場所で補正をかけることで
  環境温度などによる周波数偏移への対応速度を早くしつつ周波数の確度も向上させる。
  ->温度に顕著な変動の兆候がなければ、周波数測定を継続する。
  流用の都合でLCD接続(Encoder共通)が8線接続となっているが、新規作成の場合は
  EncoderをSPI接続のものに変更して、4線接続とすることで信号ラインの不足を補える。
*/
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac; // constructor
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <TinyGPS++.h>
TinyGPSPlus gps;
#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(6, 7); // RX, TX
#define rs 2
#define enable 3
#define rw 9
#define d0 A3
#define d1 10
#define d2 A2
#define d3 11
#define d4 A1
#define d5 12
#define d6 A0
#define d7 13
LiquidCrystal lcd(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7);
#define enter_sw A6   // front SW 10以下 ON 2000以上 OFF
#define en_en 4       // EAW - Absolute Contacting Encoder LCD DATA LINE共通
#define ocxo 5;       //OCXO 入力ピン
#define gps1ppm 8;    //GPS 1Hz 入力ピン
const uint8_t debug = 0; // 1: debug code output
uint32_t debug_f = 0 ;
const uint8_t ratio = 2; // 分周比
/* 精度設定 */
uint8_t accuracy = 0; /* 単位の周波数測定のカウンター */
const uint8_t accuracy_max = 20; /* 計測時間とDAC精度を考えると10で十分と思うが... */
#define target 10000000 /* 設定周波数 */
const uint64_t count_ref = target * (100 / ratio);
/* VOCXO Voltage */
const int dacvoltmax = 4095;
const int dacvolt_norm = 2048;
int dacvolt = dacvolt_norm; /* VOCXO Voltage = 2.0V 初期値:2048 */
int dacvolt_d = 0; /* 値はダミー */
volatile uint16_t intcarry;
volatile uint16_t carry;    /* 割り込みルーチン内で割り込み追い越しを配慮した溢れ回数を保存*/
volatile uint32_t start_cnt;
volatile uint32_t end_cnt;
volatile uint16_t gps_loop;
uint64_t  freq1000;
uint8_t ignore;
uint64_t count[accuracy_max] = {};
uint32_t count_chk;
volatile uint8_t f_check; //周波数処理確認フラグ 1:処理が必要な状態、0:処理完了 2:追い越しが発生!
int val;
uint8_t enter;
int val_old;
uint8_t encode_old = 0;
char gps_loop_msg3[4] = "***";
char freq_msg15[16] = " ********.**Hz ";
const uint8_t encode[256] =                    //もし255なら読み取りミスで無視する。
{
  56, 40, 55, 24, 255, 39, 52, 8, 57, 255,
  255, 23, 255, 36, 13, 120, 255, 41, 54, 255,
  255, 255, 53, 7, 255, 255, 255, 20, 19, 125,
  18, 104, 105, 255, 255, 25, 106, 38, 255, 255,
  58, 255, 255, 255, 255, 37, 14, 119, 118, 255,
  255, 255, 107, 255, 255, 4, 255, 3, 255, 109,
  108, 2, 1, 88, 255, 89, 255, 255, 255, 255,
  51, 9, 10, 90, 255, 22, 11, 255, 12, 255,
  255, 42, 43, 255, 255, 255, 255, 255, 255, 255,
  255, 21, 255, 126, 127, 103, 255, 102, 255, 255,
  255, 255, 255, 255, 255, 91, 255, 255, 255, 255,
  255, 116, 117, 255, 255, 115, 255, 255, 255, 93,
  94, 92, 255, 114, 95, 113, 0, 72, 71, 255,
  68, 73, 255, 255, 29, 255, 70, 255, 69, 255,
  255, 35, 34, 121, 255, 122, 255, 74, 255, 255,
  30, 6, 255, 123, 255, 255, 255, 124, 17, 255,
  255, 255, 67, 26, 255, 27, 28, 255, 59, 255,
  255, 255, 255, 255, 15, 255, 255, 255, 255, 255,
  255, 255, 255, 5, 255, 255, 255, 110, 255, 111,
  16, 87, 84, 255, 45, 86, 85, 255, 50, 255,
  255, 255, 46, 255, 255, 255, 33, 255, 83, 255,
  44, 75, 255, 255, 31, 255, 255, 255, 255, 255,
  255, 255, 32, 100, 61, 101, 66, 255, 62, 255,
  49, 99, 60, 255, 47, 255, 255, 255, 48, 77,
  82, 78, 65, 76, 63, 255, 64, 98, 81, 79,
  80, 97, 96, 112
};
uint8_t read_Enter() { // Enter SW
  uint8_t value;
  int enter_val = analogRead(enter_sw);
  if (enter_val < 100) value = 1;
  else value = 0;
  return value;
}
uint8_t read_Encoder() { // Encoder(8bits)の値を読む
  uint8_t value;
  pinMode(d0, INPUT);
  pinMode(d1, INPUT);
  pinMode(d2, INPUT);
  pinMode(d3, INPUT);
  pinMode(d4, INPUT);
  pinMode(d5, INPUT);
  pinMode(d6, INPUT);
  pinMode(d7, INPUT);
  //   digitalWrite(en_en, LOW);
  PORTD &= ~_BV(4);
  value = digitalRead(d7) * 128 + digitalRead(d6) * 64 + digitalRead(d5) * 32 + digitalRead(d4) * 16;
  value = value + digitalRead(d3) * 8 + digitalRead(d2) * 4 + digitalRead(d1) * 2 + digitalRead(d0);
  //value = (PINB & _BV(5)) * 128 + (PINC & _BV(0)) * 64 + (PINB & _BV(4)) * 32 + (PINC & _BV(1)) * 16;
  //value = value + (PINB & _BV(3)) * 8 + (PINC & _BV(2)) * 4 + (PINB & _BV(2)) * 2 + (PINC & _BV(3));
  //    digitalWrite(en_en, HIGH);
  PORTD |= _BV(4);
  pinMode(d0, OUTPUT);
  pinMode(d1, OUTPUT);
  pinMode(d2, OUTPUT);
  pinMode(d3, OUTPUT);
  pinMode(d4, OUTPUT);
  pinMode(d5, OUTPUT);
  pinMode(d6, OUTPUT);
  pinMode(d7, OUTPUT);
  value = encode[value - 1];
  if (value == 255) value = encode_old; else encode_old = value;
  return value;
}
ISR(TIMER1_CAPT_vect)
{
  /*
    WDT Reset
    if(カウンターフラグ無効)
      WDT起動
      ICR1に保存された値を保存
      カウンターフラグ有効に
    else
      ICR1に保存された値を保存
      溢れ割り込み追い越しをチェック
      桁溢れ回数を保存(intcarry -> carry + 1?)
      周波数処理確認フラグ += 1
    if(周波数処理確認フラグ > 1)
      発生回数・オーバーフロー回数を初期化
      カウンターフラグ無効
  */

  wdt_reset();
  gps_loop += 1;

  if (ignore) {
    if (start_cnt > 0) {
      /* 最初の1回はICR1値が不安定なので無視 */
      ignore = 0;
      /* GPS 1ppm 監視 WDT start */
      {
        MCUSR = 0;
        WDTCSR |= 0b00011000; //WDCE WDE set
        WDTCSR =  0b01000000 | 0b000111; /* /WDIE set 2s WDT */
      }
    }
    start_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if ((start_cnt < 9000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から... */
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
    }
    intcarry = 0;
    gps_loop = 0;

  } else {
    if (gps_loop == 100) {
      end_cnt = (word)ICR1;
      /* キャり追い越し補正する */
      if ((end_cnt < 9000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から3160... */
        intcarry += 1;
        TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      }
      carry = intcarry;
      f_check += 1;
      gps_loop = 0;
      intcarry = 0;
    }
  }
  if (f_check > 1) {
    ignore = 1;
    f_check = 0;
  }
}
ISR(TIMER1_OVF_vect) {
  /* オーバーフロー回数を加算 */
  if (!ignore)intcarry += 1;
}
/*
  WDT設定(2s)は、WDTCSRを47hにセットして、wdt_reset();
*/
ISR(WDT_vect) {
  wdt_reset();
  /*
      カウンターフラグを無効にする
      発生回数・オーバーフロー回数を初期化
  */
  start_cnt = 0;
  gps_loop = 0;
  ignore = 1;
  f_check = 0;
  {
    MCUSR = 0;
    WDTCSR |= 0b00011000; /* WDCE WDE set */
    WDTCSR =  0b00000000; /* status clear */
  }
}
void setup()
{
  pinMode(en_en, OUTPUT);
  digitalWrite(en_en, HIGH);
  delay(1);

  lcd.begin(20, 2);
  lcd.setCursor(0, 0);
  lcd.print("OCXO + MCP4725 10MHz");
  lcd.setCursor(0, 1);
  lcd.print(" 2018.12 by Drg.Jr. ");

  Serial.begin(115200);
  Serial.println("Hello World!");
  Serial.println("OCXO + MCP4725 10MHz"); Serial.println("");

  // pulse gen setup init clock
  // initialize the MCP4725
  dac.begin(0x60);
  dac.setVoltage(dacvolt, false); //DACに値(0-4095)を設定する

  delay(5000);

  gpsSerial.begin(9600); // ソフトウェアシリアルの初期化
  gpsSerial.print("\r\n");
  gpsSerial.print("\r\n");
  /*
    u-blox 6
    Receiver Description
    Including Protocol Specification を参考
  */
  gpsSerial.print("$PUBX,41,1,0007,0003,4800,0*13\r\n");   // GPSモジュールのbpsを変更
  gpsSerial.begin(4800);                                   // ソフトウェアシリアルの再初期化
  gpsSerial.print("\r\n");
  gpsSerial.print("\r\n");


  //  lcd.clear();

  intcarry = 0;
  start_cnt = 0;
  end_cnt = 0;
  freq1000 = 0;
  ignore = 1;
  gps_loop = 0;
  for (int i = 0 ; i <= accuracy_max ; i ++) {
    count[i] = 0;
  }

  /*カウンターセットアップ*/
  cli();
  TCCR1A = 0;   /* TCCR1A タイマモード */
  TCCR1B = 0x46;      /*ICP, T1入力、立ち上がり,立下りを検知 */
  TCNT1 = 0x0000;  /* タイマ1の初期値設定   */
  TIMSK1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込みの許可 */
  //  TIFR1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込み要求クリア */
  sei(); /* 全体の割り込み許可 */
}
void loop()
{
  char msg20[21];
  char msg4[5];
  byte val;
  byte enter;

  /* GPS module Accsess */
  while (gpsSerial.available()) {
    char read_gps = gpsSerial.read();
    gps.encode(read_gps);
    if ((gps.satellites.isUpdated()) && (gps.time.isUpdated())) {

      val = read_Encoder();
      enter = read_Enter();

      char sa[3];
      byte jst = gps.time.hour() + 9;
      if (jst >= 24) jst = jst - 24;
      if (enter == 0) {
        sprintf(msg20, "GpsSat.%s   %2d:%02d:%02d",
                dtostrf(gps.satellites.value(), 2, 0, sa),
                jst, gps.time.minute(), gps.time.second() );
      } else {
        sprintf(msg20, "GpsSat.%s %4d/%02d/%02d",
                dtostrf(gps.satellites.value(), 2, 0, sa),
                gps.date.year(), gps.date.month(), gps.date.day() );
      }
      lcd.setCursor(0, 1);
      if (val != val_old) {
        lcd.print("                    ");
        lcd.setCursor(0, 1);
        switch (int(val / 16)) {
          case 1:
            lcd.print("Latitude : ");
            lcd.print(gps.location.lat(), 6); // Latitude in degrees (double)
            break;
          case 2:
            lcd.print("Longitude : ");
            lcd.print(gps.location.lng(), 6); // Longitude in degrees (double)
            break;
          case 3:
            lcd.print("Speed (m/s)");
            lcd.print(gps.speed.mps()); // Speed in meters per second (double)
            break;
          case 4:
            lcd.print("Speed (km/s)");
            lcd.print(gps.speed.kmph()); // Speed in kilometers per hour (double)
            break;
          case 5:
            lcd.print("Course (deg)");
            lcd.print(gps.course.deg()); // Course in degrees (double)
            break;
          case 6:
            lcd.print("Altitude (m)");
            lcd.print(gps.altitude.meters()); // Altitude in meters (double)
            break;
          case 7:
            lcd.print("Altitude (km)");
            lcd.print(gps.altitude.kilometers()); // Altitude in kilometers (double)
            break;
          case 8:
            lcd.print("HDOP : ");
            lcd.print(gps.hdop.value()); // Horizontal Dim. of Precision (100ths-i32)
        }
        val_old = val;
      } else {
        lcd.print(msg20);
      }

    }
  }

  /*----------Freq Counter & VOCXO Control----------*/
  if (f_check == 1) {
    count_chk = ((carry * 65536) + end_cnt) - start_cnt;
    start_cnt = end_cnt ;
    f_check = 0;

    /* 起動15分後、OCXOの周波数が安定したら周波数補正開始 */
    /* 異常値を無視する */
    if (((millis() > 900000) || (debug)) && (abs(count_chk - count_ref) < 1500)) {

      byte a0, a1;
      if (accuracy == 0) {
        a0 = 0;
        a1 = accuracy_max - 1;
      } else {
        a0 = accuracy;
        a1 = accuracy - 1;
      }

      count[a0] = count_chk;
      accuracy += 1;
      if (accuracy > accuracy_max) accuracy = 0;

      byte n = 0;
      uint64_t count_sum = 0;
      for (int i = 0 ; i < accuracy_max ; i ++) {
        if ( count[i] != 0 ) {
          n += 1;
          count_sum = count_sum + count[i];
        }
      }
      if (n > 0) {
        freq1000 = count_sum;
      } else {
        freq1000 = 0;
      }

      int count_dif = (freq1000 - (count_ref * n)) / ratio;
      int sum_dif = count_sum - count_ref * n;

      if (abs(count_dif) < 1) {
        dacvolt_d = 1;
      }
      else {
        dacvolt_d = abs(count_dif) * 2.4;
      }
      if ((sum_dif < 0) && (count[a0] < (count_ref - 0))) dacvolt += dacvolt_d;
      if ((sum_dif > 0) && (count[a0] > (count_ref + 0))) dacvolt -= dacvolt_d;
      if (dacvolt > dacvoltmax) dacvolt = dacvoltmax; else if (dacvolt < 0)dacvolt = 0;

      char s8[9];
      char acc = '-';
      if ((n / ratio) >= 10) {
        uint16_t freq1000_surplus = (freq1000 % (n * (100 / ratio))) * 1000 / (n * (100 / ratio));
        sprintf(freq_msg15, " %s.%03dHz", dtostrf(uint32_t(freq1000 / (n * (100 / ratio))), 8, 0, s8), freq1000_surplus);
        if (abs(count_dif) < 8) acc = 'o'; else acc = 'x';
      } else {
        uint16_t freq1000_surplus = (freq1000 % (n * (100 / ratio))) * 100 / (n * (100 / ratio));
        sprintf(freq_msg15, " %s.%02dHz ", dtostrf(uint32_t(freq1000 / (n * (100 / ratio))), 8, 0, s8), freq1000_surplus);
      }

      dac.setVoltage(dacvolt, false); //DACに値(0-4095)を設定する

      lcd.setCursor(0, 0);
      lcd.print(acc);
      lcd.print(freq_msg15);

      if (dacvolt > dacvoltmax) {
        dacvolt = dacvoltmax;
        /* エラー表示を入れる */
        lcd.print( " NG ");
      } else if (dacvolt < 0) {
        dacvolt = 0;
        /* エラー表示を入れる */
        lcd.print( " NG ");
      } else if (abs(dacvolt - dacvolt_norm) > 500) {
        /* 警告表示を入れる */
        lcd.print( " CHK");
      } else lcd.print( "    ");

      if (debug>0 ) {
        Serial.print(msg20 ); Serial.print("," );
        Serial.print(long(freq1000 / 100) ); Serial.print(int(freq1000 % 100) ); Serial.print("," );
        Serial.print(long(count_sum / 10000) ); Serial.print(int(count_sum % 10000) ); Serial.print("," );
        Serial.print(uint32_t (count_chk) ); Serial.print("," );
        Serial.print(uint32_t (count_ref) ); Serial.print("," );
        Serial.print(n ); Serial.print("," );
        Serial.print(acc ); Serial.print("," );
        Serial.print(sum_dif ); Serial.print("," );
        Serial.print(count_dif ); Serial.print("," );
        Serial.print(dacvolt_d ); Serial.print("," );
        Serial.print(dacvolt ); Serial.print("," );
        Serial.println(gps.failedChecksum() );
      }
    } else {
      char s8[9];
      freq1000 = count_chk;
      uint16_t freq1000_surplus = (freq1000 % (100 / ratio)) * 100 / (100 / ratio);
      sprintf(freq_msg15, " %s.%02dHz", dtostrf(uint32_t(freq1000 / (100 / ratio)), 8, 0, s8), freq1000_surplus);
      lcd.setCursor(0, 0);
      lcd.print("Wait! ");
      lcd.print(freq_msg15);

    }

  }
}

注意

MCP4725 DACモジュール使用にあたりGND共通で使用する場合は、モジュールボードにある出力側のGNDは未接続にします。接続した場合、ループが形成されて動作不良が発生します。

WINGONEER MCP4725 DACモジュールI2CインタフェースDA変換

周波数測定プログラム作成

arduino 24MHZ(25MHz)動作確認を兼ねて、基準クロック発生器のプログラムを改変し、周波数測定プログラムを作成しました。

周波数測定プログラムには、いくつか便利なライブラリーが公開されていますが、ノーマル状態でないと正常に動作しませんし、高精度と書かれているものでもそれなり程度なので、高クロックでの動作確認を兼ねて、周波数測定プログラムを作成しました。

手抜きで、システムクロックを25MHz決め打ちで作っていますが、F_CPUによってパラメータ(定数)を切り替えるようにするといろいろなシステムでも動作可能となります。システムクロックの誤差も補正できるので、測定できる周波数の上限はシステムクロックの1/2.3程度(24MHzで10.5MHz、16MHzで7MHz程度)ですが、….

arduino 24MHZ(25MHz)動作確認用に周波数測定

F_CPUによって以下の表の黄色の部分の定数に差し替えます。

TIMER2_OCR2A、ratio_vctの場所、conversion(補正値)は、10000000倍した整数にします。補正値には必要に応じてシステムクロックの補正分も盛り込みます。

F_CPU TCCR2B(CS2) TCNT2 → ratio        
               
25000000 1024 218 55.7398687214612 217 56 0.99992064629751 100.007936
24999899 1024 217 55.9953299706135 217 56 0.999916606618099 100.008340033694
24000000 1024 220 53.0260180995475 216 54 1.00006400409626 99.9936
24000000 1024 216 54.0034562211982 243 50 0.960553278688525 104.106666666667
20000000 1024 243 40.0230532786885 216 45 1.00006400409626 99.9936
20000000 1024 216 45.0028801843318 251 39 0.993653337403337 100.63872
16000000 1024 251 31.0019841269841 251 31 1.00006400409626 99.9936
16000000 1024 255 30.517578125 251 31 1.00006400409626 99.9936
8000000 256 251 62.0039682539683 251 62 1.00006400409626 99.9936
8000000 1024 216 18.0011520737327 216 18 1.00006400409626 99.9936

プログラム本体は、以下。

#define FAST_MODE と #define ALONE_MODE のコメントアウトするかどうかで、1Sごとの通常測定と高精度測定、外部1pps入力と自前クロック(単独モード) での測定と切替ができます。

  • 計測対象クロックは、5に入力。
  • 外部1ppsは、8に入力。
  • 単独モードの場合は、11と8を結線
  • T0はシステム、T1は周波数測定、T2は自前クロックに使用しています。
  • LCDコントラストは、簡易ソフトウェアPWM
/*
   周波数カウンター
   カウンタ変数をULL化
   CPUを8MHzから24MHzに、それに伴いダイレクト計測周波数も2.5MHzから10MHzへ
   (※現状、テスト評価用の25MHzの設定。ALONE部分はクロックに合わせて変更が必要)
  ・割り込み処理ルーチンを最低限の処理にする。
  ・周波数測定方法を1000sを最長に100s間隔で周波数を表示
  最終的に1000sでの平均周波数を表示。
   +----------------------------+
   |Frequency/stability/accuracy|
   +----------------------------+
*/
//#include <Wire.h>
#include <LiquidCrystal.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#define rs 10
#define enable 9
#define rw 7
#define d4 6
#define d5 4
#define d6 3
#define d7 2
#define v0 12 //固定 ソフトウェアPWMの関係。変更する場合はPWMの部分も修正
#define bl 13
LiquidCrystal lcd(rs, rw, enable, d4, d5, d6, d7);
const int lcd_cont = 20; //LCDコントラスト ソフトウェアPWM(%)
#define ocxo 5       //OCXO 入力ピン
#define gps1ppm 8    //GPS 1Hz 入力ピン
#define FAST_MODE     //高精度測定時は、コメントアウト
#define ALONE_MODE    //GPS 1pps使用時は、コメントアウト。単独モード。 11と8を結線
const uint8_t debug = 0; // 1: debug code output
uint32_t debug_f = 0 ;
#define TIMER2_OCR2A_VAL  217                               // F_CPUによりratioと組み合わせ決定:224
#define TIMER2_TCCR2B_VAL (1<<CS22) | (1<<CS21) | (1<<CS20) // F_CPUによりratioと組み合わせ決定:1024
#ifdef ALONE_MODE
#define TIMER2_TCCR2A_VAL (1<<COM2A0) | (1<<WGM21) // 比較一致でOC2Aピン トグル(交互)出力&CTC動作
const uint32_t conversion = 9999166;   // 補正値(1/10000000)、計算値+OSC誤差の吸収
const uint8_t ratio = 1;
const uint16_t ratio_vct = 56 * ratio; // 分周比を考慮したカウント単位,F_CPUにより決定
const uint8_t accuracy_max = 5; /* ratioの整数n倍、時間をかけて正確に周波数を計測... */
volatile uint16_t carry_chk = 3000; /* キャリー追い越しチェック幅 -65535 */
#else
#define TIMER2_TCCR2A_VAL 0 // OC2Aピン 切断&標準動作
const uint32_t conversion = 10000000; // 補正値(1/10000000)
volatile uint16_t carry_chk = 6000; /* キャリー追い越しチェック幅 -65535 */
#ifdef FAST_MODE
const uint8_t ratio = 1; // 分周比を考慮したカウント単位
const uint8_t ratio_vct = ratio;
const uint8_t accuracy_max = 1; /* ratioの整数n倍、時間をかけて正確に周波数を計測... */
#else
const uint8_t ratio = 100; // 分周比を考慮したカウント単位
const uint8_t ratio_vct = ratio;
const uint8_t accuracy_max = 10; /* ratioの整数n倍、時間をかけて正確に周波数を計測... */
#endif
#endif
/* 精度設定 */
uint8_t accuracy = 0; /* 単位の周波数測定のカウンター */
volatile uint16_t intcarry;
volatile uint16_t carry;    /* 割り込みルーチン内で割り込み追い越しを配慮した溢れ回数を保存*/
volatile uint32_t start_cnt;
volatile uint32_t end_cnt;
volatile uint16_t gps_loop;
uint64_t  freq1000;
uint8_t ignore;
uint32_t count[accuracy_max] = {};
uint64_t count_chk;
volatile uint8_t f_check; //周波数処理確認フラグ 1:処理が必要な状態、0:処理完了 >2:処理追い越しが発生!
void timer_start(void)
{
  TCCR2A = TIMER2_TCCR2A_VAL;
  OCR2A = TIMER2_OCR2A_VAL;
  GTCCR = (1 << PSRASY);
  TCCR2B = TIMER2_TCCR2B_VAL;
  //  TIMSK2 = (1<<OCIE2A);
  DDRB |= (1 << PB3);
  //  PORTB &= ~_BV(PB3);
}
ISR(TIMER1_CAPT_vect) {
  /*
    WDT Reset
    if(カウンターフラグ無効)
      WDT起動
      ICR1に保存された値を保存
      カウンターフラグ有効に
    else
      ICR1に保存された値を保存
      溢れ割り込み追い越しをチェック
      桁溢れ回数を保存(intcarry -> carry + 1?)
      周波数処理確認フラグ += 1
    if(周波数処理確認フラグ > 1)
      発生回数・オーバーフロー回数を初期化
      カウンターフラグ無効
  */

  wdt_reset();
  ++ gps_loop;

  if (ignore > 0) {
    /* 最初はICR1値が不安定なので無視 */
    -- ignore ;
    /* GPS 1ppm 監視 WDT start */
    {
      MCUSR = 0;
      WDTCSR |= 0b00011000; //WDCE WDE set
      WDTCSR =  0b01000000 | 0b000111; /* /WDIE set 2s WDT */
    }
    start_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if ((start_cnt < carry_chk) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から... */
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
    }
    intcarry = 0;
    gps_loop = 0;

  } else {
    if (gps_loop == ratio_vct) {
      end_cnt = (word)ICR1;
      /* キャり追い越し補正する */
      if ((end_cnt < carry_chk) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から3160... */
        ++ intcarry;
        TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      }
      carry = intcarry;
      ++ f_check;
      gps_loop = 0;
      intcarry = 0;
    }
  }
  if (f_check > 1) {
    ignore = 2;
    f_check = 0;
  }
}
ISR(TIMER1_OVF_vect) {
  /* オーバーフロー回数を加算 */
  if (!ignore) ++ intcarry;
}
/*
  WDT設定(2s)は、WDTCSRを47hにセットして、wdt_reset();
*/
ISR(WDT_vect) {
  wdt_reset();
  /*
      カウンターフラグを無効にする
      発生回数・オーバーフロー回数を初期化
  */
  start_cnt = 0;
  gps_loop = 0;
  ignore = 2;
  f_check = 0;
  {
    MCUSR = 0;
    WDTCSR |= 0b00011000; /* WDCE WDE set */
    WDTCSR =  0b00000000; /* status clear */
  }
}
void setup() {
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("FreqCounter 10MHz");
  lcd.setCursor(0, 1);
  lcd.print(" 2018.12 by Drg.Jr. ");
  pinMode(v0, OUTPUT);
  //  digitalWrite(v0, LOW);     // LCD コントラスト
  //  PORTB &= ~_BV(4); //LOW    // SW PWMの為、PINコントロールを高速化
  //  PORTB |= _BV(4); //HIGH

  pinMode(bl, OUTPUT);
  digitalWrite(bl, HIGH);    // LCD バックライト ON

  if (debug) {
    Serial.begin(115200);
    Serial.println("Hello World!");
    Serial.println("");
  }


  //  lcd.clear();


  intcarry = 0;
  start_cnt = 0;
  end_cnt = 0;
  freq1000 = 0;
  ignore = 2;
  gps_loop = 0;
  f_check = 0;

  /*カウンターセットアップ*/
  timer_start(); //Stand alone用 基準クロック発生PB3/pin11

  cli();
  TCCR1A = 0;   /* TCCR1A タイマモード */
  TCCR1B = 0x46;      /*ICP, T1入力、立ち上がり,立下りを検知 */
  TCNT1 = 0x0000;  /* タイマ1の初期値設定   */
  TIMSK1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込みの許可 */
  //  TIFR1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込み要求クリア */
  sei(); /* 全体の割り込み許可 */
}
void loop() {
  /*----------Freq Counter & VOCXO Control----------*/
  if (f_check == 1) {

    count_chk = ((carry * 65536) + end_cnt) - start_cnt;
    start_cnt = end_cnt ;

    f_check = 0;

    if (!ignore) {
      count_chk = count_chk * conversion / 10000000; // 既知誤差の補正
      count[accuracy] = count_chk;

      if (accuracy < (accuracy_max - 1))accuracy += 1; else accuracy = 0;

      byte n = 0;
      uint64_t count_sum = 0;
      for (int i = 0 ; i < accuracy_max ; ++i) {
        if (count[i] > 0) {
          ++n;
          count_sum = count_sum + count[i];
        }
      }
      if (n > 0) {
        freq1000 = count_sum;
      } else freq1000 = 0;

      char msg15[15];
      char s8[9];
      if ((ratio * n) >= 1000) {
        uint16_t freq1000_surplus = (freq1000 % (ratio * n)) * 1000 / (ratio * n);
        sprintf(msg15, " %s.%03dHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8), freq1000_surplus);
      } else if ((ratio * n) >= 100) {
        uint16_t freq1000_surplus = (freq1000 % (ratio * n)) * 100 / (ratio * n);
        sprintf(msg15, " %s.%02dHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8), freq1000_surplus);
      } else if ((ratio * n) >= 10) {
        uint16_t freq1000_surplus = (freq1000 % (ratio * n)) * 10 / (ratio * n);
        sprintf(msg15, " %s.%01dHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8), freq1000_surplus);
      } else {
        sprintf(msg15, " %sHz ", dtostrf(uint32_t(freq1000 / (ratio * n)), 8, 0, s8));
      }

      lcd.setCursor(0, 2);
      lcd.print("                    ");
      lcd.setCursor(0, 2);
      lcd.print(msg15);

      if (debug) {
        char msg4[4];
        sprintf(msg4, "%04d", uint16_t(freq1000 % 10000));
        Serial.print(uint32_t(freq1000 ));  Serial.print("," );
        Serial.print(msg4 );  Serial.print("," );
        Serial.print(long(count_chk ) ); Serial.print("," );
        Serial.print(n ); Serial.print("," );
        Serial.print(accuracy ); Serial.println("" );
      }

    } else {
      count[accuracy_max] = {};
      accuracy = 0;
    }

  }
  //--------------------------------------------
  /*
      LCDコントラスト調整
      高速化の為に直接レジスタを操作
      LCDコントラストの制御ピンを変更する場合は注意
  */
  int pwb_time = (micros() % 1000) / 10;
  if (pwb_time > lcd_cont) PORTB &= ~_BV(4); /*LOW*/ else PORTB |= _BV(4); /*HIGH*/
}

最後に、基準クロック発生器とツーショット

基準クロック発生器 と オーバークロック動作確認中のArduino 25MHz

秋月電子のATmega168/328マイコンボードキットを24MHzで動かす方法

まずは、16MHzでArduino化します。

方法は、いろいろな方が説明されているので、そちらをじっくり読めばできると思います。

実は、私は組立後ロクに確認もしないで行ったので、ブートローダーの書き込みは完了するのにスケッチが書き込めなくハマってしまいました。

原因は、ハンダ忘れが2か所も….汗

まぁ、うまくいかないときこそ、基本に立ち返って、ですね。

Arduino化が正常に完了したら、いよいよ24MHzで動作させましょう!

インターネット上に公開されている情報からは、ほぼ30MHzまでは動作するようなので、事前チェックは不要かもしれませんが、まず、16MHzの状態でシリアルコンソール74880bpsで通信できるスケッチを書き込んでおいて、24MHzにして、シリアルコンソールを11520bpsにして期待値どおり表示が出ることを確認します。

Arduino 1.8.5にArduinor Diecimila 24MHz版を追加する。

boards.txtを編集します。

Arduino 1.8.5をインストールした場所(例えばc:\Program Files (x86)\Arduino\hardware\arduino\avr)にもありますが、編集するのは、「ユーザーフォルダー\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.6.23」にあるファイルです。インストールした場所にあるのはインストール時のオリジナルで、こちらを編集しても反映されません。

反映されない場合は、一度ボードマネージャを立ち上げると反映されると思います。Updateは上書きされる可能性があるので、少なくとも修正部分は控えておくと良いと思います。

「Arduino Duemilanove or Diecimila w/ ATmega328P」の定義を丸ごとコピーして、以下のように編集して差し込みます。

編集の肝は、f_cpu と upload.speed

##############################################################

diecimila24.name=Arduino Diecimila 24

diecimila24.upload.tool=avrdude
diecimila24.upload.protocol=arduino

diecimila24.bootloader.tool=avrdude
diecimila24.bootloader.low_fuses=0xFF
diecimila24.bootloader.unlock_bits=0x3F
diecimila24.bootloader.lock_bits=0x0F

diecimila24.build.f_cpu=24000000L
diecimila24.build.board=AVR_DUEMILANOVE24
diecimila24.build.core=arduino
diecimila24.build.variant=standard

## Arduino Diecimila 24 w/ ATmega328P
## ———————————————-
diecimila24.menu.cpu.atmega328=ATmega328P

diecimila24.menu.cpu.atmega328.upload.maximum_size=30720
diecimila24.menu.cpu.atmega328.upload.maximum_data_size=2048
diecimila24.menu.cpu.atmega328.upload.speed=86400

diecimila24.menu.cpu.atmega328.bootloader.high_fuses=0xDA
diecimila24.menu.cpu.atmega328.bootloader.extended_fuses=0xFD
diecimila24.menu.cpu.atmega328.bootloader.file=atmega/ATmegaBOOT_168_atmega328.hex

diecimila24.menu.cpu.atmega328.build.mcu=atmega328p

## Arduino Diecimila 24 w/ ATmega168
## ———————————————
diecimila24.menu.cpu.atmega168=ATmega168

diecimila24.menu.cpu.atmega168.upload.maximum_size=14336
diecimila24.menu.cpu.atmega168.upload.maximum_data_size=1024
diecimila24.menu.cpu.atmega168.upload.speed=28800
diecimila24.menu.cpu.atmega168.bootloader.high_fuses=0xdd
diecimila24.menu.cpu.atmega168.bootloader.extended_fuses=0xF8
diecimila24.menu.cpu.atmega168.bootloader.file=atmega/ATmegaBOOT_168_diecimila.hex

diecimila24.menu.cpu.atmega168.build.mcu=atmega168

 

これで、シリアルコンソールや基本的な時間関数などは、正常に動作するようになり、特に24MHz化を気にする必要はなくなります。

ただ、ライブラリーで、CPUクロックf_cpuを考慮していない場合は注意が必要です。

16MHz版に対して1.5倍のSpeedとなりました。

先の記事の10HHz 標準クロックの周波数測定も、これを使えば、直接10MHzを扱えるので同じ精度が4倍のスピードで完了して環境温度等による周波数変動に対する追従特性の大幅な向上が可能となります。

問題点

今回実験に使用したボードは、たまたまリセット時間が長いのか?もしくは168Pだからか?スケッチの書き込みも問題ない様ですが、Arduinoのブートローダーの仕組み上、CPUが高速になるとブートローダーが待ちきれず書き込み済みのスケッチに動作を移すことがあるようです。

先の記事の作成に使用したのと同じ怪しいArduino Pro mini 8MHzで24MHz動作を確認したところまさにこの現象が発生しました。AVR自体は32MHzでもちゃんと動作しているのに20MHz以上のクロックではスケッチが書き込めない事象で、以下の暫定対策で24MHzでも書き込みできることを確認しました。

高速CPUに対応できるブートローダーへ変更が正しい対応と思いますが、暫定回避策としてDTRとリセットの間のコンデンサーの容量を大きくするなど物理t的にリセットパルスを少し長くする(電源オフ時に残留電荷で壊す可能性もありますし、どのくらいか難しいですが...)対策でもなんとかなりそう。。。。最悪、タイミングが難しいが人間リセットも。。。汗。(RXでLEDが光るようになっていればこれが光ったのをみてResetを解除すればよいらしい。)

実際にやってみましたが、コンデンサーを取り付ける方法は有効ですが、壊す可能性もあり、手動リセットはオーバークロックの場合は、タイミングが難しくちょっと無理っぽいので、Arduino側のReset入力のコンデンサーをショート(もしくはパス)して、USB-Serial変換ボード側でDTRをトリガーで負パルスを発生するようにNE555(100uF、100kVR)でワンショット、出力を少しなまらして(TrのBaseとGND間に200Ωと33uF)、Trでひっくり返してArduinoのDTR(Reset)へという専用書き込み器(ジャンパーで通常モードと切替)を作成しました。クロック・個体種別によってパルス幅は調整が必要かもしれませんが、標準のリセットのタイミングより少し遅らせる(なまらせた部分)ことが重要なポイントとなりました。

今のところ100%成功しています。AVRの本来の低消費電力に反しますが、目的によっては、24MHz(簡単な確認では32MHzでも問題ない模様で十分なマージンがあると思って良さそう。)で動作させることもできる準備が出来ました。

クロックアップArduino用書き込みTool

GPS 1ppsから10MHzの標準周波数を発生する装置を作成

故障したGPSモジュール(購入時からの部品取り付け不良)を再生して作ったGPSモジュールからダイレクトPULL方式による標準器を作ってしまったので、暫く作りかけで放置状態でした「REF SIGNAL GENERATOR / GPS」が完成しました。まだ、ちょこちょこプログラムは修正するかもしれませんが、ひとまず完成ということでお披露目。

こちらは、現在、周波数カウンターの基準クロックとして活躍してくれている、GPSモジュールにPLL ICを直結してダイレクトに10MHzを発生する標準器モドキです。独立したオシレーターを持たないので、衛星が3個以上安定して補足できないと使えず、瞬間はジッターが多く、短時間での精度が必要な用途には使えません。原理は簡単!GPSモジュール(u-blox 6)から比較的ジッターの少ない2MHzを出して、IC NB3N502(14 MHz to 190 MHz PLL
Clock Multiplier)で5倍して、10MHzを得ています。ケースは100均で購入。室外に設置したGPSアンテナを含めて5000円に満たない簡単標準10MHz発振器です。ジッターが大きく、ここのパルスの精度が必要なオーディオADC/DACの基準クロックなどには使えませんが、周波数カウンターの基準クロックに使う分には十分な性能と思います。

今回、作成したのは、VOCXOをGPS 1PPSクロックで補正して、正確な10MHzを出力する装置です。衛星をロストしても長時間高い精度の周波数でジッターの少ない標準クロックを安定して供給できます。

高い精度といっても諸事情で、±0.01Hz以下程度の性能になっていますが、個人で使用するには何に使うの?ってくらいの十分な性能と思います。

標準周波数発生装置 REF SIGNAL GENERATOR / GPS の概要

主な部品

VOCXO:OFC MC862X4-004W

GPSモジュール:u-blox 6

コントローラ:Arduino Pro mini /8MHz..

DAC:MCP4725

ケース:映像関係のジャンクから流用。ロジックICもこのジャンクから取り外したものを使用

主な機能

標準周波数:10MHz 4ch、

      1pps 1ch、

      1MHz 1ch(1MHz予定、現状はコネクタのみ)

表示:20 x 2 バックライトあり、

   Power&1pps 表示LED

SW: PushするとLCDの時間表示が、年月日表示になる。

ロータリーエンコーダ: 通常、LCD2行目の表示は、補足中の衛星数と時間表示ですが、これを回すと一時的にGPSモジュールから得られる緯度経度などいくつかのデータを表示する。まぁ。ロータリーエンコーダがあるのに使わないのはさみしいので遊びでつけたおまけ程度(^-^;

GPS周波数標準器 (SATELLITE CONTROLLED FREQUENCY STANDARD)通常表示

回路概要

ArduinoのT1タイマーの標準タイマー機能と捕獲割り込み、溢れ割り込みを使った周波数カウンタで、GPS1ppsを捕獲信号としてICP1にVOCXOのクロックをT1に入力してこれをカウントして、目的周波数差分を計測、差分補正分のVOCXOの制御電圧をDACで発生させて、周波数を補正しています。

使用したArduino pro miniは、Arduinoについて何も知らない時に購入した8MHz版でしたので、10MHzを4分周した2.5MHzを時間軸を長くすることで測定精度を確保しています。

VOCXOは、本来5V駆動品と思いますが、3.3Vでも安定動作するようなので、レギュレータを抱き合わせにして、DACの基準電圧を兼ねた4Vで稼働させています。レギュレータがVOCXOと同じ温度に保たれることで、安定したDACの基準電圧を得ています。(5Vで駆動した方が環境温度に対する耐性が高いかもしれませんが、流用電源の関係で安定化すると4Vとなってしまいました。

30分程度で実用レベル(±0.1Hz程度)にはなりますが、残念ながら、完全安定状態(0.004Hz程度まで安定)になるまでの時間は、4分周して周波数測定している関係もあり、2時間程度とちょっと時間がかかります。(現時点のスケッチでは….汗)

こまかい部分は、Arduinoのスケッチを掲載するんでそちらを参考頂ければわかると思いますので回路図は省略。

本当は、作成中につまらないミスで最初に作成した回路図と最終回路で差分があり、修正版回路図を作成してない為(-_-;)

その分、スケッチには不要なHW情報も若干記載

Arduino スケッチ(プログラム)

Arduinoのスケッチを作成するにあたり、いろいろ役に立ちそうな事があったので、応用できそうな部分など、ボチボチ説明できればと思います。

まぁ、拙いソースなのであとで自分が応用する為のメモみたいなものですが、.....

で、いきなりスケッチ(ちょっと長いですが)

/*
   GPS base 10MHz with MCP4725
   +----------------------------+
   |Frequency/stability/accuracy|
   | GPS Status                 |
   +----------------------------+
*/
#include <Wire.h>
#include <LiquidCrystal.h>
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac; // constructor
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <TinyGPS++.h>
TinyGPSPlus gps;
#include <SoftwareSerial.h>
SoftwareSerial gpsSerial(6, 7); // RX, TX
#define rs 2
#define enable 3
#define rw 9
#define d0 A3
#define d1 10
#define d2 A2
#define d3 11
#define d4 A1
#define d5 12
#define d6 A0
#define d7 13
LiquidCrystal lcd(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7);
#define enter_sw A6 // front SW 10以下 ON 2000以上 OFF
#define en_en 4 // EAW - Absolute Contacting Encoder LCD DATA LINE共通
#define ocxo25mhz 5; //OCXO 10MhHzの4分周2.5mhz 
#define gps1ppm 8;    //GPS 1Hz
byte debug = 1; // 1: debug code output
uint32_t debug_f = 0 ;
uint32_t sc = 0;
uint32_t ec = 0;
/* 精度設定 */
const int stability = 50; /* 前回測定より約0.5Hz以内の変動になれば、補正開始 */
int accuracy = 40; /* 40で決め打ちしている箇所あり。修正は要注意! */
const int accuracy_max = 1200; /* 400の整数倍1600まで可能だが...時間とDAC精度を考えると800で十分 */
/* VOCXO Voltage */
const int dacvoltmax = 4095;
const int dacvolt_norm = 2037;
int dacvolt = 2048; /* VOCXO Voltage = 2.0V 初期値:2048 */
int dacvolt_d = 10; /* 値はダミー */
volatile unsigned int intcarry;
volatile unsigned int start_cnt;
volatile unsigned int end_cnt;
volatile unsigned long  freq100_old;
volatile unsigned long  freq100;
volatile byte ignore;
unsigned int gps_loop;
volatile unsigned long count;
volatile unsigned long count_old;
int val;
byte enter;
int val_old;
byte enter_old = 2;
byte encode_old = 0;
unsigned long millis_old;
char gps_loop_msg3[4] = "***";
char freq_msg15[16] = " ********.**Hz ";
const byte encode[256] =                    //もし255なら読み取りミスで無視する。
{
  56, 40, 55, 24, 255, 39, 52, 8, 57, 255,
  255, 23, 255, 36, 13, 120, 255, 41, 54, 255,
  255, 255, 53, 7, 255, 255, 255, 20, 19, 125,
  18, 104, 105, 255, 255, 25, 106, 38, 255, 255,
  58, 255, 255, 255, 255, 37, 14, 119, 118, 255,
  255, 255, 107, 255, 255, 4, 255, 3, 255, 109,
  108, 2, 1, 88, 255, 89, 255, 255, 255, 255,
  51, 9, 10, 90, 255, 22, 11, 255, 12, 255,
  255, 42, 43, 255, 255, 255, 255, 255, 255, 255,
  255, 21, 255, 126, 127, 103, 255, 102, 255, 255,
  255, 255, 255, 255, 255, 91, 255, 255, 255, 255,
  255, 116, 117, 255, 255, 115, 255, 255, 255, 93,
  94, 92, 255, 114, 95, 113, 0, 72, 71, 255,
  68, 73, 255, 255, 29, 255, 70, 255, 69, 255,
  255, 35, 34, 121, 255, 122, 255, 74, 255, 255,
  30, 6, 255, 123, 255, 255, 255, 124, 17, 255,
  255, 255, 67, 26, 255, 27, 28, 255, 59, 255,
  255, 255, 255, 255, 15, 255, 255, 255, 255, 255,
  255, 255, 255, 5, 255, 255, 255, 110, 255, 111,
  16, 87, 84, 255, 45, 86, 85, 255, 50, 255,
  255, 255, 46, 255, 255, 255, 33, 255, 83, 255,
  44, 75, 255, 255, 31, 255, 255, 255, 255, 255,
  255, 255, 32, 100, 61, 101, 66, 255, 62, 255,
  49, 99, 60, 255, 47, 255, 255, 255, 48, 77,
  82, 78, 65, 76, 63, 255, 64, 98, 81, 79,
  80, 97, 96, 112
};
byte read_Enter() { // Enter SW
  byte value;
  int enter_val = analogRead(enter_sw);
  if (enter_val < 100) value = 1;
  else value = 0;
  return value;
}
byte read_Encoder() { // Encoder(8bits)の値を読む
  byte value;
  pinMode(d0, INPUT);
  pinMode(d1, INPUT);
  pinMode(d2, INPUT);
  pinMode(d3, INPUT);
  pinMode(d4, INPUT);
  pinMode(d5, INPUT);
  pinMode(d6, INPUT);
  pinMode(d7, INPUT);
  //   digitalWrite(en_en, LOW);
  PORTD &= ~_BV(4);
  value = digitalRead(d7) * 128 + digitalRead(d6) * 64 + digitalRead(d5) * 32 + digitalRead(d4) * 16;
  value = value + digitalRead(d3) * 8 + digitalRead(d2) * 4 + digitalRead(d1) * 2 + digitalRead(d0);
  //value = (PINB & _BV(5)) * 128 + (PINC & _BV(0)) * 64 + (PINB & _BV(4)) * 32 + (PINC & _BV(1)) * 16;
  //value = value + (PINB & _BV(3)) * 8 + (PINC & _BV(2)) * 4 + (PINB & _BV(2)) * 2 + (PINC & _BV(3));
  //    digitalWrite(en_en, HIGH);
  PORTD |= _BV(4);
  pinMode(d0, OUTPUT);
  pinMode(d1, OUTPUT);
  pinMode(d2, OUTPUT);
  pinMode(d3, OUTPUT);
  pinMode(d4, OUTPUT);
  pinMode(d5, OUTPUT);
  pinMode(d6, OUTPUT);
  pinMode(d7, OUTPUT);
  value = encode[value - 1];
  if (value == 255) value = encode_old; else encode_old = value;
  return value;
}
ISR(TIMER1_CAPT_vect)
{
  /*
    WDT再起動(WDT Reset)
    if(カウンターフラグ無効)
    ICR1に保存された値を保存
    カウンターフラグ有効に
    if(発生回数が規定値)
    なら周波数を算出・表示
    発生回数・オーバーフロー回数を初期化
    カウンターフラグ無効
  */
  wdt_reset();
  gps_loop += 1;

  if (ignore) {
    if (start_cnt > 0) {
      /* 最初の1回はICR1値が不安定なので無視 */
      ignore = 0;
      /* GPS 1ppm 監視 WDT start */
      {
        MCUSR = 0;
        WDTCSR |= 0b00011000; //WDCE WDE set
        WDTCSR =  0b01000000 | 0b000111; /* /WDIE set 2s WDT */
      }
    }
    start_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if ((start_cnt < 6000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から... */
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      if (debug) debug_f = 2;
    }
    intcarry = 0;
    gps_loop = 0;

  } else if (gps_loop == accuracy) {
    count_old = count;
    freq100_old = freq100;
    end_cnt = (word)ICR1;
    /* キャり追い越し補正する */
    if (debug) {
      debug_f = intcarry;
      sc = start_cnt;
      ec = end_cnt;
    }
    if ((end_cnt < 6000) && (TIFR1 & 0x01)) { /* CPUの割り込み処理が6サイクル+実行中コマンドだが実測から3160... */
      intcarry += 1;
      TIFR1 = 0b00000001; //Timer/Counter Overflow Flag clear(TOV1)
      if (debug) debug_f = 1;
    }
    count = ((intcarry * 65536) + end_cnt) - start_cnt;

    if ((accuracy >= 400)) {
      freq100 = count / (gps_loop / 400);
    } else {
      freq100 = count * 10; //  40で決め打ち
    }

    /* 異常値を無視する */
      if (abs(freq100 - 1000000000) > 1500) {
      count = count_old;
      freq100 = freq100_old;
      if (debug) debug_f = 0XFFFF;
      }
      

    char s8[9];
    int freq100_surplus = freq100 % 100;
    sprintf(freq_msg15, " %s.%02dHz ", dtostrf((freq100 / 100), 8, 0, s8), freq100_surplus);
    start_cnt = end_cnt ;
    gps_loop = 0;
    intcarry = 0;

    /* 起動15分後、OCXOの周波数が安定したら周波数補正開始 */
    if ((abs(count - count_old) < stability) && (!ignore) && ((millis() > 900000) || (debug))) {
      unsigned long count_ref;
      if ((accuracy >= 400)) {
        count_ref = 1000000000 * (accuracy / 400);
        dacvolt_d = abs(count - count_ref) * 800 / accuracy ;
        if (abs(count - count_ref) <= 0) {
          accuracy = accuracy + ((accuracy < accuracy_max) * 400) ;
        } else {
          if (abs(count - count_ref) >= 50) {
            accuracy = 40;
          } else if (abs(count - count_ref) >= 5) {
//            accuracy = accuracy - (accuracy > 400) * 400;
            accuracy = 400;
          }
        }
        if (count < (count_ref - 1)) dacvolt += dacvolt_d;
        if (count > (count_ref + 1)) dacvolt -= dacvolt_d;
      } else {
        count_ref = 100000000;
        dacvolt_d = abs(count - count_ref) * 3.3 ;
        if (abs(count - count_ref) <= 5) {
//          accuracy = 400 + (count == count_ref) * 400; /* OCXOが十分調整されてる場合のみ*/
          accuracy = 400;
        }
      if (count < count_ref) dacvolt += dacvolt_d;
      if (count > count_ref) dacvolt -= dacvolt_d;
     }    }  } } ISR(TIMER1_OVF_vect) {  /* オーバーフロー回数を加算 */  if (!ignore)intcarry += 1; } /*   WDT設定(2s)は、WDTCSRを47hにセットして、wdt_reset(); */ ISR(WDT_vect) {  wdt_reset();  /*       カウンターフラグを無効にする       発生回数・オーバーフロー回数を初期化   */  start_cnt = 0;  gps_loop = 0;  ignore = 1;  {    MCUSR = 0;    WDTCSR |= 0b00011000; /* WDCE WDE set */    WDTCSR =  0b00000000; /* status clear */  } } void setup() {  pinMode(en_en, OUTPUT);  digitalWrite(en_en, HIGH);  delay(1);  lcd.begin(20, 2);  lcd.setCursor(0, 0);  lcd.print(" OCXO + MCP4725 10MHz");  lcd.setCursor(0, 1);  lcd.print(" 2018.11 by Drg.Jr. ");  Serial.begin(115200);  Serial.println("Hello World!");  Serial.println("OCXO + MCP4725 10MHz"); Serial.println("");  // pulse gen setup init clock  // initialize the MCP4725  dac.begin(0x60);  delay(5000);  gpsSerial.begin(9600); // ソフトウェアシリアルの初期化  gpsSerial.print("\r\n");  gpsSerial.print("\r\n");  /*     u-blox 6     Receiver Description     Including Protocol Specification を参考   */  gpsSerial.print("$PUBX,41,1,0007,0003,4800,0*13\r\n");   // GPSモジュールのbpsを変更  gpsSerial.begin(4800);                                   // ソフトウェアシリアルの初期化  gpsSerial.print("\r\n");  gpsSerial.print("\r\n");  lcd.clear();  intcarry = 0;  start_cnt = 0;  end_cnt = 0;  freq100_old = 0;  freq100 = 0;  ignore = 1;  gps_loop = 0;  millis_old = millis();  /*カウンターセットアップ*/  cli();  TCCR1A = 0;   /* TCCR1A タイマモード */  TCCR1B = 0x46;      /*ICP, T1入力、立ち上がり,立下りを検知 */  TCNT1 = 0x0000;  /* タイマ1の初期値設定   */  TIMSK1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込みの許可 */  //  TIFR1 = 0x21;  /* 入力キャプチャ1オーバーフロー割り込み要求クリア */  sei(); /* 全体の割り込み許可 */ } void loop() {  char msg20[21];  char msg4[5];  byte val;  byte enter;  dac.setVoltage(dacvolt, false); //DACに値(0-4095)を設定する  if ((millis() % 10) == 0) {    char acc;    if (!ignore) {      if (accuracy == accuracy_max) acc = 'o';      else if (accuracy >= 400) acc = '+'; else acc = '-';    } else {      acc = 'x';    }    sprintf(msg4, "%04d", dacvolt);    lcd.setCursor(0, 0);    lcd.print(acc);    lcd.print(freq_msg15);    lcd.print(msg4);    val = read_Encoder();    enter = read_Enter();    if (freq100 != freq100_old) {      if (dacvolt > dacvoltmax) {        dacvolt = dacvoltmax;        /* エラー表示を入れる */      }      if (dacvolt < 0) {        dacvolt = 0;        /* エラー表示を入れる */      }      if (abs(dacvolt - dacvolt_norm) > 500) {        /* 警告表示を入れる */      }    }  }  /* GPS module Accsess */  {    while (gpsSerial.available()) {      char read_gps = gpsSerial.read();      gps.encode(read_gps);      if ((gps.satellites.isUpdated()) && (gps.time.isUpdated())) {        char sa[3];        byte jst = gps.time.hour() + 9;        if (jst >= 24) jst = jst - 24;        if (enter == 0) {          sprintf(msg20, "GpsSat.%s   %2d:%02d:%02d",                  dtostrf(gps.satellites.value(), 2, 0, sa),                  jst, gps.time.minute(), gps.time.second() );        } else {          sprintf(msg20, "GpsSat.%s %4d/%02d/%02d",                  dtostrf(gps.satellites.value(), 2, 0, sa),                  gps.date.year(), gps.date.month(), gps.date.day() );        }        lcd.setCursor(0, 1);        if (val != val_old) {          lcd.print("                    ");          lcd.setCursor(0, 1);          switch (int(val / 16)) {            case 1:              lcd.print("Latitude : ");              lcd.print(gps.location.lat(), 6); // Latitude in degrees (double)              break;            case 2:              lcd.print("Longitude : ");              lcd.print(gps.location.lng(), 6); // Longitude in degrees (double)              break;            case 3:              lcd.print("Speed (m/s)");              lcd.print(gps.speed.mps()); // Speed in meters per second (double)              break;            case 4:              lcd.print("Speed (km/s)");              lcd.print(gps.speed.kmph()); // Speed in kilometers per hour (double)              break;            case 5:              lcd.print("Course (deg)");              lcd.print(gps.course.deg()); // Course in degrees (double)              break;            case 6:              lcd.print("Altitude (m)");              lcd.print(gps.altitude.meters()); // Altitude in meters (double)              break;            case 7:              lcd.print("Altitude (km)");              lcd.print(gps.altitude.kilometers()); // Altitude in kilometers (double)              break;            case 8:              lcd.print("HDOP : ");              lcd.print(gps.hdop.value()); // Horizontal Dim. of Precision (100ths-i32)          }          val_old = val;        } else {          lcd.print(msg20);        }      }      //      if (gpsSerial.overflow()) Serial.println("SoftwareSerial overflow!");    }    if ((debug) && ((millis() - millis_old) >= 1000) && (gps_loop == 1)) {      millis_old = millis();      Serial.print(msg20 ); Serial.print("," );      Serial.print(freq100 ); Serial.print("," );      Serial.print(count ); Serial.print("," );      Serial.print(accuracy ); Serial.print("," );      Serial.print(dacvolt ); Serial.print("," );      Serial.print(gps.charsProcessed() ); Serial.print("," );      Serial.println(gps.failedChecksum() );      if (debug_f && (debug > 1)) {        if (debug_f < 3 )Serial.println(" Overflow Flag clear" );        Serial.print(sc ); Serial.print("," ); Serial.println(ec );        debug_f = 0;        sc = 0;        ec = 0;      }    }  }  //  delay(0); }

ハードウェアの改善点

Ardurnoは、16MHz版で2分周の5MHz入力、可能であれば、24MHzまでオーバークロックして10MHzを生で計測が理想と思います。但し、20MHzあたりから、GPSモジュールの感度に大きく影響してくるようで電源周り、電磁波の回り込みなど配慮が必要)GPSモジュールとのコミュニケーションはSoftwareSirialを使用することになるので、新規に作成するなら、最低16MHz版を使用すべきと思います。8MHz版だと4800bpsまで落としてやっと何とか情報を引き出せる状態となっています。

DACの基準電源は、DACの最低動作電圧2.7Vまで下げた方が少しでも細かい制御が可能になり、精度をもう一桁上げられるかもしれません。現状は0.003Hz/1LSB程度)

今回はDACに12bitsのMCP4725を使用していますが、より細かい制御が期待できる秋月電子通商などでも扱っているMCP3425(16Bit ADC I2C 基準電圧内蔵)が良いかもしれません。基準電源も内蔵してい点でも扱いやすくなります。次回作成の機会があれば、使いたいと思います。

回路を簡素化して、コストも下げたい場合は、スイッチサイエンスが取り扱っているA-Star 328PB Micro – 5V/20MHzw

を使うのも良いかと思います。2個の16ビットカウンタと第2のUSARTが追加されているので、DACの代わりにPWMを使い、GPSモジュールとの通信もより簡単になると思います。

 

(次回へ続く)….予定 (^-^;