From 728e95a8b6230363e810dc3a8272e12986a07ebd Mon Sep 17 00:00:00 2001 From: Michael Buesch Date: Sun, 21 Jul 2019 13:38:14 +0200 Subject: simulator: Add some circuit simulators Signed-off-by: Michael Buesch --- firmware/simulator/circuits/pwm.py | 83 +++++++++++++++++++++ firmware/simulator/circuits/rcnet.py | 102 ++++++++++++++++++++++++++ firmware/simulator/circuits/sine.py | 67 +++++++++++++++++ firmware/simulator/circuits/voltagedivider.py | 49 +++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 firmware/simulator/circuits/pwm.py create mode 100644 firmware/simulator/circuits/rcnet.py create mode 100644 firmware/simulator/circuits/sine.py create mode 100644 firmware/simulator/circuits/voltagedivider.py diff --git a/firmware/simulator/circuits/pwm.py b/firmware/simulator/circuits/pwm.py new file mode 100644 index 0000000..dc330f5 --- /dev/null +++ b/firmware/simulator/circuits/pwm.py @@ -0,0 +1,83 @@ +""" +# Xytronic LF-1600 +# Open Source firmware +# PWM calculation +# +# Copyright (c) 2019 Michael Buesch +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +__all__ = [ + "PWMGenerator", +] + +class PWMGenerator(object): + __slots__ = ( + "f", + "duty", + "cycleT", + "out", + "__errors", + ) + + def __init__(self, f, duty=0.0): + self.f = max(f, 0.0) + self.duty = max(min(duty, 1.0), 0.0) + self.cycleT = 0.0 + self.out = False + self.__errors = 0 + + @property + def cycleDur(self): + return 1.0 / self.f + + def calc(self, dt): + if self.f: + self.cycleT += dt + dur = self.cycleDur + wrap = False + if self.out: + if self.cycleT >= dur: + if self.duty < 1.0: + self.out = False + wrap = True + else: + if self.cycleT >= dur * (1.0 - self.duty): + if self.duty > 0.0: + self.out = True + else: + wrap = True + if wrap: + self.cycleT -= dur + if self.cycleT >= dur: + if self.__errors < 10: + print("The PWM frequency %f Hz is " + "too fast. Loosing steps." % ( + self.f)) + self.__errors += 1 + self.cycleT = 0.0 + return self.out + +if __name__ == "__main__": + import time + pwm = PWMGenerator(f=0.5, duty=0.3) + dt = 0.1 + i = 0.0 + while 1: + out = pwm.calc(dt) + print("t=%.1f, out=%d, cycleT=%.3f" % (i, int(out), pwm.cycleT)) + time.sleep(dt) + i += dt diff --git a/firmware/simulator/circuits/rcnet.py b/firmware/simulator/circuits/rcnet.py new file mode 100644 index 0000000..352c57a --- /dev/null +++ b/firmware/simulator/circuits/rcnet.py @@ -0,0 +1,102 @@ +""" +# Xytronic LF-1600 +# Open Source firmware +# R/C network calculation +# +# Copyright (c) 2019 Michael Buesch +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import math + +__all__ = [ + "RCNet", +] + +class RCNet(object): + """Discrete R/C network calculation. + """ + + __slots__ = ( + "r", # Resistor value, in Ohms. + "c", # Capacitor value, in Farad. + "v", # Capacitor voltage. + "i", # Network current, in Ampere. + ) + + def __init__(self, r, c, v=None, q=None): + self.r = r + self.c = c + self.v = v or 0.0 + if q is not None: + assert(v is None) + self.q = q + self.i = 0.0 + + @property + def q(self): + """Get charge, in Coulomb. + """ + return self.c * self.v + + @q.setter + def q(self, newQ): + """Set charge, in Coulomb. + """ + self.v = newQ / self.c + + def calc(self, vIn, dt): + """Calculate the next discrete step. + Returns the new C voltage. + """ + c, r = self.c, self.r + dv = vIn - self.v + fact = math.exp((-1.0 / (r * c)) * dt) + self.i = (dv / r) * fact + self.v += dv * (1.0 - fact) + return self.v + + def __str__(self): + return "v=%.3f V, i=%.3f mA, q=%.3f uC" % ( + self.v, + self.i * 1e3, + self.q * 1e6) + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + rc = RCNet(r=(10e3), + c=(220e-6), + q=0.0) + + dt = 0.01 + times = [t * dt for t in range(500)] + values = [] + for t in times: + rc.calc(vIn=(5.0 if t <= 3.0 else -2.0), + dt=dt) + print("t=%.3f s, %s" % (t, str(rc))) + values.append((rc.q * 1e6, rc.i * 1e3, rc.v)) + + fig, ax = plt.subplots(3, sharex=True) + ax[0].plot(times, [val[0] for val in values], label="Q (uC)") + ax[0].legend() + ax[1].plot(times, [val[1] for val in values], label="I (mA)") + ax[1].legend() + ax[2].plot(times, [val[2] for val in values], label="U (V)") + ax[2].legend() + ax[2].set_xlabel("t (s)") + plt.show() diff --git a/firmware/simulator/circuits/sine.py b/firmware/simulator/circuits/sine.py new file mode 100644 index 0000000..d24e8f8 --- /dev/null +++ b/firmware/simulator/circuits/sine.py @@ -0,0 +1,67 @@ +""" +# Xytronic LF-1600 +# Open Source firmware +# Sine calculation +# +# Copyright (c) 2019 Michael Buesch +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + +import math + +__all__ = [ + "SineGenerator", +] + +class SineGenerator(object): + __slots__ = ( + "f", + "peakV", + "cycleT", + "v", + ) + + def __init__(self, f, rmsV=5.0): + self.f = max(f, 0.0) + self.peakV = rmsV * math.sqrt(2) + self.cycleT = 0.0 + self.v = 0.0 + + @property + def rmsV(self): + return self.peakV / math.sqrt(2) + + @property + def cycleDur(self): + return 1.0 / self.f + + def calc(self, dt): + if self.f: + self.cycleT += dt + self.v = self.peakV * math.sin(math.pi * 2 * + (self.cycleT / self.cycleDur)) + return self.v + +if __name__ == "__main__": + import time + sine = SineGenerator(f=0.5) + dt = 0.1 + i = 0.0 + while 1: + v = sine.calc(dt) + print("t=%.1f, out=%.1f" % (i, v)) + time.sleep(dt) + i += dt diff --git a/firmware/simulator/circuits/voltagedivider.py b/firmware/simulator/circuits/voltagedivider.py new file mode 100644 index 0000000..b207bf1 --- /dev/null +++ b/firmware/simulator/circuits/voltagedivider.py @@ -0,0 +1,49 @@ +""" +# Xytronic LF-1600 +# Open Source firmware +# Voltage divider calculation +# +# Copyright (c) 2019 Michael Buesch +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" + + +class VoltageDivider(object): + """Voltage divider calculation. + """ + + __slots__ = ( + "r1", + "r2", + "i", + ) + + def __init__(self, r1, r2, i=0.0): + self.r1 = r1 + self.r2 = r2 + self.i = i + + @property + def u1(self): + return self.i * self.r1 + + @property + def u2(self): + return self.i * self.r2 + + def calc(self, uIn): + self.i = uIn / (self.r1 + self.r2) + return self.u2 -- cgit v1.2.3