iinsouciant / THE-SHRUBBERS

SJSU 2021-22 ME 195 senior project with Summer Selness, Ryan Sands, and Gustavo Garay
1 stars 0 forks source link

Butterworth Low Pass Filter #7

Closed iinsouciant closed 2 years ago

iinsouciant commented 2 years ago

Sensor noise and external disturbances may cause unstable or undesired responses in the system. To combat this, we have chosen to implement a second order Butterworth filter -- a type of low pass filter. The benefit of a second order Butterworth filter compared to a first order RC low pass filter by increasing attenuation and reducing the transition band.

image Credit: Curio Res

By using the second order filter, we marginally increase the complexity while notably improving desired behavior. Our goal is to implement this in discrete time to better read the sensor values from the sonar, pH (#6), and EC (#5) sensors. To design ours for the Raspberry Pi, we loosely followed the procedure from Curio Res' tutorial.

To design the desired filter, we must first choose a cutoff frequency to create the filter transfer function and its coefficients. image

where s_k is the k-th pole of the transfer function.

We first assume DC gain G = 1 and normalize cutoff frequency (w_c = 1) which will then be modified to fit the chosen cutoff frequency. This allows us to easily calculate the Butterworth characteristic equation as a sum. image image image

We can then modify the normalized polynomial with our chosen cutoff frequency. image ![image] (https://user-images.githubusercontent.com/17360719/154150570-40cc8f0f-c9a9-4934-b67f-3b260c3d00d0.png)

import numpy as np
from scipy import signal

a = np.zeros(self.n + 1)
gamma = np.pi / (2.0 * self.n)
a[0] = 1  # first coeff always 1
for k in range(self.n):
     rfac = np.cos(k * gamma) / np.sin((k+1) * gamma)
     a[k+1] = rfac*a[k] 
# Adjust for cutoff frequency
self.b = np.zeros(self.n + 1)
for k in range(self.n + 1):
      self.b[self.n - k] = a[k] / pow(self.wc, k)
# coefficients stored in b 

We can then use scipy to discretize these coefficients:

denom = self.b
lowPass = signal.TransferFunction(1, denom)
dt = 1.0/self.sf
discreteLowPass = lowPass.to_discrete(dt, method='gbt', alpha=0.5)
self.num = discreteLowPass.num
self.den = -discreteLowPass.den

The number of coefficients should come out to be n+1 or one greater than the order of the filter. We then turn that into an equation we can use to compute the new filtered value. image

Nb = len(self.num)
# check if we have prior values to pass into filter
try:
       self.f_vals
except NameError:
       self.f_vals = np.zeros(self.n)
       self.s_vals = np.zeros(self.n)

# use coeffs and prior discrete values to get new filtered value
filt_new = self.num[0]*s_val
for f, y, a, b in zip(reversed(self.f_vals), reversed(self.s_vals), self.den[1:], self.num[1:]):
        filt_new +=  a*f + b*y

We can then store the new filtered value and sensor values for subsequent uses.