Digital Signal Processing
Chapter topics covered in this session:
By the end of this session, you should be able to:
Problem context (ECE / biomedical):
Engineering target:
We cascade three second‑order IIR notch filters:
Each notch:
Combined response ≈ product of three notches.

Key ECG components in a single heartbeat:
R is the tallest positive peak; Q and S are small negatives around R.
Heart rate estimation from ECG:

Goals:
Design summary (for heart‑rate detection path):

Important
The bandpass‑filtered ECG (0.25–40 Hz) is good for heart‑rate detection, but not for full diagnostic ECG analysis (which needs ≈0.01–250 Hz).
For a second‑order digital notch filter:
We choose a pole radius \(r < 1\) from:
\[ r = 1 - \frac{BW}{f_s}\pi = 1 - \frac{4}{600}\pi \approx 0.9791 \]
Normalized digital frequency (in degrees):
\[ \theta = \frac{f_0}{f_s}\times 360^\circ = \frac{60}{600}\times 360^\circ = 36^\circ \]
Compute:
Gain scale factor \(K\):
\[ K = \frac{1 - 2r\cos\theta + r^2}{2 - 2\cos\theta} \approx 0.9803 \]
The 60‑Hz section:
\[ H_1(z) = \frac{0.9803 - 1.5862 z^{-1} + 0.9803 z^{-2}}{1 - 1.5842 z^{-1} + 0.9586 z^{-2}} \]
Difference equation (input \(x(n)\), output \(y_1(n)\)):
\[ \begin{aligned} y_1(n) &= 0.9803\,x(n) - 1.5862\,x(n-1) + 0.9802\,x(n-2) \\ &\quad + 1.5842\,y_1(n-1) - 0.9586\,y_1(n-2) \end{aligned} \]
Other two notch sections:
\[ H_2(z)=\frac{0.9794 - 0.6053 z^{-1} + 0.9794 z^{-2}}{1 - 0.6051 z^{-1} + 0.9586 z^{-2}} \]
\[ \begin{aligned} y_2(n) &= 0.9794\,y_1(n) - 0.6053\,y_1(n-1) + 0.9794\,y_1(n-2) \\ &\quad + 0.6051\,y_2(n-1) - 0.9586\,y_2(n-2) \end{aligned} \]
\[ H_3(z)=\frac{0.9793 + 0.6052 z^{-1} + 0.9793 z^{-2}}{1 + 0.6051 z^{-1} + 0.9586 z^{-2}} \]
\[ \begin{aligned} y_3(n) &= 0.9793\,y_2(n) + 0.6052\,y_2(n-1) + 0.9793\,y_2(n-2) \\ &\quad - 0.6051\,y_3(n-1) - 0.9586\,y_3(n-2) \end{aligned} \]
Note
In implementation, you typically realize each second‑order section (“biquad”) separately and cascade them: \[ y_3(n) = H_3(z)\,H_2(z)\,H_1(z)\,x(n) \]

After hum removal (\(y_3(n)\)), we still see:
We design a 4th‑order Chebyshev bandpass:
Resulting transfer function:
\[ H_4(z)=\frac{0.0464 - 0.0927 z^{-2} + 0.0464 z^{-4}}{1 - 3.3523 z^{-1} + 4.2557 z^{-2} - 2.4540 z^{-3} + 0.5506 z^{-4}} \]
Difference equation (input \(y_3\), output \(y_4\)):
\[ \begin{aligned} y_4(n) &= 0.0464\,y_3(n) - 0.0927\,y_3(n-2) + 0.0464\,y_3(n-4) \\ &\quad + 3.3523\,y_4(n-1) - 4.2557\,y_4(n-2) \\ &\quad + 2.4540\,y_4(n-3) - 0.5506\,y_4(n-4) \end{aligned} \]

Program 8.16 (conceptual structure):
ecgbn.dat.b1,a1, b2,a2, b3,a3.y1, y2, y3.bilinear), get b,a.y3 to get bandpassed ECG y4.You can encourage students to:
threshold, see impact on detected heart rate.We approximate the R‑R interval count by counting zero crossings vs. a threshold on \(y_4(n)\).
For each sample \(n\):
Define signs w.r.t. a threshold (e.g., 0.5): \[ \text{cur_sign} = \begin{cases} 1 & x(n) \ge \text{threshold} \\ -1 & x(n) < \text{threshold} \end{cases} \] \[ \text{pre_sign} = \begin{cases} 1 & x(n-1) \ge \text{threshold} \\ -1 & x(n-1) < \text{threshold} \end{cases} \]
Zero crossing at sample n: \[ \text{ZeroCross}(n) = \frac{|\text{cur_sign} - \text{pre_sign}|}{2} \]
Total zero crossings: sum over n.
Number of peaks ≈ half the zero crossings.
Heart rate estimation:
\[ HR = \frac{60}{\frac{N}{f_s}} \times \frac{\text{zero crossings}}{2} \]
In the example:
Compute HR:
\[ \begin{aligned} HR &= \frac{60}{\frac{1500}{600}} \times \frac{6}{2} \\ &= \frac{60}{2.5} \times 3 \\ &= 24 \times 3 = 72\ \text{beats per minute}. \end{aligned} \]
Tip
This is a crude detector but works reasonably well with a clean, band‑limited ECG. Modern systems rely on more sophisticated feature detection (e.g., QRS detectors).
In practice:
General IIR form (first order):
\[ H(z) = \frac{b_0 + b_1 z^{-1}}{1 + a_1 z^{-1}} \]
After quantization:
\[ H^q(z) = \frac{b_0^q + b_1^q z^{-1}}{1 + a_1^q z^{-1}} \]
Poles and zeros:
\[ z_1 = -\frac{b_1^q}{b_0^q}, \qquad p_1 = -a_1^q \]
Second‑order case:
\[ H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}} \]
\[ H^q(z) = \frac{b_0^q + b_1^q z^{-1} + b_2^q z^{-2}}{1 + a_1^q z^{-1} + a_2^q z^{-2}} \]
Zeros:
\[ z_{1,2} = -\frac{1}{2}\frac{b_1^q}{b_0^q} \pm j\left(\frac{b_2^q}{b_0^q} - \frac{1}{4}\left(\frac{b_1^q}{b_0^q}\right)^2\right)^{1/2} \]
Poles:
\[ p_{1,2} = -\frac{1}{2} a_1^q \pm j\left(a_2^q - \frac{1}{4}(a_1^q)^2\right)^{1/2} \]
Given ideal filter:
\[ H(z) = \frac{1.2341 + 0.2126 z^{-1}}{1 - 0.5126 z^{-1}} \]
We have 1 sign bit + 6 magnitude bits = 7‑bit fixed‑point, with scaling chosen so max coefficient lies in [1,2).
Scale by \(2^5\):
Quantized transfer function:
\[ H^q(z) = \frac{1.21875 + 0.1875 z^{-1}}{1 - 0.5 z^{-1}} \]
Poles and zeros (before vs. after):
Note
Even small coefficient rounding changes pole/zero positions, which changes magnitude and phase response. Here, changes are mild; but in high‑order filters, effects can accumulate.
Original design (fs = 8 kHz, passband ripple 0.5 dB, \(f_c = 3.4\) kHz):
\[ H(z) = \frac{0.7434 + 1.4865 z^{-1} + 0.7434 z^{-2}}{1 + 1.5149 z^{-1} + 0.6346 z^{-2}} \]
Quantization:
Quantized filter:
\[ H^q(z) = \frac{0.7500 + 1.484375 z^{-1} + 0.7500 z^{-2}}{1 + 1.515625 z^{-1} + 0.640625 z^{-2}} \]
Poles/zeros (approx):
Magnitude/phase comparison:

Observation:
DTMF (Dual‑Tone Multi‑Frequency):

Example: Key “7” → 852 Hz (row) + 1209 Hz (column).
System roles:
We use a second‑order IIR whose impulse response is a sinusoid:
\[ H(z) = \frac{z \sin\Omega_0}{z^2 - 2 z \cos\Omega_0 + 1} = \frac{z^{-1} \sin\Omega_0}{1 - 2 \cos\Omega_0 z^{-1} + z^{-2}} \]
With sampling rate \(f_s\) and tone frequency \(f_0\):
\[ \Omega_0 = \frac{2\pi f_0}{f_s} \]
Difference equation (input \(x(n)\), output \(y(n)\)):
\[ y(n) = \sin\Omega_0\,x(n-1) + 2\cos\Omega_0\,y(n-1) - y(n-2) \]
To generate a pure tone of amplitude A:

Given: \(f_s = 8000\) Hz, \(f_0 = 1000\) Hz.
Compute:
\[ \Omega_0 = 2\pi\frac{1000}{8000} = \frac{\pi}{4} \]
\[ \sin\Omega_0 = \sin\left(\frac{\pi}{4}\right) \approx 0.707107 \]
\[ 2\cos\Omega_0 = 2\cos\left(\frac{\pi}{4}\right) \approx 1.414214 \]
Filter:
\[ H(z) = \frac{0.707107 z^{-1}}{1 - 1.414214 z^{-1} + z^{-2}} \]
MATLAB (Program 8.18) uses impulse input to filter this system and plots:

To generate key “7”:
Implementation:

Program 8.19 (MATLAB) does:
y852 and y1209.y7 = y852 + y1209.Result:

We want to detect specific tones (e.g., only 7 DTMF frequencies) from a signal. Full FFT:
Goertzel:
Key idea: interpret a DFT bin as the output of a resonator filter excited by the signal.
DFT of length N:
\[ X(k) = \sum_{n=0}^{N-1} x(n) e^{-j\frac{2\pi kn}{N}} \]
We can rewrite in terms of convolution with a sequence \(h_k(n) = e^{j\frac{2\pi k n}{N}}\):
\[ X(k) = \sum_{n=0}^{N-1} x(n) h_k(N-n) \]
If we treat \(h_k(n)\) as an impulse response of an IIR filter:
\[ H_k(z) = \sum_{n=0}^{\infty} h_k(n) z^{-n} = \frac{1}{1 - e^{j\frac{2\pi k}{N}} z^{-1}} = \frac{1}{1 - W_N^{-k} z^{-1}} \]
After some algebra and multiplying numerator/denominator by \(1 - W_N^k z^{-1}\):
\[ H_k(z) = \frac{1 - W_N^k z^{-1}}{1 - 2\cos\left(\frac{2\pi k}{N}\right) z^{-1} + z^{-2}} \]
This is the Goertzel filter – a second‑order resonator with resonance at \(\Omega = 2\pi k/N\).
Using direct‑form II (Fig. 8.53):

Algorithm for bin k, length N:
For \(n = 0, 1, \dots, N\):
\[ v_k(n) = 2\cos\left(\frac{2\pi k}{N}\right) v_k(n-1) - v_k(n-2) + x(n) \]
Then:
\[ y_k(n) = v_k(n) - W_N^k\,v_k(n-1) \]
At n = N:
\[ X(k) = y_k(N) \]
But in many applications we only need \(|X(k)|\), not the complex phase.
Using the same state sequence \(v_k(n)\) (with \(x(N)=0\)):
\[ |X(k)|^2 = v_k^2(N) + v_k^2(N-1) - 2\cos\left(\frac{2\pi k}{N}\right) v_k(N) v_k(N-1) \]
This avoids complex multiplications, only uses real arithmetic.
A simplified filter (Fig. 8.54) has transfer function:

\[ G_k(z) = \frac{V_k(z)}{X(z)} = \frac{1}{1 - 2\cos\left(\frac{2\pi k}{N}\right) z^{-1} + z^{-2}} \]
Algorithm (modified Goertzel):
Given: \(x(0)=1, x(1)=2, x(2)=3, x(3)=4\). We want bin \(k=1\), \(N=4\).
Compute constants:
\[ 2\cos\left(\frac{2\pi}{4}\right) = 0, \quad W_4^1 = e^{-j\frac{\pi}{2}} = -j \]
Simplified recurrence:
\[ x(4) = 0 \]
For \(n=0,\dots,4\):
\[ v_1(n) = -v_1(n-2) + x(n) \]
Output:
\[ y_1(n) = v_1(n) + j v_1(n-1) \]
Compute sequence step‑by‑step (given in text), final result:
This matches what you’d get from direct DFT.
Same sequence: \(x = [1,2,3,4]\), \(k=0\), \(N=4\).
Here: \(2\cos(0) = 2\).
Recurrence:
\[ v_0(n) = 2 v_0(n-1) - v_0(n-2) + x(n) \]
After computing \(v_0(0)\dots v_0(4)\):
Magnitude:
\[ \begin{aligned} |X(0)|^2 &= v_0^2(4) + v_0^2(3) - 2 v_0(4)v_0(3) \\ &= 30^2 + 20^2 - 2\cdot30\cdot20 = 100 \end{aligned} \]
Amplitude:
\[ A_0 = \frac{1}{4}\sqrt{100} = 2.5 \]
Again, matches DFT result.
Function interface:
Logic:
vk.Verification (Example 8.28):
For DTMF: we know the 7 possible tone frequencies:
\[ \{697, 770, 852, 941, 1209, 1336, 1477\}\ \text{Hz} \]
Design:
Table 8.12 (precomputed):
| f (Hz) | k |
|---|---|
| 697 | 18 |
| 770 | 20 |
| 852 | 22 |
| 941 | 24 |
| 1209 | 31 |
| 1336 | 34 |
| 1477 | 38 |
We implement 7 parallel Goertzel filters, one for each k.

Processing steps:
For received DTMF window \(x(n)\), run modified Goertzel for each bin \(k\in\{18,20,22,24,31,34,38\}\) to get \(|X(k)|^2\).
Convert to single‑sided amplitude: \[ A_k = \frac{2}{N}\sqrt{|X(k)|^2} \]
Ideally only two \(A_k\) values are large (one row tone, one column tone).
Set a threshold: \[ \text{threshold} = \frac{\sum_{k=1}^7 A_k}{4} \]
For each k:
This yields a 7‑bit pattern that uniquely maps to one keypad digit.
Given:
\[ k_L \approx \frac{852}{8000}\cdot205 \approx 22 \]
\[ k_H \approx \frac{1209}{8000}\cdot205 \approx 31 \]
Compute coefficients:
\[ 2\cos\left(\frac{2\pi\cdot22}{205}\right) = 1.5623 \]
\[ 2\cos\left(\frac{2\pi\cdot31}{205}\right) = 1.1631 \]
Transfer functions:
\[ H_{22}(z) = \frac{1}{1 - 1.5623 z^{-1} + z^{-2}} \]
\[ H_{31}(z) = \frac{1}{1 - 1.1631 z^{-1} + z^{-2}} \]
DSP equations (with \(x(205)=0\)):
\[ v_{22}(n) = 1.5623\,v_{22}(n-1) - v_{22}(n-2) + x(n) \]
\[ v_{31}(n) = 1.1631\,v_{31}(n-1) - v_{31}(n-2) + x(n) \]
\[ |X(22)|^2 = v_{22}^2(205) + v_{22}^2(204) - 1.5623\,v_{22}(205)\,v_{22}(204) \]
\[ A_{22} = \frac{2}{205}\sqrt{|X(22)|^2} \]
\[ |X(31)|^2 = v_{31}^2(205) + v_{31}^2(204) - 1.1631\,v_{31}(205)\,v_{31}(204) \]
\[ A_{31} = \frac{2}{205}\sqrt{|X(31)|^2} \]
These two will stand out above other \(A_k\)’s.
Key parts of the script:
Generator:
key, construct yDTMF = sum of two appropriate tones.Detector:
For each target frequency bin, create denominator coefficients of Goertzel IIR:
Plot filter bank frequency responses:

Filter DTMF signal with each filter to get y697, y770, ....
Use modified Goertzel formula at n = 205,206 to compute magnitudes m(1..7).
Convert to single‑sided magnitude m = 2*m/205.
Threshold and logic:
th = sum(m)/4.m to [0 or 1] to form a 7‑bit pattern.if statements to map that pattern to a detected key.We now summarize the three IIR design approaches used in this chapter:
And give guidance on when to use which.
Steps (BLT):
Characteristics:
Procedure:
Limitations:
Use when:
Procedure outline:
Advantages:
Disadvantages:
| Design Method | Filter Types | Uses Ripple/Stopband Specs? | Special Requirements | Complexity |
|---|---|---|---|---|
| BLT | LP, HP, BP, BS | Yes | None | High (but standard) |
| Impulse Invariant | LP, BP (mainly) | Yes (via analog design) | Sampling rate >> cutoff (LP) or >> upper cutoff (BP) to avoid aliasing | Moderate |
| Pole‑Zero Placement | 2nd‑order BP/notch, 1st‑order LP/HP | No (only 3 dB level) | Narrow bands or simple edges; usually low order | Simple |
Performance example:

Notch filter design (2nd order):
Pole radius (for bandwidth BW at fs): \[ r = 1 - \frac{BW}{f_s}\pi \]
Angle for notch at \(f_0\): \[ \theta = 2\pi \frac{f_0}{f_s} \quad \text{(or } \theta^\circ = 360^\circ \frac{f_0}{f_s}\text{)} \]
Scale factor K (see text Eq. 8.50 type): \[ K = \frac{1 - 2r\cos\theta + r^2}{2 - 2\cos\theta} \]
General 2nd‑order IIR (biquad):
\[ H(z)=\frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}} \]
Difference equation:
\[ y(n) = b_0 x(n) + b_1 x(n-1) + b_2 x(n-2) - a_1 y(n-1) - a_2 y(n-2) \]
Goertzel recurrence (modified):
State update: \[ v_k(n) = 2\cos\left(\frac{2\pi k}{N}\right) v_k(n-1) - v_k(n-2) + x(n) \]
Squared magnitude: \[ |X(k)|^2 = v_k^2(N) + v_k^2(N-1) - 2\cos\left(\frac{2\pi k}{N}\right) v_k(N) v_k(N-1) \]
Single‑sided amplitude: \[ A_k = \frac{2}{N} \sqrt{|X(k)|^2} \]
Heart‑rate from zero crossings:
\[ HR\ (\text{bpm}) = \frac{60}{N/f_s} \cdot \frac{\text{zero crossings}}{2} \]
DTMF bin mapping:
\[ k = \text{round}\left( \frac{f}{f_s} N \right) \]
Tip: Use this deck to experiment with:
Try editing the loop range or the formula.
Let’s start from the 60 Hz notch filter section used in the ECG hum eliminator.
We’ll implement:
\[ H_1(z) = \frac{0.9803 - 1.5862 z^{-1} + 0.9803 z^{-2}}{1 - 1.5842 z^{-1} + 0.9586 z^{-2}} \]
and visualize its frequency response.
Use the slider to adjust the notch frequency between 40 and 100 Hz and see how the response changes.
We’ll create a synthetic ECG‑like pulse plus 60 Hz hum and see how the notch filter cleans it up.
Try: change the hum amplitude or notch frequency and re‑run.
We’ll reproduce the idea from Example 8.24 interactively:
\[ H(z) = \frac{b_0 + b_1 z^{-1}}{1 + a_1 z^{-1}} \]
Use sliders to adjust the quantized coefficients and watch:
We’ll build an interactive Goertzel analyzer for a single DFT bin.
Use sliders to:
Then we’ll compute (|X(k)|) using the modified Goertzel algorithm.
Let’s build a small DTMF generator you can experiment with.
We’ll approximate the idea of Program 8.19:
Now we’ll build a minimal DTMF detector for a single key window.
Steps:
Use these interactive blocks to:
Tip
Try to break the designs: - What happens if the sampling rate changes but the same coefficients are used? - How sensitive is the DTMF detector to frequency drift or noise?
This kind of hands‑on “stress testing” is a great way to build intuition about real‑world DSP systems.