openEMSで2.4GHzのバンドパスフィルタ

今日は友達にopenEMSの使い方を教えるために、簡単なバンドパスフィルタをシミュレーションした。 最初にQucsを使って適当にマイクロストリップのフィルタを作ってみた。基板はFR4で誘電率4.4、厚さ1.6 mmとした。マイクロストリップの幅は3 mmでだいたい50 Ωになる。長さ34 mmで2.4 GHz付近で共振するはず。λ/4だけカップリングさせて、はじっこは折り曲げたあと長さ10 mmのフィードラインを通してポートに接続した。

Qucsで簡単なフィルタを設計

Qucsのシミュレーション結果は下のようになった。センターは2.4 GHzよりちょっと低い2.34 GHzくらいになった。これをあとでopenEMSのシミュレーション結果と比較する。

Qucsのシミュレーション結果

久々のopenEMSだったが、なんとかなった。記事の一番下にOctaveスクリプトを載せる。openEMSでは3DモデルをOctave/Matlabスクリプトで描く必要があり、最初は結構これに苦労するのだが、その分シミュレーションが動いたときの達成感も大きい。メッシュは金属のエッジ付近を細かく、何もないところは粗くするようにした。最初にDetectEdges関数でメタルのエッジを検出したあと、resolution/5だけずらしたものを加えて、さらにDetectEdges関数で全体のエッジを検出し、SmoothMesh関数で滑らかにメッシュを分割した。

openEMSのモデル

openEMSのシミュレーション結果は下のようになった。センターは2.31 GHzくらいでQucsの結果より若干低い。S21の落ち方がQucsよりゆるいかんじ。

openEMSのシミュレーション結果

フィールドダンプを加えて電界分布をParaViewで表示させてみた。いいね。

電界分布をParaViewで表示

Octaveスクリプト

close all
clear
clc

physical_constants;
unit = 1e-3;

% Design parameters
W = 3;   % MSL width
L = 34;  % MSL length
S = 1;   % MSL spacing
t = 1.6; % FR4 substrate thickness
H = 10;  % Simulation box height
Lf = 10; % Feeding line length
Ws = 50; % Substrate width
Ls = 90; % Substrate length

epsilon_r = 4.4; % FR4 dielectric constant
f0 = 2.4e9; % Center frequency
fc = 1e9;   % Simulation frequency range

% Setup FDTD
FDTD = InitFDTD('EndCriteria', 1e-5, 'NrTS', 50000);
%FDTD = InitFDTD('EndCriteria', 1e-5, 'NrTS', 20000, 'OverSampling', 50);
FDTD = SetGaussExcite(FDTD, f0, fc);
BC = { 'MUR' 'MUR' 'MUR' 'MUR' 'PEC' 'MUR' };
FDTD = SetBoundaryCond(FDTD, BC);

% Setup CSX
CSX = InitCSX();

% Materials
% FR4
CSX = AddMaterial(CSX, 'FR4');
CSX = SetMaterialProperty(CSX, 'FR4', 'Epsilon', epsilon_r);
% Metal
CSX = AddMetal(CSX, 'PEC');

% Substrate
start = [-Ws/2 -Ls/2 0];
stop  = [Ws/2 Ls/2 t];
CSX = AddBox(CSX, 'FR4', 1, start, stop);

% MSL
start = [-W/2 -L/2 t];
stop  = [W/2 L/2 t];
CSX = AddBox(CSX, 'PEC', 999, start, stop);

start = [W/2+S 0 t];
stop  = [1.5*W+S L t];
CSX = AddBox(CSX, 'PEC', 999, start, stop);

start = [-W/2-S 0 t];
stop  = [-1.5*W-S -L t];
CSX = AddBox(CSX, 'PEC', 999, start, stop);

start = [1.5*W+S L t];
stop  = [1.5*W+S+Lf L-W t];
CSX = AddBox(CSX, 'PEC', 999, start, stop);

start = [-1.5*W-S -L t];
stop  = [-1.5*W-S-Lf -L+W t];
CSX = AddBox(CSX, 'PEC', 999, start, stop);

% Lumped ports
% Port 1
start = [-1.5*W-S-Lf -L+W t];
stop  = [-1.5*W-S-Lf -L 0];
[CSX port{1}] = AddLumpedPort(CSX, 999, 1, 50, start, stop, [0 0 1], true);
% Port 2
start = [1.5*W+S+Lf L-W t];
stop  = [1.5*W+S+Lf L 0];
[CSX port{2}] = AddLumpedPort(CSX, 999, 2, 50, start, stop, [0 0 1], false);

% Field dump
start = [-Ws/2 -Ls/2 t/2];
stop  = [Ws/2 Ls/2 t/2];
CSX = AddDump(CSX, 'Et');
CSX = AddBox(CSX, 'Et', 0, start, stop);

% Mesh
mesh.x = [];
mesh.y = [];
mesh.z = [];

mesh = DetectEdges(CSX, mesh, 'SetPropertyType', {'Metal', 'Excitation'});
resolution = 1;

mesh.x = [mesh.x-resolution/5 mesh.x mesh.x+resolution/5];
mesh.y = [mesh.y-resolution/5 mesh.y mesh.y+resolution/5];
mesh.z = [mesh.z resolution/5 t-resolution/5 t+resolution/5 H];

mesh = DetectEdges(CSX, mesh);
mesh = SmoothMesh(mesh, resolution);

CSX = DefineRectGrid(CSX, unit, mesh);

% Write openEMS compatible xml-file
Sim_Path = 'tmp';
Sim_CSX = 'BandpassFilter2400MHz.xml';
[status message messageid] = rmdir(Sim_Path, 's');
[status message messageid] = mkdir(Sim_Path);

WriteOpenEMS([Sim_Path '/' Sim_CSX], FDTD, CSX);
CSXGeomPlot([Sim_Path '/' Sim_CSX]);

RunOpenEMS(Sim_Path, Sim_CSX, '');

% Post processing
f = linspace(f0-fc, f0+fc, 401);
port = calcPort(port, Sim_Path, f, 'RefImpedance', 50);

S11 = port{1}.uf.ref ./ port{1}.uf.inc;
S21 = port{2}.uf.ref ./ port{1}.uf.inc;

figure('Position', [100 100 480 320]);
plot(f/1e9, 20*log10(abs(S11)), 'r-', 'DisplayName', 'S_{11}');
hold on
plot(f/1e9, 20*log10(abs(S21)), 'b-', 'DisplayName', 'S_{21}');
grid on
legend('Location', 'southeast');
xlabel('Frequency (GHz)');
ylabel('S Parameters (dB)');
xlim([f0-fc f0+fc]/1e9);

積層セラミックコンデンサのDCバイアス特性を利用してVCOを作ってみる(小ネタ)

1. はじめに

積層セラミックコンデンサには、DCバイアスをかけると静電容量が大きく低下してしまうものがある。一般に、温度特性によってDCバイアス特性もおおよそ決まっている。温度特性がCHやC0GのものはDCバイアスに対して容量変化はほとんどなく、B特性やF特性のものは定格電圧までかけると容量が半分以下になってしまう。

例えば、耐圧6.3 V、100 μFの積層セラミックコンデンサを5 Vの電源につけたとき、実際には30 μFくらいの性能しかなかったりする。また、スイッチング回路に使うと、その非線形性から高電圧が発生したりすることがあるようだ1

今回は、逆に、積層セラミックコンデンサのDCバイアス特性を利用して、VCOを作ってみようと思った。VCOはLC共振のクロスカップル発振回路を使う。

2. 部品さがし

まずは、DCバイアス特性が悪いコンデンサを探した。VCOの発振周波数をできるだけ高くしたいので、容量が小さいものを選びたい。だけど、容量が~nF以下のやつはたいていC0G特性かCH特性なので今回は使えない。また、耐圧が低い方が、容量変化も大きいはず。秋月電子で探していたらいい感じなものを見つけた。

チップ積層セラミックコンデンサー 0.01μF25V B 0603 (50個入): パーツ一般 秋月電子通商-電子部品・ネット通販

f:id:neurois:20211016193740p:plain:h180
積層セラミックコンデンサ 10 nF, 25 V, B特性, 0603 (0201)

チップの積層セラミックコンデンサで、10 nF、耐圧25 V、B特性で50個入りで100円。大きさが0603(0201)なのでかなり小さい。データシートを見るとゼロバイアスで10 nFあるのが、定格の25 Vで2.6 nFまで落ちるので今回の目的においてはかなり優秀だ。容量比で言うとだいたい4くらい。

f:id:neurois:20211016201918p:plain
DCバイアス-キャパシタンス特性

ちなみに、村田製作所コンデンサやインダクタはSimSurfingというツールを利用して、部品のデータやSpiceモデルをダウンロードしたりすることができる。おすすめ。 設計支援ソフトウェア SimSurfing | 設計支援ツール | 村田製作所

3. VCOの回路

回路図を下に載せた。VCOはふつうのクロスカップル発振回路とした。トランジスタは部品箱にあった2SK4150というMOSFETを使った(これ、大学1年生のときに買ったやつで、ちょっと懐かしい思い出がある2)。図のC1とC2に、上で選んだ積層セラミックコンデンサを使う。


f_{\mathrm{osc}}=\frac{1}{2\pi\sqrt{L_1C_1}}

発振周波数はL1, L2とC1, C2の共振で決まり、上の式となる。計算すると、L1=47 µHとC1=10 nFのとき、発振周波数は232 kHzになる。C1とC2の真ん中から100 kΩを通してDCバイアスをかけられるようにした。

f:id:neurois:20211017003102p:plain
回路図

余ってた片面ユニバーサル基板を使った。ちっさいチップコンデンサは、まず小さく切った基板にはんだ付けしたあと、足を2本生やしてリード線タイプのかんじにしてみた。

f:id:neurois:20211016222458p:plain:h180
完成した基板

4. 発振特性の測定

テスターで積層セラミックコンデンサにかかる電圧を測定しながら、もうひとつのテスターで発振周波数を測定した。DCバイアスは秋月電子の昇圧回路キットを用いた。発振周波数は0 Vのとき221 kHzで、27 V(定格25 Vをちょっとオーバー)のとき406 kHzだった。周波数も可変幅も期待通りの結果になったと思う。

f:id:neurois:20211017000702p:plain
測定結果:DCバイアス-発振周波数
f:id:neurois:20211017001053p:plain:h180
測定のようす(その1)
f:id:neurois:20211017001119p:plain:w320
測定のようす(その2)

ソース・メジャー・ユニット(SMU)の製作

f:id:neurois:20210923045631p:plain:h240

1. はじめに

ソース・メジャー・ユニット(SMU)というのを製作してみた。SMUは測定器のひとつで、ダイオードトランジスタのI-V特性を調べたりするのに使われている。買うと高いので、自作できないかと前々から思っていた。

SMUは下図のような構成となっている。電流源(current source)と電圧源(voltage source)があり、測定対象(DUT)にかかる電圧と電流をスイープしながら測定することができる。

f:id:neurois:20210923051815p:plain
ソース・メジャー・ユニット(SMU)の構成

2. ハードウェア

2.1. 概要

回路の概略図を下に載せた。USBで制御するためにFT2232Dというチップを使ってみた。FT2232DはMPSSEという機能を搭載していて、USBからI2C通信やSPI通信などが行えるようになっている。今回は、これを利用してI2CインターフェースのD/A、A/Dチップを使おうと思った。

f:id:neurois:20210923131850p:plain
回路全体の概略図

D/Aコンバータに書き込むことで、-8.2 V~8.2 Vの範囲の出力電圧を設定できるようになっている。また、直列に入れた抵抗の電圧降下をA/Dコンバータで読み取ることで、電流を測定することができる。マルチプレクサでA/Dの入力を切り替えて電圧と電流の両方を測定できるようにした。

※ 測定で電流源を使う機会はあまりないので今回は省いた。

2.2. 回路図

下に全体の回路図を載せた(パスコンなどは省略してある)。 全体の回路図

電圧源回路

D/Aコンバータ(MCP4725)の出力は0~3.3 Vの範囲なので、これを-8.2~+8.2 Vの範囲になるようにオペアンプU6(MAX9945)で減算回路を組んでいる。トランジスタQ3, Q4はオペアンプの出力電流をブーストするために入れている。オペアンプの出力電流が6 mA以上になると、抵抗R21/R23(100 Ω)の電圧が0.6 VくらいになってQ3/Q4がオンし始める。そこからは、オペアンプの代わりにQ3/Q4が出力を補ってくれるので、大電流が扱えるという仕組み。最初この回路見たとき、オペアンプの電源端子に抵抗が入っていて不思議な感じがしたけど、なかなか良いのではと思う。

電流検出抵抗

電圧源の後ろには、電流検出用に抵抗1 Ω、10 Ω、100 Ω、1 kΩを接続した。電流を測定する際は、これらの抵抗の両端の電圧差をA/D変換する。測定レンジに応じて抵抗値をリレーで切り替えられるようにした。小さい電流を測りたければ大きい抵抗値を選ぶことで、精度を上げることができる(オームの法則)。例えば、1 μA以下は1 kΩ、1〜10 μAは100 Ω、...みたいな感じにする。実際には、100 Ωを選択すると、1 Ωと10 Ωも直列に入ってしまうので、111 Ω(= 1+10+100)の電圧降下をみることになるが、ソフトウェア側で値を変えるだけなので問題ない。

電流検出

電流検出抵抗の電位がA/Dチップの入力範囲(0~3.3 V)にないので、そのままではA/D変換できない。なので、計装アンプAD620というのを使って、検出した電圧をリファレンス1.65 Vに持ってくるようにした。AD620のゲインはRGピンに繋ぐ抵抗によって設定することができる(下の式で計算できる)。今回は、RG=5.49 kΩ(4.99k+1k//1k)という値を使ったので、ゲインは10倍である。AD620は値段が高い(秋月で810円)。


G=\frac{49.4\ k\Omega}{R_G}+1

A/Dコンバータは16-bit分解能のMCP3425を使った。ゲイン1, 2, 4, 8倍のPGAプログラマブル・ゲイン・アンプ)を内蔵している。すごい。値段もまあまあ安い(秋月で200円)。

電源・その他

電源はすべてUSBから供給するので、PCにさすだけで使える。USBコネクタはType-Cコネクタを使ってみた。Type-Cデビューしてしまった。I2C通信のSDAは双方向なので、FT2232DのDOピン(ADBUS1)とDIピン(ADBUS2)をショートさせる必要がある。5 Vから±12 Vを生成するところは、秋月のDC/DCコンバータキット(https://akizukidenshi.com/catalog/g/gK-12337/)を使った。

3. 製作

3.1. プリント基板の設計

今回はKiCadを使ってみた。今までEagleを使ってたんだけど、なんかツイッターの人たちみんなKiCad使ってるし、良さそうだったので僕ものりかえた。使ってみたかんじは、うん。KiCadの方が良いね。

f:id:neurois:20210923223956p:plain
KiCadで設計したパターン

設計したプリント基板は、Fusion PCBに作ってもらった。注文してからちょうど2週間で届いた。基板は10枚で$5で、送料が約$15かかった。品質はすごく良かった。

f:id:neurois:20210923232357p:plain
Fusion PCBで注文したプリント基板

3.2. 完成

プリント基板にはちゃんとソルダーレジストがのってて、しかもシルクスクリーンも印刷されてて、自作の感光基板と比べたら超はんだづけが簡単だった。

f:id:neurois:20210923232534p:plain
完成

4. ソフトウェア

4.1. FT2232DのMPSSE機能

FT2232D、FT2232H、FT232H、FT4232HにはMPSSE(Multi-Protocol Synchronous Serial Engine、マルチプロトコル・シンクロナス・シリアル・エンジン)という機能があり、SPI通信やI2C通信をエミュレートすることができる。FTDIのアプリケーションノートにD2XXライブラリを使ったサンプルプログラムがあるので、それを参考にしながらプログラムを書いた。

f:id:neurois:20210923234916p:plain
つたないMPSSEの図

一度MPSSE機能をオンしたら、上図のようにFT_Write()関数でコマンドデータを書き込むことで、決められた動作をさせることができる。例えば、「nバイトのデータをクロックの立ち下がりエッジで送るコマンド(0x11)」とか「nビットをクロックの立ち上がりエッジで読み取るコマンド(0x22)」とかがある。それらを組み合わせることで色々な通信プロトコルに対応することができる。

注意点は、FT2232Dには使えないコマンドがあること(あとから気づいた)。I2C通信する際に、3-フェーズクロッキングのコマンド(0x8C)がFT2232Dではサポートされていなかったので、1bitずつデータを送るコマンドを使わないといけなかった。

MPSSEの詳しいことについては、忘れないうちに別の記事にでもまとめておこうと思ってるけど思ってるだけ。

4.2. QtでGUIアプリケーション

Qtを使って簡単なGUIの測定アプリを作ってみた。使い方は単純で、FT2232Dの接続を確認したら、電圧と電流の上限値とステップ値を入力してスタートボタンをクリックするだけ。すると、スイープ測定が開始して、結果をリアルタイムでグラフに表示してくれる。

f:id:neurois:20210923235907p:plain
Qtで作ったGUI

測定中、プログラムはD/Aコンバータに値を書き込んで、A/D変換が終わるのを待って、A/Dコンバータから値を読み取って、、、という処理を繰り返す。この間、GUIのウィンドウが固まってしまうのはなんかダサいので、QThreadでマルチスレッドにしてみた(マルチスレッドとか何も分かってないけど、分かってなくてもなんとなく使えてしまうのがQt)。また、測定開始時にちょっとしたキャリブレーションをするようにした。D/Aコンバータのゼロ点を探すのと、電流がゼロの時のA/Dコンバータのオフセットを調べておいて後から引き算する。

5. 測定テスト

5.1. pnダイオードのI-V特性を測ってみる

たまたまpnダイオード1N4148が落ちていたので、I-V特性を測ってみた。

f:id:neurois:20210924001507p:plain
1N4148を取り付けたところ

下に測定結果を載せる。横軸は順方向電圧 VF、縦軸は順方向電流 IF(ログスケール)。1 nA程の小さい電流まで測れている。すごい。すごくない?

f:id:neurois:20210924001916p:plain
1N4148のI-V特性の測定結果

5.2. npnトランジスタのIC-VCE特性を測ってみる

次は、2SC1815(セカンドソース品)のIC-VCE特性を測ってみた。9 V電池と100 kΩの抵抗で適当なベース電流を流しながら、コレクタ-エミッタ間の特性を測定した。下図がその測定結果。それっぽい曲線が出た。あまり良いトランジスタでは無さそう。

f:id:neurois:20210924002308p:plain
2SC1815のIC-VCE特性の測定結果

6. 反省点など

今回はコストを抑えるためもあって、電流制限は完全にソフト側で行なっている。なので、例えばツェナーダイオードとか測ろうとすると急に電流が流れすぎて壊してしまう可能性がある。

D/AコンバータのINL(積分誤差)が思っていた以上に悪かった。データシートのTyp.見て安心してたんだけど、結局5倍くらい悪かったので残念。ハズレを引いたかもしれない。もっとキャリブレーションをガチでやろうとするなら、2個のD/Aコンバータの出力を1:1000くらいの比で合成して分解能細かくするとかありかもしれない(こうすれば実質20bitくらいの分解能になりそう)。

電流検出抵抗を切り替えるのにリレーを使った。MOSFETはゲートの漏れ電流が出力に流れてしまいそうだったのでやめた。フォトMOSFETとかならどうなんだろうか。まあでも、リレーを使ったことで測定中にカチカチ音が鳴ってちょっとかわいいので、これはこれで良い。

7. まとめ

SMUを作ったよ、という話。

Lattice FPGA MachXO2でLEDチカチカ

f:id:neurois:20200829185934p:plain:h150:right 以前から秋月電子で600円で買えるLatticeのFPGA、MachXO2(LCMXO2-256HC-4TG100C)が気になっていたので、簡単にLEDチカチカまで試してみた。

回路図

回路図。最小構成にしてある。

f:id:neurois:20200829184442p:plain

MachXO2は不揮発のコンフィグメモリを内蔵していて、JTAGで書き込むことができる。書き込みには秋月電子で売られているFT2232のUSB-シリアル変換モジュールを使った。JTAGピンは4本(TCK、TDI、TDO、TMS)あって、TDI、TDO、TMSは4.7kΩでプルアップ、TCKは2.2kΩでプルダウンするのが良いらしい。また、TCKに10pF程度のキャパシタをつけると書き込みが安定する。

チカチカさせるLEDはFPGAの2ピンに接続した。MachXO2は内部発振器を持っているので、発振器を外付けしなくてもLEDチカチカできる。ただし、クロックの精度はあまり良くない(±5%)。

回路を作る

今回使うFPGA、LCMXO2-256HC-4TG100C。QFP100の変換基板を使ってみた。この変換基板は裏側にVCCとGND用のパターンがあるので、電源ピンだけ先に配線した。

f:id:neurois:20200829210835p:plain:h200 f:id:neurois:20200829210844p:plain:h200

回路全体。USB-シリアル変換モジュールとの配線にはブレッドボードを使った。一度書き込んでしまえば、あとはブレッドボードの配線は外しても動作する。

f:id:neurois:20200829212246p:plain:h200

Lattice Diamondのインストール

LatticeのFPGAをプログラムするにはLattice Diamondというソフトを使う。まずは、Lattice Semiconductorのホームページに行って、右上の「Sign In|Register」からアカウントを作ってログインする。 www.latticesemi.com

ログインしたら「製品」->「Lattice Diamond」のページに行って、「Diamond Base ダウンロード」からLattice Diamondのインストーラをダウンロードする。インストーラを起動して適当にぽちぽちすればインストールされる。

これだけだと「ライセンスファイルが見つかりません」みたいなメッセージが出て起動できないので、フリーライセンスを取得する必要がある。先ほどのページから「ライセンスを取得」をクリック。「ラティスDiamond無料ライセンス」の下の「無料ライセンスを取得する」をクリックする。

「Host NIC (physical address)」を入力する欄が出てくるので、ここに12桁の物理アドレスを入力する。物理アドレスは、Windowsならコマンドプロンプト

 ipconfig /all 

とすれば調べられる。その下の「I verify that I am not an employee of...」のチェックボックスに✓して、「Generate License」をクリックする。

すると、「license.dat」というファイルが添付されたメールが送られてくるので、このライセンスファイルを「C:\lscc\diamond\3.10_x64\license」のフォルダに入れる。これで、ライセンスファイルが自動的に読み込まれて、Lattice Diamondがちゃんと起動するようになる。

f:id:neurois:20200830124235p:plain
Lattice Diamond

プロジェクトの作成

「File」->「New」->「Project」でプロジェクトを作成できる。「New Project」ウィザードが出るので、プロジェクト名と保存する場所を入力する。今回、プロジェクト名は「BlinkLED」としてみた。「Select Device」で「LCMXO2-256HC-4TG100C」を選んで、「Select Synthesis Tool」で「Lattice LSE」を選択する。最後に「Finish」でプロジェクトが作成される。「~.ldf」がプロジェクトの設定ファイルのようだ。

f:id:neurois:20200830132550p:plain

HDLで論理回路の作成

「File」->「New」->「File...」をクリック、ファイルの種類を「Verilog Files」として「BlinkLED.v」を作成する。内部発振器はOSCHのインスタンスを作ることで使えて、周波数はOSCH_inst.NOM_FREQで設定する。デフォルトの周波数は2.08MHzで、最大133MHzまで設定できる。今回は16.63MHzにしてみた。24-bitカウンタの最上位ビットをLEDに出力するようにした。だいたい1Hzになるはず。

module BlinkLED(
    input rst,
    output led
);

reg [23:0] count;
assign led = ~count[23];

// internal oscillator, 16.63 MHz
defparam OSCH_inst.NOM_FREQ = "16.63";
OSCH OSCH_inst(.STDBY(1'b0), .OSC(clk), .SEDSTDBY());

// 24-bit counter
always @(posedge clk or posedge rst) begin
    if(rst) count <= 24'd0;
    else count <= count + 24'd1;
end

endmodule

左側の「Process」タブの「Lattice Synthesis Engine」を右クリック->「Run」で論理合成が実行される。「Done: completed successfully」のメッセージが出ればOK。

ピンアサインの設定

「Tools」->「Package View」を開くと、ピンの配置を確認することができる。ピンにカーソルを合わせるとピンの機能も確認できる。Bottom Viewになっている場合は、右クリックからTop Viewにできる。

f:id:neurois:20200830144653p:plain:h300

「Tools」->「Spreadsheet View」を開く。ここでピンアサインを設定していく。今回は、rst入力を1ピン、led出力を2ピンにしたいので、下のように設定した。

f:id:neurois:20200830150007p:plain

ピンアサインの設定は「~.lpf」ファイルに保存される。中身はこんなかんじ。

BLOCK RESETPATHS ;
BLOCK ASYNCPATHS ;
LOCATE COMP "rst" SITE "1" ;
LOCATE COMP "led" SITE "2" ;
IOBUF PORT "rst" IO_TYPE=LVCMOS33 ;
IOBUF PORT "led" IO_TYPE=LVCMOS33 ;

書き込み(コンフィグレーション)

左側の「Process」タブの「Export Files」の下の「JEDEC File」に✓を入れてRunすると、「Map Design」「Place & Route Design」「Export Files」の順に実行されて、「~.jed」ファイルが生成される。これをFPGAに書き込む。

f:id:neurois:20200830150944p:plain

まず、USB-シリアル変換モジュールとPCをUSB-miniケーブルでつなぐ。

「Tools」->「Programmer」を開くと、JTAGの接続を聞いてくるので、FTDIのデバイスを選択して、「Detect Cable」で「USB2 - FTUSB-1 (Dual RS232 A Location 0001)」の方を選択する。

f:id:neurois:20200830154555p:plain

「Device Family」「Device」「File Name」の項目を正しく設定して、プログラムアイコンをクリックするとコンフィグレーションが開始する。「Status」のところが「PASS」となればOK。

f:id:neurois:20200830154608p:plain

LEDチカチカできた

LEDチカチカできた。コオロギが鳴いてる。

スマホだとなぜか動画が再生できない。わからんち)

openEMSを使ってみる。その4:2D Cylindrical Wave

Tutorial: 2D Cylindrical Wave

今回は「2D Cylindrical Wave」のチュートリアル Tutorial: 2D Cylindrical Wave - openEMS をやってみた。

Octaveスクリプト

いつもの。実行環境を空の状態にしておく。

close all; clear; clc

シミュレーションの半径を2.56 m(=2560 mm)とする。領域をr方向に5つに分割して、半径が境界を跨ぐと、θ方向のメッシュの数が2倍になるようにする。こうすることで、中心付近の電磁界のオーバーサンプリングを防ぐことができる。

physical_constants
mesh_res = 10;
radius = 2560;
split = ['80,160,320,640,1280'];
split_N = 5;
height = mesh_res*4;

周波数は1 GHzとする。波源は中心から1300 mm、45°の位置にずらして配置する。

f0 = 1e9;
excite_offset = 1300;
excite_angle = 45;

FDTDオブジェクトを初期化する。InitFDTD関数の中でMultiGridのオプションに先ほどの領域分割を与える。境界条件は+r方向をPMLに割り当てる。

FDTD = InitFDTD('NrTS',100000,'EndCriteria',1e-4,'CoordSystem',1,'MultiGrid',split);
FDTD = SetGaussExcite(FDTD,f0,f0/2);
BC = [0 3 0 0 0 0];             % pml in positive r-direction
FDTD = SetBoundaryCond(FDTD,BC);

内側の領域(r<80 mm)でのθ方向のメッシュラインの数を50として、外側の領域(r>1280 mm)のメッシュラインの数を計算する。+1してるのは植木算的な意味。

% 50 mesh lines for the inner most mesh
% increase the total number of mesh lines in alpha direction for all sub-grids
N_alpha = 50 * 2^split_N + 1;

CSXCADを初期化してメッシュを定義する。DefineRectGrid関数は円筒座標のグリッドにも使える。

CSX = InitCSX('CoordSystem',1);
mesh.r = SmoothMeshLines([0 radius],mesh_res);
mesh.a = linspace(-pi,pi,N_alpha);
mesh.z = SmoothMeshLines([-height/2 0 height/2],mesh_res);
CSX = DefineRectGrid(CSX,1e-3,mesh);

波源を置く。AddExcitation関数の第3引数で、波源の種類を1=「E-field hard excitation」に設定する。「hard excitation」では、波源での電磁界が強制的に(他の入射してくる波に関係なく)決まるのに対し、「soft excitation」は波源が作る電磁界と他の進行波との重ね合わせとなる。

start = [excite_offset excite_angle/180*pi-0.01 -20];
stop =  [excite_offset excite_angle/180*pi+0.01  20];
 
CSX = AddExcitation(CSX,'excite',1,[0 0 1]);
CSX = AddBox(CSX,'excite',0,start,stop);

フィールドをダンプする領域を定義する。今回は時間領域での結果をvtk形式で保存し、周波数領域(Frequency=f0)の結果をhdf5形式で保存する。AddDump関数のDumpTypeを0にすると「E-field time-domain dump」、10にすると「E-field frequency-domain dump」となる。

start = [mesh.r(1)   mesh.a(1)   0];
stop =  [mesh.r(end-8) mesh.a(end) 0];
 
% time domain vtk dump
CSX = AddDump(CSX,'Et_ra','DumpType',0,'FileType',0,'SubSampling','4,10,1');
CSX = AddBox(CSX,'Et_ra',0,start,stop);
 
% frequency domain hdf5 dump
CSX = AddDump(CSX,'Ef_ra','DumpType',10,'FileType',1,'SubSampling','2,2,2','Frequency',f0);
CSX = AddBox(CSX,'Ef_ra',0,start,stop);

FDTDとCSXxmlに書き込んで、シミュレーションを実行する。

Sim_Path = 'tmp';
Sim_CSX = '2D_CC_Wave.xml';
 
[status, message, messageid] = rmdir(Sim_Path,'s'); % clear previous directory
[status, message, messageid] = mkdir(Sim_Path); % create empty simulation folder
 
WriteOpenEMS([Sim_Path '/' Sim_CSX],FDTD,CSX);
RunOpenEMS(Sim_Path,Sim_CSX);


作成した3DモデルをCSXGeomPlot関数で確認してみる。円形にメッシュがあって、中心から離れて45°の位置にぽつんと青色のポートが見えているので良さそうだ。

CSXGeomPlot([Sim_Path '/' Sim_CSX]);

f:id:neurois:20200714003341p:plain
AppCSXCAD

周波数領域の電界分布をプロット

出力したhdf5形式のファイルを読み込み、電界分布をプロットする。

[field mesh_h5] = ReadHDF5Dump([Sim_Path '/Ef_ra.h5']);

r = mesh_h5.lines{1};
a = mesh_h5.lines{2};
a(end+1) = a(1);            %closeup mesh for visualization
[R A] = ndgrid(r,a);
X = R.*cos(A);
Y = R.*sin(A);

Ez = squeeze(field.FD.values{1}(:,:,1,3));
Ez(:,end+1) = Ez(:,1);      %closeup mesh for visualization

E_max = max(max(abs(Ez)));  %get maximum E_z amplitude

while 1
    for ph = linspace(0,360,41) %animate phase from 0..360 degree
        surf(X,Y,real(Ez*exp(1j*ph*pi/180)),'EdgeColor','none')
        caxis([-E_max E_max]/10)
        zlim([-E_max E_max])
        pause(0.3)
    end
end

f:id:neurois:20200714012845g:plain
E_z(f) animation

openEMSを使ってみる。その3:Circular Waveguide

Tutorial: Circular Waveguide

今回は「Circular Waveguide」(円形導波管)のチュートリアル Tutorial: Circular Waveguide - openEMS をやってみた。前回の「Rectangular Waveguide」と基本的に流れは同じなので、前回あまり説明しなかったSパラメータの計算方法についてもう少し詳しく書いていこうと思う。

Octaveスクリプト

いつものように実行環境を空にしておく。

close all; clear; clc

単位をmmとする。

physical_constants;
unit = 1e-3; %drawing unit in mm

導波管の長さを2 m(=2000 mm)、半径を35 cm(=350 mm)とする。

% waveguide dimensions
length = 2000;
rad = 350;     %waveguide radius in mm

着目する周波数帯域を300 MHzから500 MHzとする。

% frequency range of interest
f_start =  300e6;
f_stop  =  500e6;

メッシュの細かさを決める。今回は円筒座標系(ρ,φ,z)で与えている。

mesh_res = [10 2*pi/49.999 10]; %targeted mesh resolution

FDTDオブジェクトを初期化。座標系を円筒座標系(=1)としている。

FDTD = InitFDTD('EndCriteria',1e-4,'CoordSystem',1);
FDTD = SetGaussExcite(FDTD,0.5*(f_start+f_stop),0.5*(f_stop-f_start));

境界条件を設定する。±Z方向をPML (perfectly matched layer) としている。

% boundary conditions
BC = [0 0 0 0 3 3]; %pml in pos. and neg. z-direction
FDTD = SetBoundaryCond(FDTD,BC);

CSXCADを作成してメッシュを定義する。

CSX = InitCSX('CoordSystem',1); % init a cylindrical mesh
mesh.r = SmoothMeshLines([0 rad],mesh_res(1)); %mesh in radial direction
mesh.a = SmoothMeshLines([0 2*pi],mesh_res(2)); % mesh in azimuthal dir.
mesh.z = SmoothMeshLines([0 length],mesh_res(3));
CSX = DefineRectGrid(CSX,unit,mesh);

円形導波管のポートを追加する。モードはTE11

start=[mesh.r(1)   mesh.a(1)   mesh.z(8)];
stop =[mesh.r(end) mesh.a(end) mesh.z(15)];
[CSX, port{1}] = AddCircWaveGuidePort(CSX,0,1,start,stop,rad*unit,'TE11',0,1);
 
start=[mesh.r(1)   mesh.a(1)   mesh.z(end-13)];
stop =[mesh.r(end) mesh.a(end) mesh.z(end-14)];
[CSX, port{2}] = AddCircWaveGuidePort(CSX,0,2,start,stop,rad*unit,'TE11');

電界分布を取得する領域を追加する。

CSX = AddDump(CSX,'Et','FileType',1,'SubSampling','4,4,4');
start = [mesh.r(1)   mesh.a(1)   mesh.z(1)];
stop  = [mesh.r(end) mesh.a(end) mesh.z(end)];
CSX = AddBox(CSX,'Et',0,start,stop);

FDTDの設定とCSXCADデータをxmlに書き込んで、シミュレーションを実行する。

Sim_Path = 'tmp';
Sim_CSX = 'circ_wg.xml';

[status, message, messageid] = rmdir(Sim_Path,'s');
[status, message, messageid] = mkdir(Sim_Path);

WriteOpenEMS([Sim_Path '/' Sim_CSX],FDTD,CSX);
RunOpenEMS(Sim_Path,Sim_CSX)

作成した3DモデルはAppCSXCADで確認できる。CSXGeomPlot関数でxmlファイルを指定するだけなので簡単である。

CSXGeomPlot([Sim_Path '/' Sim_CSX]);

f:id:neurois:20200712202824p:plain
AppCSXCAD

Sパラメータとインピーダンスの計算

ここの部分は前回説明を省いたので少し詳しく書いてみる。Sパラメータを計算するには以下の後処理が必要となる。着目している周波数のベクトルをcalcPort関数に渡している。この関数は、それぞれの周波数におけるポートの電圧と電流、伝搬定数、特性インピーダンスを計算してくれるようだ。

freq = linspace(f_start,f_stop,201);
port = calcPort(port,Sim_Path,freq);

Sパラメータは次のスクリプトで計算できる。ここで、「uf」は周波数に対する電圧、「if」は周波数に対する電流、「inc」は入射(incident)、「ref」は反射(reflected)、「tot」はtotalの意味である。

s11 = port{1}.uf.ref./port{1}.uf.inc;
s21 = port{2}.uf.ref./port{1}.uf.inc;
ZL = port{1}.uf.tot./port{1}.if.tot;

S11 はポート1に入射する電力の波 a1 と 反射する電力の波 b1 の比で与えられるが、無損失の場合 a1b1 は電圧に比例するので、a1port{1}.uf.inc(ポート1に入射する電圧)、b1port{1}.uf.ref(ポート1で反射する電圧)に対応する。したがって上のような記述となる。


計算したSパラメータをプロットする。

figure
plot(freq*1e-6,20*log10(abs(s11)),'k-','Linewidth',2);
xlim([freq(1) freq(end)]*1e-6);
grid on;
hold on;
plot(freq*1e-6,20*log10(abs(s21)),'r--','Linewidth',2);
l = legend('S_{11}','S_{21}','Location','Best');
set(l,'FontSize',12);
ylabel('S-Parameter (dB)','FontSize',12);
xlabel('frequency (MHz) \rightarrow','FontSize',12);

f:id:neurois:20200712201739p:plain
S-parameters

計算したインピーダンスと理論値を比較する。

figure
plot(freq*1e-6,real(ZL),'Linewidth',2);
hold on;
grid on;
plot(freq*1e-6,imag(ZL),'r--','Linewidth',2);
plot(freq*1e-6,port{1}.ZL,'g-.','Linewidth',2);
ylabel('ZL (\Omega)','FontSize',12);
xlabel('frequency (MHz) \rightarrow','FontSize',12);
xlim([freq(1) freq(end)]*1e-6);
l = legend('\Re(Z_L)','\Im(Z_L)','Z_L analytic','Location','Best');
set(l,'FontSize',12);

f:id:neurois:20200712201802p:plain
Waveguide impedance

openEMSを使ってみる。その2:Rectangular Waveguide

Tutorial: Rectangular Waveguide

今回は「Rectangular Waveguide」(方形導波管)のチュートリアルTutorial: Rectangular Waveguide - openEMS)をやってみた。

Octaveスクリプト

最初に実行環境を空にしておく。

close all; clear; clc

物理定数(光速とか誘電率とか)を読み込んで、単位をμmとする。

physical_constants
unit = 1e-6; %drawing unit in microns

WR42の規格の方形導波管の寸法を指定する。幅10.7 mm、高さ4.3 mm、長さ50 mm。

a = 10700;   %waveguide width
b = 4300;    %waveguide height
length = 50000;

使用する周波数帯域を20 GHzから26 GHzとする。4行目では中心周波数 f_0=24 GHz の波長をμmで求めている。

f_start = 20e9;
f_0     = 24e9;
f_stop  = 26e9;
lambda0 = c0/f_0/unit;

方形導波管の伝搬モードをTE10とする。方形導波管の電磁波の伝搬の仕方は複数あり、進行方向に対して電界が垂直なものをTEモード(Transverse electric modes)、磁界が垂直なものをTMモード(Transverse magnetic modes)と呼んでいる。さらに、電界・磁界の分布によってTEmn、TMmn(m,n=0,1,2,...)がある。

TE_mode = 'TE10';

メッシュの細かさを波長あたり30セルとする。

mesh_res = lambda0./[30 30 30];


FDTDオブジェクトを初期化する。SetGaussExcite関数は、時間領域でガウス関数を包絡線とするような波源を設定することができる。第2引数は中心周波数で、第3引数は20dBカットオフ周波数を指定する。ガウス関数フーリエ変換ガウス関数なので、周波数の幅を決めると時間波形の幅が決まるっぽい。

FDTD = InitFDTD('NrTS',1e4,'OverSampling',5);
FDTD = SetGaussExcite(FDTD,0.5*(f_start+f_stop),0.5*(f_stop-f_start));

境界条件を設定する。z方向に伝搬する波を吸収するために、導波管の両端をPML(perfectly-matched layers)に割り当てる。導波管の壁はPEC(perfect electric conductor)にする。0=PEC、3=PML。

BC = [0 0 0 0 3 3]; %pml in pos. and neg. z-direction
FDTD = SetBoundaryCond(FDTD,BC);


CSXCADのメッシュを設定する。SmoothMeshLines関数は、第1引数で指定した区間を、第2引数の分解能で分割してくれる関数である。

CSX = InitCSX();
mesh.x = SmoothMeshLines([0 a],mesh_res(1));
mesh.y = SmoothMeshLines([0 b],mesh_res(2));
mesh.z = SmoothMeshLines([0 length],mesh_res(3));
CSX = DefineRectGrid(CSX,unit,mesh);

次は導波管のポートを設定する。ポート1は下端から11セルの位置、ポート2は上端から13セルの位置に置く。ポートは直方体の形であり、励振(excitation)は始点の位置、電圧・電流のプローブは終点の位置となる。終点はポートの基準面となる。
AddRectWaveGuidePort関数の第10引数は振幅であり、ポート1はActive(振幅=1で励振)、ポート2はPassive(振幅を指定しない or 振幅=0)としている。

start=[mesh.x(1)   mesh.y(1)   mesh.z(11)];
stop =[mesh.x(end) mesh.y(end) mesh.z(15)];
[CSX, port{1}] = AddRectWaveGuidePort(CSX,0,1,start,stop,'z',a*unit,b*unit,TE_mode,1);
 
start=[mesh.x(1)   mesh.y(1)   mesh.z(end-13)];
stop =[mesh.x(end) mesh.y(end) mesh.z(end-14)];
[CSX, port{2}] = AddRectWaveGuidePort(CSX,0,2,start,stop,'z',a*unit,b*unit,TE_mode);

時間領域の電界分布(E-field)をキャプチャする領域を定義する。AddDump関数の中で'FileType'属性を指定しているが、これは保存するファイル形式(0=vtkファイル、1=hdf5ファイル)を決めている。

CSX = AddDump(CSX,'Et','FileType',1,'SubSampling','2,2,2');
start = [mesh.x(1)   mesh.y(1)   mesh.z(1)];
stop  = [mesh.x(end) mesh.y(end) mesh.z(end)];
CSX = AddBox(CSX,'Et',0,start,stop);


最後に、FDTDオブジェクトとCSXCADオブジェクトをxmlファイルに書き込み、シミュレーションを実行する。

Sim_Path = 'tmp_mod';
Sim_CSX = 'rect_wg.xml';
 
[status, message, messageid] = rmdir(Sim_Path,'s');
[status, message, messageid] = mkdir(Sim_Path);
 
WriteOpenEMS([Sim_Path '/' Sim_CSX],FDTD,CSX);
RunOpenEMS(Sim_Path,Sim_CSX)

結果

電界分布

下のスクリプトでE-fieldをプロットすることができる。まああってそう。

dump_file = [Sim_Path '/Et.h5'];
PlotArgs.slice = {a/2*unit b/2*unit 0};
PlotArgs.pauseTime=0.01;
PlotArgs.component=0;
PlotArgs.Limit = 'auto';
PlotHDF5FieldData(dump_file,PlotArgs)

f:id:neurois:20200705064820g:plain
E-field

結果をHDF5ファイルで保存したことで、ParaViewを使わずにOctaveだけで確認することができる。このHDF5というフォーマット、初めて知ったのだが一般に使われているのか。調べてみるとHDFViewというソフトで見ることができて、pythonとか他の言語でも読み書きできるっぽい。

Sパラメータ

Sパラメータを計算するためのスクリプト。「uf」は周波数領域の電圧、「if」は周波数領域の電流、「inc」は入射(incident)、「ref」は反射(reflected)、「tot」はトータルという意味、、、らしいがかなり分かりにくい。

freq = linspace(f_start,f_stop,201);
port = calcPort(port, Sim_Path, freq);
 
s11 = port{1}.uf.ref./ port{1}.uf.inc;
s21 = port{2}.uf.ref./ port{1}.uf.inc;
ZL = port{1}.uf.tot./port{1}.if.tot;
ZL_a = port{1}.ZL; % analytic waveguide impedance

で、プロットするためのスクリプト

figure
plot(freq*1e-6,20*log10(abs(s11)),'k-','Linewidth',2);
xlim([freq(1) freq(end)]*1e-6);
grid on;
hold on;
plot(freq*1e-6,20*log10(abs(s21)),'r--','Linewidth',2);
l = legend('S_{11}','S_{21}','Location','Best');
set(l,'FontSize',12);
ylabel('S-Parameter (dB)','FontSize',12);
xlabel('frequency (MHz) \rightarrow','FontSize',12);

f:id:neurois:20200705071502p:plain
S-parameters

導波管のインピーダンスを理論値と数値計算で比較。一致しててすごい(すごい)。

figure
plot(freq*1e-6,real(ZL),'Linewidth',2);
hold on;
grid on;
plot(freq*1e-6,imag(ZL),'r--','Linewidth',2);
plot(freq*1e-6,ZL_a,'g-.','Linewidth',2);
ylabel('ZL (\Omega)','FontSize',12);
xlabel('frequency (MHz) \rightarrow','FontSize',12);
xlim([freq(1) freq(end)]*1e-6);
l = legend('\Re(Z_L)','\Im(Z_L)','Z_L analytic','Location','Best');
set(l,'FontSize',12);

f:id:neurois:20200705071504p:plain
Waveguide impedance