From 473eb899a767dd0c57eafa303778684e716840d0 Mon Sep 17 00:00:00 2001 From: Michelle Date: Sat, 24 Jan 2026 21:10:50 +0100 Subject: [PATCH] Initial --- IIR_filter.py | 23 ++++++++ __pycache__/IIR_filter.cpython-312.pyc | Bin 0 -> 1276 bytes __pycache__/fft.cpython-312.pyc | Bin 0 -> 2311 bytes __pycache__/fft_meas.cpython-312.pyc | Bin 0 -> 2356 bytes __pycache__/ftt.cpython-312.pyc | Bin 0 -> 2311 bytes __pycache__/ftt_meas.cpython-312.pyc | Bin 0 -> 1569 bytes __pycache__/zerocross_meas.cpython-312.pyc | Bin 0 -> 2151 bytes fft.py | 58 +++++++++++++++++++++ fft_meas.py | 49 +++++++++++++++++ main.py | 46 ++++++++++++++++ zerocross_meas.py | 41 +++++++++++++++ 11 files changed, 217 insertions(+) create mode 100644 IIR_filter.py create mode 100644 __pycache__/IIR_filter.cpython-312.pyc create mode 100644 __pycache__/fft.cpython-312.pyc create mode 100644 __pycache__/fft_meas.cpython-312.pyc create mode 100644 __pycache__/ftt.cpython-312.pyc create mode 100644 __pycache__/ftt_meas.cpython-312.pyc create mode 100644 __pycache__/zerocross_meas.cpython-312.pyc create mode 100644 fft.py create mode 100644 fft_meas.py create mode 100644 main.py create mode 100644 zerocross_meas.py diff --git a/IIR_filter.py b/IIR_filter.py new file mode 100644 index 0000000..0c3b2c7 --- /dev/null +++ b/IIR_filter.py @@ -0,0 +1,23 @@ +import numpy as np + +import matplotlib.pyplot as plt +import fft + +class IIR_1_Order_LP(object): + """docstring for fft_meas""" + def __init__(self, fs, fc=200): + self.fs = fs + self.fc = fc + self.dt = 1/self.fs + self.RC = 1 / (2*np.pi*self.fc) + self.alpha = self.dt / (self.RC + self.dt) + + self.y = 0 + + def step(self, x): + self.y = self.y + self.alpha * (x - self.y) + return self.y + + + + diff --git a/__pycache__/IIR_filter.cpython-312.pyc b/__pycache__/IIR_filter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acd085470273be7474b2a12ba3eae6b1de3e8e2b GIT binary patch literal 1276 zcmZt_OG_I;cxHC<&?Lqe2}oNb4N9yUEa<89q7aml77IP(GNhS}uIwi1?4~xsLkiFqIj&d5xvql~8_C%o3k#>N(1rY|{DIDPS|8ezqpVRE&w9m^MaET1>2 zu4IkeAgdi)(Bk(0*%|inKBBflfPDfSywy1jbh|Ed16KoJiKtsEyqyu#>l*C(UFsTF zpdjCwGdvlFXV~ z)<~`wb7fsAm_{>b5V=8oq& z>u0IcRCnr0H@pDK`5%E`Eq*ZDhZHDxb3kGQ_M*H8qAi9ZOzs?z-t-x0#Zuj8U4;RP zRS@I3wRZPfzeWk1P7odjYd!)1=utox{$Pltu^1l{tZbM{sXB3|fkG(M#sUCnULf3D zZLA)yw>MjxotX|h%b(^?=ev`O-M}LBVfK6|)QmJDwM9Mw4(SB$eFY4k3}#fw>I$XS zFr|8tl{G;JDScPYY6FdrQuun5@^SFt5dfc&9mlSai&ok|ETbXbQ%B$N!#?qmP=B0| zQO8!8!47%~WxZ74@X3%hrf1EPRx~yBE$oY+EbjnICC#)X_~Vwh_;y#xnNFGq;C7w* p(*MjU`8j_K3bF6T6oB6n#`pq_|3cFjX!wsD!lQ?8{vtrO?|(B!0B!&P literal 0 HcmV?d00001 diff --git a/__pycache__/fft.cpython-312.pyc b/__pycache__/fft.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7352838d2c23d12f212937f4062ba033ab148756 GIT binary patch literal 2311 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 literal 0 HcmV?d00001 diff --git a/__pycache__/ftt.cpython-312.pyc b/__pycache__/ftt.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f26874bc3e050d31cbfb3e6074f5b35a3d3e2105 GIT binary patch literal 2311 zcmb7FO-vg{6yDkY^$)?60!fn+hrb$$S|qfG6t!}p$ssKgZABqUg%I;ffR8YNR&i=1#XjzAVTkB$Vi-ak$B?K zN1^ZV2wB$&4PYY{o!D~qWm>}sM>G_*Dl=?Ndp7H|1eW|n-LuZXc>Hk06QVN`xrA&r zybhL79G%8o%dE%OIsI|xlfKK`8S(rX@xmGLzBA(e9{g#wqH9%J4tS7(<5j_{XE&$| z5;ce{62RJdq&fBFTq~{fRZTmG{jf-6y{_)N8YV38`6RlJpait~3z})H;=QB89dyQk z=N=nSbmvArSvPt8s=7{i_VH6bA$mjY{FvLT>zp2RPkGijEhmDYB#r=9nY!TPRRx_Vm8cNzdyv~*xVV{ z<5(97Zk}kyxEDu|K)sfz>vW&%OBO!Rslt%Q_rmkDt1;-5PU3845mxDXiLaaeslJ>< z>;7sc7FdmWm@Ym)3tVG--Yxg#dKji?LT6D8d0jv!^e5O)hzL8fkAeI6*G|FOH2NHI zmA~GLoP$(K&Oj=e(IA=0K_-#PW);YaikO*}A+sbaSryW0S zr6x0wR`ZGmDRD}LoT$V#F>@6$g9S(?RLG1^X0mC>$vHKd%4Q%Kf#4RT6N;RNK!jil zf;glmCWj!EyPJdnA!Z?i!lz^vx9#QQ^0YU4H=CBDqLvcX=x{bZmzFb{8oim+qBoVK zG?bNOHJZ@0H*<@SN+dL;3z6WyR&Svq7{pP4V1-hsIA(;NF#KxMTASHsg{@e*W98Bg z(`tl{SVHix38sLoV!3OFZ?msf_)D9h!iSA!w9E4M#|z^n(F&V#ancCwG2F`xoN#}k zuwVfchAG}NUfDZoc!D@43KOM#@lGuR&g3kx;xj@iZQzD^RG^=$);>ZEYa+G_ALkTWs*% z``_Q#xUu=xw;xs7B0HhpiqLC>jaZ@c|KbiJ-z*UKQ8EeVIExyFr}hyeX;p!`QBr$`^S%e z8hbeQDE3JFd3fv2FQ5H3_v_^2z7KbUjmA(36mI-M2T%b64QtoU>(zAZm@RG$@A$(w zO0oN(57qNvfDnR+J7mQy)gIdB+qXgcA?~FJIWiKVAd{I(=N1+Gx8}UJhlJ9N^-eTA i*WlV&4Xr9r%Ct&By-$ literal 0 HcmV?d00001 diff --git a/__pycache__/ftt_meas.cpython-312.pyc b/__pycache__/ftt_meas.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cac1a691176c349873e7e58bbd691e2ce313e09b GIT binary patch literal 1569 zcmaJ>&1)M+6rY)0ec1Z49p!_TrXn%5H>43Q$-yNgg&5pibZN;!PzG7EveuEb&hFT$ zghJ(_Qi~XDLqT;L2!TLb{FjuTT3?i{4-V}iz8TbBa_W2gp%o?c3B7sWZ{Eyr_SaNu zk^tH9b>k%^E7xVFh^Jk$bdtWMbVf9nV)L;rM8CtT&Ii%@XRpu>SbAMAJ{h!!enqiU`O86Gu> z7Ac(IxF=C$m~J7{k`Alb4wLwAIEvAb4lOvWvF1ctx@FpqDoV2CL@G7Yw7jcx4T>SY za8zm3bxmvg>$?fjA<^GlW??wCTi z^A+i#;o4f$iF0l=49R(GIsn79z=S}x8^AhbkW6=GyK{qNYHPW-=!vf7D3RW_cR$=+ z-n;R*_4qb;-aAT9?Noc!UD@B*+c->L1e)tilC2J zli*Kd1rK5|>ny}sS!9S$YYG{@Do24^c5$Prp4AOHgZ>#{HXarLDQBI-Xw{bbS}Qd| z2C5kh`y5z@97iU$7kBRV?*0}z`$T;j+mHPod3TUG)4kqZIL=<^UjJrcb7AY#&0ELO ziS5sJ?)C0{RsSn9v-7a`@LSn$?lqs!{y6t+?l5zu^U3jKV)M@F1c_x&6C|3mEN@AnBw zOx&XGWERfpGQ^dQGj+)e`p%HW+0-zbU`a0)!{E6o@MN5*xxA{ErI(rmM_BH4{HC{! gTh?W8;Ip^!U{4iF=^teBZ}mJ)Zf*QSU~#kl1y5!q`v3p{ literal 0 HcmV?d00001 diff --git a/__pycache__/zerocross_meas.cpython-312.pyc b/__pycache__/zerocross_meas.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46bbb66d9d152643e6f7b7a650f87ae3c151ae25 GIT binary patch literal 2151 zcmZuy&2Jk;6rb5$uRr1>j-8LDBB=`@)NNX;B2a-)sf4A(QW7da9IQ$!*SijO{gv5` z8e2KIm7~Tdrza;|0ul$JAaUW)KLLpgDWIB750wy7bBk)=&=c>?de=?rk^JU;&U^Fb z_twAk_xlK}|E5vxP)2w;*#A#%TqpKbDxkrwq%5CE<0><-Mx2}s`!Jn z->C{Jf|{*WCK1=t5f;zTdh|1ph+qn79e$~ZpJ7&0FhDd4retLDnkl83wqm-GDqx^x z(^XcAIn@+1>YC|`$3IijnVg|9a{&3P%BM`#n6hxw^eo)8ED_O^bS;@+$*KB=~0wSA!8^?W$cvE zHh3{h5=fP_`~%gg+B+?O|EAhlt}oZ)mS|}Q20@CnI@?eXq?uNqAGYb%U;ws@t$y^p z@HF_wli;O;;HB@4AMftnee~|{!H=s`8<*RVgq%PYUXCN{J7{&2AVzv(2__9J4tnq$ zYu$;#3;o2gWvkHB!IGmSSqC*;-8$Xx;1k~7iGg^?cY3Mx4&-N3V094J^#LnZGO|wA zXx87=+ng#?`r+<^;vkvdb-=cF_lkAbKv!dPJi`p+?{Pk8dqi|*5MP)t-Y2IIIJvLX z)AL^7;Y?zgz*LA!d>|O!BjRIN3d3$O96125p_SMm%2*B?LV}m%SZ2@yIDw78X3{kt zY~!1aqSsjT6* zi%I5+^XL?kjJ-UyK_5pkL9;of#Ig-p=Wz`_u9_2gTR~BSS{WPjls5nh>z7UaA>3{ zKa{Jp8y_A|5IM}wK$WQutbip1c@A>C9e}(i#6Kr{bSXl4i}omQk&c03t1;NQ*BXOg zng|$ldq04gIpOt^={mM20al0Rq|ADZ%Pes-TOzU!lj6?3>HGN&u>_O^5FNXkj!g}V)_&% zUsP9f$omy#Wi^@WSlo&N|5}Q|#vqns#;Pg|;vV6bx4I^w9SLGy$M)CApLC)v_=Fqu z#hHLG_TmB+LTwp{6^70cujwkR=HcTh6iVh$K53M4MI)EF4c}AbOu4g*rJP}k@R!Mg z5N687+gUAT@PzDb&~z(#>G{aI%XM}c4A?mx+j=ZgN)Ji!Z*uOC_zua?KhiV}ZGHJK IfsKd%4;w3=ApigX literal 0 HcmV?d00001 diff --git a/fft.py b/fft.py new file mode 100644 index 0000000..1007dfb --- /dev/null +++ b/fft.py @@ -0,0 +1,58 @@ +import numpy as np + + +def fft(x, fs, X_nom=None, epsilon=None): + + # Implementation from IEC 61000-4-7:2002/AMD1:2008 + + + # Data length + N = len(x) + # Number of positive-frequency bins + K = int(np.floor(N/2)) + # Frequency axis (0 .. fs/2) + #freq = np.linspace(start=0, stop=fs/2, num=K+1, endpoint=True) + freq = np.arange(K + 1) * fs / N + + # allocate + a = np.zeros(K + 1) + b = np.zeros(K + 1) + c = np.zeros(K + 1) + Y_C = np.zeros(K + 1) + phi = np.zeros(K + 1) + + n = np.arange(N) + + # DC + c[0] = np.mean(x) # c0 per IEC + a[0] = 2 * c[0] # not really used; just for completeness + b[0] = 0.0 + + # k = 1..K + for k in range(1, K+1): + angle = 2 * np.pi * k * n / N + a[k] = (2/N) * np.sum(x * np.cos(angle)) + b[k] = (2/N) * np.sum(x * np.sin(angle)) + + # Nyquist (if N even): do NOT apply the 2/N doubling + if (N % 2 == 0) and (k == K): + a[k] *= 0.5 + b[k] *= 0.5 + c[k] = np.sqrt(a[k]*a[k] + b[k]*b[k]) + + # RMS value calculated in Eq.2. + Y_C[k] = c[k] / np.sqrt(2) + + + # Phase: apply dead-band if provided, else always compute + if (X_nom is not None) and (epsilon is not None): + if (np.abs(a[k]) <= epsilon * X_nom) and (np.abs(b[k]) <= epsilon * X_nom): + phi[k] = 0.0 + continue + + # IEC quadrant handling (equivalent to the piecewise definition) + phi[k] = np.arctan2(a[k], b[k]) + + + + return freq, a, b, c, Y_C, phi \ No newline at end of file diff --git a/fft_meas.py b/fft_meas.py new file mode 100644 index 0000000..3bf0318 --- /dev/null +++ b/fft_meas.py @@ -0,0 +1,49 @@ +import numpy as np + +import matplotlib.pyplot as plt +import fft + +class fft_meas(object): + """docstring for fft_meas""" + def __init__(self, fs=50e3, tn=0.2): + self.fs = fs + self.ts = 1/fs + self.tn = tn + self.N = int(int(fs*tn)) + self.data = np.zeros(self.N) + self.idx = -1 + self.time = 0 + + self.freq = 0 + self.a = 0 + self.b = 0 + self.c = 0 + self.Y_C = 0 + self.phi = 0 + + def step(self, data, time, f_H1, unit): + + if time - self.time > self.ts: + self.time = time + self.idx += 1 + + self.data[self.idx] = data + + if self.idx == self.N-1: + + + self.freq , self.a, self.b , self.c, self.Y_C, self.phi = fft.fft(x=self.data, fs=self.fs) + + self.idx = -1 + + + + def plot(self): + #fs, a, b, c, YC, phi = ftt.fft(Ph1, sample_freq) + + plt.plot(self.freq, self.c) + plt.xlabel("x") + plt.ylabel("y") + plt.title("Simple plot") + plt.show() + diff --git a/main.py b/main.py new file mode 100644 index 0000000..baac427 --- /dev/null +++ b/main.py @@ -0,0 +1,46 @@ + +import numpy as np +import fft_meas +import zerocross_meas + + +# to do +# Need a zero-cross algorithem to comply with 61000-4-30 + + + +def main(): + time = 0 + delta_t = 1e-6 + t_stop = 1.1 + + + + amplitude = 1 + base_freq = 50 + + + fft_measurements = fft_meas.fft_meas(fs=50e3, tn=0.2) + zerocross = zerocross_meas.zerocross_meas(fs=50e3, tn=1) + + for i in range(0, int(t_stop/delta_t)+1): + Ph1 = np.sin(2*np.pi*time*base_freq) + + + zerocross.step(x=Ph1, time=time) + + fft_measurements.step(data=Ph1, time=time, f_H1=zerocross.freq, unit="I") + + + + time += delta_t + + + zerocross.print() + #fft_measurements.plot() + + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/zerocross_meas.py b/zerocross_meas.py new file mode 100644 index 0000000..9c8e432 --- /dev/null +++ b/zerocross_meas.py @@ -0,0 +1,41 @@ +import numpy as np + +import matplotlib.pyplot as plt +import fft +import IIR_filter + +class zerocross_meas(object): + """docstring for fft_meas""" + def __init__(self, fs=50e3, tn=1): + self.fs = fs + self.ts = 1/self.fs + self.tn = tn + self.time = 0 + self.freq = 0 + self.freq_ts = 0 + self.y = 0 + self.y_old = 0 + self.idx = 0 + self.LPfilter = IIR_filter.IIR_1_Order_LP(fs=self.fs, fc=200) + + def step(self, x, time): + + if time - self.time > self.ts: + self.time = time + self.y = self.LPfilter.step(x) + + if self.y_old < 0 and self.y > 0: + self.idx += 1 + + + if time - self.freq_ts > self.tn: + self.freq = self.idx / self.tn + self.freq_ts = time + self.idx = 0 + + + self.y_old = self.y + + + def print(self): + print(self.freq)