ペEの日記

ペ  ゆるふわ電子工作系ブログ。

いまの職場にうんざりです。私の技術力を買ってくれる企業さんいませんか?アナログ・RF回路設計、MMIC・導波管デバイス設計経験あり、C言語/C++/Python/Matlab/Octave経験あり、シミュレータADS/HFSS/LTspice/QUCS/ngspice/openEMS経験あり。ちな、ガチコミュ障×人見知り×陰キャです。合わない上司の下で働かされて鬱の経験もあり。


記事一覧

NgspiceでVerilog-A(アナログハードウェア記述言語)を試してみる

はじめに

Ngspice等のSPICE系のシミュレータでは、デフォルトでトランジスタモデルやダイオードモデルが用意されており、ネットリストにモデルパラメータを指定するだけで使えるようになる。たとえば、MMBT3904というトランジスタを使用したい場合は次のように記述する。

.model MMBT3904 NPN (IS=48.3f NF=1.00 BF=410 VAF=114
+ IKF=0.121 ISE=13.1p NE=2.00 BR=4.00 NR=1.00
+ VAR=24.0 IKR=0.300 RE=2.63 RB=10.5 RC=1.05
+ XTB=1.5 CJE=9.67p VJE=1.10 MJE=0.500 CJC=8.70p VJC=0.300
+ MJC=0.300 TF=440p TR=74.7n EG=1.12 )

Q1 nc nb ne MMBT3904

こういったSPICEモデルは、"Verilog-A"というアナログ回路に特化したハードウェア記述言語で定義されている。Verilog-AのコードをコンパイルしてSPICEに読み込ませることで、ネットリストとしても扱えるようになる。Verilog-Aを自分で書けるようになれば、独自の非線形回路モデルを作ることも可能になり、回路シミュレーションの幅がより一層広がるだろう。

今回は導入として、Verilog-AコンパイラであるOpenVAFを用いて、Verilog-Aで書かれた回路モデルをNgspiceに読み込ませて回路シミュレーションを行うまでの手順を紹介する。OpenVAFもNgspiceもオープンソースのフリーウェアなので気軽に使用することができる。

OpenVAF(Verilog-Aコンパイラ)のインストール

まずはOpenVAFをインストールしよう。下のリンクにインストール手順が書かれているので、この通りに進めればよい。

Installation | OpenVAF

OpenVAF自体のインストールは、ダウンロードリンクから最新バージョンのプリコンパイルされたバイナリファイルを解凍するだけで使えるようになる。

下に書いてある通り、OpenVAFはC言語のリンカーを必要とするらしい。Linuxの場合はldコマンド、Windowsの場合はMSVC(Microsoft Visual C++)を追加でインストールしておく。

下のコマンドでヘルプが表示されればインストール完了!

openvaf --help

使い方

下のリンクに使い方の例がいくつか紹介されているので、まずはこれを試してみよう。

Examples | OpenVAF

Example 1では、HICUMモデルの例が示されている。HICUMモデルとは、ドイツのドレスデン工科大学(TU Dresden)が開発しているトランジスタモデルらしい。「download here」のところから、

  • hicumL2V3p0p0.va(HICUMモデルが書かれたVerilog-Aファイル)
  • model.l(モデルパラメータを定義したネットリストファイル)
  • netlist_osdi.sp(Ngspiceのスクリプトファイル)

の3つをダウンロードする。

下のコマンドを実行して、Verilog-Aファイル(hicumL2V3p0p0.va)をコンパイルすると、OSDIファイル※(hicumL2V3p0p0.osdi)が生成される。

※ OSDI:Open Source Device Interface、デバイスモデル用に標準化されたシミュレータ無依存のインターフェースらしい。

openvaf hicumL2V3p0p0.va

次に、Ngspiceのシミュレーションを実行する。スクリプトファイルの.control~.endcセクション内で、pre_osdi hicumL2V3p0p0.osdiという制御文によってOSDIファイルを読み込んでいる。

ngspice netlist_osdi.sp

下図のようなシミュレーション結果が表示されれば成功。

自分でVerilog-Aを書いてみる(その1)

Verilog-Aの書き方に関しては下のサイトが分かりやすい。

Introduction to Verilog-A — Documentation

私もVerilog-Aは未経験なので、まずは簡単なところから始めてみる。試しに、等価直列抵抗(ESR)と等価直列インダクタンス(ESL)を持つコンデンサモデルを作成してみよう。下にVerilog-AコードとNgspiceスクリプトを示す。

file: my_capacitor.va

`include "constants.h"
`include "discipline.h"

module my_capacitor (t1,t2);

inout           t1,t2;
electrical      t1,t2,n1,n2;

//Branch definitions
branch (t1,n1)  br_cap;
branch (n1,n2)  br_rser;
branch (n2,t2)  br_lser;

//Parameter initialization
parameter real c    = 1;
parameter real rser = 0;
parameter real lser = 0;

analog begin
    I(br_cap)  <+ c*ddt(V(br_cap));
    V(br_rser) <+ rser*I(br_rser);
    V(br_lser) <+ lser*ddt(I(br_lser));
end

endmodule

file: test_my_capacitor.spice

.title my_capacitor.osdi test

v1 n01 0   dc 0 ac 1
n1 n01 0   c_10nF 

.model c_10nF my_capacitor c=10n rser=10m lser=1n

.control
pre_osdi my_capacitor.osdi

ac dec 100 1Meg 1000Meg
let z=abs(v(n01)/i(v1))
settype impedance z

plot xlog ylog z
.endc

.end

下のコマンドでコンパイル、シミュレーションを実行する。

openvaf my_capacitor.va
ngspice test_my_capacitor.spice

実行結果:

キャパシタンス:c=10 nF、等価直列抵抗:rser=10 mΩ、等価直列インダクタンス:lser=1 nHのac解析結果。よさそう!

自分でVerilog-Aを書いてみる(その2)

次は、もう少し複雑にして、セラミックコンデンサのDCバイアス特性を持たせてみよう。印加電圧に対するキャパシタンスを下式で与えるとする。以下、変数は小文字、定数は大文字で表記する。

 \displaystyle
c(v) = \frac{C_{0}}{1+(v/V_{0})^2}

ここで、 C_0はゼロバイアスにおけるキャパシタンス、 V_0はキャパシタンスが半分になる電圧である。セラミックコンデンサに蓄積される電荷量を qとすれば、0~ vまで積分して

 \displaystyle
q = \int_{0}^{v} c(v)\cdot\mathrm{d}v=C_{0}V_{0}\arctan(v/V_{0})

と表せる。キャパシタに流れる電流 iは電荷量 qの時間微分なので、

 \displaystyle
i = \frac{\mathrm{d}q}{\mathrm{d}t}

以上をVerilog-Aに落とし込むと次のようになる。

file: my_capacitor.va

`include "constants.h"
`include "discipline.h"

module my_capacitor (t1,t2);

inout           t1,t2;
electrical      t1,t2,n1,n2;

//Branch definitions
branch (t1,n1)  br_cap;
branch (n1,n2)  br_rser;
branch (n2,t2)  br_lser;

//Parameter initialization
parameter real c0   = 1;
parameter real v0   = 1;
parameter real rser = 0;
parameter real lser = 0;

real q;

analog begin
    //DC bias characteristics of ceramic capacitors
    q = c0*v0*atan(V(br_cap)/v0);
    I(br_cap)  <+ ddt(q);

    V(br_rser) <+ rser*I(br_rser);
    V(br_lser) <+ lser*ddt(I(br_lser));
end

endmodule

file: test_my_capacitor.spice

.title my_capacitor.osdi test

v1 n01 0   dc 0 ac 1
n1 n01 0   c_10nF 

.model c_10nF my_capacitor c0=10n v0=15 rser=10m lser=1n

.control
pre_osdi my_capacitor.osdi

let v=0
let vstop=20
let vstep=10

while v <= vstop
    alter v1 dc v

    $ ac analysis
    ac dec 100 1Meg 1000Meg
    let v=v+vstep
end

* calculate impedance
let z1=abs(1/ac1.i(v1))
let z2=abs(1/ac2.i(v1))
let z3=abs(1/ac3.i(v1))

* color0 i background, color1 is grid & text color, color2..22 are for graph plots
set color0=white color1=gray color2=red color3=blue color4=green color5=black
set wfont='DejaVu Sans Mono'
set wfont_size=12
set xbrushwidth=2

plot z1 z2 z3 xlog ylog
.endc

.end

実行結果:

下図は、DCバイアスを0/10/20 Vと変化させたときのインピーダンス特性の結果を示している。印加電圧が高くなるほど容量が小さくなり、ESLとの自己共振周波数が右にシフトしていることが確認できる。

ちゃんとモデル化できた!やったぜ。

SCPIコマンド構文解析をC言語で実装してみた

はじめに

今回は、計測器に広く使われているSCPIコマンド(スキッピーと読むらしい)の構文解析をC言語で実装したので紹介する。将来的には、これをマイコンに実装してオリジナル測定器をつくりたい。

コマンド仕様

コマンド仕様についてはここ(SCPI Basics)が参考になる。

たとえば、私の所持しているSIGLENT製スペアナ(SSA3021X)で周波数スパンを設定するためのコマンドは

   [:SENSe]:FREQuency:SPAN <freq>

という構文になっている(SSA3000X_ProgrammingGuide.pdf)。

簡単に説明すると、角括弧 [ ] で囲まれた部分は省略可能、大文字部分は必須、小文字部分は省略可能である。キーワードをコロン( : )で区切ることで、階層化されたコマンド体系にすることができる。大文字小文字の区別はなく、FREQuencyはFREQUENCY、frequency、FREQ、freqのどれでも良い。ただし、FREQueなど小文字部分が途切れてしまうのはダメ。

以上を踏まえて、スパンを100 MHzに設定したいときは

   :SENSe:FREQuency:SPAN 100000000

としても良いし、

   :freq:span 100e6

でも良い。

実装

記事の最後にソースコード全体を載せている。注意点として、RAMの少ないマイコンに実装することを目的としているので、malloc()関数等の動的なメモリ確保は使用せず、文字列処理に必要なメモリバッファは静的領域に置いている。また、スレッドセーフではない。

ヘッダーファイル内でコマンドの最大文字数と最大引数を定義。

#define MAXIMUM_SCPI_COMMAND_LENGTH     (128U)
#define MAXIMUM_SCPI_ARGUMENT_COUNT     (8U)

C言語にはクラス(class)がないが、代わりに構造体を用いて実装した。SCPIParser構造体を下のように定義する。

typedef struct _SCPIParser {
    char buf[MAXIMUM_SCPI_COMMAND_LENGTH + 1];
    char *argv[MAXIMUM_SCPI_ARGUMENT_COUNT];
    size_t argc;
} SCPIParser;

メンバー変数のbufはコマンド文字列のバッファ。argcとargvはmain関数のコマンドライン引数と同様で、たとえば":SENSe:FREQuency:SPAN 100000000"の文字列で初期化した場合、argcは2、argv[0]には":SENSe:FREQuency:SPAN"、argv[1]には"100000000"の文字列が格納されるようになっている。

また、SCPIParser構造体オブジェクトを初期化するSCPIParser_initialize()関数、および、SCPIコマンドの構文一致を判定するSCPIParser_match()関数を実装した。どちらも第一引数にSCPIParser構造体のポインタを取る。

void SCPIParser_initialize(SCPIParser *Obj, const char *string);
bool SCPIParser_match(const SCPIParser *Obj, const char *expression);

使い方

以下、使い方の例を示す。

#include "scpiparser.h"

/* コマンドライン */
static char commandLine[MAXIMUM_SCPI_COMMAND_LENGTH + 1] = "freq:span 100e6";

int main()
{
    bool m;
    SCPIParser parser;

    /* SCPIParser構造体をコマンドラインで初期化 */
    SCPIParser_initialize(&parser, commandLine);
    
    /* 構文一致するか判定 */
    m = SCPIParser_match(&parser, "[:SENSe]:FREQuency:SPAN <freq>");
    if (m) { /* 一致   */ }
    else   { /* 不一致 */ }

    return 0;
}

ソースコード

ヘッダーファイル:scpiparser.h

#ifndef _SCPI_PARSER_H
#define _SCPI_PARSER_H

/* Included Files */
#include <stddef.h>
#include <stdbool.h>

/* Provide C++ Compatibility */
#ifdef __cplusplus
extern "C" {
#endif

#define MAXIMUM_SCPI_COMMAND_LENGTH     (128U)
#define MAXIMUM_SCPI_ARGUMENT_COUNT     (8U)
    
    typedef struct _SCPIParser {
        char buf[MAXIMUM_SCPI_COMMAND_LENGTH + 1];
        char *argv[MAXIMUM_SCPI_ARGUMENT_COUNT];
        size_t argc;
    } SCPIParser;
    
    void SCPIParser_initialize(SCPIParser *Obj, const char *string);
    bool SCPIParser_match(const SCPIParser *Obj, const char *expression);
    
    
#ifdef __cplusplus
}
#endif

#endif /* _SCPI_PARSER_H */

ソースファイル:scpiparser.c

#include "scpiparser.h"
#include <string.h>
#include <ctype.h>

static int __SCPIParser_stricmp(const char *a, const char *b)
{
    int d;
    for (;; a++, b++) {
        d = tolower((int)*a)-tolower((int)*b);
        if (d || !*a) { break; }
    }
    return d;
}

static bool __SCPIParser_compare_recursive(char *a, char *b)
{
    char *tok_a, *tok_b, *p;
    char *save_ptr_a = NULL, *save_ptr_b = NULL;
    int d;

    tok_a = strtok_r(a, ":", &save_ptr_a);
    tok_b = strtok_r(b, ":", &save_ptr_b);    

    if (tok_a == NULL && tok_b == NULL) {
        return true;
    }
    else if (tok_a && tok_b) {
        d = __SCPIParser_stricmp(tok_a, tok_b);
        if (d) {
            for (p = tok_b; *p; p++) { if (islower(*p)) { *p = '\0'; } }
            d = __SCPIParser_stricmp(tok_a, tok_b);
        }
        if (!d) {
            return __SCPIParser_compare_recursive(save_ptr_a, save_ptr_b);
        }
    }
    return false;
}

static bool __SCPIParser_compare(const char *a, const char *b)
{
    static char buf_a[MAXIMUM_SCPI_COMMAND_LENGTH + 1];
    static char buf_b[MAXIMUM_SCPI_COMMAND_LENGTH + 1];

    const char *begin = strchr(b, '[');
    const char *end   = strchr(b, ']');
    bool m = false;

    if (begin == NULL && end == NULL) {
        // string `b` does not contain any optional keywords enclosed in square brackets.
        strcpy(buf_a, a);
        strcpy(buf_b, b);
        m = __SCPIParser_compare_recursive(buf_a, buf_b);
    }
    else if (begin < end) {
        // string `b` contains an optional keyword enclosed in square brackets.
        // 1) Compare the full strings.
        char *dst;
        const char *src;
        strcpy(buf_a, a);
        for (dst = buf_b, src = b; *src; src++) {
            if (src != begin && src != end) { *dst++ = *src; }
        }
        *dst = '\0';
        m = __SCPIParser_compare_recursive(buf_a, buf_b);

        // 2) Compare the omitted strings.
        if (!m) {
            strcpy(buf_a, a);
            for (dst = buf_b, src = b; *src; src++) {
                if (src < begin || src > end) { *dst++ = *src; }
            }
            *dst = '\0';
            m = __SCPIParser_compare_recursive(buf_a, buf_b);
        }
    }
    else {
        // string `b` syntax error
        ;
    }
    return m;
}

/**
 * @brief Initializes a SCPIParser object with a SCPI command string.
 *
 * The number of command arguments is set to SCPIParser::argc, and the string
 * for each argument is stored in SCPIParser::argv[index]
 * 
 * Example:
 * @code
 * SCPIParser Obj;
 * SCPIParser_initialize(&Obj, "[:SOURce:]POWer <arg1>");
 * @endcode 
 * 
 * @param Obj: Pointer to a SCPIParser object
 * @param string: SCPI command string
 * @return None
 */
void SCPIParser_initialize(SCPIParser *Obj, const char *string)
{
    const char *delimiter = "\t ";
    char *p;

    strncpy(Obj->buf, string, MAXIMUM_SCPI_COMMAND_LENGTH);
    Obj->buf[MAXIMUM_SCPI_COMMAND_LENGTH] = '\0';
    Obj->argc = 0;
    p = strtok(Obj->buf, delimiter);
    while (p && Obj->argc < MAXIMUM_SCPI_ARGUMENT_COUNT) {
        Obj->argv[Obj->argc++] = p;
        p = strtok(NULL, delimiter);
    }
}

/**
 * @brief Determines whether a SCPIParser object matches the syntax of a given SCPI command.
 * 
 * Example:
 * @code
 * bool m;
 * SCPIParser Obj;
 * SCPIParser_initialize(&Obj, "SOUR:POW 10.0");
 * m = SCPIParser_match(&Obj, "[:SOURce]:POWer <arg1>");
 * @endcode 
 * 
 * @param Obj: Pointer to a SCPIParser object
 * @param string: SCPI command string
 * @return true if the syntax matches, otherwise false
 */
bool SCPIParser_match(const SCPIParser* Obj, const char *expression)
{
    static SCPIParser Obj_exp;
    SCPIParser_initialize(&Obj_exp, expression);
    if (Obj->argc == Obj_exp.argc && Obj->argc) {
        return __SCPIParser_compare(Obj->argv[0], Obj_exp.argv[0]);
    }
    return false;
}

PIC32MXマイコンのUARTブートローダーを試してみた

1. はじめに

今回はPIC32MXマイコンのブートローダー機能を試してみたので、覚え書として記事に残しておく。ブートローダーを実装しておくと、PICkitやSnap等の専用ライターを用いずに、マイコンへの書き込みができるようになる。物を配布したりする際、エンドユーザー自身がファームウェアアップデートできるようにしたいときに便利な機能だ。

UARTやUSB、SDカード等様々なインターフェースから書き込みできるそうだが、今回はUARTブートローダーを試してみた。

USB-シリアル変換のFT232Rと組み合わせることで、普段は仮想COMポートデバイスとして動作するが、特定のコマンドを送るとブートローダーに切り替わりファームウェアアップデートが実現できるようにしたい。

2. 読むべきドキュメント

Cresent様の記事(↓)がめちゃめちゃ参考になった。PIC32ブートローダーの開発の流れや注意点が分かりやすく解説されている。
PIC32MZEFシリーズのブートローダ機能: Crescent

MPLAB Harmony v3のUARTブートローダーに関する公式ドキュメント。
1 MPLAB® Harmony Bootloader Library

MPLAB Harmony v3のGithubリポジトリ。サンプルプログラムがあるので参考にした。
GitHub - Microchip-MPLAB-Harmony/bootloader_apps_uart: Harmony 3 Bootloader UART examples

3. 開発の流れ

まず、ブートローダー用のプロジェクトと、アプリケーション用のプロジェクトは分けて作成する。ブートローダープログラムはPICkitやSnap等の専用ライターを用いてPIC32に直接書き込む。一方、アプリケーションプログラムはビルドしてバイナリファイルを生成した後、PCからPythonスクリプト"btl_host.py"を実行することでファームウェアが書き込まれるという流れとなる。

ブートローダーとアプリケーションは、それぞれ別々のフラッシュメモリに書き込むため、リンカースクリプトの一部変更が必要。また、PICにはコンフィグメモリというクロックやウォッチドッグタイマーの設定を行う特殊なメモリがあり、これをブートローダーとアプリケーションで共通にしておく必要がある。

PICのリセット後、最初にブートローダーが動作し、特定のメモリ番地の値によってファームウェアアップデートをするか、アプリケーションを実行するか分岐する。アプリケーション側からその特定のメモリ番地に決められたビットパターンを書き込みソフトウェアリセットをかけることで、ファームウェアアップデート処理に移ることができる。

4. ブートローダープロジェクトの作成

4.1. MCCの設定

ブートローダー用にプロジェクトを新規作成する。そしたらまず、MCC Content Managerを開いてbootloaderパッケージの最新バージョンをダウンロードしておく。

MPLAB Code Configurator(MCC)を開いて、左側の"Device Resources"の中から"UART Bootloader"をダブルクリックすると、下図のように"UART Bootloader"と"NVM"と"Core Timer"が自動で接続される。次に、"Device Resources"→"Peripherals"の中から"UART1"もダブルクリックして追加する。"UART1"と"UART Bootloader"を線でつなぐ。

"UART Bootloader"の設定は次のようにする。"Enable Bootlader Trigger From Firmware"にチェックを入れ、"Number Of Bytes To Reserve From Start Of RAM"を16とする。これにより、ブートローダーのトリガー判定に使うRAMが16バイト分確保されることになる。

"UART1"の設定は次のようにする。ボーレートは115,200 bps、8ビットパリティなし、ブロッキングモード(割り込みを使わないモード)。

さらに、ピンの設定(U1TX, U1RX)とクロックの設定を忘れずにする。以上の設定が終わったら、左上の"Generate"ボタンを押してコードを自動生成する。

コード生成後のプロジェクト構成は次のようになる。ブートローダー側のリンカースクリプト"btl.ld"はこのままでOK。

4.2. main.cのコーディング

生成されたmain.cに次のコードを書き足す。BTL_TRIGGER_PATTERNはトリガー条件となるビットパターン、BTL_TRIGGER_RAM_STARTはビットパターンが書き込まれる先頭番地である。bootloader_Trigger()関数はビットパターンが一致すればtrue、そうでないならfalseを返すようにする。

#define BTL_TRIGGER_PATTERN     (0x5048434DUL)

static volatile uint32_t *ramStart = (uint32_t *)BTL_TRIGGER_RAM_START;

bool bootloader_Trigger(void)
{
    uint32_t i;

    // Cheap delay. This should give at leat 1 ms delay.
    for (i = 0; i < 8000; i++)
    {
        asm("nop");
    }
    
    /* Check for Bootloader Trigger Pattern in first 16 Bytes of RAM to enter
     * Bootloader.
     */
    if (BTL_TRIGGER_PATTERN == ramStart[0] && BTL_TRIGGER_PATTERN == ramStart[1] &&
        BTL_TRIGGER_PATTERN == ramStart[2] && BTL_TRIGGER_PATTERN == ramStart[3])
    {
        ramStart[0] = 0;
        LATFbits.LATF0 = 1; // LED5
        
        return true;
    }
    
    return false;
}

以上でブートローダー側は完了。

ビルドして、PICkitやSnap等のライターでPIC32マイコンに直接書き込んでおこう。

5. アプリケーションプロジェクトの作成

5.1. MCCの設定

アプリケーション用にプロジェクトを新規作成する。MPLAB Code Configurator(MCC)を使ってクロックやピンの設定、各種ペリフェラルの設定をする。ここでは、クロックの設定はブートローダーと同じにする(コンフィグメモリを共通にするため)。

それ以外は、実装したいアプリケーションに合わせて好きなように設定してよい。今回私はUART1とFreeRTOSを乗っけることにした。MCCでの設定が完了したら、左上の"Generate"ボタンを押してコードを自動生成する。

5.2. リンカースクリプトの設定

次にリンカースクリプトの作成手順を説明する。

コード自動生成後、"p32MX340F512.ld"というリンカーファイルが生成されているが、これはブートローダーを使わない場合のリンカースクリプトなので、内容を書き換える必要がある。

いったん、"p32MX340F512.ld"のファイルをコピーして、ファイル名を"btl_app.ld"としよう。"Linker Files"のところで右クリック→"Add Existing Item..."から"btl_app.ld"を追加する。さらに、元々の"p32MX340F512.ld"は右クリック→"Remove From Project"でプロジェクトから除外する。

以下、"btl_app.ld"の変更内容を説明する。理解に苦しむが、Microchipのサンプルプログラムと照らし合わせながら変更していけばOK。

(補足:コードブロック内、行先頭の「-」は削除、「+」は追記の意味)

  • 下の部分を削除
- OPTIONAL("libmchp_peripheral.a")
- OPTIONAL("libmchp_peripheral_32MX340F512H.a")
  • _ebase_addressã‚’0x9FC01000から0x9D001000に変更
- PROVIDE(_ebase_address = 0x9FC01000);
+ PROVIDE(_ebase_address = 0x9D001000);
  • メモリアドレスの変更
- _RESET_ADDR                    = 0xBFC00000;
- _BEV_EXCPT_ADDR                = 0xBFC00380;
- _DBG_EXCPT_ADDR                = 0xBFC00480;
- _DBG_CODE_ADDR                 = 0xBFC02000;
- _DBG_CODE_SIZE                 = 0xFF0;
- _GEN_EXCPT_ADDR                = _ebase_address + 0x180;

+ _RESET_ADDR                    = 0xBD000000;
+ _GEN_EXCPT_ADDR                = _ebase_address + 0x180;
  • メモリ領域の変更
MEMORY
{
-   kseg0_program_mem     (rx)  : ORIGIN = 0x9D000000, LENGTH = 0x80000
-   kseg0_boot_mem              : ORIGIN = 0x9FC00490, LENGTH = 0x970
-   exception_mem               : ORIGIN = 0x9FC01000, LENGTH = 0x1000
-   kseg1_boot_mem              : ORIGIN = 0xBFC00000, LENGTH = 0x490
-   debug_exec_mem              : ORIGIN = 0xBFC02000, LENGTH = 0xFF0
-   config3                     : ORIGIN = 0xBFC02FF0, LENGTH = 0x4
-   config2                     : ORIGIN = 0xBFC02FF4, LENGTH = 0x4
-   config1                     : ORIGIN = 0xBFC02FF8, LENGTH = 0x4
-   config0                     : ORIGIN = 0xBFC02FFC, LENGTH = 0x4
-   kseg1_data_mem       (w!x)  : ORIGIN = 0xA0000000, LENGTH = 0x8000

+   kseg0_program_mem     (rx)  : ORIGIN = 0x9D001000, LENGTH = 0x80000 - 0x1000
+   kseg1_boot_mem              : ORIGIN = 0xBD000000, LENGTH = 0x490

+   /* Bootloader Trigger Pattern 16 Bytes */
+   kseg1_data_mem       (w!x)  : ORIGIN = 0xA0000000 + 16, LENGTH = 0x8000 - 16

  sfrs                        : ORIGIN = 0xBF800000, LENGTH = 0x100000
  configsfrs                  : ORIGIN = 0xBFC02FF0, LENGTH = 0x10
}
  • config_~~セクションを削除
- SECTIONS
- {
-   .config_BFC02FF0 : {
-     KEEP(*(.config_BFC02FF0))
-   ...
-   .config_BFC02FFC : {
-     KEEP(*(.config_BFC02FFC))
-   } > config0
- }
  • bev_excpt, dbg_excpt, dbg_codeセクションを削除
SECTIONS
{
  /* Boot Sections */
  .reset _RESET_ADDR :
  {
    KEEP(*(.reset))
    KEEP(*(.reset.startup))
  } > kseg1_boot_mem
-   .bev_excpt _BEV_EXCPT_ADDR :
-   ...
-   .dbg_excpt _DBG_EXCPT_ADDR (NOLOAD) :
-   ...
-   .dbg_code _DBG_CODE_ADDR (NOLOAD) :
-   {
-     . += (DEFINED (_DEBUGGER) ? _DBG_CODE_SIZE : 0x0);
-   } > debug_exec_mem
  • 割り込みベクターの領域をexception_memからkseg0_program_memに変更(.vector_0~.vector_63まで全部)
  .vector_0 _ebase_address + 0x200 + ((_vector_spacing << 5) * 0) :
  ...
-   } > exception_mem
+   } > kseg0_program_mem
  • デバッグ例外処理とコンフィグ処理を除外する記述を追加
  ...
  /DISCARD/ : { *(.note.GNU-stack) }
  /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) *(.discard) }
+   /DISCARD/ : { *(._debug_exception) }
+   /DISCARD/ : { *(.config_*) }
}

見慣れない用語としてKSEG0やKSEG1というのがあるが、これはMIPSコアのメモリ領域の呼び方で、KSEG0はキャッシュ可能、KSEG1はキャッシュ不可能な領域となっているらしい。PIC32のデータシートにメモリアドレスマップが載っているので、それと見比べながら理解しておきたい。

5.3. コーディング

アプリケーション側からファームウェアアップデートする処理を実装する。以下、実装例を示す。

APP_SystemReset()はソフトウェアからリセットをかける関数。PIC32のリセット手順については、Microchipのアプリケーションノート:DS60001118(https://ww1.microchip.com/downloads/en/devicedoc/60001118h.pdf)の7.3.4に記載があるので参考にする。

APP_TriggerBootloader()はブートローダーをトリガーする関数。ramStart[0]~[3]にビットパターン(BTL_TRIGGER_PATTERN)を書き込んだ後リセットする。

#include <pic32m_builtins.h>
#include <sys/kmem.h>

/* Bootloader */
#define BTL_TRIGGER_RAM_START   KVA0_TO_KVA1(0x80000000)
#define BTL_TRIGGER_PATTERN     (0x5048434DUL)
#define DCACHE_CLEAN_BY_ADDR(start, sz)

static uint32_t *ramStart = (uint32_t *)BTL_TRIGGER_RAM_START;

void APP_SystemReset()
{
    /* Wait until all DMA transfers are complete. */
    // TODO---
    
    /* Stop the real time scheduler. */
    vTaskEndScheduler();
    
    /* Disable all interrupts. */
    __builtin_disable_interrupts();
    
    /* Software Reset Command Sequence */
    /* See application note DS60001118, section 7.3.4. */
    
    /* Perform a system unlock sequence */
    SYSKEY = 0x00000000; //write invalid key to force lock
    SYSKEY = 0xAA996655; //write key1 to SYSKEY
    SYSKEY = 0x556699AA; //write key2 to SYSKEY
    // OSCCON is now unlocked
    /* Set SWRST bit to arm reset */
    RSWRSTSET = 1;
    
    /* Read RSWRST register to trigger reset */
    (void)RSWRST;
    
    /* Prevent any unwanted code execution until reset occurs */
    while (1);
}

void APP_TriggerBootloader()
{
    ramStart[0] = BTL_TRIGGER_PATTERN;
    ramStart[1] = BTL_TRIGGER_PATTERN;
    ramStart[2] = BTL_TRIGGER_PATTERN;
    ramStart[3] = BTL_TRIGGER_PATTERN;
    
    DCACHE_CLEAN_BY_ADDR(ramStart, 16);
    
    APP_SystemReset();
}

5.4. ビルド

HEXファイル(.hex)からバイナリファイル(.bin)を生成するための設定をする。プロジェクトのプロパティを開き、"Building"→"Execute this line after build"にチェックを入れ、下のコマンドを入力する。

${MP_CC_DIR}/xc32-objcopy -I ihex -O binary ${DISTDIR}/${PROJECTNAME}.${IMAGE_TYPE}.hex ${DISTDIR}/${PROJECTNAME}.${IMAGE_TYPE}.bin

ビルドは"Build Main Project"のほうを実行する(プロダクト版)。

BUILD SUCCESSFULL (total time: 1s)

というメッセージが表示されれば成功。

<プロジェクト名>.X/dist/default/production/<プロジェクト名>.X.production.bin
のバイナリファイルが生成されているはず。

6. ソースコード

ここまでのソースコードを参考までに共有する。

ブートローダープロジェクト
chipKIT_uart_bootloader.zip - Google ドライブ

アプリケーションプロジェクト
chipKIT_uart_app.zip - Google ドライブ

7. ハードウェア

ハードウェアは、最近マルツで買ったchipKIT uC32評価ボードを使用する。PIC32MX340F512Hが搭載されている。

一つつまづいたのが、Arduino IDEに対応するためなのか、FT232RQの#DTR(Data Terminal Ready)信号が0.1 uFを通してPIC32のリセットに繋がっていた。これだとPCから仮想COMポートを開いた瞬間にハードウェアリセットがかかってしまい、ファームウェアの書き込みに失敗するという現象が起こった。ジャンパピンJP1の裏側をカッターでパターンカットすることで解決した。

8. ファームウェアの書き込み

ここ(bootloader/tools at master · Microchip-MPLAB-Harmony/bootloader · GitHub)からPythonスクリプト"btl_host.py"をダウンロードする。PySerial(Pythonでシリアル通信を行うためのパッケージ)がインストールされていない場合はpip install pyserialでインストールしておこう。

コマンドプロンプトを開いて、下のコマンドを実行してバイナリファイル(chipKIT_uart_app.X.production.bin)を書き込んでみる。

python btl_host.py -v -i COM5 -d pic32mx -a 0x9D000000 -p 4096 -f chipKIT_uart_app.X.production.bin

次のようなメッセージが表示されれば、ブートローダーから書き込み成功!!!

書き込み後アプリケーションプログラムが実行状態となり、PuTTYからターミナルを開いて*IDN?コマンドを送るとDIGILENT,CHIPKIT-UC32,001,1.0.0,rev0という文字列が返ってくるようになっている。再度ファームウェアを書き込みたい場合は、:SYSTem:FIRMware:UPDateコマンドを送ることでファームウェアアップデート待機状態になる。

もう一度Pythonコマンドを実行して、同様にファームウェアが書き換えられることを確認できた。

やったぜ。

PIC32MXマイコン+FreeRTOSでLEDチカチカ

1. はじめに

最近、久しぶりにPICマイコンで遊んでみているので、LEDチカチカするまでの手順を覚え書として残しておこうと思う。ひたすらレジスタを叩いていた昔と比べると、ボタンポチポチでコード生成してくれるツールやライブラリが充実していて、かなり開発しやすくなっていると感じる。

2. ハードウェア

Digilent製のchipKIT uC32というPIC32MX340F512Hを搭載した評価ボードをマルツで買ったので、これを使う。見た目はArduinoだがArduino IDEは使用せず、Microchip Technology社の標準の開発環境であるMPLAB X IDEを用いて開発することとする。

赤色でかっこいい。

chipKIT uC32評価ボード

ここ(chipKIT® PIC32® Development Platform)にchipKIT uC32ボードの詳細な情報が載っており、右のほうに回路図も公開されている。PIC32のGPIOから制御できるLEDは2つで、RG6ピンとRF0ピンに接続されている。また、クロック源として8.000 MHzの水晶振動子が接続されている(内蔵PLLでクロックは最大80 MHzまで設定可能)。

プログラムの書き込みは純正のインサーキットデバッガ・プログラマMPLAB Snapを使用する。PICkit 5との違いはHigh-Voltage Programmingが必要な古いPICマイコンに対応していない点、ライターからの電源供給ができない点くらいで、他に大きな違いはない。(MPLAB Snapは昔秋月で1,700円くらいで買った記憶があるが、今見たら7,700円って... しかも在庫切れ。PICkit 5は17,800円もするし、ICD 5は69,800円、値上がりしすぎじゃないですか...)

MPLAB Snapインサーキットデバッガ・プログラマ

MPLAB Snapのピンアサインを下図に示す。MPLAB Snapの基板に三角マーク"▼"がついているほうが1番ピンである。chipKIT uC32評価ボードにも書き込み用のスルーホール(JP3)があるので、ここにMPLAB Snapを接続する。

MPLAB Snapのピンアサイン(データシートより抜粋)

3. ソフトウェア

3.1. 開発環境のセットアップ

まずは、Microchip社の標準の統合開発環境MPLAB X IDEをインストールしよう。MPLAB X IDEのインストール完了後、C/C++コンパイラであるXC8, XC16, XC32もインストールするよう勧めてくるので、その通りにXC32も入れると良い。

MPLAB X IDEの起動画面

新しいプロジェクトを作成する。右上のメニューから、"File"→"New Project"を選択。

ダイアログが表示されるので、"Categories: Microchip Embedded"、"Projects: Application Project(s)"が選択された状態で"Next"をクリック。

"Family: 32-bit MCUs (PIC32)"、"Device: PIC32MX340F512H"を選択。"Tool"は後からでも設定できるが、PCがMPLAB Snapを認識している場合はシリアル番号が表示されるのでそれを選択して"Next"をクリック。

"Compiler Toolchains: XC32"を選択して"Next"をクリック。

"Project Name"と"Project Location"を入力する。私は、それぞれ「chipKIT_blinking_leds」、「~/MPLABXProjects/chipKIT_blinking_leds」としてみた。"Encoding"はUTF-8が無難。はじめてMPLAB X IDEでプロジェクト作成する場合は、"Open MCC on Finish"のチェックボックスは外しておいたほうが良い。これについては後述する。

3.2. MCC Content Managerのセットアップ

MPLAB X IDEには、クロック、GPIOピン、ペリフェラル等の設定をボタンぽちぽちだけでできてしまうMCC(MPLAB Code Configuratorの略)という便利機能がある。ただ、MPLAB X IDEインストール直後はパッケージが空の状態なので、欲しい機能の最新バージョンをリポジトリから落としてくる必要がある。ここでは、その操作方法を説明する。

まず、"CM"アイコンをクリックしてMCC Content Managerを起動する(下図)。

MCC Content Managerの起動

"Harmony v3 - Chip Support Package"カテゴリの"csp"パッケージは必須なので、これの最新バージョンを選択して、右上のApplyボタンをクリックするとダウンロードが開始される。また、今回はFreeRTOSでLEDチカチカすることが目的なので、"FreeRTOS-Kernel"パッケージもダウンロードしておく。

MCCパッケージのダウンロード

メニューバーの"Tools"→"Options"→"Plugins"で、ダウンロード元のリポジトリとダウンロード先の場所を確認できる。デフォルトはそれぞれ、Github(Microchip MPLAB Harmony · GitHub)、「ユーザーディレクトリ/.mcc/HarmonyContent」となっているはず。もし通信エラーとかでダウンロードが進まないときは、いったんHarmonyContentフォルダーを空にして再度試すと上手くいくかもしれない。

3.3. MPLAB Code Configurator(MCC)でコード生成

次に、MPLAB Code Configuratorを使ってコードを生成する手順について説明する。"MCC"アイコンをクリックして起動する。MCC Content Managerはもう使わないので閉じてよい。

MPLAB Code Configuratorの起動

左側の"Device Resources"のリストから、"Third Party Libraries"→"RTOS"→"FreeRTOS"をダブルクリックすればFreeRTOSが追加される。右側の"Configuration Options"の中で詳細な設定をしていく。今回はほぼほぼデフォルトのままで良いが、PIC32MX340F512HのRAMが32 KBしかないので"Total heap size"を28,000(デフォルト値)から16,000に減らした。

FreeRTOSの設定

次に、クロックの設定をする。中央の"Plugins"ドロップダウンから"Clock Configuration"を選択すると"Clock Diagram"タブが開く(下図)。今回はデフォルトの80 MHzとした。レジスタの設定値も可視化されていて、とても分かりやすい。

クロックの設定(Clock Diagram)

続いて、GPIOピンの設定も行う。"Plugins"ドロップダウンから"Pin Configuration"を選択すると、"Pin Diagram"、"Pin Table"、"Pin Settings"の3つのタブが開く。今回はRG6ピンとRF0ピンがLEDに割り当てているので、これらのピンの機能を"GPIO"、入出力方向を"Out"に設定する。

GPIOピンの設定(Pin Diagram)

以上の設定が終わったら、左上にある"Generate"ボタンを押せばコードが自動生成される。コード生成後のフォルダー構成を下図に示す。

コード生成後のフォルダー構成

3.4. FreeRTOSのタスクを作成

書き換えが必要なのはmain.cだけ。ここに、LEDをチカチカさせるタスク関数を記述し、xTaskCreate()関数でタスクをスケジューラに登録、vTaskStartScheduler()でスケジューラを開始させれば良い。以下にmain.cのコードを示す。

LED4(RG6ピン)は0.1秒周期でチカチカさせ、LED5(RF0)は1秒周期でチカチカさせるようにしてみた。

すごい簡単。

// *****************************************************************************
// *****************************************************************************
// Section: Included Files
// *****************************************************************************
// *****************************************************************************

#include <stddef.h>                     // Defines NULL
#include <stdbool.h>                    // Defines true
#include <stdlib.h>                     // Defines EXIT_FAILURE
#include "definitions.h"                // SYS function prototypes

// FreeRTOSのインクルード
#include "FreeRTOS.h"
#include "task.h"

// LED4(RG6ピン)を0.1秒周期でチカチカするタスク
void vTaskBlinkLED4(void *pvParameters)
{
    while (true) {
        LATGbits.LATG6 ^= 0b1;
        vTaskDelay(50 / portTICK_PERIOD_MS);
    }
}

// LED5(RF0ピン)を1秒周期でチカチカするタスク
void vTaskBlinkLED5(void *pvParameters)
{
    while (true) {
        LATFbits.LATF0 ^= 0b1;
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
}

// *****************************************************************************
// *****************************************************************************
// Section: Main Entry Point
// *****************************************************************************
// *****************************************************************************

int main ( void )
{
    /* Initialize all modules */
    SYS_Initialize ( NULL );
    
    // LEDチカチカするタスクを作成
    xTaskCreate(vTaskBlinkLED4, "LED4 blinking", 100, NULL, 1, NULL);
    xTaskCreate(vTaskBlinkLED5, "LED5 blinking", 100, NULL, 1, NULL);
    
    // RTOSのスケジューラを開始
    vTaskStartScheduler();

    while ( true )
    {
        /* Maintain state machines of all polled MPLAB Harmony modules. */
        SYS_Tasks ( );
    }

    /* Execution should not come here during normal operation */

    return ( EXIT_FAILURE );
}

4. 結果

ビルドして

BUILD SUCCESSFULL (total time: 1s)

というメッセージが表示されれば成功。

ビルド成功!

デバイスに書き込んでみる。

おっ、おおお!

左のLEDは0.1秒周期、右のLEDは1秒周期でチカチカしている。やったぜ。

LEDチカチカ成功!!!

ショットキーダイオードでコムジェネレータ作ってみた

はじめに

前の記事:

で、ショットキーバリアダイオードを用いた半波整流回路により、源振を25 MHzとして帯域~2 GHzくらいまでの高調波を発生できることを確認した。しかしながら、源振に使用していたファンクションジェネレータに含まれるスプリアスが邪魔をして、あまりきれいなスペクトルとは言えなかった。そこで、スプリアスのない水晶発振器に置き換えることにした。

回路

回路図を下図に示す。電源は9 V電池からレギュレータで5 Vに落としている。電源電圧がふらつくとコムの出力レベルが変動してしまうため、ここはきちんと作っておきたい。ちなみに、筆者はAmazonで買った充電式の9 V電池を使用しており、内部的には4.2Vのリチウムイオン電池が2個直列になっているようで正確には8.4 Vとなる。

源振の水晶発振器はコルピッツ発振回路を使用する。MMBT3904のエミッタから発振器出力を取りだして、さらにエミッタ接地増幅回路で増幅してドライブレベルを確保する。電圧波形の傾きがコムの帯域に大きく影響するため、できるだけ高い振幅でショットキーを叩きたい。そこで、巻き数比1:2(インピーダンス比1:4)のトランスで昇圧することとした。トランスは#61材のフェライトコア(FT-23-61)に7回トリファイラ巻きして作る。

コムを発生させる部分はショットキーダイオード1SS154を用いて半波整流した後、LCのハイパスフィルタ(微分回路)で低周波成分をばっさりカットする。ここは前回の記事で説明した通り。

しかし、これだけだと出力ポートのリターンロスがかなり悪いので、バッファアンプとしてBGA420(帯域~3 GHz)を後段に入れることにした。また、右下がりな周波数特性をできるだけフラットに近づけたいので、イコライザ回路を出力に付けている。100/75/100 Ωのπ型アッテネータを基本に、10 pFでブリッジすることで右上がりな特性を上乗せする。

コムジェネレータの回路図

製作

電源回路・水晶発振回路・ドライバアンプはシールドメッシュタイプのユニバーサル基板上に実装した。コム生成回路とバッファアンプ(BGA420)は、1 mm厚の銅張基板をスジボリカーバイトでパターニングして実装した。ケースはタカチのプラスチックケースSW-120S(W:60×D:120×H:24 mm)が余っていたのでこれに入れてみた(本当はシールドケースに入れたほうが良いんだろうけど...)。

実装後の写真

正面には出力のSMAコネクタ、背面には電源スイッチ。頑張ってスライドスイッチに合う四角い穴をあけてみたら、すごくいいかんじ!

正面(出力ポート側)

背面(電源スイッチ側)

評価結果

下図にスペアナの測定画面を示す。スイープの範囲はDC~2.1 GHzまでとしている。一応2.1 GHzまで高調波が出ていることが確認できる。

スペクトラム, DC~2.1 GHz, RBW: 1 kHz, VBW: 1 kHz

次に、レベルの安定性について評価してみた。スイッチを入れてから30分くらい暖機運転したのち、さらに30分間くらいかけて20回繰り返し測定を行った。手動でちまちまポテチ食べながらスペアナのスイープボタン押してデータ保存して、、、の繰り返しで地味な作業だった。

下図にピークレベルの最小値・最大値をエラーバーに表示したグラフを示す。落ち込んでいるピークは変動が激しく、また1 GHz以上は全体的に変動が大きいように感じられる。逆に言えば、1 GHz以下で落ち込んでいる点を除けば、1 dB未満の変動で安定しているピークも多い。

1 GHz以下で使用するのが実用的な感じ。

安定性の評価(エラーバーは20回繰り返し測定の最小値・最大値)

SMAコネクタについてちょっと真面目に考えてみた話

はじめに

今回は、SMAコネクタについて少し真面目に考えてみたので記事にまとめる。SMAコネクタは同軸コネクタの一つで最大18 GHzまで取り扱うことができる。下図のように取付方法による違いでいくつか種類がある。

プリント基板に実装する場合、エッジマウントとスルーホールマウントが候補となるが、エッジマウント方式の方がマイクロストリップ線路→同軸線路の変換がスムーズに行われるため高周波特性が良い。一方、スルーホールマウント方式は寄生成分の影響で周波数特性が悪そうという想像はできるが、実際どれくらいの周波数まで使えるのかよく分からない。ただ、基板に垂直に出したいときに便利だし、他のスルーホール部品と同様にはんだ付けできるという利点もある。

そこで、今回はスルーホールマウント方式でどれだけ高周波信号を通せるのか調べてみることにした。

SMAコネクタの種類

下図に、スルーホールマウントのSMAコネクタを基板に実装した際の模式図を示す。まず、注意しておきたいのは、信号ピンが裏面に突き出ているとその部分がモノポールアンテナとなって放射してしまうので、できるだけ面一になるようにカットしてからはんだ付けするのが良さそうである。

ここで、周波数特性を悪化させそうな寄生成分について考えてみよう。まず、GNDピンがコネクタ中心から離れているため、この経路に寄生インダクタンスが生じると予想できる。次に、基板パターンとコネクタ下部が接近する部分に寄生容量が付くと思われる。また、信号ピンのスルーホールと周囲のGNDパターンとの間にも寄生容量が生じると考えられる。

PCBマウント方式のSMAコネクタ

openEMSによる電磁界シミュレーション

以上の予想をした上で、FDTD法の電磁界シミュレータであるopenEMSを用いて解析を行った。openEMSのスクリプトは記事の最後に載せる。

下図に、openEMSの電磁界解析3Dモデルを示す。ポート1をマイクロストリップポート、ポート2を同軸ポートに設定した。基板は1mm厚のFR4基板を想定しており、誘電率は以前の記事(Fusion PCBのFR-4の誘電率を測ってみた - ペEの日記)で調べたεr=4.95という値を用いている。導体は完全導体(PEC: perfect electric conductor)、同軸コネクタ内の樹脂はテフロン(PTFE)で誘電率はεr=2.0とした。

電磁界解析3Dモデル

以下、解析結果を示す。下図の反射特性(S11)に注目すると、3 GHzではリターンロス15 dBを割っており、6 GHzではリターンロス10 dB程度。予想通り、やはり高周波特性はあまり良くない結果となった。

解析結果#1:反射特性(S11)と通過特性(S21)

下図に、S11のスミスチャートを示す。低周波は50 Ω(スミスチャートの真ん中)からスタートして、そこから容量性(スミスチャートの下側、虚部が負)に振れていることが読み取れる。つまり、上で予想した寄生成分のうち、寄生インダクタンスよりも寄生容量のほうが支配的だということが分かった。

解析結果#1:スミスチャート(S11)

yz平面(x=0)の断面における電界強度分布を示す。基板パターンとコネクタ下部が接近する部分と、信号ピンのスルーホール~GNDパターン間に寄生容量が生じ、電界が集中しているのが確認できる。

(追記:励振ポートから天井の吸収境界に漏れ出てしまっているので、もっと天井を高くしたほうが良かったかも)

yz平面(x=0)の電界強度分布(入力:3 GHz CW)

アニメーション

インピーダンスマッチングの改善

マッチングパターンを追加してインピーダンスマッチングの改善を試みる。下図のように、SMAコネクタから引き出すマイクロストリップの幅を1.8 mm→0.9 mmにしてみる。幅0.9 mmで特性インピーダンスはZ₀≃70 Ωとなる。容量成分が付いているのだから、逆に誘導性を追加して打ち消してやればマッチング改善できるだろうという考え方である。

電磁界解析3Dモデル(インピーダンスマッチング)

マッチングパターンの詳細を下図に示す。

マッチングパターン

以下、解析結果を示す。反射特性(S11)に注目すると、3 GHzでのリターンロスは13 dB→21 dBまで改善した。5 GHzでもリターンロス15 dB以上取れている。これなら、私が趣味で使う周波数範囲では十分である。やったぜ。

解析結果#2:反射特性(S11)と通過特性(S21)

解析結果#2:スミスチャート(S11)

メーカーのテストデータは...

秋月電子に売られているCOSMTEC製のライトアングルSMAコネクタ(S-037-TGG)のテストデータを見てみると、こんな↓測定方法をしている。PCBマウントなのに、コネクタ同士直付けである。たしかに、こうすれば帯域6 GHzでVSWR<1.3(リターンロス>17.7 dB)という仕様を満たせるかもしれないが、ちょっとズルい気がする。

S-037-TGGのデータシートより抜粋

openEMS/GNU Octaveスクリプト

以下、今回用いたOctaveスクリプトを載せる。なお、スクリプト内で使用しているGradedMesh()関数については前の記事(openEMSのメッシュを切る関数自作した - ペEの日記)で説明しているので参照されたい。

file: coaxial_to_microstrip_transition.m

close all
clear
clc

physical_constants;
unit = 1e-3; % drawing unit is mm.

%% Design parameters %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% coaxial connector
r_inner = 0.635;          % inner coaxial radius
r_outer = 2.05;           % outer coaxial radius
r_outer_shell = 3.5;      % outer shell coaxial radius
l_gap = 0.6;              % distance from pcb top to coax bottom
l_coax = 7;               % coaxial length

% pcb
r_through_hole = 0.75;    % through-hole radius
r_th_pad = 1;             % through-hole pad radius
l_pitch = 5.08;           % ground pin pitch
l_clearance = 0.5;        % clearance between metal patterns
l_margin = 1;             % margin for something
w_microstrip = 0.9;       % microstrip line width
l_microstrip = 5;         % microstrip line length
w_microstrip_feed = 1.8;  % microstrip feed line width
l_substrate = 30;         % substrate length
t_substrate = 1;          % substrate thickness

% material properties
epsilon_PTFE = 2.0; % permittivity of PTFE
epsilon_FR4 = 4.95; % permittivity of FR4

% excitation frequency
f0 = 3e9; % center
fc = 3e9; % 20-dB cutoff

%% Setup FDTD %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Gaussian excitation
FDTD = InitFDTD('EndCriteria', 1e-5, 'NrTS', 30000);
FDTD = SetGaussExcite(FDTD, f0, fc);

% Sinus excitation
%FDTD = InitFDTD('EndCriteria', 1e-5, 'NrTS', 10000, 'OverSampling', 50);
%FDTD = SetSinusExcite(FDTD, f0);

% boundary condition
BC = { 'MUR', 'MUR', 'PML_8', 'MUR', 'MUR', 'PML_8' };
FDTD = SetBoundaryCond(FDTD, BC);

%% Setup CSX %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
CSX = InitCSX();
% FR4
CSX = AddMaterial(CSX, 'FR4');
CSX = SetMaterialProperty(CSX, 'FR4', 'Epsilon', epsilon_FR4);
% PTFE
CSX = AddMaterial(CSX, 'PTFE');
CSX = SetMaterialProperty(CSX, 'PTFE', 'Epsilon', epsilon_PTFE);
% metal (PEC)
CSX = AddMetal(CSX, 'PEC');

%% Setup CSX geometry %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% substrate
start = [-l_substrate/2 -l_substrate/2 0];
stop  = [l_substrate/2 l_substrate/2 -t_substrate];
CSX = AddBox(CSX, 'FR4', 1, start, stop);

% microstrip line
start = [-w_microstrip/2 -l_microstrip 0];
stop  = [w_microstrip/2 0 0.035];
CSX = AddBox(CSX, 'PEC', 999, start, stop);

% connector pins
xy = (l_pitch/2)*[0,0;1,1;1,-1;-1,1;-1,-1];
for n = 1:5
    CSX = AddCylinder(CSX, 'PEC', 999, [xy(n,:) 0], [xy(n,:) l_gap], r_inner);
    % through holes and pads
    start = [l_pitch/2 l_pitch/2 0];
    stop  = [l_pitch/2 l_pitch/2 -t_substrate];
    CSX = AddCylinder(CSX, 'PEC', 999,...
        [xy(n,:) 0], [xy(n,:) -t_substrate], r_through_hole);
    CSX = AddCylinder(CSX, 'PEC', 999, [xy(n,:) 0], [xy(n,:) 0.035], r_th_pad);
    CSX = AddCylinder(CSX, 'PEC', 999,...
        [xy(n,:) -t_substrate], [xy(n,:) -t_substrate-0.035], r_th_pad);
end

% connector base
start = [-r_outer_shell, -r_outer_shell, l_gap];
stop  = [r_outer_shell, r_outer_shell, l_gap+1.5];
CSX = AddBox(CSX, 'PEC', 1, start, stop);

% bottom ground
w = l_substrate/2-l_margin-r_th_pad-l_clearance;
r = l_substrate/2-l_margin-w/2;
CSX = AddCylindricalShell(CSX, 'PEC', 999,...
    [0 0 -t_substrate], [0 0 -t_substrate-0.035], r, w);

start = [-l_substrate/2 l_substrate/2 -t_substrate];
stop  = [ l_substrate/2 r_outer_shell -t_substrate-0.035];
CSX = AddBox(CSX, 'PEC', 999, start, stop);
CSX = AddBox(CSX, 'PEC', 999, start.*[1 -1 1], stop.*[1 -1 1]);

start = [l_substrate/2 -l_substrate/2 -t_substrate];
stop  = [r_outer_shell  l_substrate/2 -t_substrate-0.035];
CSX = AddBox(CSX, 'PEC', 999, start, stop);
CSX = AddBox(CSX, 'PEC', 999, start.*[-1 1 1], stop.*[-1 1 1]);

%% Setup CSX mesh %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
mesh.x = [];
mesh.y = [];
mesh.z = [-5 linspace(l_gap+1.5, l_gap+l_coax, 15)];

mesh = DetectEdges(CSX, mesh);
mesh = GradedMesh(mesh, 1);
CSX = DefineRectGrid(CSX, unit, mesh);

%% Setup CSX field dump %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% y-z plane (x=0)
start = [0 mesh.y(1) mesh.z(1)];
stop  = [0 mesh.y(end) mesh.z(end)];
CSX = AddDump(CSX, 'Et');
CSX = AddBox(CSX, 'Et', 0, start, stop);

%% Setup CSX port %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% port 1: microstrip port
start = [-w_microstrip_feed/2 -l_substrate/2 0];
stop  = [w_microstrip_feed/2 -l_microstrip -t_substrate];
shift = (l_substrate/2-l_microstrip)/3;
[CSX, port{1}] = AddMSLPort(CSX, 999, 1, 'PEC', start, stop,...
    'y', [0 0 -1], 'ExcitePort', true, 'FeedShift', shift);

% port 2: coaxial port
start = [0 0 l_gap+l_coax];
stop  = [0 0 l_gap];
[CSX port{2}] = AddCoaxialPort(CSX, 999, 2, 'PEC', 'PTFE',...
    start, stop, 'z', r_inner, r_outer, r_outer_shell);

%% Write xml file and run openEMS %%%%%%%%%%%%%%%%%%%%%%%%%
Sim_Path = 'tmp';
Sim_CSX = 'tmp.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]);
%return
RunOpenEMS(Sim_Path, Sim_CSX, '');

%% Post processing %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
f = linspace(f0-fc, f0+fc, 601);
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;

% plot S11, S21
figure('Position', [100 100 500 300]);
plot(f/1e9, 20*log10(abs(S11)), '-r', 'DisplayName', 'S_{11}');
hold on
grid on
plot(f/1e9, 20*log10(abs(S21)), '-b', 'DisplayName', 'S_{21}');
legend('Location', 'southeast');
xlabel('frequency (GHz)');
ylabel('S_{11}, S_{21} (dB)');
ylim([-30, 0]);

% plot S11 smith chart
plotRefl(port{1}, 'fmarkers', [100e6, f0+fc]);
legend('Location', 'eastoutside');
set(gcf, 'Position', [100 100 500 300]);

MSペイントという回路図エディタのすすめ

はじめに

みなさんは、MSペイント(Microsoft Paint)をご存知だろうか。私は昔から使っていて、このブログに載せている図のほとんどをペイントで作成している。マイクロソフト製のソフトはWordやExcel等ゴミソフトが多い中、ペイントだけは評価できる。今回はペイントを使った回路図の描き方について紹介する。

インストール

Windows OSのみ対応している。Windows 10までは標準でインストールされている。Windows 11からはゴミアップデートによりゴミソフト化してしまったので、下のサイトを参考にWindows 10バージョンのClassic Paintをインストールする。

Get Old Classic Paint for Windows 11 (Windows 10 app version)

使い方

まず、下の画像をダウンロードし、別ウィンドウのペイントで開いておこう。

回路記号(シンボル)

これで準備が整ったので、回路図を作成していく。

ペイントを起動したら、ショートカットキー「Ctrl + G」を押してみよう。すると100%倍率のとき10px間隔のグリッドが表示されるだろう。Windowsユーザーのほとんどがこの機能を知らないのである。もったいない。

欲しいシンボルをコピー&ペーストしてキャンバスに配置していく。このとき、シンボルピンをグリッドに合わせて配置すると良い。「選択」→「透明の選択」を有効にすると背景色が被さらない。

配線は「鉛筆」か「直線」ツールで線幅を1pxに設定して描く。「ブラシ」はアンチエイリアシングが効いていて線がぼやけるので絶対に使用しない。ピクセル単位で編集することになるため、マウス操作のエイムスキルが重要となる。

保存は可逆圧縮のPNG形式がおすすめ。JPEGだとノイズが入るため後から編集するときに面倒になる。

回路図の作成デモ

コムジェネレータ用の発振回路の設計

はじめに

前の記事:ショットキーダイオードを用いたコムジェネレータの実験 - ペEの日記

でショットキーバリアダイオードを用いたコムジェネレータの実験をした。基本波の発振源としてファンクションジェネレータを使用していたが、源振に含まれるスプリアス成分によってノイズフロアがわちゃわちゃしていた。そこで今回は、水晶発振器を用いた綺麗な発振源を設計し、ngspiceでシミュレーションしてみた。

そのうち部品を揃えて作ってみたい(妄想で終わることも多々...)。

回路

回路図を下に示す。発振回路は25 MHzの水晶振動子を使用したコルピッツ発振回路とする。コルピッツ発振回路は下図のキャパシタc02とc03の分圧比で帰還量を調整する。c02の容量を小さくすれば、q01のベース-エミッタ間電圧が大きくなり発振しやすくなるが、そのぶん波形は歪む。とりあえず、c02=c03=47 pFとしてみる。発振回路の出力はq01のエミッタから取る。

これだけだとコムジェネレータの入力信号としては弱いので、エミッタ接地増幅回路で増幅する。r04=100 Ωで出力インピーダンスを決め、自己バイアス回路によりq02に定常状態で30 mA程度流れるようにr03=10kと決めた。さらに、出力信号の振幅を高くするために、インピーダンス比l01:l02=1:4(巻き数比は1:2)のトランスを入れる。これは61材のフェライトコアにトリファイラ巻きしたコイルを想定している。

回路図(ノード名は赤、部品名は青で表記)

シミュレーション結果

以下、ngspiceの解析結果を示す。ngspiceのスクリプトは記事の最後に載せている。発振振幅が徐々に大きくなり、3 msくらいで発振安定状態になるようすが見て取れる。

解析結果:発振開始時の波形(ノード:n02, n03, n07)

発振安定時の波形↓。200 nsの間に5周期含まれているので、ちゃんと25 MHzで発振していることが分かる。発振回路の電圧波形n02、n03を見ると上側がクリップしている。コムジェネレータはどうせ後で歪ませるので今回はこれで良いかな。n07はエミッタ接地増幅回路の出力波形で、後段のダイオードx02、x03によってクリップして矩形形状となっている。

解析結果:発振安定時の波形(ノード:n02, n03, n07)

コムジェネレータの出力波形↓。これは前回の記事でも説明したが、l03とc06のハイパスフィルタにより短パルス波形となっていることが確認できる。これに沢山の高調波成分が含まれていて、櫛状のスペクトラムが得られる。

解析結果:コムジェネレータの出力波形(ノード:n08, n09)

Ngspiceスクリプト

以下、今回使用したngspiceスクリプトを載せる。なお、MMBT3904のSPICEモデルは、Diodes Inc.のページにて公開されているファイルをインクルードして使用した。

MMBT3904: NPN, 40V, 0.2A, SOT23 (Transistor (BJT) Master Table)

また、水晶振動子はサブサーキットでモデル化しており、下の記事が参考になった。

水晶振動子を使用した発振回路 | CQ出版社 オンライン・サポート・サイト CQ connect

.title crystal oscillator
.include MMBT3904.spice.txt

***********************************************************
* 25-MHz Crystal
.subckt Xtal25 1 2
cp 1 2 5p
cs 1 3 0.004p
ls 3 4 10.136m
rs 4 2 80
.ends

$***********************************************************
$* Xtal25 testbench
$*
$v01 n01 0   dc 0 ac 1
$r01 n01 n02 1
$x01 n02 0   Xtal25
$
$.control
$ac lin 1000 24.98Meg 25.02Meg
$let z11=v(n02)/v(n01,n02)
$settype impedance z11
$
$* color0 is background, color1 is grid & text color, color2..22 are for graph plots
$set color0=white color1=gray color2=red color3=blue color4=black
$set wfont='DejaVu Sans Mono'
$set wfont_size=12
$set xbrushwidth=2
$
$plot abs(z11) ylog
$.endc

***********************************************************
* 1SS154, Toshiba, schottky barrier diode
*
* SOT23 package model
.subckt 1SS154 1 2
d1 4 5 1SS154_die
c1 1 0 0.13p
c2 3 0 0.01p
c3 5 0 0.015p
c4 2 0 0.13p
l1 1 3 0.65n
l2 3 4 0.05n
l3 5 2 0.7n
.ends

* bare die model
.model 1SS154_die D (is=2.8n rs=6.0 eg=0.69 xti=2.0 bv=6.0 trs1=6m ikf=7m
+ cjo=0.66p m=0.23 vj=0.25)

***********************************************************
* colpitts oscillator circuit
*
vcc cc  0   pulse(0 5 0 1n 1n 1 1)
x01 n01 0   Xtal25
q01 cc  n02 n03 DI_MMBT3904
r01 cc  n02 10k
r02 n03 0   330
c01 n01 n02 0.01u
c02 n02 n03 47p
c03 n03 0   47p
c04 n03 n04 47p

* buffer amplifier circuit
*
q02 n05 n04 0 DI_MMBT3904
r03 n05 n04 10k
r04 cc  n05 100
c05 n05 n06 0.01u
l01 n06 0   1u
l02 n07 0   4u
k12 l01 l02 0.97

* comb generator circuit
*
x02 0   n07 1SS154
x03 n07 n08 1SS154
r05 n08 0   100
l03 n08 0   47n
c06 n08 n09 10p

* load resistor
*
r06 n09 0   50

***********************************************************
* transient simulation
*
.control
tran 1n 5m

* color0 is background, color1 is grid & text color, color2..22 are for graph plots
set color0=white color1=gray color2=red color3=blue color4=green color5=black
set wfont='DejaVu Sans Mono'
set wfont_size=12
set xbrushwidth=1

plot v(n02) v(n03) v(n07)
plot v(n02) v(n03) v(n07) xlimit 4.9998m 5m
plot v(n08) v(n09) xlimit 4.9998m 5m ylimit -0.3 0.3
.endc

.end

ショットキーダイオードを用いたコムジェネレータの実験

はじめに

コムジェネレータとは入力信号の2倍、3倍、4倍、...の高調波を沢山発生させるもので、スペクトルが櫛状(comb)に見えることからそう呼ばれている。時間波形で考えると、一定周期で繰り返す短パルスをつくることでコムが発生する。

ふつうコムジェネレータは、ステップリカバリーダイオード(SRD)という特殊なダイオードを用いて実現される。pn接合ダイオードが順方向バイアスから逆方向バイアスに切り替わる際、蓄積キャリアが抜けきるまでオン状態が続き(逆回復時間)、キャリアが抜けきるとオフ状態へと遷移する(遷移時間)。ステップリカバリーダイオードはこの遷移時間が数十ピコ秒と非常に短く、数十GHzまで伸びる広帯域の短パルスを容易に発生することができる。

しかし、ステップリカバリーダイオードはそう簡単に手に入るものでもない。そこで今回は、ショットキーバリアダイオードを使ってコムを発生させる実験をしてみた。

実験

下図に、試作したコムジェネレータの回路図を示す。入力はファンクションジェネレータで25 MHz、2.5 Vppの正弦波とし、これをショットキーバリアダイオードD1、D2で半波整流する。半波整流回路によって波形の負側が完全にクリップされ、正負が逆転する瞬間に強い歪が発生する。これだけだと基本波成分が強すぎるので、L1とC1のハイパスフィルタを後段に入れている。出力をスペクトラムアナライザで観測する。

(補足:ショットキーバリアダイオードは逆回復時間はほとんど発生しないため、キレの良い整流作用が得られる。逆回復時間を利用したステップリカバリダイオードとは根本的に原理が異なるので注意。)

試作したコムジェネレータの回路図

試作した基板↓。銅張基板をスジ彫りカーバイトで削ってパターニングし実装した。インダクタは前の記事( ガラスビーズコア高周波巻き線インダクタの自作 - ペEの日記)で紹介したようにガラスビーズに銅線を巻いて作製した。

試作結果

試作結果(拡大写真)

測定のようす↓。ファンクションジェネレータはOWON製のHDS272S、スペアナはSIGLENT製のSSA3021Xを使用した。

測定のようす

スペアナの測定結果を示す。コム状のスペクトラムが見られた。高周波側の減衰は大きいが、後段にアンプを入れて持ち上げればもう少し使いやすくなると思う。ノイズフロアがわちゃわちゃしているのは、おそらくファンクションジェネレータが出しているノイズが原因だと思う。源振はもう少しキレイな発振器にしたいところ。

スペクトラム測定結果

RFミキサのシミュレーション

はじめに

前の記事

でバランの試作・モデル化、ショットキーバリアダイオードのモデル化をした。ミキサを構成する部品がだいたいそろったので、今回はこれらを組み合わせてミキサを設計していく。

ミキサの回路と動作原理

今回試すミキサ回路を下図に示す。シングルバランストミキサの構成で、バランスさせたLO信号でショットキーバリアダイオード(1SS154)をたたく方式とした。筆者はずっと帯域1 GHzのスペアナ自作を夢見ているので、その初段に使うミキサとして、RF:9 kHz~1000 MHz、IF:1100 MHz、LO:1100~2100 MHzの周波数範囲が得られれば嬉しい。

※図中にngspiceネットリストのノード名を赤文字で記している。

ミキサの回路図(シングルバランストミキサ)

以下、ミキサの動作原理について述べる。下図にダイオードの電流電圧特性を示す。ダイオードに順方向バイアスが加わっているとき、交流的にはほとんどオン抵抗と等価となる。一方、逆バイアスのとき、交流的にはほとんど端子間容量しか見えない。このように、ダイオードはスイッチのように動作し、ダイオードが飽和するレベルのLO信号を注入するとオンとオフが半周期ずつ繰り返す。

ダイオードの電流電圧特性

これをミキサ回路に当てはめて考えると、オンとオフそれぞれの状態における等価回路は下図のようになる。オン時はシャント抵抗が小さいためRF→IFの経路が遮断され、オフ時は端子間容量がほとんどオープンとなってRF→IFの経路はつーつーの状態となる。したがって、LOによってRFの信号が変調されてIFに出力される。

ミキサの動作原理

LOをバランスさせ、RF、IFを中点から取り出すことで、LO→RF、LO→IFの漏れを最小限に抑えることができる。

また、この回路ではRFとIFに対称性があり周波数の大小関係はない。しかし、一般的にはRFがIFより周波数が高いことが多く、その場合はR3をキャパシタに、R4をインダクタに置き換えてフィルタ特性を持たせることでミキサの変換効率を高めることができるだろう。

解析手法について

ミキサの解析にはハーモニックバランスという解析手法がよく用いられる。これは、複数のトーン信号が非線形回路に入力された際、入力信号の基本波・高調波成分とそのミキシングプロダクトを周波数領域で計算し、収束条件を満たすまで振幅と位相を反復計算するというものである。フリーのシミュレータだとQUCSに実装されている。しかし、QUCSのハーモニックバランス解析は使ったことがあるが、収束しづらかったりエラーが出たりで若干非力な印象を受けた(QUCSは開発が2017年で止まっていて、QucsStudioのほうは改善されているかも?)。

そこで今回は、ngspiceを使ってミキサのシミュレーションを試してみる。残念ながら、ngspiceにハーモニックバランス解析は実装されていないが、一方で、SPICE由来の強力なトランジェント解析が使える。トランジェント解析で出力された時間波形をフーリエ変換して、特定の周波数成分のみを抽出することで、ハーモニックバランス解析と同様の出力結果を得ることができる。ngspiceは制御文(while文, if文)や変数を扱えるスクリプト言語であり、周波数条件を変えながら繰り返しトランジェント解析を回してフーリエ変換する今回の用途にはぴったりである。

まずは、ミキサの最も重要な特性である変換利得(conversion gain)を計算したい。そこで、下図に示すテスト回路を作成する。

ミキサのテスト回路

ngspiceでテスト回路を表現すると下のような記述となる。vlo、vrf、vifをインピーダンス50 Ωの電圧源として定義している。スクリプト全体は記事の最後に載せている。

* mixer test circuit
vlo lo 0 dc 0 portnum 1 z0 50 sin(0 1 2100Meg)
vrf rf 0 dc 0 portnum 2 z0 50 sin(0 1 1100Meg)
vif if 0 dc 0 portnum 3 z0 50
x1 lo rf if mixer

ここで、dBmとVの単位換算に注意する必要がある。dBm→Wの変換は、p [W]=1 mW×10^(p [dBm]/10)であり、50 Ω系における電圧実効値はvrms [Vrms]=√(50 Ω×p [W])、電圧波高値はvpk [Vpeak]=√2×vrmsとなる。さらに、この値は出力インピーダンス50 Ωの信号源に50 Ω負荷を接続したときの負荷における電圧値となるため、ngspiceの電圧源に設定する振幅は2×vpkとする必要がある。スクリプトでは以下の部分。

* LO/RF power in dBm
let lopower=15
let rfpower=0

* convert power to voltage source amplitude
let loampl=2*sqrt(2*50*dbmtow(lopower))
let rfampl=2*sqrt(2*50*dbmtow(rfpower))

上で計算した電圧振幅loamplおよびrfamplを、alterコマンドを用いて電圧源vlo、vrfに反映する。

alter @vlo[sin]=[ 0 $&loampl $&lofreq ]
alter @vrf[sin]=[ 0 $&rfampl $&rffreq ]

周波数とパワーのセットアップができたら、トランジェント解析を実行する。今回は1 psステップで100 nsの解析時間とした。

$ run transient simulation
    tran 1p 100n

トランジェント解析が完了すると各ノードの電圧波形がベクトルデータとして出力される。IFポートの電圧波形v(if)をフーリエ変換(同期検波)することにより、ほしい周波数成分のみを抽出する。フーリエ変換の計算式は上の図中に記している。

$ calculate IF output using synchronous detection
    let vif_i=sqrt(2)*mean(v(if)*cos(2*pi*iffreq*time))
    let vif_q=sqrt(2)*mean(v(if)*sin(2*pi*iffreq*time))
    let conversiongain[index]=10*log10((vif_i^2+vif_q^2)/50/1e-3)-rfpower

解析結果

変換利得およびリターンロスの解析結果を以下に示す。変換利得は-17~-15 dBとなった。市販のミキサと比べると良いとは言えないが、趣味で使う分には問題ないように思う。近いうちに試作してみたい。

変換利得 vs RF freq(IF freq=1100 MHz)

リターンロスは各ポートだいたい10 dB程度。RF(S22)とIF(S33)は対称なのでグラフがぴったり重なっている。

リターンロス(S11, S22, S33)

Ngspiceのスクリプト

以下、ngspiceのスクリプトを載せる。

file: rfmixer.spice

.title single balanced schottky diode mixer simulation

***********************************************************
* 1SS154, Toshiba, schottky barrier diode
* SOT23 package model
.subckt 1SS154 1 2
d1 4 5 1SS154_die
c1 1 0 0.13p
c2 3 0 0.01p
c3 5 0 0.015p
c4 2 0 0.13p
l1 1 3 0.65n
l2 3 4 0.05n
l3 5 2 0.7n
.ends

* bare die model
.model 1SS154_die D (is=2.8n rs=6.0 eg=0.69 xti=2.0 bv=6.0 trs1=6m ikf=7m
+ cjo=0.66p m=0.23 vj=0.25)

***********************************************************
* balun
*
.subckt balun5T 1 2 3 4
.param ind1=15n ind2=1.8n cap1=0.25p cap2=0.9p kval=0.9
* ind1: mutual (coupled) inductance
* ind2: feeding wire inductance
* cap1: interwinding capacitance
* cap2: coupled capacitance
* kval: coupling coefficient
lm1 11 22 {ind1}
lm2 44 33 {ind1}
k lm1 lm2 {kval}
l1  1  11 {ind2}
l2  2  22 {ind2}
l3  3  33 {ind2}
l4  4  44 {ind2}
c1  11 22 {cap1}
c2  44 33 {cap1}
c3  11 44 {cap2}
c4  22 33 {cap2}
.ends

***********************************************************
* single balanced diode mixer
*
.subckt mixer lo rf if
x1 lo pos neg 0 balun5T
x2 pos com 1SS154
x3 com neg 1SS154
r1 pos 0  27
r2 neg 0  27
r3 rf com 10
r4 if com 10
*c1 if com 10p
*l1 rf com 10n
.ends

***********************************************************
* mixer test circuit
*
vlo lo 0 dc 0 portnum 1 z0 50 sin(0 1 2100Meg)
vrf rf 0 dc 0 portnum 2 z0 50 sin(0 1 1100Meg)
vif if 0 dc 0 portnum 3 z0 50
x1 lo rf if mixer

.func dbmtow(x)={1e-3*10^(x/10)}

***********************************************************
* mixer conversion gain analysis
*
.control
let n=10
let index=0
let step=100Meg $ frequency step

* vectors to store results
let conversiongain=vector(n)
let freq=vector(n)

* LO/RF power in dBm
let lopower=15
let rfpower=0

* convert power to voltage source amplitude
let loampl=2*sqrt(2*50*dbmtow(lopower))
let rfampl=2*sqrt(2*50*dbmtow(rfpower))

while index lt n
    $ alter frequencies
    let lofreq=1100Meg+step*(index+1)
    let rffreq=step*(index+1)
    let iffreq=1100Meg
    alter @vlo[sin]=[ 0 $&loampl $&lofreq ]
    alter @vrf[sin]=[ 0 $&rfampl $&rffreq ]
    $ run transient simulation
    tran 1p 100n
    $ calculate IF output using synchronous detection
    let vif_i=sqrt(2)*mean(v(if)*cos(2*pi*iffreq*time))
    let vif_q=sqrt(2)*mean(v(if)*sin(2*pi*iffreq*time))
    let conversiongain[index]=10*log10((vif_i^2+vif_q^2)/50/1e-3)-rfpower
    let freq[index]=rffreq

    let index=index+1
end

* set vector types
settype decibel conversiongain
settype frequency freq

* color0 is background, color1 is grid & text color, color2..22 are for graph plots
set color0=white color1=gray color2=red color3=blue color4=black
set wfont='DejaVu Sans Mono'
set wfont_size=12
set xbrushwidth=2

plot conversiongain vs freq

.endc

***********************************************************
* mixer return-loss analysis
*
.control 
* run S-parameter simulation
sp lin 1000 3Meg 3000Meg

plot db(S_1_1) db(S_2_2) db(S_3_3) ylimit -25 0

.endc

.end

自作したバランのモデル化

はじめに

前の記事(高周波バラン(~2.5 GHz)の自作 - ペEの日記)でガラスビーズに銅線を巻いた高周波バランを試作した。今回は、前回の測定結果をもとに等価回路モデルに落とし込む。

等価回路モデル

下図にバランの等価回路モデルを示す。Lm1、Lm2は巻き線のインダクタンス、L1~L4は引き出し線の寄生インダクタンス、C1、C2は線間容量、C3、C4は撚り線にした2本の巻き線間の結合容量を表す。引き出し線は最短で接続しても1 mmくらいの長さになってしまうため、L1~L4=1.8 nHという値は妥当と言える。また、線間容量は過去の記事(ガラスビーズコア高周波巻き線インダクタの自作 - ペEの日記)からだいたい0.25 pF程度になることが分かっている。

バランの等価回路モデル

特性比較

等価回路モデルのシミュレーション結果と実測結果との比較を示す。

これ↓がシミュレーション結果(S11, S21, S31)で、

シミュレーション結果: S11, S21, S31

これ↓が実測結果。2.8 GHz付近の共振は合わせられなかったが、2.5 GHzまでは概ね一致していると言えよう。

測定結果: S11, S21, S31

Ngspiceスクリプト

以下、今回使用したNgspiceのスクリプトを載せる。.subckt ~.endsの部分がバランのサブサーキット記述となる。

.title Balun S parameter analysis

.subckt balun5T 1 2 3 4
.param ind1=15n ind2=1.8n cap1=0.25p cap2=0.9p kval=0.9
* ind1: mutual (coupled) inductance
* ind2: feeding wire inductance
* cap1: interwinding capacitance
* cap2: coupled capacitance
* kval: coupling coefficient
lm1 11 22 {ind1}
lm2 44 33 {ind1}
k lm1 lm2 {kval}
l1  1  11 {ind2}
l2  2  22 {ind2}
l3  3  33 {ind2}
l4  4  44 {ind2}
c1  11 22 {cap1}
c2  44 33 {cap1}
c3  11 44 {cap2}
c4  22 33 {cap2}
.ends

v1 n001 0 dc 0 portnum 1 z0 50
v2 n002 0 dc 0 portnum 2 z0 50
v3 n003 0 dc 0 portnum 3 z0 50
x1 n001 n002 n003 0 balun5T
.sp lin 1000 3Meg 3000Meg

.control
run
* color0 is background, color1 is grid & text color, color2..22 are for graph plots
set color0=white color1=gray color2=red color3=blue color4=black
set wfont='DejaVu Sans Mono'
set wfont_size=12
set xbrushwidth=2

plot db(S_1_1) db(S_2_1) db(S_3_1) ylimit -25 0
.endc
.end

NgspiceでSパラメータ解析

はじめに

Ngspiceを使ったSパラメータ解析のやり方を覚え書きとして残しておく。ためしに、下のバンドパスフィルタ回路を解析してみる。

全体のスクリプトは一番下に載せるが、大事なポイントを抜き出して説明すると...

まず、電圧源はRFポートとして定義することができる。portnumでポートの番号を指定して、z0でポートのインピーダンスを指定する。

v1 n001 0 dc 0 portnum 1 z0 50
v2 n003 0 dc 0 portnum 2 z0 50

あとは、Sパラメータ解析の周波数スイープの設定をするだけ。

.sp lin 1001 10Meg 200Meg

解析結果

計算結果はS_i_jという変数に出力される(iとjはポート番号)。スミスチャートも描けるし、極座標プロットも描ける。よきよき!

スクリプト

以下、Ngspiceのスクリプト。

.title LC bandpass filter S parameter analysis

* LC bandpass filter circuit
v1 n001 0 dc 0 portnum 1 z0 50
c1 n001 0 164.2p
l1 n001 0 15.58n
c2 n001 n002 5.604p
l2 n002 n003 456.5n
c3 n003 0 164.2p
l3 n003 0 15.58n
v2 n003 0 dc 0 portnum 2 z0 50

* S parameter simulation
.sp lin 1001 10Meg 200Meg

.control
run
* color0 is background, color1 is grid & text color, color2..22 are for graph plots
* color18 and color19 are used for smithgrids
set color0=white color1=gray color2=red color3=blue color4=black
set color18=gray color19=gray
set wfont='DejaVu Sans Mono'
set wfont_size=12
set xbrushwidth=2

plot db(S_1_1) db(S_2_1)
plot S_1_1 smithgrid
plot S_2_1 polar
.endc

高周波バラン(~2.5 GHz)の自作

はじめに

前の記事(ガラスビーズコア高周波巻き線インダクタの自作 - ペEの日記)でガラスビーズに銅線を巻いたインダクタの特性を評価した。今回は、ガラスビーズに2本の銅線を巻いてバランを試してみた。

原理

そもそもバラン(balun)とは、平衡回路-不平衡回路を変換するもののことを言う。バランには大雑把に電圧型バランと電流型バランの2種類ある(下図)。

電圧型バランはいわゆるトランスであり、1次巻き線で生じた磁束が2次巻き線と共有され信号が伝達する。同相モードはコイルに電圧がかからないためカットされ、平衡-不平衡の変換が実現される。

電流型バランは、同相モードに対しては、コイルに生じる磁束が強め合う向きになるためインダクタンスにより高周波成分がカットされる。差動モードに対しては、磁束が互いに打ち消しあう向きになるためインダクタンスの影響を受けず信号が伝わる。このようにして、平衡-不平衡の変換が実現される。

電圧型バランと電流型バラン

電圧型バランは高透磁率(μ)のコア材を使用するか、巻き数を多くすることでインダクタンスを稼ぐ必要がある。しかしながら、フェライト等のコア材は周波数特性が悪く、巻き数を増やすのは線間容量により自己共振周波数が下がってしまい、1 GHz超で広帯域に実現するのが難しい。したがって、今回は電流型バランを採用することとした。

バランの巻き方

Φ0.1 mmのポリウレタン銅線を2本撚り線にし、MIYUKI製の丸小ビーズ(外径2 mm)にバイファイラ巻きする。巻き数は5回とした。撚り線にすることで差動モードの特性インピーダンスが安定し周波数特性が改善する。

バイファイラ巻きしたところ(5回巻き)

これを、NanoVNAで評価する用の銅張基板に実装した。銅張基板は幅1 mmのスジボリカーバイトという工具を使用して溝を掘り、マイクロストリップ線路をパターニングしている。

基板に実装したバラン その1

基板に実装したバラン その2

測定結果

NanoVNA-F V2を使用して1 MHz~3 GHzまで測定した。NanoVNAは2ポートしか測れないので、もう一方のポートは50 Ω終端してP1-P2、P1-P3と2回に分けて測定を行った。

以下に、Sパラメータの振幅・位相特性、および、振幅アンバランスと位相アンバランスの測定結果を示す。1.5 GHz~2.5 GHzの周波数範囲で、振幅アンバランス<1 dB、位相アンバランス<10°を満たしており、わりと良い結果となった。2 GHz以下では|S11|<-13 dBと反射特性も良い。ただし、2.8 GHzに自己共振によるディップが存在し、2.5 GHz付近では|S11|≃-6 dB程度となっている。

前の記事でインダクタ特性を調べた際も5回巻きで2.5 GHzに自己共振が存在していた。巻き数を減らせばもっと高周波まで伸ばせる可能性はある。

測定結果:Sパラメータ(S11, S21, S31)

測定結果:位相特性(S21, S31)

測定結果:振幅アンバランス

測定結果:位相アンバランス

ガラスビーズコア高周波巻き線インダクタの自作

はじめに

高周波回路で使用するインダクタを自作できないかと思い、手芸用のガラスビーズに銅線を巻いて空芯コイルを作ることを思いついた。というのも、普通はコア材としてフェライトやカーボニル鉄を選ぶと思うが、1 GHz以上の高周波になるとコア材の複素透磁率は虚部が支配的となり高いQ値が得られない。また、高周波回路では数十nH程度の小さいインダクタンスを使用することが多く、空芯コイルでも十分実現可能となる。ガラスビーズの材質はガラスなので、当然透磁率はμ=1であり、はんだ付けの際の耐熱性にも優れていて、入手性も問題ない。

たぶん、おそらく、電子工作でガラスビーズにコイルを巻いたのは私が初めてなんじゃないかと思う。

巻き線インダクタの自作

コア材にはMIYUKI製のガラスビーズ、外径約2mmのものを使用し、巻き線にはΦ0.1mmのポリウレタン銅線を用いる。色々試して分かったが、MIYUKI製のビーズは形が整っていて他メーカーと比べて寸法ばらつきが小さい。350粒入り(3g)で80~100円程度なのでかなりオススメ。

ビーズに銅線を巻いていく。巻き方によってもインダクタンスが変わるため基準を決めておく。今回は、巻き始めと巻き終わりが180°反対側にくるようにし、中間の巻き線はできるだけ均等に分布するように巻いた。巻き数はガラスビーズを1周するごとに1巻きと定義する。見た目はトロイダルコイルっぽいが原理的にはソレノイドコイルに近いため、一般的なトロイダルコイルの巻き数定義とは異なる。下図のように、1T~10Tのコイルを作製した("T"はturnの意味)。

自作した巻き線インダクタ

インダクタンスの測定方法

NanoVNA-F V2でS11を測定しインピーダンスに変換してインダクタンスを測定する。NanoVNA-F V2は3 GHzまで測定可能であり、PCに接続してNanoVNA-Saverというソフトから制御した。

NanoVNA-F V2

VNAでインピーダンスを測定する際、基準面を正しく設定することが重要である。下図のようにいったんNanoVNAのポートで校正を行った後、DUTを接続するコネクタ端面まで基準面を移動する手法をとった。NanoVNAでは"electrical delay"(NanoVNA-Saverでは"calibration"→"offset delay")を設定することで、基準面の移動が可能である。offset delayの調整は、コネクタ端面でショートする治具を接続しスミスチャート上でショートに見えるように値を合わせ込む。私の測定環境ではちょうど100 psとなった。

基準面の移動その1

基準面の移動その2

校正と基準面の設定が終わったら、いよいよDUTに繋ぎ変えてS11の測定を行う。DUTは下図のようにSMAコネクタ端面に最短距離で実装している。

SMAコネクタに実装したDUT

測定結果

巻き数とインダクタンスの関係を下表および下図に示す。インダクタンスは300 MHzにおけるZ11から計算している。また、LCR並列共振回路で近似したときのキャパシタンス(線間容量)も表に記載している。線間容量は巻き数に応じて増える傾向にあるが、実装のばらつきにより巻き数との相関が取れていない部分もある。

巻き数 L: インダクタンス C: 線間容量
1T 5.49 nH 0.22 pF
2T 8.56 nH 0.22 pF
3T 12.0 nH 0.272 pF
4T 14.8 nH 0.256 pF
5T 17.9 nH 0.227 pF
6T 22.4 nH 0.238 pF
7T 28.3 nH 0.274 pF
8T 39.2 nH 0.271 pF
9T 44.4 nH 0.260 pF
10T 53.7 nH 0.272 pF

測定結果:巻き数とインダクタンスの関係

以下にインピーダンスの周波数特性のグラフを示す。黒点は測定結果、赤の点線は並列LCR共振回路でフィッティングした特性を表している(R=4 kΩ)。自己共振周波数が読み取れる。

測定結果:巻き数=1T, 2Tのインピーダンス特性

測定結果:巻き数=3T, 4Tのインピーダンス特性

測定結果:巻き数=5T, 6Tのインピーダンス特性

測定結果:巻き数=7T, 8Tのインピーダンス特性

測定結果:巻き数=9T, 10Tのインピーダンス特性

おまけ

せっかくビーズを買ったので何か編み物も作ってみたくなった。調べてみると、ブリックスティッチやスクエアステッチといった編み方があるようだ。試しに、ドット絵のクロミちゃんをスクエアスティッチ法で編んでみた(下図)。はじめてにしては上手くできたと思う。ダイソーで見つけたチェキフレームに入れてみた。磁石で壁に貼ることができる。

ショットキーバリアダイオード1SS154のモデル化

はじめに

秋月電子で購入できる高周波用ショットキーバリアダイオード1SS154(東芝製)について、Ngspiceを用いてSPICEモデルを作成したので記事に残す。秋月のショットキーの中ではこれが最も寄生容量が小さく高周波向きである。今後1SS154を使用して~2.4 GHz帯のダイオードミキサや検波器等に応用したい。NgspiceはオープンソースのSPICE系シミュレータであり、ネットリスト直書きでコマンドラインで使用する。シミュレータはLTspiceでもQucsでも良かったが、Ngspiceはスクリプト内でif文やwhile文が扱えるところに魅力を感じているので、練習も兼ねて今回はNgspiceを使用した。

↓秋月の商品ページ
高周波用ショットキーバリア・ダイオード 1SS154: 半導体 秋月電子通商-電子部品・ネット通販

SPICEモデル

作成したSPICEモデルを下に記載する。SOT23パッケージの寄生成分を表現するためにサブサーキットとして記述している。SOT23パッケージについては、Skyworksのアプリケーションノート「APN1001: Circuit Models for Plastic Packaged Microwave Diodes」を参考にした。

file: 1SS154.spice

* 1SS154, Toshiba, schottky barrier diode
* SOT23 package model
.subckt 1SS154 1 2
d1 4 5 1SS154_die
c1 1 0 0.13p
c2 3 0 0.01p
c3 5 0 0.015p
c4 2 0 0.13p
l1 1 3 0.65n
l2 3 4 0.05n
l3 5 2 0.7n
.ends

* bare die model
.model 1SS154_die D (is=2.8n rs=6.0 eg=0.69 xti=2.0 bv=6.0 trs1=6m ikf=7m
+ cjo=0.66p m=0.23 vj=0.25)

特性結果

Ngspiceによる順方向電流-電圧特性(If-Vf)、および端子間容量-逆電圧特性(Ct-Vr)の計算結果を下図に示す。

If-Vf特性結果(Ta=-25, 25, 75 ℃)
Ct-Vr特性結果(Ta=25℃, f=1 MHz)

参考までにデータシートより抜粋

データシート

Ngspiceスクリプト

自分用のメモとして、Ngspiceのスクリプトも載せておく。

file: IV_forward.spice

.title forward current versus forward voltage simulation
.include 1SS154.spice

vs n001 0 dc 0      $ voltage source
x1 n001 0 1SS154    $ diode

.control
* dc simulation command
* .dc srcnam vstart vstop vincr [src2 start2 stop2 incr2]
dc vs 0.01 0.8 0.01 temp -25 75 50

* color0 is the background, color1 is the grid and text color
* colorN(2..22) is the plotted line color
set color0=rgb:ff/ff/ff color1=rgb:80/80/80 color2=rgb:ff/00/00
set xbrushwidth=2
set wfont='DejaVu Sans Mono'
set wfont_size=12
plot -i(vs) ylog xlimit 0 0.8 ylimit 1e-5 0.1
.endc
.end

file: CV.spice

.title total capacitance versus reverse voltage simulation
.include 1SS154.spice

vs n001 0 dc 0 ac 1 $ voltage source
rs n001 n002 1      $ source impedance
x1 0 n002 1SS154    $ diode

.options temp=25

.control
let n=101
let x=vector(n) $ vector for x axis: reverse voltage
let y=vector(n) $ vector for y axis: total capacitance

* sweep reverse voltage
let index=0
let vstep=5/(n-1)
while index < n
    alter vs dc vstep*index
    ac lin 1 1Meg 1Meg $ .ac lin np fstart fstop
    let admit=v(n001,n002)/v(n002)
    let cap=imag(admit)/(2*pi*frequency)

    $ store the result in the vectors
    let x[index]=abs(vstep*index)
    let y[index]=abs(cap)
    let index=index+1
end
settype voltage x
settype capacitance y

* color0 is the background, color1 is the grid and text color
* colorN(2..22) is the plotted line color
set color0=rgb:ff/ff/ff color1=rgb:80/80/80 color2=rgb:ff/00/00
set xbrushwidth=2
set wfont='DejaVu Sans Mono'
set wfont_size=12
plot y vs x ylimit 0.1p 1p xlabel 'reverse voltage' ylabel 'total capacitance'
.endc
.end