diff --git a/__pycache__/fft.cpython-312.pyc b/__pycache__/fft.cpython-312.pyc index 7352838..457f226 100644 Binary files a/__pycache__/fft.cpython-312.pyc and b/__pycache__/fft.cpython-312.pyc differ diff --git a/__pycache__/fft_meas.cpython-312.pyc b/__pycache__/fft_meas.cpython-312.pyc index 1168680..24e313b 100644 Binary files a/__pycache__/fft_meas.cpython-312.pyc and b/__pycache__/fft_meas.cpython-312.pyc differ diff --git a/__pycache__/fundamental_freq_meas.cpython-312.pyc b/__pycache__/fundamental_freq_meas.cpython-312.pyc new file mode 100644 index 0000000..129b1d4 Binary files /dev/null and b/__pycache__/fundamental_freq_meas.cpython-312.pyc differ diff --git a/__pycache__/measurements.cpython-312.pyc b/__pycache__/measurements.cpython-312.pyc new file mode 100644 index 0000000..f94a016 Binary files /dev/null and b/__pycache__/measurements.cpython-312.pyc differ diff --git a/__pycache__/zerocross_meas.cpython-312.pyc b/__pycache__/zerocross_meas.cpython-312.pyc index 46bbb66..1b548f4 100644 Binary files a/__pycache__/zerocross_meas.cpython-312.pyc and b/__pycache__/zerocross_meas.cpython-312.pyc differ diff --git a/fft_meas.py b/fft_meas.py index 3bf0318..49f5cc6 100644 --- a/fft_meas.py +++ b/fft_meas.py @@ -5,7 +5,11 @@ import fft class fft_meas(object): """docstring for fft_meas""" + # 61000-4-7 says 200ms + # 10-cycles for 50Hz + # 12-cycles for 60Hz def __init__(self, fs=50e3, tn=0.2): + self.fs = fs self.ts = 1/fs self.tn = tn @@ -19,25 +23,94 @@ class fft_meas(object): self.b = 0 self.c = 0 self.Y_C = 0 + + # 61000-4-7: 3.2.3 + # Upto the 40 Harmonic per 3.3.1 Note 2 + # Harmonic order + self.Y_H = np.zeros(40) + # 61000-4-7: 3.2.3 + # Upto the 40 Harmonic per 3.3.2 Note 2 + # Grouped Harmonic order + self.Y_g = np.zeros(40) + # 61000-4-7: 3.2.2 + self.h = np.zeros(40) + + self.THD = 0 + self.THDG = 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 + self.base_freq = 0 + def step(self, y, base_freq, unit): + + self.idx += 1 + + self.data[self.idx] = y + + 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.calc_Y_H(base_freq) + self.calc_THD() + + self.calc_Y_g(base_freq) + self.calc_THDG() + + + + self.idx = -1 + + + def calc_Y_H(self, base_freq): + # 61000-4-7: 3.2.3 + if np.abs(base_freq-50) < np.abs(base_freq-60): + self.base_freq = 50 + else: + self.base_freq = 60 + + if self.base_freq == 50: + for k in range(len(self.Y_H)): + self.Y_H[k] = self.Y_C[int(10*k)] + self.h[k] = self.freq[int(10*k)] + elif self.base_freq == 60: + for k in range(len(self.Y_H)): + self.Y_H[k] = self.Y_C[int(12*k)] + self.h[k] = self.freq[int(12*k)] + + def calc_THD(self): + # 61000-4-7: 3.3.1 + self.THD = 0 + for k in range(2, len(self.Y_H)): + self.THD += np.power(self.Y_H[k]/self.Y_H[1],2) + self.THD = np.sqrt(self.THD) + + def calc_Y_g(self, base_freq): + # 61000-4-7: 3.2.4 + if np.abs(base_freq-50) < np.abs(base_freq-60): + self.base_freq = 50 + else: + self.base_freq = 60 + + if self.base_freq == 50: + for k in range(len(self.Y_g)): + self.Y_g[k] = self.Y_C[int(10*k)-1] + self.Y_C[int(10*k)] + self.Y_C[int(10*k)+1] + self.h[k] = self.freq[int(10*k)] + elif self.base_freq == 60: + for k in range(len(self.Y_g)): + self.Y_g[k] = self.Y_C[int(12*k)-1] + self.Y_C[int(12*k)] + self.Y_C[int(12*k)+1] + self.h[k] = self.freq[int(12*k)] + + def calc_THDG(self): + # 61000-4-7: 3.3.1 + self.THDG = 0 + for k in range(2, len(self.Y_H)): + self.THDG += np.power(self.Y_g[k]/self.Y_g[1],2) + self.THDG = np.sqrt(self.THDG) + print(self.THDG) + def plot(self): #fs, a, b, c, YC, phi = ftt.fft(Ph1, sample_freq) @@ -47,3 +120,11 @@ class fft_meas(object): plt.title("Simple plot") plt.show() + def plot_Y_H(self): + + plt.stem(self.h, self.Y_H, use_line_collection=True) + plt.xlabel("Harmonic order h") + plt.ylabel("RMS value Y_H,h") + plt.title("Harmonic spectrum (IEC 61000-4-7: 3.2.3)") + plt.grid(True) + plt.show() \ No newline at end of file diff --git a/fundamental_freq_meas.py b/fundamental_freq_meas.py new file mode 100644 index 0000000..e39a88a --- /dev/null +++ b/fundamental_freq_meas.py @@ -0,0 +1,51 @@ +import numpy as np +import matplotlib.pyplot as plt + + +class fundamental_freq_meas(object): + """docstring for fft_meas""" + def __init__(self, fs=50e3, tn=10): + self.fs = fs + self.ts = 1/self.fs + self.tn = tn + self.time = 0 + self.y_old = 0 + self.tn_start_time = 0 + self.tn_end_time = 0 + self.zerocross_count = 0 + # Calculated Frequency + self.freq = 50 + + self.t_zc = 0 + + def step(self, y, zerocross_flag): + + # Integrate time + self.time += self.ts + + if zerocross_flag: + denom = (y - self.y_old) + if denom != 0.0: # avoid divide-by-zero + # linear interpolation to find zero-crossing time + frac = -self.y_old / denom + self.t_zc = (self.time - self.ts) + frac * self.ts + + # store first crossing time + if self.zerocross_count == 0: + self.tn_start_time = self.t_zc + + self.zerocross_count += 1 + self.tn_end_time = self.t_zc # always keep last crossing time in this window + + # window finished (independent of whether a crossing happened exactly here) + if self.time >= self.tn: + if self.zerocross_count >= 2 and self.tn_end_time > self.tn_start_time: + self.freq = (self.zerocross_count - 1) / (self.tn_end_time - self.tn_start_time) + + # reset window state + self.zerocross_count = 0 + self.tn_start_time = 0.0 + self.tn_end_time = 0.0 + self.time -= self.tn # avoids drift vs. self.time = 0 + + self.y_old = y diff --git a/main.py b/main.py index baac427..03b4774 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,6 @@ import numpy as np -import fft_meas -import zerocross_meas +import measurements # to do @@ -12,33 +11,38 @@ import zerocross_meas def main(): time = 0 delta_t = 1e-6 - t_stop = 1.1 + t_stop = 1.2 + + ADC_sample_frequency = 50e3 + ADC_sample_period = 1/ADC_sample_frequency + ADC_timer = 0 amplitude = 1 - base_freq = 50 + base_freq = 60 + Meas_ADC = measurements.measurements(fs=ADC_sample_frequency) - 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) + if time - ADC_timer >= ADC_sample_period: + ADC_timer += ADC_sample_period - fft_measurements.step(data=Ph1, time=time, f_H1=zerocross.freq, unit="I") + Meas_ADC.Interupt(Ph1) time += delta_t + Meas_ADC.print() + Meas_ADC.plot() - zerocross.print() - #fft_measurements.plot() - diff --git a/measurements.py b/measurements.py new file mode 100644 index 0000000..2b0652e --- /dev/null +++ b/measurements.py @@ -0,0 +1,58 @@ +import numpy as np +import matplotlib.pyplot as plt + +import IIR_filter + +import zerocross_meas +import fundamental_freq_meas +import fft_meas + + +class measurements(object): + """docstring for fft_meas""" + def __init__(self, fs=50e3): + # Sample Frequency + self.fs = fs + # Sample Period + self.ts = 1/self.fs + + # Low Pass Filter + # fs = Sample Frequency, fc = LP Cutoff Frequency + self.LPfilter = IIR_filter.IIR_1_Order_LP(fs=self.fs, fc=200) + + + + # Zero Crossing Algorithem + # fs = Sample Frequency, fc = LP Cutoff Frequency + self.zerocross = zerocross_meas.zerocross_meas() + + # Fundamental Frequency Algorithem + # fs = Sample Frequency, tn = Time Period for calculations + self.base_freq = fundamental_freq_meas.fundamental_freq_meas(fs=self.fs, tn=1) + + # FFT Algorithem + # fs = Sample Frequency, tn = Time Period for calculations + # 61000-4-7 says 200ms + # 10-cycles for 50Hz + # 12-cycles for 60Hz + self.fft = fft_meas.fft_meas(fs=self.fs, tn=0.2) + + + def Interupt(self, y): + + # Filter Input + y_hat = self.LPfilter.step(y) + # Zerocross detection (self.zerocross.flag) + self.zerocross.detect(y_hat) + # Calculate the Fundamental frequency (self.base_freq.freq) + self.base_freq.step(y_hat, self.zerocross.flag) + # Calculate the FFT of the signal + self.fft.step(y, self.base_freq.freq, unit="I") + + + def print(self): + print(self.base_freq.freq) + + def plot(self): + self.fft.plot_Y_H() + diff --git a/zerocross_meas.py b/zerocross_meas.py index 9c8e432..924696d 100644 --- a/zerocross_meas.py +++ b/zerocross_meas.py @@ -6,35 +6,25 @@ 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 + def __init__(self): + + # Old value 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 + # Zerocross flag + self.flag = False - if time - self.freq_ts > self.tn: - self.freq = self.idx / self.tn - self.freq_ts = time - self.idx = 0 + def detect(self, y): + self.flag = 0 + + # If positive zerocross is detected + if self.y_old < 0 and y > 0: + self.flag = True + + self.y_old = y - self.y_old = self.y def print(self):