Simple Finite Impulse Response Notch Filter
Pieter P
This is an example on how to design a very simple FIR notch filter in the digital domain,
that can be used to filter out 50/60 Hz mains noise, for example.
It is a very simple filter, so the frequency response is not great, but it might be all you need.
It's only second order, finite impulse response, so very fast and always stable, and has linear phase.
Zero Placement
To achieve notch characteristics, we'll just place a single complex pair of zeros on the unit circle.
Let's say we have a sample frequency of
We'll first calculate the normalized frequency
A Proper Transfer Function
The transfer functions of all physical processes are proper. This means that
the degree of the numerator is not larger than the degree of the denominator.
This is clearly not the case for
The solution is simple: we'll just add two poles in
Normalization
If we want a filter with a unit DC gain (
Numerical Result
Finally, we can just plug in the value of
Bode Plot & Zero Map
Let's take a quick look at the bode plot and the locations of the zeros. The Python code to generate the Bode plot can be found below. You can clearly see the expected linear phase of a FIR filter, with a 180 phase jump when the frequency crosses the zero. The notch itself is not at all narrow. If you want a narrower notch, you could a higher FIR order, or place some poles close to the unit circle to cancel the effect of the zero.
The magnitude of the transfer function in function of the complex variable
The red curve is the image of the unit circle in the complex plane
(
Note the two zeros on the unit circle that cause the magnitude to go to
#!/usr/bin/env python3
from scipy.signal import butter, freqz
import matplotlib.pyplot as plt
from math import pi, cos
import numpy as np
f_s = 360 # Sample frequency in Hz
f_c = 50 # Notch frequency in Hz
omega_c = 2 * pi * f_c # Notch angular frequency
omega_c_d = omega_c / f_s # Normalized notch frequency (digital)
h_0 = 2 - 2 * cos(omega_c_d)
b = np.array((1, -2 * cos(omega_c_d), 1)) # Calculate coefficients
b /= h_0 # Normalize
a = 1
print("a =", a) # Print the coefficients
print("b =", b)
w, h = freqz(b, a) # Calculate the frequency response
w *= f_s / (2 * pi) # Convert from rad/sample to Hz
plt.subplot(2, 1, 1) # Plot the amplitude response
plt.suptitle('Bode Plot')
plt.plot(w, 20 * np.log10(abs(h))) # Convert to dB
plt.ylabel('Magnitude [dB]')
plt.xlim(0, f_s / 2)
plt.ylim(-60, 20)
plt.axvline(f_c, color='red')
plt.subplot(2, 1, 2) # Plot the phase response
plt.plot(w, 180 * np.angle(h) / pi) # Convert argument to degrees
plt.xlabel('Frequency [Hz]')
plt.ylabel('Phase [°]')
plt.xlim(0, f_s / 2)
plt.ylim(-90, 135)
plt.yticks([-90, -45, 0, 45, 90, 135])
plt.axvline(f_c, color='red')
plt.show()