SPICEの小瓶(6) 電圧源の波形の設定用にPythonスクリプト作成開始

Joseph Halfmoon

このところLTspiceを使うことが多くなって勝手に不便に思っているのが、独立電圧源の波形の設定です。SPICE自体は細かく時間とか制御できるので機能的にはまったく不満はありません。しかし大枠だけ決めてあとはお任せ、みたいなズボラな設定が欲しいです。そこでズボラな設定用のスクリプトの作成に着手。着手しただけ。まだ機能的には全然。

※「SPICEの小瓶」投稿順インデックスはこちら

※今回作成着手したPythonスクリプト全文は末尾に。

実機で波形を観察するときに使っているのがDigilent社のAnalog Discovery2という装置です。低価格ながらオシロやら波形ジェネレータやらいろいろ入っている「万能」PCツールです。この波形ジェネレータの設定画面が以下に。

ad2_wavegen

上記は、3角波を作るときの設定ですが、周波数、振幅、オフセットを入力すれば、私が必要とするような波形はできてしまいます。デフォルト値が入っているので、特に変更なければコマケー話は抜きにできるので、らくちん。

勿論三角波だけでなく、以下のように波形を選択可能です。それらについてもだいたい同じような感じで設定可能です。

ad2_wavegenType

一方、LTspiceの独立電圧源の波形の定義画面は以下のようです。例えば以下は、「三角波」を定義しているところですが、結構パラメータ多いです。時間などは秒単位で指定しないとなりません。計算ができない年寄りは、電卓使いますがケタ間違えることも度々。

spiceSetting

上記の設定を冒頭のアイキャッチ画像のLTspice回路で観察したものが以下です。設定はメンドイですが、単純な三角波です。

TriangleWave

私に変わって、波形を定義してくれるスクリプトということで、末尾にソースを添付したスクリプトを作り始めました。もしかすると「車輪の再発明」的な。

今のところ、正弦波、方形波、三角波だけですが、おいおい追加する予定です。

使いかたはこんな感じ。オプション引数で周波数、振幅、波形タイプなどを指定して走らせると、LTspiceに貼り付けられる定義を出力してくるので、これをコピペするっと。

triangleOpr

一応、ヘルプ機能あり。以下のごとし。基本全てにデフォルト値があるので、デフォルトのままでよければ指定は不要。

help

なお、unittestにも対応。というより、unittestないとまともに動作しないです。あってもどうだか?

unittest

一応ね、今回外形を作れた感じなので、おいおい拡張していく予定であります。

SPICEの小瓶(5) 温度を変えてシミュレーション、ついでに物理定数 へ戻る

SPICEの小瓶(7) standard.bjt、書き換えちゃって大丈夫なの? へ進む

今回作成中のPythonスクリプト、途上です。バグなどあっても知りませんよ。

#!/usr/bin/python
# coding: utf-8
### @file  waveGen.py
### @brief LTspice waveform Generator
###
### @author: Joseph Halfmoon
### @date:   April 1, 2022
"""
LTspice waveform generation script v0.1

input
=====
-F frequency
-A amplitude
-O offset
-D duty
-T type. (sine square triangle)

unittest
========
python -m unittest waveGen.uTestCase

"""
import argparse
import sys
import unittest

versionSTR = "waveGen.py v0.1"

def parse2float(st):
    """parse string to float
    
    Returns at Fail: None
    """
    try:
        work = float(st)
    except:
        return None
    return work

def chkVal(arg, dval):
    vl = parse2float(arg)
    if vl is None:
        nLen = len(arg)
        if nLen > 1:
            bdy = arg[0:nLen-1]
            lastChar = arg[nLen-1]
            vl = parse2float(bdy)
        if vl is None:
            vl = dval
    else:
        lastChar = "0"
    return (vl, lastChar)

def freqIN(arg, dval):
    prefixes = 'mkMG'
    vl, lastChar = chkVal(arg, dval)
    pfx = prefixes.find(lastChar)
    if pfx == 0:
        ex = 1e-3
    elif pfx == 1:
        ex = 1e3
    elif pfx == 2:
        ex = 1e6
    elif pfx == 3:
        ex = 1e9
    else:
        ex = 1
    return vl * ex

def vIN(arg, dval):
    prefixes = 'm'
    vl, lastChar = chkVal(arg, dval)
    pfx = prefixes.find(lastChar)
    if pfx == 0:
        ex = 1e-3
    else:
        ex = 1
    return vl * ex

class sinWave(object):
    """Wave Generation base class
    
    generate sine wave.
    """
    def __init__(self, frequency, amplitude, offset, nCyc):
        self.freq = frequency
        self.amp = amplitude
        self.ofs = offset
        self.nCyc = nCyc

    def genWave(self):
        return "SINE({0} {1} {2} 0 0 0 {3})".format(self.ofs, self.amp, self.freq, self.nCyc)

class squareWave(sinWave):
    """Square wave Generation class
    
    squareWave 0.0.
    """
    def __init__(self, frequency, amplitude, offset, nCyc, duty):
        super(squareWave, self).__init__(frequency, amplitude, offset, nCyc)
        self.risTIM = 1e-9
        self.falTIM = 1e-9
        self.tperiod = 1.0 / self.freq
        self.ton = self.tperiod * duty
        self.duty = duty
        self.vinit = self.ofs - self.amp
        self.von   = self.ofs + self.amp

    def genWave(self):
        return "PULSE({0} {1} 0 {2} {3} {4} {5} {6})".format(self.vinit, self.von, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc)

class triangleWave(squareWave):
    """Triangle wave Generation class
    
    triangleWave 0.0.
    """
    def __init__(self, frequency, amplitude, offset, nCyc, duty):
        super(triangleWave, self).__init__(frequency, amplitude, offset, nCyc, duty)
        self.risTIM = self.tperiod * self.duty
        self.falTIM = self.tperiod * (1.0 - self.duty)
        self.ton = 0.0

    def genWave(self):
        return "PULSE({0} {1} 0 {2} {3} {4} {5} {6})".format(self.vinit, self.von, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc)

class uTestCase(unittest.TestCase):

    def test_parse2float(self):
        self.assertIsNone(parse2float("1.0x"))
        self.assertAlmostEqual(1.25, parse2float("1.25"))

    def test_chkVal(self):
        vl, lc = chkVal("ABC", 1.11)
        self.assertAlmostEqual(1.11, vl)
        self.assertEqual("C", lc)
        vl, lc = chkVal("1.25", 0)
        self.assertAlmostEqual(1.25, vl)
        self.assertEqual("0", lc)
        vl, lc = chkVal("1.5m", 0)
        self.assertAlmostEqual(1.5, vl)
        self.assertEqual("m", lc)

    def test_freqIN(self):
        self.assertAlmostEqual(1000, freqIN("ABC", 1000))
        self.assertAlmostEqual(100, freqIN("100X", 2000))
        self.assertAlmostEqual(1.5, freqIN("1.5", 0))
        self.assertAlmostEqual(1.23e3, freqIN("1.23k", 0))
        self.assertAlmostEqual(1.23e-3, freqIN("1.23m", 0))
        self.assertAlmostEqual(1.23e6, freqIN("1.23M", 0))
        self.assertAlmostEqual(1.23e9, freqIN("1.23G", 0))

    def test_vIN(self):
        self.assertAlmostEqual(1.1, vIN("XYZ", 1.1))
        self.assertAlmostEqual(1.55, vIN("1.55X", 1.2))
        self.assertAlmostEqual(1.5, vIN("1.5", 0))
        self.assertAlmostEqual(100e-3, vIN("100m", 0))

    def test_sinWave(self):
        wav = sinWave(1000, 1.0, 0.0, 10)
        self.assertEqual("SINE(0.0 1.0 1000 0 0 0 10)", wav.genWave())

    def test_squareWave(self):
        wav = squareWave(1000, 1.0, 0.0, 10, 0.5)
        self.assertEqual("PULSE(-1.0 1.0 0 1e-09 1e-09 0.0005 0.001 10)", wav.genWave())

    def test_triangleWave(self):
        wav = triangleWave(1000, 1.0, 0.0, 10, 0.5)
        self.assertEqual("PULSE(-1.0 1.0 0 0.0005 0.0005 0.0 0.001 10)", wav.genWave())

def main():
    """main program.
    
    process options
    """
    parser = argparse.ArgumentParser(description=versionSTR)
    parser.add_argument('--F', nargs=1, help='frequency (default=1k[Hz])')
    parser.add_argument('--A', nargs=1, help='amplitude (defalut=1[v])')
    parser.add_argument('--O', nargs=1, help='offset (defalut=0[v])')
    parser.add_argument('--D', nargs=1, help='duty (defalut=0.5)')
    parser.add_argument('--T', nargs=1, help='type (sine square triangle defalut=sine)')
    parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False)
    args = parser.parse_args()

    if args.VERSION:
        print( versionSTR )
        sys.exit(0)

    freq = 1.0e3
    if args.F is not None:
        freq = freqIN(args.F[0], freq)
    amp = 1.0
    if args.A is not None:
        amp = vIN(args.A[0], amp)
    ofs = 0.0
    if args.O is not None:
        ofs = vIN(args.O[0], ofs)
    dty = 0.5
    if args.D is not None:
        dty = chkVal(args.D[0], dty)

    wav = sinWave(freq, amp, ofs, 10)
    if args.T is not None:
        if args.T[0].startswith("sq"):
            wav = squareWave(freq, amp, ofs, 10, dty)
        elif args.T[0].startswith("tr"):
            wav = triangleWave(freq, amp, ofs, 10, dty)

    print(wav.genWave())

    sys.exit(0)

if __name__ == "__main__":
    main()