前回、ハーフブリッジ回路もどきを駆動するシミュレーションを行うためにPWM波形を使用したいと思いました。ハーフブリッジなのでノンオーバラップ期間のある2相ね。ところが電圧源でPULSE波形を指定するのに数値指定に難渋しました。忘却力の頭では暗算できんと。そこで1年数か月ぶりに波形生成スクリプトに手を入れることに。
※「SPICEの小瓶」投稿順インデックスはこちら
※ Analog Devices, Inc. LTspice を使用させていただいて動作確認しております。今回使用のバージョンは以下です。
XVII(x64) (17.0.37.0)
waveGen.pyスクリプト
LTspiceでシミュレーションを行う場合、電圧源(Voltage Source)が備える各種の関数にパラメータを与えて所望の波形を得ることが多いと思います。正弦波は勿論、PULSE波やら各種の波形を生成可能です。しかし「メンドイ」のが各種パラメータを数値で与えないといけないことです。それも三角波とか矩形波とかパターンジェネレータだったら一発で生成できそうなシンプルな奴らが以外とメンドイっす。その対策でかねてより使っております自前ツールが waveGen.pyというPython3のスクリプトです。コマンドラインで周波数とか振幅とか波形タイプなどを与えるとLTspiceの電圧源のところにコピペできる行を生成するだけのもの。-h を与えて起動すると以下のように使用方法が表示されます。
今回更新内容
約2年近くぶりの更新内容は以下です。
-
- Dutyを指定したときに発生するバグフィックス
- –T pwm を指定することでPWM波形(2相)を出力
- –N n オプション追加。繰り返し回数指定可能とした
2番のPWM波形(2相分)を生成するのがモチベーションになりましたが、何気にずっとほったらかしてあった1番と3番にも手をいれました。
PWM波形は、SQUARE波形とは異なり0Vから振幅指定までの「デジタル」っぽい波形です。オフセットは無視。デューティを指定することで所望の波形を得るのですが、その際「逆相」のPWM波形も同時に生成します。例えばデューティ0.4と指定すると第1の波が0.4の期間ONになった後OFFったのち、0.1のノンオーバラップ期間を置いて第2の波が0.4の期間ONになりという具合です。今のところは対称な波形のみ。
ソース
Python3(動作確認バージョンは3.10.11)のソースが以下に。
#!/usr/bin/python # coding: utf-8 ### @file waveGen.py ### @brief LTspice waveform Generator ### ### @author: Joseph Halfmoon ### @date: April 1, 2022 ### @update: January 8, 2023 """ LTspice waveform generation script v0.2 input ===== --F frequency --A amplitude --O offset --D duty --N number of repetition --T type. (sine square triangle pwm) unittest ======== python -m unittest waveGen.uTestCase """ import argparse import sys import unittest versionSTR = "waveGen.py v0.2" 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 pwmWave(squareWave): """PWM wave Generation class pwmWave 0.0. """ def __init__(self, frequency, amplitude, offset, nCyc, duty): super(pwmWave, self).__init__(frequency, amplitude, offset, nCyc, duty) self.vinit = 0 self.von = self.amp self.dly1 = 0.0 self.dly2 = self.ton + (self.tperiod * (1-duty) - self.ton) / 2 def genWave(self): w1 = "PULSE({0} {1} {2} {3} {4} {5} {6} {7})".format(self.vinit, self.von, self.dly1, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc) w2 = "PULSE({0} {1} {2} {3} {4} {5} {6} {7})".format(self.vinit, self.von, self.dly2, self.risTIM, self.falTIM, self.ton, self.tperiod, self.nCyc) return w1 + "\n" + w2 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('--N', nargs=1, help='number of repetition (defalut=10)') parser.add_argument('--T', nargs=1, help='type (sine square triangle pwm 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)[0] nRep = 10 if args.N is not None: try: nRep = int(args.N[0]) except: nRep = 10 wav = sinWave(freq, amp, ofs, nRep) if args.T is not None: if args.T[0].startswith("sq"): wav = squareWave(freq, amp, ofs, nRep, dty) elif args.T[0].startswith("tr"): wav = triangleWave(freq, amp, ofs, nRep, dty) elif args.T[0].startswith("pw"): wav = pwmWave(freq, amp, ofs, nRep, dty) print(wav.genWave()) sys.exit(0) if __name__ == "__main__": main()
使用例
今回の更新の目玉のPWM波形の指定をした場合が以下に。デューティ40%のPWMということ以外は後はデフォルト値(周波数1kHz、ピークツーピークの振幅1V、繰り返し回数10回)です。
2行出力されたPULSE指定の行をLTspiceの2つの電圧源にコピペして得た信号が以下に。
手で書くとかなりメンドイのよ。