Joseph Halfmoon
前回、ハーフブリッジ回路もどきを駆動するシミュレーションを行うために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)のソースが以下に。
### @brief LTspice waveform Generator
### @author: Joseph Halfmoon
### @update: January 8, 2023
LTspice waveform generation script v0.2
--T type. (sine square triangle pwm)
python -m unittest waveGen.uTestCase
versionSTR = "waveGen.py v0.2"
vl, lastChar = chkVal(arg, dval)
pfx = prefixes.find(lastChar)
vl, lastChar = chkVal(arg, dval)
pfx = prefixes.find(lastChar)
"""Wave Generation base class
def __init__(self, frequency, amplitude, offset, nCyc):
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
def __init__(self, frequency, amplitude, offset, nCyc, duty):
super(squareWave, self).__init__(frequency, amplitude, offset, nCyc)
self.tperiod = 1.0 / self.freq
self.ton = self.tperiod * duty
self.vinit = self.ofs - self.amp
self.von = self.ofs + self.amp
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
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)
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
def __init__(self, frequency, amplitude, offset, nCyc, duty):
super(pwmWave, self).__init__(frequency, amplitude, offset, nCyc, duty)
self.dly2 = self.ton + (self.tperiod * (1-duty) - self.ton) / 2
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)
class uTestCase(unittest.TestCase):
def test_parse2float(self):
self.assertIsNone(parse2float("1.0x"))
self.assertAlmostEqual(1.25, parse2float("1.25"))
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)
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))
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))
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())
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()
freq = freqIN(args.F[0], freq)
amp = vIN(args.A[0], amp)
ofs = vIN(args.O[0], ofs)
dty = chkVal(args.D[0], dty)[0]
wav = sinWave(freq, amp, ofs, nRep)
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)
if __name__ == "__main__":
#!/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()
#!/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つの電圧源にコピペして得た信号が以下に。
手で書くとかなりメンドイのよ。
SPICEの小瓶(38) 小信号用の手元部品でハーフブリッジ・インバータ「もどき」 へ戻る
SPICEの小瓶(40) LTspiceのテキスト形式入出力、コマケー話なんだが へ進む