複素インピーダンスの場合、実数部は抵抗成分・虚数部はリアクタンス成分とわかれているので、イメージしやすいですが、電圧と電流の虚数部とは何なのでしょうか? と疑問に思ったので考えた結果をメモしておきます。

位相情報も含めた電圧・電流

実際のところ、位相も含めた電圧と電流は実数体で表現できます。つまり時間 、角周波数 、初期位相α、電圧振幅 E としたとき、時刻 t のときの電圧 v を表現するには

となります。この式は位相情報も完全に含んでいます。

ということで、位相を扱うには複素数が必須というわけではないわけで、なぜ複素数が必要なのかという気持ちになります。

しかし実数体で位相情報を含めて表現すると、上記のように三角関数を使うことになり、実際の計算はとても面倒になります。そこで、計算を便利にするために複素数の概念を入れます。

このとき使うのがオイラーの公式で、この場合、 三角関数が面倒なので指数関数にしてしまおう、そのためには電圧・電流表現を複素数体に拡大しよう、という感じのようです。

指数関数にするためにはこのような形式にする必要があるので、

と、電圧波形の式を複素形式にして (単に複素数部を付け加えているだけです。波形を表現するだけなら cos/sin はどっちがどっちでも良いので、オイラーの公式の形にあわせているだけです)

と指数関数に変換します。この形式で計算することで、オームの法則を使った普通の計算ができるようになって計算が楽になります。

例えばここでインピーダンスを考えると

e についている指数の割り算は指数同士の引き算になるので

指数の中を整理する

時間変数が消えてしまい、電圧と電流それぞれの初期位相 の位相差によって決まる定数がかかっている状態になります。この定数をまたオイラーの公式で戻してみると

この Z はインピーダンスなので、実数部は抵抗成分、虚数部はリアクタンス成分となっています。

はすなわち位相差なので、電圧と電流の位相差の cos() と sin() が単純な E と I との比にかかって複素インピーダンスを構成していることになります。

で、複素電圧と複素電流の虚数部は何なの?

複素電圧と複素電流の虚数部は何なのかというと「なんでもない」が答えのようです (単体では初期位相と関係しますが)。というのも、電圧にしろ電流にしろ負荷との関係をもって「電圧と電流の位相差」が現われるので、負荷との関係を考えない状態での、単体の複素電圧・複素電流の虚数部は何の意味もないということです。

インピーダンスは定義からして「電圧と電流の比」という関係の表現ですから、この関係の中に位相差が表現されるのは自然なことです。

  1. トップ
  2. tech
  3. 複素電圧・複素電流とはなんなのか

オイラーの公式とは一生無縁だと思っていた

げんきの絵本 おかあさんといっしょ かぞえてんぐ はって はがせる シールえほん - 講談社

講談社

3.0 / 5.0

横山だいすけおにいさんは「かぞえてんぐ」をやってるときが一番キてる(キてるとしか言いようがない)と思うので、一番かぞえてんぐが面白い。

「かぞえてんぐ」は数を数えたくて数えたくて辛抱たまらん!というキャラなのだけど、数えおわったあと絶頂とともにいきりたったところから「何か」が出てくる。

つまり数フェチで、周囲を巻き込んで数を数えるというオナニーをしたうえ絶頂するという完全なる変態である。NHK で放送されていいものなのか不思議なぐらいの変態なんだけど、実績からすると合法っぽいので、汚れちまった人間にだけ変態に見えるということなのかもしれない。

http://www.rigexpert.com/index?s=articles&f=aas で紹介されている回路を一通り自分の中で整理してまとめました。詳しい動作は今まで書いた各エントリにして、ここではそれぞれの特徴を書いてみます。

「アンテナアナライザー」と書いていますが、これをさらに発展させて入出力ポート両方で測定するようにすればネットワークアナライザになるはずです。

ダイオードディテクタ

  • ダイオードで検波するので、小電力時の非直線性が問題になり (誤差が多い) キャリブレーションが必要になる

抵抗1本タイプ

[tech] アンテナアナライザの回路 シリーズ抵抗1本型 | Tue, Mar 1. 2016 - 氾濫原

入力電圧・負荷電圧・負荷電流

  • 回路が簡単
  • 電流検出部で小さな電圧を測ることになる
  • リアクタンスの符号は不明

ブリッジタイプ

[tech] アンテナアナライザの回路 ブリッジ型 | Tue, Mar 1. 2016 - 氾濫原

入力電圧・負荷電圧・負荷電流・平衡電圧

  • 電圧反射係数を別途測っているので、SWR の精度があがる (特に1.0付近)
  • リアクタンスの符号は不明

ログアンプ検波

[tech] アンテナアナライザの回路 - ブリッジの三つの電位差を測るタイプ | Thu, Mar 3. 2016 - 氾濫原

AD8307 を使って検波するタイプ。

入力電圧・負荷電圧・平衡電圧

  • キャリブレーション不要
  • 差動接続のためブリッジの平衡部に直結できる
  • リアクタンスの符号は不明

ログアンプ+位相検出

[tech] アンテナアナライザの回路 ー 位相検出器を使ったタイプ | Fri, Mar 4. 2016 - 氾濫原

AD8302 (ログアンプ+位相差) を使って検波するタイプ

方向性結合器 複素電圧反射係数

  • キャリブレーション不要
  • 移相器を使うものは移相器の調整が必要だがリアクタンスの符号がわかる

複素入力電圧・複素負荷電圧

  • キャリブレーション不要
  • リアクタンスの符号がわかる
  • DDS が2台必要

ヘテロダイン系

[tech] アンテナアナライザの回路 ー ヘテロダインを使ったタイプ | Fri, Mar 4. 2016 - 氾濫原

  • より高い周波数を扱う場合に検討の余地あり
  • 回路はより複雑
  • DSPが必要

雑感

ダイオードディテクタを使う場合、現実的には後段にオペアンプによるバッファ回路を検出電圧分だけつける必要があり、部品数がかなり増加する。

AD8307 で検波する場合、ほかの部品は殆どいらずMCUなどのADCに接続できる。しかも精度が高い。アナログ部分が格段に少なくなるので設計しやすい。値段が高いのだけが問題。

AD8302 で検波する場合、リアクタンス符号がわかるのが嬉しい。しかし AD8302 はかなり高い。そしてDDSも2台必要。

作るなら

これまでを踏まえて、〜50MHz ぐらいまでを対象に、コスト的にそこそこで楽に作れてよさそうというのは AD8307 とブリッジを使うタイプだと思う。

コストをもう少しかけていいのなら、AD8302 + DDS2台 も十分に簡単な回路で作りやすそう。リアクタンスの符号まで知りたいならこれが一番簡単そうに思う。


リアクタンスの符号までわからなくても、現実的にはあまり問題にならないことのほうが多そう。アンテナの調整を目的とする場合リアクタンスは「同調しているかどうか」の判断に使うなら符号はいらないし、調整してリアクタンスが増えるか減るかでも符号を推測できる。

  1. トップ
  2. tech
  3. アンテナアナライザーの回路 ー まとめ

AD8302 (ログアンプ+位相検出器) を使った系です。

方向性結合器の出力を AD8302 に直接入れるタイプ

http://www.rigexpert.com/index?s=articles&f=aas

方向性結合器から進行波と反射波をとりだし、それを直接AD8302に入れるタイプのものです。

AD8302 にはログアンプであるとともに、2信号間の0〜180度の位相差の測定もできます。

位相差も測っているからリアクタンスも符号もわかると言いたいところですが、AD8302 の位相差は基準に対し0〜180度なので、この構成だと符号の区別はつきません。

AD8302 は AD8307 よりもさらに高価 (AD8302 はログアンプが2個と位相検出器が入っているので当然ですが) なので、リアクタンスの符号がわからないのはかなり残念な構成だと思います。正直、AD8302 を使って実装する価値はなさそうです。

計算

測定値として電圧反射係数 Γ の絶対値と位相角度 α がわかります。ということで反射係数の実部と虚部は簡単に求められます。

Γと伝送路インピーダンス・負荷インピーダンスの関係は


分母を有理化しつつ R + j X の形になるように (rectform()) していきます

ということで の形になったので R と X はそれぞれ

となります。計算上 X の符号は失われていませんが、そもそも最初の位相が正しかとれていないので符号がわかりません。

方向性結合器の出力の進行波側の位相を90度シフトしてから AD8302 に入れるタイプ

http://www.rigexpert.com/index?s=articles&f=aas

基準となる進行波の位相をフェーズシフターで90度進めることで、AD8302 の位相検出範囲を -90〜+90度にし、リアクタンス符号を計れるようにしたタイプです。

負荷インピーダンス自体は直接入れるタイプと同じように計算できます。こちらは符号が有効に出てきます。

広い周波数で正確にフェーズシフターを動かすのが難しいところだと思います。

抵抗ブリッジのリファレンス電圧と負荷電圧を計るタイプ

http://www.rigexpert.com/index?s=articles&f=aas

ブリッジのリファレンス複素電圧と、負荷の複素電圧を計るタイプです。AD8302 の片方には90度位相シフトした元の波形を入力し、位相検出範囲を -90〜+90度 にしています。

位相シフトした基準信号はDDSで作ります。つまりブリッジ入力用の信号源と、基準信号用の信号源とで DDS が2台必要です。しかし DDS なので広い周波数で正確な90度信号を作りだせます。

調整がいらないのが最大のメリットだと思います。

反射係数を直接測っておらず、基準電圧と負荷電圧を測っています。

E_{LOAD} と E_{REF} の絶対値と角度がわかっているので

電圧反射係数 Γ は

あとは上記の複素電圧反射係数から R と X を求める形でいけそうです。

  1. トップ
  2. tech
  3. アンテナアナライザの回路 ー 位相検出器を使ったタイプ

ヘテロダイン(周波数変換)を使ったタイプ

http://www.rigexpert.com/index?s=articles&f=aas

この方法の利点は信号を選択できることで、矩形波の高調波も信号源とできることだと思われます。つまり低い周波数しか発振できなくても、高調波を使うことで広い周波数を測定できます。

アンテナの入力に適当な信号を入力し、測定したい周波数 + f の周波数を出力に合成して、低い周波数 (中間周波数) f に測定したい周波数を変換します。この中間周波数にバンドパスフィルタをかければ目的信号を選択できます。

十分低い固定の周波数に変換されるので、実際の検波処理・位相差検出はADCとソフトウェア処理で行えます。

ダイレクトコンバージョンを使ったタイプ

http://www.rigexpert.com/index?s=articles&f=aas

ヘテロダインの一種ですが、目的周波数自体と、位相差90度の信号を出力に合成して、I/Q 信号として直接 0Hz にコンバートします。実際の検波処理・位相差検出はADCとソフトウェア処理で行います。

  1. トップ
  2. tech
  3. アンテナアナライザの回路 ー ヘテロダインを使ったタイプ

http://www.rigexpert.com/index?s=articles&f=aas

ブリッジの50Ω/50Ωで分圧したほうの電位 、負荷側の電位 、ブリッジ間の電位差 を測っているタイプです。

完全差動ログアンプのAD8307で検波を行っているのでダイオード検波にあるような非直線性を回避しつつ、外部にSPDT RFスイッチを2つだけ配置して高価なログアンプを1台ですませていて、このページの中だと一番おもしろい感じがします。

このタイプはなかなか理解できませんでした。

計算

ダメパターン

入力電圧を 1 としたとき、 の電圧は50Ωの と、負荷インピーダンス で分圧した形になりますから、

にかかる電圧は入力電圧から をひいて

の抵抗値は分っているので、電流値 を求めてみると

から を求めてみると

(2) を変形して (1) に代入すれば一発ですが、一旦電流を経由しています。

しかし測定している が絶対値なので、リアクタンスがある場合、 これでは正確に を求めることはできなそうです。

似たような方法で測定していて、上記のように説明しているページが見つかりましたが純抵抗で校正検証してるようでよくわかりませんでした……

http://www.kn9b.us/cloud-concept

じゃあどうするか

このパターンだと、ブリッジ間の電位差も測っています。この電位差は じゃないの? という感じで一見無駄のように見えますが、リアクタンス成分がある場合、検波するまでは複素数なので、同じにはなりません。

イメージとしては、 を測っているときと、オフセットされた との差の関係を見るという感じでしょうか。

複素電圧の関係を図にすると以下にようになります。青のベクトルが 、赤のベクトルが 、緑のベクトルが を表しています。

半径の円の交点の座標がわかれば の複素電圧がわかることが見てとれます。

負荷電圧と反射係数だけから負荷の複素インピーダンスを求める

(複素インピーダンスといってもリアクタンスは例によって絶対値だけです)

図形的に複素電圧がわかりそう、ということは複素インピーダンスもわかりそうだ、ということで、数式をいじくって解いてみます。

まず とブリッジの入力電圧である の関係を確認しておきます


の絶対値を式にすると

の絶対値を式にすると

(1) と (2) の連立方程式から を求めます。あきらかに面倒なので maxima で解きました。

と、やたら複雑ですが解くことができました。もっと綺麗な式になるかもしれませんが……

余談ですが maxima に連立方程式を解かせる場合、sqrt() を分解してくれないことがあるので、そこだけ自力でやったりすると解けたりします。

シミュレーションで確かめる

LTSpice でブリッジと負荷を構成し、作った負荷と、実際にシミュレーションして得た電圧から上記で求めた複素インピーダンスを比較して確認します。

以下のような回路です。

.meas を使って各ポイントの電圧を求めています。RMSを検波出力としています。こんな感じでログに出ます。

ログの出力をいちいちコピペして計算するのもダルいので、このログの出力をパースして上記式で解いてみる Ruby スクリプトを書いてみました。

コードは末尾に置きますが、結果だけコピペすると以下の通りでした。(ltspice simulated voltages) から求めたのが calculated で、calculated error に計算されるべき値との差が入っています。だいたい計算はあってそうです。

ruby 2.0.0p645 (2015-04-13 revision 50299) [universal.x86_64-darwin15]
Condition:
	{:r=>25.0, :x=>1591.5494309189535, :z=>1591.74576834947}
complex v:
	{:v_r=>0.9985228396122859, :v_i=>0.031346316992564995}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.9990147410405821, :e_diff=>0.49950737052029104}
(ltspice simulated volatages):
	{:e_ref=>0.350149, :e_load=>0.699604, :e_diff=>0.3498}
calculated:
	{:r=>25.2897097114912, :x=>1591.5067030422704, :z=>1591.7076224124105}
calculated error:
	{:r=>-0.2897097114911986, :x=>0.042727876683102295, :z=>0.038145937059425705}

Condition:
	{:r=>50.0, :x=>1591.5494309189535, :z=>1592.3346353886939}
complex v:
	{:v_r=>0.9980338412035863, :v_i=>0.03129238913528186}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.9985242920457066, :e_diff=>0.49901595225182244}
(ltspice simulated volatages):
	{:e_ref=>0.35015, :e_load=>0.69926, :e_diff=>0.349455}
calculated:
	{:r=>50.32437181406401, :x=>1589.9247651730511, :z=>1590.7210004614456}
calculated error:
	{:r=>-0.3243718140640084, :x=>1.6246657459023481, :z=>1.613634927248313}

Condition:
	{:r=>75.0, :x=>1591.5494309189535, :z=>1593.3155968164137}
complex v:
	{:v_r=>0.9975477257601216, :v_i=>0.03122332536748392}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.9980362524558889, :e_diff=>0.4985264641481664}
(ltspice simulated volatages):
	{:e_ref=>0.350146, :e_load=>0.69891, :e_diff=>0.349107}
calculated:
	{:r=>75.577314946581, :x=>1592.1658690154652, :z=>1593.9586208532223}
calculated error:
	{:r=>-0.5773149465809979, :x=>-0.6164380965117289, :z=>-0.6430240368085833}

Condition:
	{:r=>25.0, :x=>159.15494309189532, :z=>161.10647383201098}
complex v:
	{:v_r=>0.8788575625045866, :v_i=>0.25707223660794654}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.9156837609164615, :e_diff=>0.45784188045823077}
(ltspice simulated volatages):
	{:e_ref=>0.350146, :e_load=>0.6411, :e_diff=>0.320345}
calculated:
	{:r=>25.333147144876502, :x=>159.41449698716028, :z=>161.4148388281985}
calculated error:
	{:r=>-0.3331471448765022, :x=>-0.25955389526495765, :z=>-0.308364996187521}

Condition:
	{:r=>50.0, :x=>159.15494309189532, :z=>166.8241466652368}
complex v:
	{:v_r=>0.858478400162449, :v_i=>0.22523862168419417}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.8875345628445539, :e_diff=>0.4233665079824152}
(ltspice simulated volatages):
	{:e_ref=>0.350147, :e_load=>0.6215, :e_diff=>0.296282}
calculated:
	{:r=>50.449523403617995, :x=>159.4899517034167, :z=>167.27880650581648}
calculated error:
	{:r=>-0.44952340361799514, :x=>-0.3350086115213742, :z=>-0.4546598405796658}

Condition:
	{:r=>75.0, :x=>159.15494309189532, :z=>175.9411717324414}
complex v:
	{:v_r=>0.8473945832635346, :v_i=>0.19430325132965703}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.8693856067486138, :e_diff=>0.39804114103710525}
(ltspice simulated volatages):
	{:e_ref=>0.350147, :e_load=>0.608911, :e_diff=>0.278669}
calculated:
	{:r=>75.55035761860219, :x=>159.54194370186138, :z=>176.52616898484652}
calculated error:
	{:r=>-0.5503576186021917, :x=>-0.38700060996606567, :z=>-0.5849972524051168}

Condition:
	{:r=>25.0, :x=>15.915494309189533, :z=>29.636176526432088}
complex v:
	{:v_r=>0.36206078079200993, :v_i=>0.1353749068388477}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.38654155583759237, :e_diff=>0.19327077791879618}
(ltspice simulated volatages):
	{:e_ref=>0.350148, :e_load=>0.271725, :e_diff=>0.135173}
calculated:
	{:r=>25.089881853395678, :x=>16.0713690194714, :z=>29.7958230894429}
calculated error:
	{:r=>-0.08988185339567778, :x=>-0.15587471028186783, :z=>-0.15964656301081348}

Condition:
	{:r=>50.0, :x=>15.915494309189533, :z=>52.471925437378836}
complex v:
	{:v_r=>0.5123522615159288, :v_i=>0.0776115480673238}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.5181972522832335, :e_diff=>0.07858836273879492}
(ltspice simulated volatages):
	{:e_ref=>0.350148, :e_load=>0.363498, :e_diff=>0.0557211}
calculated:
	{:r=>50.08913489974573, :x=>16.133109401993394, :z=>52.62317601572157}
calculated error:
	{:r=>-0.08913489974572997, :x=>-0.21761509280386093, :z=>-0.15125057834273292}

Condition:
	{:r=>75.0, :x=>15.915494309189533, :z=>76.67009168577957}
complex v:
	{:v_r=>0.6063811091086553, :v_i=>0.0501171137437655}
(calculate with 0.5):
	{:e_ref=>0.5, :e_load=>0.6084486622335925, :e_diff=>0.11759534627353731}
(ltspice simulated volatages):
	{:e_ref=>0.350148, :e_load=>0.426484, :e_diff=>0.0828987}
calculated:
	{:r=>75.08881154062821, :x=>16.196121314941593, :z=>76.8156492144165}
calculated error:
	{:r=>-0.08881154062821395, :x=>-0.28062700575205923, :z=>-0.14555752863692817}

検証コード

require 'pp'

result = <<-EOS
.step r=25 c=1e-11
.step r=50 c=1e-11
.step r=75 c=1e-11
.step r=25 c=1e-10
.step r=50 c=1e-10
.step r=75 c=1e-10
.step r=25 c=1e-09
.step r=50 c=1e-09
.step r=75 c=1e-09


Measurement: e_ref
  step	RMS(v(ref))	FROM	TO
     1	0.350149	0	3e-06
     2	0.35015	0	3e-06
     3	0.350146	0	3e-06
     4	0.350146	0	3e-06
     5	0.350147	0	3e-06
     6	0.350147	0	3e-06
     7	0.350148	0	3e-06
     8	0.350148	0	3e-06
     9	0.350148	0	3e-06

Measurement: e_load
  step	RMS(v(load))	FROM	TO
     1	0.699604	0	3e-06
     2	0.69926	0	3e-06
     3	0.69891	0	3e-06
     4	0.6411	0	3e-06
     5	0.6215	0	3e-06
     6	0.608911	0	3e-06
     7	0.271725	0	3e-06
     8	0.363498	0	3e-06
     9	0.426484	0	3e-06

Measurement: e_diff
  step	RMS(v(load)-v(ref))	FROM	TO
     1	0.3498	0	3e-06
     2	0.349455	0	3e-06
     3	0.349107	0	3e-06
     4	0.320345	0	3e-06
     5	0.296282	0	3e-06
     6	0.278669	0	3e-06
     7	0.135173	0	3e-06
     8	0.0557211	0	3e-06
     9	0.0828987	0	3e-06
EOS

steps = []
name = nil
result.split(/\n/).each do |line|
	case line
	when /^\.step r=(?<r>\d+) c=(?<c>.+)/
		r = Regexp.last_match[:r].to_f
		c = Regexp.last_match[:c].to_f
		steps << {
			r: r,
			c: c,
			data: {}
		}
	when /^Measurement: (?<name>.+)/
		name = Regexp.last_match[:name]
		steps.last[:data]
	when /^     (?<n>\d+)	(?<value>[\d.]+)	0	3e-06/
		n = Regexp.last_match[:n].to_i - 1
		value = Regexp.last_match[:value].to_f
		steps[n][:data][name] = value
	end
end

Z_0 = 50.0
freq = 10e6;

def Math.pow(a, b)
	a ** b
end

steps.each do |step|
	farad = step[:c]
	resitance = step[:r];
	reactance = 1 / (2 * Math::PI * freq * farad);
	z = Math.sqrt(reactance * reactance + resitance * resitance);
	expected = {r: resitance, x: reactance, z: z}
	puts "Condition:\n\t#{expected}"

	e_ref = 0.5
	e_load = Math.sqrt( (resitance * resitance + reactance * reactance) / (Math.pow(resitance + Z_0, 2) + reactance * reactance) ) * 2 * e_ref;
	e_diff = Math.sqrt(
		(Math.pow(resitance - Z_0, 2) + reactance * reactance) /
		(Math.pow(resitance + Z_0, 2) + reactance * reactance)
	) * e_ref;

	v_r = (Math.pow(e_ref, 2)+Math.pow(e_load, 2)-Math.pow(e_diff, 2))/(2*e_ref);
	v_i = Math.sqrt(-Math.pow(e_ref, 4)+2*Math.pow(e_load, 2)*Math.pow(e_ref, 2)+2*Math.pow(e_diff, 2)*Math.pow(e_ref, 2)-Math.pow(e_load, 4)+2*Math.pow(e_diff, 2)*Math.pow(e_load, 2)-Math.pow(e_diff, 4))/(2*e_ref);
	puts "complex v:\n\t#{{v_r: v_r, v_i: v_i}}"

	puts "(calculate with 0.5):\n\t#{{
		e_ref: e_ref,
		e_load: e_load,
		e_diff: e_diff,
	}}"

	e_ref = step[:data]["e_ref"]
	e_load = step[:data]["e_load"]
	e_diff = step[:data]["e_diff"]

	puts "(ltspice simulated volatages):\n\t#{{
		e_ref: e_ref,
		e_load: e_load,
		e_diff: e_diff,
	}}"

	r = ((Math.pow(e_ref, 2)-Math.pow(e_diff, 2))*Z_0) / (2*Math.pow(e_ref, 2)-Math.pow(e_load, 2)+2*Math.pow(e_diff, 2));
	x = Math.sqrt(-Math.pow(e_ref, 4)+2*Math.pow(e_load, 2)*Math.pow(e_ref, 2)+2*Math.pow(e_diff, 2)*Math.pow(e_ref, 2)-Math.pow(e_load, 4)+2*Math.pow(e_diff, 2)*Math.pow(e_load, 2)-Math.pow(e_diff, 4))*Z_0/(2*Math.pow(e_ref, 2)-Math.pow(e_load, 2)+2*Math.pow(e_diff, 2));
	z = Math.sqrt(r * r + x * x)
	puts "calculated:\n\t#{{r: r, x: x, z: z}}"
	puts "calculated error:\n\t#{{r: expected[:r] - r, x: expected[:x] - x, z: expected[:z] - z}}"

	puts
end
  1. トップ
  2. tech
  3. アンテナアナライザの回路 - ブリッジの三つの電位差を測るタイプ

ほんと保育園関連のアレコレが嫌すぎて嫌すぎてどうしようもない…… なんとかしてくれ……… こんな無限に時間がないのにクソどうでもいい(子供の保育に直接関係がない) ことで時間が無駄にとられるのが嫌で嫌でしかたない。そして必要以上のコミュニケーションを求められるのが嫌で嫌でしかたがない。朝子供を送って先生を挨拶をかわすのすら嫌なのに (これは仕方ないからやってるだけで嫌なのだ)、それ以上のことを本当に心の底からしたくない。

毎日最低8時間を仕方なしに嫌々として気分で過ごしているのに、それに加えて嫌々とした気分にさせられることが自動的に追加され、何をやるにしても嫌なことが発生し続ける。果たしてこれを打ち消すぐらいの良いことがあるだろうか? 果たしてこれを打ち消すぐらいの良いことがあるだろうか?

別にすぐに死ぬ病気があって生き急いでいるとかではないが、最大でも90歳程度、そしてどんどん頭が悪くなるのを考えれば、70歳程度が人生の限界と思える。もうあと40回ぐらい春がくれば生産的な人生はおしまいなのだ。

1年にひとつ、できることが増えたとしても、あと40個しかできることは増えない。そして死ぬ。定年が65だとしたら、フルタイムで時間がとれるのはたったの5年。しかも劣化した頭の5年。始めるのに遅すぎるということはないが、決して納得いくレベルまでいかずに死ぬことになる。こういう事実にどう向きあえばいいだろうか?

この問題は生の問題であって、社会的な問題ではない。仕事に無駄に時間をとられているという点では社会的な問題だが、本質的には人間としての寿命の話だ。自分の中で納得して死ねるか、満足できるぐらいの技術力を獲得し、おれは頑張ったと思って死ねるか。そういう話だ。


よく「面白くなってきた」というところで変なことが起きて水を差されることがある。そして「せっかく面白くなってきたのに」という台無しな気分にさせられる。人生の終盤では寿命によって水を差される。

最近ちょっと数学がおもしろい。数学といってもレベルが低いことしかしてない (なんといっても僕は微分積分を高校で習っておらず、微分積分の解きかたすらしらないのだ)。

元々方程式を立てたりするのは嫌いではない。というのも機械的な操作で (つまり頭をつかわずに) 答えを導きだせる方法論として面白いからだ。行列も、連立方程式を機械的に解く方法と考えると親近感がわく。

しかし解くのは好きではない。ある操作が正しい操作かどうか、いちいち自信を持てないからだ。単に慣れてないというだけではあるけれども、ストレスになる。maxima を使うようになってからこのストレスは著しく軽減された。「これで正しんだっけ?」と思ったとき、maxima に解かせれば必ず正しいことがわかる。僕は自分を信用しておらず、機械を全面的に信頼している。


数式を読むのは英語を読むような感覚に似ている。ネイティブで理解することはできないが、頑張って読もうとすると読めることがある。読めると「そういうことか」と思うことがある。

「xx が xx なのは本当なのか」という不安を解消するのに数式が役立ってくれるように感じる。

やりたいことに対して時間が足りなすぎる。

http://www.rigexpert.com/index?s=articles&f=aas

このページが面白いので、1つ1つ見ていきたいという気持ちがあります。まずは一番上の Analyzers based on diode detectors のうち、抵抗1本を検出に使うものです。

やってることは負荷インピーダンスを電流と負荷電圧から求め、さらに入力電圧との関係からリアクタンスの絶対値まで求めるものです。

計算

1 を 、2 を 、3 を という名前にします。

負荷 に対し、それぞれの電圧を としとき、負荷 にかかっている電圧 の絶対値(検波値) は

回路全体にかかる電圧

(1) の式を について解いて

(3) を (2) に代入する。

これを について解く

(3) の式のルートをとって を求めると

また、回路全体の電流

負荷 はそれぞれ

となり、抵抗成分とリアクタンス成分の絶対値が求まる (容量性か誘導性かはわからない)

  1. トップ
  2. tech
  3. アンテナアナライザの回路 シリーズ抵抗1本型

http://www.rigexpert.com/index?s=articles&f=aas

ブリッジタイプ (図の右側。2番)

これはリターンロスブリッジを使う形のものです。

  • 1を
  • 2を
  • 3を
  • 4を

とすると

から電圧反射係数が求められます。 で入力電圧を求め、 で得た電圧反射係数を正規化するイメージ)

から負荷インピーダンスの絶対値がわかります。

ブリッジのイメージをつかむ

一番問題なのはブリッジに接続されている がどのようになるかです。負荷側の電位は50Ωと負荷インピーダンスとの分圧、もう片方は50Ωと50Ωの分圧で固定になっています (すなわち入力電圧の0.5倍が基準点)。

ここでもし負荷インピーダンスが50Ωであれば、負荷側の分圧も入力電圧の0.5倍になり、ブリッジ部分の電位差はゼロになります。ブリッジの平衡状態です。

負荷インピーダンスが25Ωになると、25/(25+50)=0.333... で電位差が -0.1666...、100Ωになると 100/(100+50)=0.666... で電位差が 0.1666... になります。つまりここの電位差は基準インピーダンス50Ωに対しての比になります。

もし入力電圧が1Vだとしたら、この電位差の2倍がすなわち電圧反射係数になります。(反射係数には複素数の場合、角度が出てくるが、位相は計っていないのでこれはわからず、この反射係数は絶対値です)

ブリッジ電位差が反射係数になっていることを確かめる

ブリッジ中の負荷インピーダンスを とし、それ以外のインピーダンス値を とすると、入力電圧が1Vのときのブリッジの電位差 E は以下のようになります。ここで、 をブリッジの50Ω/50Ωで分圧された側の電位、 を負荷側の電位としています。

電圧反射係数は

なので、1Vのとき、ブリッジの電位差は反射係数の半分になっています。また、 は入力の半分の電圧になりますから、ブリッジ電位差 で正規化することで反射係数そのものを求められます。

反射係数の大きさと、負荷インピーダンスがわかると、負荷の成分がわかる

反射係数の大きさと、負荷インピーダンスがわかると、まず負荷の抵抗成分を求められます。

反射係数の大きさ 、伝送路インピーダンス 、負荷インピーダンス の関係は以下の通りです。

は 50Ω なので 50 で置き換え、Z は R + jX に置き換えます。

絶対値同士の比なので分子分母とも絶対値にします

負荷インピーダンスの絶対値が既知ですから

(2) を (1) に代入します

難しいので maxima で解かせて

R が求められれば 、Z との関係から X の絶対値も求められます。

あれ? VSWR って簡単な比で求められなかったけ?

VSWR は以下のような式で求められますが、これは負荷インピーダンスが純抵抗な場合 (jX = 0) だけで、純抵抗でなければ反射係数を一旦求める必要があります。

この式を前提にインピーダンス比がSWRになる(インピーダンスがSWRに直接関連づく)と覚えていると、SWRと負荷インピーダンスの値から R が求められる理屈がわからなくなります。

インピーダンス比からSWRを求めた場合、純抵抗を想定してSWR値を求めたわけですから、このSWRと負荷インピーダンスからRを逆算すると、必ず jX の項は0になります。

別の説明をすると、たとえ同じ絶対値の負荷インピーダンスでも、リアクタンス成分でSWRは上がります。Z = 50 であっても、50 + 0j の場合と 0 + 50j の場合では違うよということで、前者のSWRは1ですが後者は無限です。

  1. トップ
  2. tech
  3. アンテナアナライザの回路 ブリッジ型

リターンロスブリッジを買ってみたので、手元にあるいろんなものを測ってみました。

測定方法

普通のリターンロスブリッジなので、DUT をオープンしてノーマライズ、DUT に測定対象を接続してリターンロスを読みます。

ダミーロードたち

SMA コネクタの小電力ダミーロードたちです

ちょっとだけ長いやつ (耐電力 2Wのもの)

短いやつ 1W か 0.5W

もうひとつ短いやつ

ダミーロードが悪いのかリターンロスブリッジが悪いのかよくわかりませんが、そこそこまでしか測れなそうです。実用上はまぁまぁ使える感じでしょうか…

アマチュア無線用の M コネクタ定格15Wダミーロードです

第一電波工業 ダイヤモンド ダミーロード DL50A - ダイヤモンドアンテナ

ダイヤモンドアンテナ

4.0 / 5.0

DC-1GHz まで使える (1.15以下 (DC-800MHz) 1.20以下 (800-1000MHz) というスペックですが、1.15=23.13dB / 1.20=20.83dB なのでスペック通りの測定はできてない感じです。

ハンディ機用ホイップアンテナ

144MHz/430MHz 帯用のホイップアンテナです。1/4λなので人体アースしながら測っています。アースしないと全然違う帯域に同調していました。

モービル用マルチバンドホイップ

ベランダに設置してあるホイップです。最近調整してませんが… 2014年の10月あたりの記録はこれです 500 Can't connect to lowreal.net:443 (certificate verify failed)

なぜかちょっとずつズレているようにみえます。

ちなみに DSA815 には VSWR モードがあって、実はリターンロスからVSWR換算を自動でやってくれます。これを使って 21MHz 帯を見てみました。

7MHz も見てみました。

アンテナアナライザーでも測ってみました。

アンテナアナライザーとTG付きのスペクトラムアナライザー

リターンロスブリッジを使えばTG付きのスペクトラムアナライザーはスカラー型のネットワークアナライザと似た機能を持つようになります。しかし位相は測れないので決してベクター型ネットワークアナライザの代わりはできません。

アンテナアナライザーはベクター型ネットワークアナライザのアンテナ特化版です。(ベクター型ではないアンテナアナライザーもありますが…) なので R + jX を分けて表示することができ、

  • アンテナが共振しているのに抵抗成分のミスマッチでVSWRが高い (トランスをつかえば解決)
  • アンテナが共振していなくてVSWRが高い (アンテナ自体の調整が必要)

という原因をわけて解析することができます。


50MHz 帯まではアンテナアナライザーがあるのでスペアナでリターンロスを測る機会はまずなさそうです。しかしそれ以上高い周波数ではスペアナ+リターンロスブリッジが活躍できそうです。

  1. トップ
  2. tech
  3. スペアナとリターンロスブリッジを使ってSWRを測ってみる

OCXO の VFC ピンでの可変範囲を GPS の 1PPS を使って調べた | tech - 氾濫原 でとりあえず可変範囲ぐらいはわかったので、半固定抵抗を使ってだいたい10MHzぐらいにあわせてみたいと思います。

でもって、回路やコードの構成を変えました。

MPU のクロック自体を OCXO の 10MHz とする

前回は mbed デフォルト (内蔵の12MHz CRクロックを逓倍した48MHz) でやっていました。別にこれでもいいのですが、せっかく高精度な 10MHz があるのに、これをクロックにしないのももったいないなという感じになりました。

それに、LPC1114 自体のクロック源をOCXOにすれば、内蔵カウンタでフリーランさせた値がそのまま周波数カウントになるので、GPIOを1ピン節約できます。

つまり 10MHz 5逓倍で 50MHz にして LPC1114 最大クロックで動かしてみたいと思います。OCXO の周波数カウントは5分周すれば元の10MHzになりそうです。

システムクロックとして外部クロック(水晶発振器)を使うには

用語ですが「システムオシレータ」と「システムクロック」に区別があって、システムオシレータは外部水晶発振子を使うことを想定した内蔵発振回路、システムクロックはシステムオシレータまたはBYPASSされた外部クロックを指しています。

まず OCXO は発振器なので、内蔵システムオシレータは使わないようにします。

ドキュメントを見るわけですが、システムオシレータコントロールレジスタ (SYSOSCCTRL) の BYPASS が日本語版だと間違っているので原文を見る必要があります。

Bypass enabled. PLL input (sys_osc_clk) is fed directly from the XTALIN pin bypassing the oscillator. Use this mode when using an external clock source instead of the crystal oscillator.

となっていて、XTALIN から直接クロックを入力する場合には、内蔵のシステムオシレータをバイパスするという意味になります。

まぁとにかく XTALIN には外部クロックを直接入れれば良いのですが (XTALOUT はフロート)、ここで罠があります。XTALIN は他のピンと違って 1.8V 以上入力してはならないことになっています。

5V クロックなら 5.6k / 2.4k、3.3V クロックなら 1.2k / 1k あたりの分圧が必要です。

ただ、どうも OCXO の出力そのままだとうまく分圧できなかったので、一旦バッファ (74HCU04しかなかったのでこれで…) で受けてからクロック入力に使いました。

外部クロック設定コード

ぶっちゃけかなりハマりました。デバッグするためには CLKOUT を有効にして、どの段階までちゃんと出力が出ているかを確認する必要があります。

レジスタ自体はデータシート通りに記述すれば良いのですが、関連レジスタがかなり多いのでたいへんです……

ちょっとハマったのは SYSPLLCTRL の MSEL が実際の分周数-1を設定する必要がありました。(データシートにももちろん書いてありますが…)

やってることは

  1. (mbed の初期化では IRC=12MHz を PLL で 48MHz にされています)
  2. PLL を設定しなおすので、メインクロックソースを IRC にしておく (12MHz動作に)
  3. 一旦 PLL 回路の電源を切る (ついでのシステムオシレータ回路も確実に切っておく)
  4. システムオシレータをバイパスする設定をする
  5. PLL 関連レジスタを適当に設定して 5 逓倍できるようにする
  6. PLL のクロックソースをシステムクロックに
  7. PLL 回路の電源を入れ、PLL のロックを待つ
  8. メインクロックを PLL 出力に変える (この時点で 50MHz 動作に)
  9. IRC の電源を切る (切らなくてもいいんですが)
  10. SystemCoreClock を50MHzに更新しておく

という感じです。

void set_system_clock_to_external_10mhz() {
#ifdef DEBUG_CLOCK
	// XXX: set CLKOUT to debug (dp24)
	LPC_IOCON->PIO0_1 = (0b001<<0/*FUNC=CLKOUT*/);
	LPC_SYSCON->CLKOUTCLKSEL = 0b11; /*0b00=IRC, 0b01=SysOsc, 0b10=WDT, 0b11=Main*/
	LPC_SYSCON->CLKOUTDIV = 1;
	LPC_SYSCON->CLKOUTUEN = 0;
	LPC_SYSCON->CLKOUTUEN = 1;
#endif

	// Power down config (irc=on, sysosc=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	// Ensure clock source to internal in temporary
	LPC_SYSCON->MAINCLKSEL = 0b00;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0);

	// Power down config (syspll=off, sysosc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<5/*SYSOSC_PD*/) |
		(1<<7/*SYSPLL_PD*/)
	);

	// Set System OSC = BYPASS (clock fed to XTALIN directly 1.8V / XTALOUT must be floating)
	LPC_SYSCON->SYSOSCCTRL = (1<<0/*BYPASS*/);

	// PLL Clock Source = System OSC
	LPC_SYSCON->SYSPLLCLKSEL = (0b01<<0/*SEL*/);

	// Update PLL Source
	LPC_SYSCON->SYSPLLCLKUEN = 0;
	LPC_SYSCON->SYSPLLCLKUEN = 1;
	while ( (LPC_SYSCON->SYSPLLCLKUEN & 0b1) == 0 ); 

	// Set PLL
	// M = F_clkout / F_clkin
	// FCCO = 2 * P * F_clkout (P = {1, 2, 4, 8}) (FCCO=156-320MHz)
	// F_clkout = 50MHz / M = 5 / P = 2 / FCCO = 200MHz
	LPC_SYSCON->SYSPLLCTRL = ( (5 - 1)<<0/*MSEL*/) | (0b01<<5/*PSEL*/);

	// Power down config (syspll=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<7/*SYSPLL_PD*/)
	);
	// Wait for PLL Lock
	while ( (LPC_SYSCON->SYSPLLSTAT & 0b1) == 0 );

	// Update Main Clock to PLL Output
	LPC_SYSCON->MAINCLKSEL = 0b11;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0); 

	// Power down config (irc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	SystemCoreClock = 50000000;
}


周波数カウント部分の変更

タイマーをタイマーとして使い、システムクロックをそのままカウントします。キャプチャピンはGPS1PPSをキャプチャするようにし、キャプチャのタイミングでキャプチャレジスタにタイマカウンタをコピー(ハードウェア動作)させて割込みを生成します。GPIO のピンチェンジ割込みは使いません。

レジスタの設定は問題ないのですが、割込み周りでだいぶハマりました。

  • mbed の InterruptManager で TIMER_32_0_IRQn の割込みを設定しようとしたがうまくいかなかった
  • NVIC_EnableIRQ しないとダメだった
  • extern "C" を忘れてて割込みが呼ばれずしばらくはまった

また、前回よりも精度を高めるため、10秒、100秒、1000秒のゲートでの誤差を表示するようにしました。dHz (デシヘルツ) cHz (センチヘルツ) mHz (ミリヘルツ) 単位で表示しているのが誤差の項目です。

±1カウントエラーがあるので、多少 +1/-1 がでることがあります。

1000秒単位での調整は面倒なので、10秒単位の調整しかしてませんが、このように手で雑に調整してもこのぐらいの精度が出ていました。

コード

LPC1114 はSRAMが4KBと少ないため、カウントされた値をそのまま持つのではなく、誤差を signed で持つようにしています。そもそもOCXOの可変範囲が±5Hz程度しかないので、それ以上の情報を持つ必要がありません。ということで int8_t の配列で履歴を持って、直近のn件を合計して見る形になっています。

これにより、例えばカウンタをそのまま持つには10Mは16bitに納まらないので32bitの配列にしなければなりませんが、誤差だけを保持すれば4分の1の容量ですみます。というか uint32_t だとLPC1114では1000個の履歴を持てません。

constexpr uint32_t CLOCK = 10000000;
constexpr uint16_t HISTORY = 1000;

int8_t errors[HISTORY];
uint16_t error_index = 0;
uint16_t error_count = 0;

volatile bool updated = 0;
Serial serial(USBTX, USBRX);

extern "C" void TIMER32_0_IRQHandler (void) {
	static uint32_t prev = 0;
	uint32_t count = LPC_TMR32B0->CR0;
	uint32_t pps_counter;

	if (prev < count) {
		pps_counter = count - prev;
	} else {
		// overflowed
		pps_counter = (0xffffff - prev) + count + 1;
	}
	prev = count;

	int16_t error = static_cast<int32_t>(CLOCK) - static_cast<int32_t>(pps_counter);
	if (abs(error) < 15) {
		error_index = (error_index + 1) % HISTORY;
		errors[error_index] = error;
		if (error_count < HISTORY) {
			error_count++;
		}
		updated = 1;
	}

	LPC_TMR32B0->IR = (1<<4/*CR0 Interrupt*/);
}

int main() {
	set_system_clock_to_external_10mhz();
	NVIC_EnableIRQ(TIMER_32_0_IRQn);

	// enable 32bit counter
	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9/*CT32B0 32bit counter clock*/);

	// Capture pin dp14 (for gps 1pps)
	LPC_IOCON->PIO1_5         |= (0b010<<0/*FUNC=CT32B0_CAP0*/);

	// Match output (not used)
	//	LPC_IOCON->PIO1_6         |= (0b010<<0/*FUNC=CT32B0_MAT0*/);
	//	LPC_IOCON->PIO1_7         |= (0b010<<0/*FUNC=CT32B0_MAT1*/);
	//	LPC_IOCON->PIO0_1         |= (0b010<<0/*FUNC=CT32B0_MAT2*/);
	//	LPC_IOCON->R_PIO0_11      |= (0b011<<0/*FUNC=CT32B0_MAT3*/);

	// Prescaler
	LPC_TMR32B0->PR  = 4;
	// Capture on CAP0 Rising Edge (to CR0) and Enable Interrupt
	LPC_TMR32B0->CCR =
		(1<<0/*CAP0RE*/) |
		(1<<2/*CAP0I*/);
	LPC_TMR32B0->MCR = 0;
	LPC_TMR32B0->CTCR = (0b00<<0/*Counter/Timer Mode=Timer*/);
	LPC_TMR32B0->TCR = (1<<0/*Counter Enable*/);

	serial.baud(115200);
	for (;;) {
		if (updated) {
			updated = 0;
			serial.printf("[%d] last: %+d\n", error_count, errors[error_index]);

			if (error_count >= 10) {
				int32_t sum = 0;
				for (int i = 0; i < 10; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+ddHz\n", error_count, sum);
			}
			if (error_count >= 100) {
				int32_t sum = 0;
				for (int i = 0; i < 100; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+dcHz\n", error_count, sum);
			}
			if (error_count >= 1000) {
				int32_t sum = 0;
				for (int i = 0; i < 1000; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				// 1000_0000000
				serial.printf("[%d] %+dmHz\n", error_count, sum);
			}
		}
	}
}
  1. トップ
  2. tech
  3. OCXO と GPS 1PPS その2

VFC を PWM によるデジタルコントロールにしてみました。

前回は多回転ボリュームを使って手動で調整をしました。これだと10秒単位ぐらいならともかく、1000秒単位の調整は面倒くさすぎてやっていられません。のでここをMCUで自動化します。

PWM による VFC のコントロール

OCXO から出ている Vref を PWM でスイッチングしてローパスフィルタにかけてコントロール電圧を生成します。DAC デバイスの電源電圧を Vref にするという形でもよさそうですが手元に DAC デバイスがないので PWM でやっています。

PWM の周波数は約30Hzです。これは mbed の PwmOut の period_us() に int の最大値 (1<<15) を設定した値になります。(なんでこのAPIはintなんでしょうかね?)

これにより、int16_t 変数でパルス幅を保持して、pulsewidth_us() が最大の分解能を持つようにしています。

PWMの周波数が30Hzぐらいなので、外部フィルタとしてかなり低い周波数のローパスフィルタを使っています。応答性が高くてもあまり意味がないないし困るので1Hzぐらいのローパスフィルタでも良さそうです。

また、PWM 周波数を変えた直後はグリッチが発生するので、2秒間のGPS1PPS割込みを無視するようなコードを加えました。

制御

PID制御などではなく、まずは適当にインクリメント・デクリメントし続けるような実装にしました。

当然 PID 制御したほうがいいのですが、とりあえず頭を使いたくなかったので…

結果

シリアルに出している直近1000秒ごとの誤差合計を、雑なワンライナーで整形して Google Spreadsheet でグラフ化してみました。主にソフトウェア的要因と思われる過補正を繰替えしている雰囲気を感じられますが、概ね±10mHz(±1ppb)におさまっています。

GPSDO

調整を自動化したので、これはもはやGPSDOと呼べそうです。PID制御にするなど、ソフトウェア的な制御改良の余地はありますが、GPSDOの大枠が実装できました。

どちらかといえばあとはアナログ回路の要素が多くなります。といっても以下らへんを実装すれば実用的になりそうです。

  • 出力が矩形波なので高次のローパスフィルタないしバンドパスフィルタを設計実装する
  • 波形整形するバッファを使っていないのでバッファのしかたを変える
  • GPSのアンテナ入力を外部入力に変える
  • GPS FIx されず、1PPS 信号がきてないときのタイムアウト処理を追加
  • ケースに入れる

アナログまわりが勉強中なので現状の技術力だと難しい面もありますが、製作例もあるのでなんとかはなりそうです。とはいえ、作っても現状だと応用先がスペアナぐらいしかないので、あんまりやる気がありません。

かなり簡単な構成でppbオーダーの高精度なクロックが得られるのは面白いです。もうちょっとアナログ回路の技術力があればさくっと作りあげられそうです。

コード

#include "mbed.h"
#include <stdint.h>
#include <inttypes.h>

void set_system_clock_to_external_10mhz() {
#ifdef DEBUG_CLOCK
	// XXX: set CLKOUT to debug (dp24)
	LPC_IOCON->PIO0_1 = (0b001<<0/*FUNC=CLKOUT*/);
	LPC_SYSCON->CLKOUTCLKSEL = 0b11; /*0b00=IRC, 0b01=SysOsc, 0b10=WDT, 0b11=Main*/
	LPC_SYSCON->CLKOUTDIV = 1;
	LPC_SYSCON->CLKOUTUEN = 0;
	LPC_SYSCON->CLKOUTUEN = 1;
#endif

	// Power down config (irc=on, sysosc=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	// Ensure clock source to internal in temporary
	LPC_SYSCON->MAINCLKSEL = 0b00;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0);

	// Power down config (syspll=off, sysosc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<5/*SYSOSC_PD*/) |
		(1<<7/*SYSPLL_PD*/)
	);

	// Set System OSC = BYPASS (clock fed to XTALIN directly 1.8V / XTALOUT must be floating)
	LPC_SYSCON->SYSOSCCTRL = (1<<0/*BYPASS*/);

	// PLL Clock Source = System OSC
	LPC_SYSCON->SYSPLLCLKSEL = (0b01<<0/*SEL*/);

	// Update PLL Source
	LPC_SYSCON->SYSPLLCLKUEN = 0;
	LPC_SYSCON->SYSPLLCLKUEN = 1;
	while ( (LPC_SYSCON->SYSPLLCLKUEN & 0b1) == 0 ); 

	// Set PLL
	// M = F_clkout / F_clkin
	// FCCO = 2 * P * F_clkout (P = {1, 2, 4, 8}) (FCCO=156-320MHz)
	// F_clkout = 50MHz / M = 5 / P = 2 / FCCO = 200MHz
	LPC_SYSCON->SYSPLLCTRL = ( (5 - 1)<<0/*MSEL*/) | (0b01<<5/*PSEL*/);

	// Power down config (syspll=on)
	LPC_SYSCON->PDRUNCFG     &= ~(
		(1<<7/*SYSPLL_PD*/)
	);
	// Wait for PLL Lock
	while ( (LPC_SYSCON->SYSPLLSTAT & 0b1) == 0 );

	// Update Main Clock to PLL Output
	LPC_SYSCON->MAINCLKSEL = 0b11;
	LPC_SYSCON->MAINCLKUEN = 0x00;
	LPC_SYSCON->MAINCLKUEN = 0x01;
	while ( (LPC_SYSCON->MAINCLKUEN & 0b1) == 0); 

	// Power down config (irc=off)
	LPC_SYSCON->PDRUNCFG     |= (
		(1<<0/*IRCOUT_PD*/) |
		(1<<1/*IRC_PD*/)
	);

	SystemCoreClock = 50000000;
}

constexpr uint32_t CLOCK = 10000000;
constexpr uint16_t HISTORY = 1000;

int8_t errors[HISTORY];
uint16_t error_index = 0;
uint16_t error_count = 0;

volatile bool updated = 0;
volatile uint8_t skip_second = 0;
Serial serial(USBTX, USBRX);
PwmOut osc_control(dp1);

extern "C" void TIMER32_0_IRQHandler (void) {
	static uint32_t prev = 0;
	LPC_TMR32B0->IR = (1<<4/*CR0 Interrupt*/);

	uint32_t count = LPC_TMR32B0->CR0;
	uint32_t pps_counter;

	if (prev < count) {
		pps_counter = count - prev;
	} else {
		// overflowed
		pps_counter = (0xffffff - prev) + count + 1;
	}
	prev = count;

	if (skip_second > 0) {
		skip_second--;
		return;
	}

	int16_t error = static_cast<int32_t>(pps_counter) - static_cast<int32_t>(CLOCK);
	if (abs(error) < 15) {
		error_index = (error_index + 1) % HISTORY;
		errors[error_index] = error;
		if (error_count < HISTORY) {
			error_count++;
		}
		updated = 1;
	}
}

int16_t pulsewidth = 16500;

void vcf_plus(uint8_t count) {
	pulsewidth -= count;
	osc_control.pulsewidth_us(pulsewidth);
	skip_second = 2;
	serial.printf("osc_control = %d\n", pulsewidth);
}

void vcf_minus(uint8_t count) {
	pulsewidth += count;
	osc_control.pulsewidth_us(pulsewidth);
	skip_second = 2;
	serial.printf("osc_control = %d\n", pulsewidth);
}

int main() {
	set_system_clock_to_external_10mhz();
	NVIC_EnableIRQ(TIMER_32_0_IRQn);

	osc_control.period_us(1<<15);
	osc_control.pulsewidth_us(pulsewidth);

	// enable 32bit counter
	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9/*CT32B0 32bit counter clock*/);

	// Capture pin dp14 (for gps 1pps)
	LPC_IOCON->PIO1_5         |= (0b010<<0/*FUNC=CT32B0_CAP0*/);

	// Match output (not used)
	//	LPC_IOCON->PIO1_6         |= (0b010<<0/*FUNC=CT32B0_MAT0*/);
	//	LPC_IOCON->PIO1_7         |= (0b010<<0/*FUNC=CT32B0_MAT1*/);
	//	LPC_IOCON->PIO0_1         |= (0b010<<0/*FUNC=CT32B0_MAT2*/);
	//	LPC_IOCON->R_PIO0_11      |= (0b011<<0/*FUNC=CT32B0_MAT3*/);

	// Prescaler
	LPC_TMR32B0->PR  = 4;
	// Capture on CAP0 Rising Edge (to CR0) and Enable Interrupt
	LPC_TMR32B0->CCR =
		(1<<0/*CAP0RE*/) |
		(1<<2/*CAP0I*/);
	LPC_TMR32B0->MCR = 0;
	LPC_TMR32B0->CTCR = (0b00<<0/*Counter/Timer Mode=Timer*/);
	LPC_TMR32B0->TCR = (1<<0/*Counter Enable*/);

	serial.baud(115200);
	for (;;) {
		if (serial.readable()) {
			char c = serial.getc();
			serial.putc(c);
			serial.printf(" %d\n", c);
			if (c == '+' || c == 'p') {
				vcf_plus(100);
			} else
			if (c == '-' || c == 'm') {
				vcf_minus(100);
			}
		}

		if (updated) {
			updated = 0;
			int8_t last = errors[error_index];
			serial.printf("[%d] last: %+d\n", error_count, last);
			if (last >= 2) {
				vcf_minus(1);
			} else
			if (last <= -2) {
				vcf_plus(1);
			}

			if (error_count >= 10) {
				int32_t sum = 0;
				for (int i = 0; i < 10; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+ddHz\n", error_count, sum);

				if (sum >= 2) {
					vcf_minus(1);
				} else
				if (sum <= -2) {
					vcf_plus(1);
				}

			}
			if (error_count >= 100) {
				int32_t sum = 0;
				for (int i = 0; i < 100; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				serial.printf("[%d] %+dcHz\n", error_count, sum);

				if (sum >= 2) {
					vcf_minus(1);
				} else
				if (sum <= -2) {
					vcf_plus(1);
				}
			}
			if (error_count >= 1000) {
				int32_t sum = 0;
				for (int i = 0; i < 1000; i++) {
					sum += errors[ (error_index + HISTORY - i) % HISTORY ];
				}
				// 1000_0000000
				serial.printf("[%d] %+dmHz\n", error_count, sum);

				if (sum >= 2) {
					vcf_minus(1);
				} else
				if (sum <= -2) {
					vcf_plus(1);
				}
			}
		}
	}
}
  1. トップ
  2. tech
  3. OCXO と GPS 1PPS その3

ebay で注文した OCXO が届いたのでテストしてみました。このぐらいの小さな OCXO です。

MORION MV102 10MHz +12V OCXO

出力は HCMOS 矩形波 (10kΩ 39pF 負荷) となっています。正弦波出力が欲しかったのですが間違えて買ってしまいました。バッファしてフィルタすればいい気はします。とりあえずデジタル回路に直結でき、軽くテストするには便利なのでこれはこれで良いことにします。

GND          Us

RF   Uref   Uin

というピンアサインになっています。Us は +12V入力、RF は出力、Uref はリファレンス電圧(5V)、Uin はVCOの入力のようです。

Uin には 0〜5V で入力し、これにより±4e-7 (±4Hz) 調整できるというスペックです。

とりあえず 12V 加えるだけで 10MHz の出力が得られます。約 200mA ぐらい (2.5W程度) の消費電力があります。2時間ぐらい通電してみましたが特に下がりませんでした。クソ寒い部屋でやっているせいかもしれませんが…

OCXO なので結構熱くなります。しばらく触っていられるぐらいなので45〜55℃ぐらいでしょうか…

GPS の 1PPS で周波数カウントしてみる

GPSモジュールはこの前試した NEO-6Mです。3pin から線を別途引き出して使っています。窓際でやっているので、あまり安定した受信とはいえませんが、3D Fix したタイミングで計ってみました。

MPU として LPC1114FN28 を使って、以下のようなコードを書きました。ハードウェアカウンタは mbed の API からは使えるようになっていないので、データシートを読む必要があります。

本当は、ハードウェアでカウントしつつ、1PPS シグナルのキャプチャ信号でカウンタをキャプチャレジスタに自動的に入れて、同時に割込みを発生させてその値を読み出すみたいにしたかったのですが、少なくとも LPC1114 においてはそういうことはできないようでした (2つ以上のキャプチャピンを持っていない・外部入力カウンタとして使う場合、キャプチャレジスタを使用することはできない)

なので、実際のコードでは 32bit のカウンタを外部入力(10MHz)でフリーランカウントさせ、GPS の1PPSのタイミングでカウンタの値を読みこんで、前回との差分をとって周波数を求めています。

1PPS ごとの表示なので、簡単なコードですが、GPS同期の正確なゲート時間1秒周波数カウンタとして動いています。

#include "mbed.h"
#include <stdint.h>

constexpr uint32_t CLOCK = 10000000;
constexpr uint16_t HISTORY = 1000;

int16_t errors[HISTORY];
uint16_t error_index = 0;
uint16_t error_count = 0;

volatile bool updated = 0;
Serial serial(USBTX, USBRX);

InterruptIn gps1pps(dp25);

int main() {
	// enable 32bit counter
	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<9/*CT32B0 32bit counter clock*/);

	// Capture pin dp14
	LPC_IOCON->PIO1_5         |= (0b010<<0/*FUNC=CT32B0_CAP0*/);

	// Match output (not used)
	//	LPC_IOCON->PIO1_6         |= (0b010<<0/*FUNC=CT32B0_MAT0*/);
	//	LPC_IOCON->PIO1_7         |= (0b010<<0/*FUNC=CT32B0_MAT1*/);
	//	LPC_IOCON->PIO0_1         |= (0b010<<0/*FUNC=CT32B0_MAT2*/);
	//	LPC_IOCON->R_PIO0_11      |= (0b011<<0/*FUNC=CT32B0_MAT3*/);

	LPC_TMR32B0->PR  = 0;
	LPC_TMR32B0->CCR = 0;
	LPC_TMR32B0->CTCR =
		(0b01<<0/*Counter/Timer Mode=Counter, Rising Edge*/) |
		(0b00<<2/*Count Input Select*/);
	LPC_TMR32B0->TCR = (1<<0/*Counter Enable*/);

	gps1pps.rise([]{
		static uint32_t prev = 0;

		uint32_t count = LPC_TMR32B0->TC;
		uint32_t pps_counter;
		if (prev < count) {
			pps_counter = count - prev;
		} else {
			// overflowed
			pps_counter = (0xffffff - prev) + count + 1;
		}
		prev = count;
		serial.printf("pps_counter: %lu\n", pps_counter);

		int16_t error = static_cast<int32_t>(CLOCK) - static_cast<int32_t>(pps_counter);
		error_index = (error_index + 1) % HISTORY;
		errors[error_index] = error;
		if (error_count < HISTORY) {
			error_count++;
		}
		updated = 1;
	});

	// NVIC_SetPriority();

	serial.baud(115200);
	for (;;) {
		if (updated) {
			updated = 0;
//			serial.printf("last: %d\n", errors[error_index]);
//
//			uint32_t sum = 0;
//			for (int i = 0; i < error_count; i++) {
//				sum += errors[ (error_index + HISTORY - i) % HISTORY ];
//			}
//			uint32_t error = sum * 1000 / error_count;
//			serial.printf("sum(%ds): %d\n", error_count, error);
		}
	}
}

結果

VFC 端子を 0V〜5V まで可変させて周波数を読んでみました。仕様上は の調整範囲 (±4Hz) があると書いてありました。実際やってみると以下のように ±5Hz ぐらいの可変ができるようです。

0〜5V の範囲で10Hz可変なので、2 Hz/V、2mHz/mV です。DAC で 1mHz 単位ぐらいで電圧を設定するなら、最低でも14bit程度の精度が必要そうです。

  1. トップ
  2. tech
  3. OCXO の VFC ピンでの可変範囲を GPS の 1PPS を使って調べた

答: デフォルトではしない

ARMのNVIC (Nested Vector Interrupt Controller) は名前の通りネスト可能ですが、実行中の処理よりも優先順位が高いものだけがネストします(ネストとは割り込み処理中にさらに割り込みが発生すること)。

mbed の場合、優先度を設定するAPIはないため、全割り込みは同一優先順位となり、他の割り込みがネストすることはありません (ライブラリ側でやってる場合を除く)。

優先度をつけるには

もし優先順位をつけてネスト可能にする場合、CMSIS(ARM規定のAPI群)のAPI (NVIC_SetPriority()) を使えば可能です。ただ、このAPIを使うためには、mbedのどのAPIでどんな割り込みを設定しているかを把握している必要があります (mbed 側のコードを全部読む必要がある)。

Cortex-M0 の NVIC の割り込みの優先の仕組み

優先順位は0から3までの4段階あり、0が最も優先です。

デフォルトでは全ての割り込みが0に設定されているので、相互に割り込みがネストすることはなく、通常処理が動いている間だけ割り込みが発生します。

また、通常処理についても、__disable_irq() を呼ぶと一時的に優先度0のコードになり、割り込みが発生しなくなります。__enable_irq() で元の優先度に戻ります。

外部ピン変化割り込みで情報が失われるケース

割り込み発生から、次の割り込みまでの間に2回ピン変化が起こると、2つめの変化による割り込み情報は失われます。

ある割り込みを優先した場合、その割り込みと同じ優先度の割り込み(その割り込み自身を含む)の処理時間だけ考えればいいことになります。

  1. トップ
  2. tech
  3. mbed InterruptIn はネストするか

 -

5.0 / 5.0

2013年の3月に買ったルンバをオフィシャルメンテナンスに出しました。約3年ぐらい、平日は毎日稼動させていました。

最近どうもガタガタ、ガタガタと言うときがあり、調べてみると後退したり旋回するときに、片方のモーターが動いていないような挙動でした。前進のときは問題なく、特定の場合だけなります。

一応一通り分解して清掃はしてみましたが、おそらく一番の原因であろうタイヤユニットまわりは中まで分解できるようになっておらず、他全てを綺麗にしても直りませんでした。

この時点では2つの選択肢がありました。

  • タイヤユニットだけを買って交換する 約6000円 (ただし直るかわからない)
  • オフィシャルメンテナンスに出す 定額だと約13000円〜 (直る)

前進は大丈夫で後退だけがダメ、なおかつ片方だけという状態だったので、なんとなく内部のDCモータードライバ回路の一部がおかしいのではないかという疑いもありました。もしそうならタイヤユニットを買っても無駄になってしまいます。

ということで自力で頑張るのは早々に諦めてオフィシャルメンテナンスに出しました。

結果

(途中で「バッテリーもヘタっているから変えるか? 変えるなら18000円のプランになるが良いか?」という趣旨の電話がありました。差額が5000円なので、まぁどうせだからと思い18000円のプランに変えました。オフィシャルバッテリはそもそも10000円ぐらいするので差額5000円なら互換品なみに安い)

返ってきたルンバは全体的に綺麗になっていました。メンテナンスの結果のレポートによると

  • 内部基板ユニットの交換
  • タイヤユニットの交換
  • エッジクリーニングブラシユノットの交換
  • メインブラシユニットの交換
  • フィルタの交換
  • バッテリーの交換(新型のXLifeバッテリーになった)

という感じで、ダストボックスと本体以外ほとんど全部変わってました。内部基板ユニットの不具合がやはりあったみたいで(モータードライバかどうかはわかりませんが)、自力でどうにかできる範囲ではなかったので今回に関しては判断は正しかったようです。

バッテリの寿命も伸びたみたいなので、とりあえずこれであと2年ぐらいは元気に動いてくれることを期待します。

オフィシャルメンテナンスは高いか?

メンテナンス費用には送料も含まれています。だいたい往復で3000円ぐらいかかるとすると、約10000円(ブラシパック)/15000円(サービスパック)が実際の工費になります。

700シリーズ メインブラシ+フレキシブルブラシ + エッジブラシ1本 - iRobot (アイロボット)

iRobot (アイロボット)

3.0 / 5.0

メインブラシ及びエッジクリーニングブラシが約1500円

ルンバ(Roomba)iRobot(アイロボット) 700シリーズ専用フィルター2セット(4個) - アイロボット(IRobot)

アイロボット(IRobot)

3.0 / 5.0

フィルタが1セットあたり約600円

はブラシパックの場合必ず交換、サービスパックの場合は必要に応じて交換

Roomba ルンバ エンハンスドクリーニングヘッド 【並行輸入品】 - アイロボット(IRobot)

アイロボット(IRobot)

3.0 / 5.0

もし壊れているなら上のようなモジュール(6000円〜)が交換

ルンバ用バッテリー 日本企業による販売365日保証 長寿命2倍 長時間稼働2.5時間 ルンバ500 600 700 800 900シリーズ対応 5bt_ao - Orange Line

Orange Line

3.0 / 5.0

サービスパックの場合バッテリーが必ず交換 (↑これは互換品ですが)


ということで、単体として高いかというとそうでもなく、点検修理の工数を考えるとむしろお得ぐらいの価格設定です。


ただ、修理の場合実際あたまに浮かぶのは買い替えでしょう。別のルンバに買い替えるなら修理のほうが当然安くすみます。他社製品は今回検討しませんでしたが、もし安くていいのがあれば修理と競合しそうです。

any() 系のマッチャは常に「あらゆるオブジェクト」にマッチします。この挙動は Javadoc にも書いてあって、もしかすると将来的に変更するかも、みたいなことが書いてあります。

じゃあなんで any(Class) とか anyString() とかあるのか?という気持ちになるわけですが、これはたぶんオーバーロードで複数の選択肢がある場合に、ある特定のメソッドを確定するときに便利だからだ、と思います。

なお、あるクラスのインスタンスかどうかをチェックするマッチャは isA(Class clazz) のようです。

  1. トップ
  2. tech
  3. Mockito の any(Class<?> clazz) や anyString() や他の any ナンチャラは型チェックはしない

一定時間で何かをする、といえばタイマーの割込みを使うことでしょうが、タイマーを使いたくないないし使えないということもあると思います。

そういうときループをぶんまわしながら、時刻などを比較して一定時間ごとに処理をするという方法をとったりすることがあります。今回はそれを簡単に書けるスニペットを考えたという話です。

他のライブラリでどうにかする

こういうことをするライブラリを調べてみると、Metro というのがあります。これは以下のようにして使うライブラリのようです。

Metro interval250 = Metro(250); 

void loop() {
    if (interval250.check()) {
        // ここで 250msごとの処理
    }
}

もちろん悪くはないのですが、変数宣言と実際の処理が分かれており、余計な変数を宣言する必要があってなんか嫌です。(変数名をいちいち考えたくないという意味です)

loop() でいきなり使える定期実行

そこで以下のようなスニペットを考えました。interval class が本体です。

template <uint16_t time>
class interval {
	uint32_t next_run = 0;

	template <class T>
	void _run(T func) {
		uint32_t now = millis();
		if (next_run < now) {
			func();
			next_run = now + time;
		}
	}

	interval() {}
public:

	template <class T>
	static void run(T func) {
		static interval<time> instance;
		instance._run(func);
	}
};

void setup() {
	Serial.begin(9600);
}

void loop() {
	interval<250>::run([]{
		Serial.println("250ms 1");
	});

	interval<250>::run([]{
		Serial.println("250ms 2");
	});

	interval<1000>::run([]{
		Serial.println("1000ms");
	});

}

loop() 内でいきなり適当な書きかたをするだけですみます。

interval クラス

事前準備 (グローバル変数やstatic変数を自力で宣言したり) をせず、いきなり静的な関数ないしメソッドを読んで使うことができるようにするため、以下のような感じになっています。

まず、interval クラスは time をテンプレート引数にとっているので、time ごとに別のクラスが作られます。

各 time を持つ interval クラスには、さらに引数 func の型をテンプレート引数にとる run() という static メソッドを持っています。

run() は内部で static 変数として interval

func には lambda 式を書くことを想定しています。lambda 式の型は書く度に違うものになるので、同じ time を持つ処理も複数書けます。

つまり、テンプレート引数を使うことで、グローバルな状態を複数作っています。自分で余計な変数を宣言せずに「状態」を持つためにはこのような小細工が必要なようです。

これにより、各 time と引数 func ごとに interval

  1. トップ
  2. tech
  3. Arduino で一定時間ごとに何かをする interval クラス

FT82-43 に20ターンでやってみましたが、結果が芳しくありませんでした。

そこで、FT82-61 に 30 ターンという、おそらくこれが最善だろうと思われる組合せでやってみることにしました。昔に

FT82-61 (AL= 79) のほうが透磁率がちょうど良さそうだけど、手元になかった。

http://lowreal.net/2014/11/11/1

と書いていました。このとき実はコアを買って巻くところまではやったのですが、実際に換装するところまでやっていませんでした。1年ちょっと越しの挑戦です。

結果

SWR / リターンロス

結合器自体の SWR/RL です。

全く問題ない感じです。

方向性など

FT82-43 T20
測定周波数 kHz IN [V] OUT [V] FWD REF IN [W] OUT [W] FWD [W] REF [W] Insertion Loss Coupling Isolation Directivity
1910 51 51 2.490 0.060 52.02 52.02 0.124002 0.000072 0.00 -26.23 -58.59 -32.36
7010 49 49 2.470 0.090 48.02 48.02 0.122018 0.000162 0.00 -25.95 -54.72 -28.77
28010 52 53 2.700 0.278 54.08 56.18 0.145800 0.001546 -0.17 -25.69 -45.44 -19.75
50010 55 52 2.970 0.530 60.50 54.08 0.176418 0.005618 0.49 -25.35 -40.32 -14.97
FT82-61 T30
測定周波数 kHz IN [V] OUT [V] FWD REF IN [W] OUT [W] FWD [W] REF [W] Insertion Loss Coupling Isolation Directivity
1910 49 49 1.671 0.004 48.02 48.02 0.055845 0.000000 0.00 -29.34 -81.76 -52.42
7010 49 49 1.666 0.005 48.02 48.02 0.055511 0.000001 0.00 -29.37 -79.82 -50.45
28010 52 53 1.850 0.008 54.08 56.18 0.068450 0.000001 -0.17 -28.98 -76.26 -47.28
50010 52 53 2.132 0.013 54.08 56.18 0.090908 0.000003 -0.17 -27.74 -72.04 -44.30


下側が FT82-61 での結果です。

50MHz でも 44dB とれています。 (SWR 計としては 25dB 以上なら良い)

精度

正確な信号源があるわけではないので精度というのもあれですが、以下のようになりました。

スペアナの 0dBm 出力と、FT-450D の 40W 出力を使って

solve([2188 * a + b = 2109, 1050 * a + b = 959], [a, b]);

という連立方程式を maxima に解かせてキャリブレーション値を求めています。KX3 の表示電力と出力電力はあんまり信用していないのでキャリブレーションに入れていません。KX3 以外では誤差が10%範囲に納まっています。

仕様変更

30ターンにしたので、カップリングは -29.54dB になっています。アッテネータとあわせて 45.64dB の減衰。-26.36〜61.64dBm (2μW〜1458W)

感想

センサー部分としては十分上出来になったと思います。すくなくとも前回作ったときよりは、かなり良くなりました。

これで I2C 接続の SWR 計ができたことになるので、インターフェイスをうまいこと作ってあげたいという気持ちになりました。

  1. トップ
  2. tech
  3. 自作 デジタル SWR 計(再) 2 | 方向性結合器の改善編