Mở đầu: khi đầu ra quay lại làm đầu vào
Mọi công cụ series này xây tới giờ — DFT, FFT, FIR — đều xử lý tín hiệu theo MỘT CHIỀU: đầu vào chảy qua,
ra đầu ra, xong. Nhưng có một lớp hệ thống hoàn toàn khác: hệ ĐỆ QUY, nơi đầu ra QUAY LẠI làm một phần đầu
vào cho chính bước tính tiếp theo (gọi là feedback). Một bộ tích luỹ đơn giản
y[n] = y[n-1] + x[n] đã đủ phá vỡ mọi công cụ DTFT quen thuộc — DTFT giả định hệ thống có đáp
ứng xung HỮU HẠN, còn hệ đệ quy có thể "nhớ" mãi mãi.
Bài này giới thiệu công cụ tổng quát hơn — Z-transform — rồi khai thác một hình ảnh hình học đẹp đến bất ngờ: chỉ cần biết vị trí vài điểm trên một mặt phẳng, có thể ĐỌC RA đáp ứng tần số bằng MẮT, biết ngay hệ có ổn định hay không, và thậm chí XÂY một máy tạo dao động số chỉ từ đúng 2 điểm. Đây cũng là bài ra mắt flagship demo của cả series — Pole–Zero Filter Lab.
1. Z-transform: ngôn ngữ chung của mọi datasheet DSP
Z-transform của 1 tín hiệu $x[n]$ định nghĩa: $X(z) = \sum_n x[n] z^{-n}$, với $z$ là 1 biến PHỨC bất kỳ.
Ký hiệu $z^{-1}$ trong mọi sơ đồ khối DSP (kể cả datasheet chip thật) có nghĩa CỐ ĐỊNH: "trễ đúng 1 mẫu" —
$z^{-1} X(z)$ tương ứng với tín hiệu $x[n-1]$. Với công cụ này, một phương trình sai phân ĐỆ QUY như
y[n] = x[n] + 0.5·y[n-1] viết lại gọn gàng trong miền $z$: $Y(z) = X(z) + 0.5 z^{-1} Y(z)$ —
đại số thuần tuý, không cần "theo dõi thời gian" nữa.
2. Hàm truyền H(z) = B(z)/A(z): tên gọi thành hình ảnh
Từ phương trình sai phân, chuyển sang hàm truyền $H(z) = Y(z)/X(z) = B(z)/A(z)$ chỉ mất 3 bước cơ học: lấy Z-transform 2 vế, gom $Y(z)$ một bên $X(z)$ một bên, rồi chia. $B(z)$ (tử số) và $A(z)$ (mẫu số) đều là đa thức theo $z^{-1}$. Nghiệm của TỬ số gọi là zero (tại đó $H(z)=0$ — filter "câm"), nghiệm của MẪU số gọi là pole (tại đó $H(z) \to \infty$ — filter "hét").
// Nguoc lai: cho TRUOC vi tri pole/zero, ra NGAY he so a/b - "ten goi thanh hinh anh"
function polyFromRoots(roots) {
let coeffs = [{ re: 1, im: 0 }];
for (const r of roots) {
// ... khai trien tich (1 - r*z^-1) ...
}
return coeffs; // [c0, c1, c2, ...] - luon c0 = 1
}
// Verified: cap lien hop r=0,7, theta=pi/3 -> he so THUC c1=-2r*cos(theta), c2=r^2
3. Hình học kể chuyện: đọc đáp ứng tần số bằng mắt
Đây là insight đẹp nhất của bài: $|H(e^{j\omega})|$ tính bằng tích khoảng cách từ điểm $e^{j\omega}$ (đi bộ quanh vòng tròn đơn vị khi $\omega$ chạy từ 0 tới $\pi$) tới TỪNG zero, chia cho tích khoảng cách tới TỪNG pole:
$$|H(e^{j\omega})| = \dfrac{\prod_k |e^{j\omega} - \text{zero}_k|}{\prod_k |e^{j\omega} - \text{pole}_k|}$$
Không cần tính toán gì — chỉ cần NHÌN: zero nằm ĐÚNG TRÊN vòng tròn tại tần số nào → khoảng cách bằng 0 tại đó → $|H|=0$ → một notch khoét sạch đúng tần số đó. Pole nằm GẦN vòng tròn → khoảng cách nhỏ ở mẫu số → $|H|$ vống lên rất cao → một đỉnh cộng hưởng. Verify bằng self-test: hệ 1 pole tại $z=0,5$ (thực) cho $|H(e^{j0})| = 1/(1-0,5) = 2$ và $|H(e^{j\pi})| = 1/(1+0,5) = 0,667$ — khớp CHÍNH XÁC công thức đại số trực tiếp, không sai số.
4. Ổn định: một quy tắc, nhìn một cái biết ngay
Quy tắc ổn định của hệ đệ quy đơn giản đến bất ngờ:
MỌI pole phải nằm TRONG vòng tròn đơn vị ($|pole| < 1$). Verify bằng đáp ứng xung THẬT
(dùng iirFilterDirect(), chạy trực tiếp phương trình sai phân — chưa phải dạng tối ưu Direct
Form II Transposed của Bài 11, nhưng đủ để thấy đúng hành vi):
| Vị trí pole | Đáp ứng xung sau 100 mẫu | Ý nghĩa |
|---|---|---|
| $r = 0,9$ (TRONG vòng tròn) | $y[99] \approx 0,00003$ | Tắt dần về 0 — ổn định |
| $r = 1,1$ (NGOÀI vòng tròn) | $y[99] \approx 12.528$ | Tăng trưởng mũ — filter "nổ" |
| $r = 1,0$, $\theta=0$ (ĐÚNG trên vòng tròn) | $y[99] = 1$ (không đổi) | Không tắt, không nổ — tích luỹ mãi mãi |
| Cặp pole $r=1,0$, $\theta=\pi/4$ (trên vòng tròn) | Dao động chu kỳ đúng 8 mẫu, biên độ không đổi | Oscillator số — dùng chính hiện tượng "mất ổn định biên" để TẠO SÓNG |
5. Thực hành: Pole–Zero Filter Lab
Kéo pole (×, đỏ) và zero (○, xanh) trên mặt phẳng z — mỗi điểm tự động thêm liên hợp phức đối xứng để hệ số luôn là số thực. Đáp ứng tần số và đáp ứng xung cập nhật tức thì; bấm "Nghe" để lọc một đoạn nhiễu trắng bằng CHÍNH bộ lọc đang vẽ. Kéo pole ra ngoài vòng tròn để nghe/thấy filter mất ổn định — biên độ phát ra được TỰ ĐỘNG giới hạn để bảo vệ tai:
Đáp ứng tần số |H(f)| (dB):
Đáp ứng xung y[n]:
Kéo × (pole) hoặc ○ (zero) trên mặt phẳng z. Pole ra NGOÀI vòng tròn (viền đỏ) = mất ổn định — vẫn nghe được tiếng "nổ" nhưng biên độ đã bị giới hạn tự động, an toàn cho tai.
import { polyFromRoots, freqRespFromPZ, polesStable, iirFilterDirect, whiteNoise } from './dsp-core.js';
function updateFilter(pole, zero) {
const poles = [pole, { re: pole.re, im: -pole.im }]; // tu dong lien hop
const zeros = [zero, { re: zero.re, im: -zero.im }];
const a = polyFromRoots(poles).map((c) => c.re); // he so mau so THUC
const b = polyFromRoots(zeros).map((c) => c.re); // he so tu so THUC
const stable = polesStable(poles);
const freqResp = freqRespFromPZ(zeros, poles, 1, 200); // ve |H(f)|
const noise = Array.from({ length: 4000 }, (_, n) => whiteNoise(n, 0.3, 3));
const filtered = iirFilterDirect(noise, b, a); // LOC THAT tren nhieu trang
// an toan: chi giam am luong khi nang luong THAT SU tang truong (dang no),
// dao dong bien do khong doi (oscillator tren vong tron) van phat binh thuong
const half = filtered.length / 2;
const isExploding = maxAbs(filtered.slice(half)) / maxAbs(filtered.slice(0, half)) > 3;
const peak = maxAbs(filtered);
const safeAudio = filtered.map((v) => (v / peak) * (isExploding ? 0.12 : 0.5));
return { a, b, stable, freqResp, safeAudio };
}
Tóm lược
- ✅ Z-transform $X(z) = \sum x[n] z^{-n}$ tổng quát hoá DTFT — xử lý được cả hệ ĐỆ QUY (feedback) mà FIR/DTFT không đủ sức.
- ✅ $H(z) = B(z)/A(z)$: zero = nghiệm tử số (filter "câm"), pole = nghiệm mẫu số (filter "hét").
- ✅ Đọc đáp ứng tần số bằng HÌNH HỌC: tích khoảng cách tới zero ÷ tích khoảng cách tới pole — verified khớp chính xác công thức đại số trực tiếp.
- ✅ Ổn định ⟺ MỌI pole trong vòng tròn đơn vị — verified: $r=0,9$ tắt dần, $r=1,1$ nổ tới hơn 12.000 lần chỉ sau 100 mẫu, $r=1,0$ dao động period đúng 8 mẫu không tắt không nổ (oscillator số).
- ✅ Pitfall: pole gần vòng tròn ổn định "trên giấy" nhưng dễ tràn số khi cài đặt thật.
- ✅ Ra mắt flagship Pole–Zero Filter Lab — nền tảng tái sử dụng ở Bài 11 (Biquad Cookbook).
Trắc nghiệm ôn tập
Câu 1
Vì sao cần Z-transform thay vì chỉ dùng DTFT/DFT như các bài trước?
Câu 2
Trong H(z) = B(z)/A(z), một ZERO nằm ĐÚNG TRÊN vòng tròn đơn vị tại một tần số cụ thể có ý nghĩa gì?
Câu 3
Verified: pole tại r=1,0 (đúng trên vòng tròn), θ=π/4 cho đáp ứng xung dao động period 8 mẫu, KHÔNG tắt dần theo thời gian. Điều này ứng dụng vào đâu?
Câu 4
Verified: pole tại r=0,999 vẫn thoả điều kiện ổn định toán học (|pole|<1). Vì sao đây vẫn là một cạm bẫy thực tế?
Tải file code thực hành minh họa bài học
File JavaScript DSPJS (dsp-core.js) — thư viện DSP tự viết dùng xuyên suốt cả
15 bài, Bài 10 vừa thêm số phức đầy đủ (cộng/trừ/nhân/chia), polyFromRoots(),
freqRespFromPZ(), polesStable(), iirFilterDirect(), kèm self-test
đối chiếu đúng mọi hành vi trong bài (chạy node dsp-core.js, không cần cài thêm gì):
Bình luận