Z变换工程实战:收敛域、极点到数字滤波器与离散控制 在数字信号处理和离散控制这两块地里干活的人几乎都绕不开一个坎连续域里的拉普拉斯变换刚用顺手一碰到采样序列、差分方程摆在面前的工具就换成了Z变换。很多人第一次学它是被离散版拉普拉斯这句话带过去的结果做题靠背变换对真到了要设计一个数字滤波器、或者把一个模拟控制器离散化下到板子上时才发现当年没抠明白的收敛域、极点位置、频率畸变全都变成现场调试时莫名其妙的振荡和溢出。这篇东西不打算按教材目录再捋一遍定义和证明那是课本的活。我想做的是把Z变换里那些真正会在工程现场起作用的部分挑出来讲清楚它为什么非存在不可、z平面上的几何到底对应什么物理量、变换对和性质里哪几条最容易用错、逆变换三条路各自适合什么场景、系统函数怎么读、连续域设计往离散域搬的时候代价在哪、以及上机验证时哪些数值坑会让你的滤波器直接炸掉。适合已经学过一遍但用得磕磕绊绊的人也适合正在做数字滤波、离散控制、或者要把一段连续模型落到嵌入式代码里的朋友。1. 从能不能不用Z变换说起离散信号分析为什么非得引入这一层1.1 傅里叶变换在离散序列上撞到的三堵墙离散时间傅里叶变换DTFT定义成 X(e^{jω}) Σ x[n]e^{-jωn}看着挺美但它有一个前置条件这个级数得收敛。对于像 u[n] 这种既不衰减也不增长的序列级数在ω0附近就已经发散了对于 a^n u[n] 且 |a|1 这种指数增长的序列更是连边都沾不上。这是第一堵墙——不是所有有用的序列都存在DTFT。第二堵墙是代数上的。离散系统的输入输出关系是一阶或者高阶的差分方程形如 y[n] -Σa_k y[n-k] Σb_k x[n-k]。你想解这个方程用DTFT当然也能做但卷积定理在收敛条件上绑手绑脚而Z变换能把差分方程直接变成z的有理函数代数式移位变成乘 z^{-1}求解退化成解代数方程这是质的差别。第三堵墙最关键稳定性信息藏在圆外DTFT看不见。DTFT只取单位圆上的值也就是 |z|1 这一条线上。可一个系统的极点如果跑到单位圆外面去了DTFT在单位圆上照样能算出一个频响曲线看起来还挺正常但系统其实已经发散了。Z变换是全平面的函数极点在哪儿、单位圆在不在收敛域里一目了然。做工程的人最怕的就是曲线好看但系统是坏的Z变换恰恰是避免这件事的工具。提示DTFT不是被Z变换取代了它是Z变换在单位圆上的一个特例切片前提是单位圆落在收敛域内部。这个前提被忽略的时候算出来的频响就是没有物理意义的。1.2 把 e^{jωn} 换成 z^n一个加了个衰减因子的小动作从 e^{jωn} 到 z^n字面上只是把 jω 换成了复数 z。但这个替换的威力在于模长可调。e^{jωn} 的模永远是1所以级数里每一项都不衰减稍不留神就发散而 z^n 的模是 r^n只要取 r1无穷远处的项就被压下去了右边序列的级数就收敛了。反过来要处理左边序列n 趋于负无穷取 r1 反而能把左边压住。这就是收敛域ROC概念的来源它不是数学上的补丁而是你主动选的衰减因子所对应的那一圈区域。同一个代数表达式配不同的ROC对应的是完全不同的两个序列。a^n u[n] 和 -a^n u[-n-1] 的Z变换都是 1/(1-az^{-1})前者ROC是 |z||a|后者是 |z||a|。只看表达式不看ROC等于只看了一半题目。2. z e^{sT} 这条映射链z平面上的每块几何都在说物理2.1 从拉普拉斯到Z的那一步代换信号被周期T采样之后离散序列的拉普拉斯变换里会出现 e^{-snT} 这样的项。令 z e^{sT}整个表达式就化简成 z^{-n} 的幂级数这就是Z变换。所以严格来讲Z变换不是另一个变换而是采样信号的拉普拉斯变换在变量替换后的形式。把 s σ jω 代进去z e^{σT}·e^{jωT}。模长 |z| e^{σT}幅角 ∠z ωT。这两句几乎把整个复平面映射关系讲完了s平面区域对应z平面区域物理含义左半平面σ0单位圆内部|z|1衰减模态稳定的连续极点虚轴σ0单位圆|z|1等幅振荡临界右半平面σ0单位圆外部|z|1增长模态不稳定实轴正实轴单调指数平行于虚轴、间隔 2π/T绕原点一圈频谱周期延拓最后一行是采样带来的本质后果s平面沿虚轴每走 2π/T映射到z平面就是绕单位圆转一整圈所以这个映射是多对一的。连续域里无穷多条平行的带宽被折叠到同一个z平面上。混叠现象在复平面上的样子就是这条多对一映射。2.2 收敛域的形状规律三种序列三种ROCROC的形状不是随机的它由序列的延展方向决定规律非常干净右边序列n≥N₁ROC 是 |z| r_max即最外侧极点之外的区域。因果序列属于这一类。左边序列n≤N₂ROC 是 |z| r_min最内侧极点之内。双边序列ROC 是一个圆环 r₁ |z| r₂夹在两层极点之间。有限长序列如果 n 从0到NROC 是除 z0 外的整个平面如果还含负n则要挖掉 z0 或 z∞。有个细节几乎每个初学者都会栽ROC永远不会包含极点。极点处的X(z)是无穷大级数当然不收敛所以ROC的边界一定由极点划出来。看到极点第一反应应该是这是一条边界而不是这是一个特征点。2.3 同一个X(z)表达式对应多个序列靠ROC消歧义前面提过 a^n u[n] 和 -a^n u[-n-1] 共享同一个表达式。这件事在工程上有个很实在的推论如果你只拿到了一个传递函数的系数没拿到ROC信息你是无法唯一确定时域响应的。同样的系数配 |z||a| 是一个因果稳定的衰减序列配 |z||a| 是一个反因果的发散序列。实际工作里怎么避免这个坑绝大多数数字滤波器设计流程默认因果系统所以ROC自动取最外侧极点之外。但如果做的是图像处理里的零相位滤波比如前后各滤一遍的那种做法系统本质上就是非因果的ROC 会横跨单位圆这时候用因果极点全在圆内那套判据去套就会得出错误结论。碰到这类场景老老实实用ROC 是否包含单位圆来判断稳定性而不是套因果判据。3. 常用变换对与性质真正高频使用的只有一小撮3.1 必须闭着眼睛写出来的变换对变换对不需要背一整本表真正高频的也就七八条。下面这张表里的每一条我在工作中几乎都手推过至少一次建议至少记住前四条时域 x[n]Z变换 X(z)收敛域δ[n]1全平面u[n]z/(z-1)|z|1a^n u[n]z/(z-a)|z||a|n·a^n u[n]az/(z-a)²|z||a|cos(ω₀n)u[n]z(z-cosω₀)/(z²-2zcosω₀1)|z|1sin(ω₀n)u[n]z·sinω₀/(z²-2zcosω₀1)|z|1-a^n u[-n-1]z/(z-a)|z||a|(n1)a^n u[n]z²/(z-a)²|z||a|注意表中我用的是z/(z-a) 这种分子带z的写法而不是 1/(1-az^{-1})。两种写法等价但工程上更推荐前者原因到逆变换那节会讲——它直接对应部分分式之后乘回一个z这个标准动作不容易漏步骤。正弦余弦这一对的形式各家教材略有差异差别主要在分子上有没有那个 z。判断方法很简单把 z→∞ 代进去看极限值是不是等于 x[0]。cos(0)1而 z(z-cosω₀)/(z²-2zcosω₀1) 在 z→∞ 时趋于1对得上sin(0)0带 z 的分子在 z→∞ 时趋于0也对得上。用初值定理做自检比死记硬背靠谱得多。3.2 时移性质双边和单边是两回事双边Z变换的时移性质干净利落x[n-k] ↔ z^{-k}X(z)ROC 除了可能在 z0 或 z∞ 处有增减之外不变。所以在解差分方程时把它当普通代数运算用就行。麻烦出在单边Z变换上。如果序列在 n0 时不为零那么 x[n-k]u[n] 的变换里会多出一串初始条件项Z{x[n-k]u[n]} z^{-k}·[ X(z) Σ_{m1}^{k} x[-m]z^{m} ]很多人做手算时把这个尾巴丢了结果解出来的响应和仿真对不上。丢掉的项恰好就是系统初始储能贡献的那部分响应。如果题目明确说零初始条件这些项自然是零可以省但在分析带初值的系统、或者做递推公式推导时漏掉它就会得出错误的零输入响应。3.3 初值定理和终值定理条件比结论重要初值定理如果 x[n] 是因果序列那么 x[0] lim_{z→∞} X(z)。这个用起来几乎没有风险我常拿它做快速验算——算完一个变换对先看 z→∞ 的极限对不对能挡掉一大半低级错误。终值定理lim_{n→∞} x[n] lim_{z→1}(1-z^{-1})X(z)。这条被滥用的程度远超它应得的关注度。它的成立条件是(1-z^{-1})X(z) 的所有极点都在单位圆内或者说 X(z) 的极点都在单位圆内、至多在 z1 处有一个一阶极点。如果X(z)有个极点在 z1.2那 x[n] 是指数发散的可你套公式一算还能算出一个有限的稳态值来——这个数纯属虚构。我在做数字控制器稳态误差分析时被这条坑过一次当时算的是单位阶跃输入下的稳态跟踪误差系统开环传递函数里有个不太起眼的极点位置算错了小数点落在单位圆外结果公式给的稳态误差是个很漂亮的常数实际仿真里输出早就冲出去了。从那以后我养成了一个习惯用终值定理之前先把极点位置打印一遍确认没有漏网的圆外极点再套公式。4. 逆变换的三条路线什么时候该用哪一条4.1 部分分式展开为什么标准动作是先除以z这是最常用的方法也是手算最靠谱的方法。它的核心操作不是直接对 X(z) 做部分分式而是先算 X(z)/z 的部分分式再整体乘回 z。这个绕一步的原因值得说透。如果直接对 X(z) 分解比如 X(z) 1/((z-0.5)(z-0.8))拆成 A/(z-0.5) B/(z-0.8)查表得到的是 A·0.5^{n-1}u[n-1] 这种带 n-1 的形式不仅要处理时移还很容易在n0处搞错边界。而先除以z变成 X(z)/z 1/(z(z-0.5)(z-0.8))拆完之后再乘回z得到的是 A·z/(z-0.5) 这种形式直接查表就是 A·0.5^n u[n]没有多余移位边界天然正确。拿个具体例子走一遍。设 X(z) z²/((z-0.5)(z-0.8))ROC 为 |z|0.8因果。先算X(z)/z z/((z-0.5)(z-0.8)) A/(z-0.5) B/(z-0.8)用留数法求系数A 0.5/(0.5-0.8) -5/3B 0.8/(0.8-0.5) 8/3。于是X(z) -5/3 · z/(z-0.5) 8/3 · z/(z-0.8)查表得 x[n] [-5/3·(0.5)^n 8/3·(0.8)^n]u[n]。验算一下x[0] -5/3 8/3 1而 X(z) 在 z→∞ 时极限是1初值定理吻合。x[∞] 0因为两个极点都在单位圆内衰减型合理。这三步自检花不了三十秒但能挡住绝大多数符号错误。4.2 留数法重复极点时的标准处理逆变换的围线积分定义是 x[n] (1/2πj)∮_C X(z)z^{n-1}dzC 取在ROC内部、逆时针环绕原点。用留数定理展开就是ROC内所有极点的留数之和。单极点的留数简单Res lim_{z→p}(z-p)X(z)z^{n-1}。真正容易出事的是重极点。m 阶极点的留数公式是Res 1/(m-1)! · lim_{z→p} d^{m-1}/dz^{m-1}[ (z-p)^m X(z)z^{n-1} ]对 z^{n-1} 这一项求导会带出 (n-1)(n-2)... 这样的组合因子最后结果里出现 n 的多项式这正是重极点对应多项式乘指数型时域响应的来源。求导这一步手算特别容易掉系数我一般会用两种办法交叉验证一是对 n0,1,2 手工递推算几个值二是直接写代码做数值逆变换比对。还有个边界情况要留意当 n 取负值时X(z)z^{n-1} 在原点附近的奇异性会变化有时候需要把 z∞ 处的留数也算进来相当于对围线外部的极点反向求和注意符号。如果只处理因果序列n≥0且系统是因果的这部分一般不用管做双边序列分析时就必须考虑。4.3 幂级数长除法不求闭式解只求前几个数把 X(z) 写成 z^{-1} 的幂级数 X(z) x[0] x[1]z^{-1} x[2]z^{-2} ...然后用分子除以分母按 z^{-1} 的升幂顺序做长除商的前几项就是序列的前几个采样值。这个方法得不到闭式表达式但它的优点是纯机械操作不需要因式分解、不需要求留数只要系数写对就一定能算出来。什么时候用它两个场景。第一是拿到一个新系统想看它的冲激响应前几个点判断有没有异常的瞬时跳变长除法做两三次就有感觉了。第二是做交叉验证用部分分式算完闭式解代 n0,1,2,3 得到一串数再用长除法或者直接写代码递推算一遍两个结果对上才敢往报告里写。4.4 一条实用的选择原则我自己的习惯是需要闭式解并且要给物理洞察比如分析模态、判断衰减速度走部分分式碰到高阶或者重极点、手算不现实直接上数值工具需要快速验算用幂级数展开或者递推。千万别硬扛着对八阶系统做手算部分分式那是浪费时间还可能算错用代码把分母多项式的根求出来分解系数自然就有了。5. 系统函数 H(z) 与差分方程一张零极点图能读出系统的全部行为5.1 从差分方程到 H(z) 的机械映射线性常系数差分方程 y[n] -Σ_{k1}^{N}a_k y[n-k] Σ_{k0}^{M}b_k x[n-k]两边做Z变换零初值整理得H(z) Y(z)/X(z) (Σ b_k z^{-k}) / (1 Σ a_k z^{-k})分母写成 1 Σa_k z^{-k} 是工程惯例因为这样 a_k 就是差分方程里反馈项的系数不用取负号。H(z) 的零点来自分子多项式极点来自分母多项式。这个映射是纯机械的差分方程的系数直接就是传递函数的系数不需要任何推导。工程上这个映射的价值在于双向可用。拿到别人给的一串滤波器系数可以立刻还原出差分方程写进代码里跑反过来从物理需求出发设计出的零极点位置也能直接变成代码里的系数。中间没有黑箱。5.2 稳定性判据和它的边界因果系统的稳定性判据只有一句话全部极点在单位圆内。这个结论其实来自绝对可和 ⟹ 有界输入有界输出稳定而因果系统加上ROC在最外侧极点之外两者一结合单位圆要落在ROC内就只能要求最外侧极点也在圆内。但这句话有两个前提经常被忘掉第一只对因果系统成立。非因果系统完全可以是稳定的——只要ROC横跨单位圆就行哪怕有极点在圆外。图像处理里的零相位滤波、语音处理里的双向滤波都属于这类用因果判据去判会误判。第二极点在单位圆上属于临界情况。z1 处的极点对应的是一阶不稳定阶跃输入下输出线性增长z-1 对应不衰减的交替振荡。工程上严格来说都不算稳定但很多人会写等幅振荡所以稳定这是危险的表述。真正需要这类特性的场合很少通常只在振荡器设计里刻意为之。顺便说个实践中的细节极点接近单位圆时系统会有很高的 Q 值时域上表现为长尾振荡频域上表现为尖锐的谐振峰。这时候系数只要有一丁点量化误差极点就可能被推到圆外。所以极点位置和极点距离单位圆的余量这两个指标要一起看后者在定点实现里往往是决定性的。5.3 频率响应就是让 z 沿着单位圆走一圈频率响应 H(e^{jω}) 就是把 H(z) 里的 z 取成 e^{jω}让 ω 从 0 走到 π对应数字频率的 0 到奈奎斯特频率。几何意义很直观从每个零点指向单位圆上那一点画一个向量长度相乘从每个极点也画一个向量长度相除。幅频响应就是这两组向量长度的比值相频响应就是它们角度之差的总和。这个几何视角解释了很多现象。极点靠近单位圆在小角度范围内从极点到单位圆的距离变得很小分母变小幅频就出现峰零点落在单位圆上那个频率点的幅频被彻底压到零这就是陷波器的做法。设计一个要滤掉 50Hz 工频干扰的滤波器本质上就是在对应数字频率处放一个零点。注意这里的 ω 是数字角频率单位是弧度每采样点和连续域的 rad/s 差一个采样周期 T。看到 ω0.1 时先确认单位是 0.1 rad/sample 还是 0.1 rad/s混淆之后算出来的滤波器截止频率会差好几个数量级。6. 把连续域设计搬到离散域三种离散化方法各自的代价6.1 冲激响应不变法混叠是绕不过去的最直观的离散化思路是让离散冲激响应采样自连续冲激响应h[n] T·h_c(nT)。这个方法的好处是时域波形保真脉冲响应形状一致。但频域上采样会导致频谱周期延拓也就是把 H_c(jω) 沿着频率轴以采样频率为周期复制叠加。如果原来的连续系统是低通、带宽远小于采样率叠加的副本基本不重叠效果很好如果原来是高通或带阻高频分量直接被折叠到低频出来的滤波器频响面目全非。所以冲激响应不变法的适用边界很明确只适合低通和窄带带通带宽要显著低于采样率的一半。高通、带阻基本别用。这是教科书上会写但很多人不往心里去的结论我见过不止一次有人拿它做高通然后奇怪为什么通带里全是波纹。6.2 双线性变换与频率预畸变没有混叠但频率轴被拉伸双线性变换用的是代换 s (2/T)·(z-1)/(z1)反解 z (1sT/2)/(1-sT/2)。这个映射的漂亮之处在于它把整个 s 平面左半边一对一地压到单位圆内部把整条虚轴一次性卷到单位圆上没有任何重叠。所以它没有混叠问题高通、带阻随便用。代价是频率轴的非线性畸变。虚轴上的 ω_a 和单位圆上的 ω_d 满足ω_a (2/T)·tan(ω_d·T/2)低频时 tan(x)≈x两者近似线性对应越靠近奈奎斯特频率tan 拉伸越剧烈ω_d 趋于 π 时 ω_a 趋于无穷。也就是说你在连续域设计的频率点映射到离散域后会跑偏。解决办法是预畸变先在目标数字频率 ω_d 处反算出对应的连续频率 ω_a (2/T)tan(ω_d T/2)用这个 ω_a 去设计连续域滤波器再做双线性变换。这样关键频率点比如截止频率能精确落位其他频率点仍然会畸变但通常可以接受。举个具体数字感受一下T1ms采样率1000Hz想在 100Hz 处设截止。数字频率 ω_d 2π·100/1000 0.628 rad。预畸变后 ω_a 2000·tan(0.314) ≈ 2000×0.3254 ≈ 650.8 rad/s对应 103.6Hz。如果不做预畸变直接按100Hz628 rad/s设计再变换实际截止频率会落在 96.6Hz 附近偏了3.4%。截止频率越高这个偏差越大到 400Hz 时偏差能到十几个百分点。6.3 零阶保持器那半个采样周期的延迟不是小事实际系统里连续对象前面挂的是DAC它做的是零阶保持。把零阶保持 连续对象整体离散化得到的传递函数是H(z) (1-z^{-1})·Z{ H_c(s)/s }这个做法在数字控制里几乎是标配因为它精确描述了实际硬件的输入输出关系。但零阶保持会引入大约 T/2 的等效相位延迟在闭环带宽接近采样率十分之一的时候这点相位延迟会明显吃掉相位裕度。我在调一个电流环的时候吃过这个亏连续域算出来的相位裕度有 55 度离散化之后实测阶跃响应有过冲振荡回头一算相位裕度只剩 30 度出头差的二十多度就是零阶保持延迟贡献的。后来把采样率提高了一倍问题才缓解。所以做数字控制采样率不是随便定的它直接进入稳定性预算。经验上闭环带宽不要超过采样频率的十分之一到五分之一具体要看延迟预算。另一个常被忽略的点零阶保持离散化得到的 H(z) 分子分母阶数关系和连续域不一样通常分子会多一拍延迟也就是 b₀0。如果你的代码里按b₀ 非零的假设分配内存或者做实时优化这个结构差异会导致逻辑出错。7. 上机验证把纸面推导跑一遍比检查十遍手算都快7.1 用 Python 验证变换对和收敛域验证变换对最简单的方式就是在某个具体的 z 上算级数求和和公式值对比。下面这段代码验证 a^n u[n] 的变换import numpy as np a 0.9 n np.arange(0, 200) x a ** n # 取收敛域内的一个 z比如 |z| |a| z 1.2 X_series np.sum(x * z ** (-n)) # 级数直接求和 X_closed z / (z - a) # 闭式解 print(f级数求和: {X_series:.10f}) print(f闭式解 : {X_closed:.10f}) print(f误差 : {abs(X_series - X_closed):.2e}) # 故意取收敛域外的 z 试试 z_bad 0.8 print(f域外级数: {np.sum(x * z_bad ** (-n)):.4f} (发散无意义))把 z 取在 0.8小于 |a|0.9在ROC之外级数会明显发散这个对比能很直观地建立ROC不是可选项的直觉。我建议每个学期至少做一次这样的实验比背十遍定义管用。7.2 极点和稳定性先打印再画图拿到一组滤波器系数第一件事不是画频响而是算极点模长import numpy as np from scipy import signal b [0.2, 0.4, 0.2] # 分子系数 a [1.0, -1.6, 0.89] # 分母系数 z, p, k signal.tf2zpk(b, a) print(极点:, p) print(极点模长:, np.abs(p)) print(最大模长:, np.max(np.abs(p)), 稳定 if np.max(np.abs(p)) 1 else 不稳定)这个习惯非常值钱。因为频响曲线在极点位于圆外时看起来可能完全正常——它只是在单位圆上取值看不到圆外的极点。尤其是在做仿真时模型跑个几百个点看不出问题结果上板子跑久了就溢出。我的规则是任何一组新系数进代码之前先过一遍极点模长检查最大模长超过 0.999 就要警觉。7.3 高阶直接型的数值事故与二阶节拆分这是数字滤波器实现里最经典的一个坑。一个八阶以上的直接型DF-I 或 DF-II滤波器系数本身就极其敏感——极点位置对系数的偏导数在高Q情况下可以达到很大的数值。典型现象是用双精度浮点算 freqz频响曲线完美把系数写成 16 位定点频响直接歪掉甚至时域跑着跑着就发散。根因在于极点聚簇。八阶巴特沃斯低通的极点会挤在靠近单位圆的一小片区域里系数上一个最低有效位的跳动就可能让某对共轭极点整体平移一个可观的距离越过单位圆。标准解法是拆成二阶节biquad / SOS级联。每节只负责一对共轭极点系数敏感度从整体降到了局部。验证方法很直接from scipy import signal import numpy as np b, a signal.butter(8, 0.2) # 8阶低通 sos signal.tf2sos(b, a) # 拆成二阶节 w1, h1 signal.freqz(b, a, worN4096) # 直接型 w2, h2 signal.sosfreqz(sos, worN4096) # 二阶节级联 print(两种实现的最大频响偏差:, np.max(np.abs(h1 - h2)))双精度下两者几乎一致但一旦把系数量化到定点差距就出来了。所以我给的建议是工程实现一律用 SOS 或格型结构不要用高阶直接型哪怕浮点平台也尽量用 SOS因为可移植性和可控性更好。7.4 定点实现时最阴的一个坑极限环定点实现还有一个浮点里根本不会出现的问题——零输入极限环。原因是舍入操作本身是非线性的反馈回路里一旦引入了量化误差系统在输入为零时也可能维持一个小幅度的持续振荡。幅度通常在几个最低有效位量级听起来不大但如果后面还挂着积分器或者高增益环节它会被放大成实打实的误差。缓解办法有几条采用饱和舍入而不是截断舍入截断会在直流附近产生固定偏差更容易激励极限环在反馈路径上串联极小的噪声整形或者干脆提高字长让量化台阶远小于信号动态范围。选择哪条取决于资源预算但至少要意识到这个现象存在别把实测的微小振荡当成测量噪声忽略了。8. 几个反复踩到的坑和一份可以贴在显示器边的排查清单把这么多年的经验压缩一下最后这部分是我遇到频率最高的几类问题按症状—根因—处置整理成表方便对照排查症状可能的根因处置频响曲线正常但系统发散极点跑到单位圆外而频响看不到圆外先算极点模长不要只看曲线手算响应和仿真对不上单边Z变换时移漏了初始条件项检查序列 n0 部分是否为零稳态误差公式给出漂亮但错误的值终值定理条件不满足有圆外极点套用前先检查 (1-z⁻¹)X(z) 的极点逆变换结果总是差一个 n 的移位部分分式没先除 z改成先算 X(z)/z 再乘回 z高通滤波器通带波纹异常用了冲激响应不变法换双线性变换截止频率对不上理论值双线性变换没做预畸变按 ω_a(2/T)tan(ω_dT/2) 反算定点实现跑久了自己振荡零输入极限环改饱和舍入或提高字长高阶滤波器频响在定点下畸变直接型系数敏感度过高拆成二阶节级联再补充几条不在表里的注意事项都是文档里不太会写、但实际会咬人的采样周期 T 的量纲问题。差分方程的系数里b_k 往往带着 T 或者 T² 这样的因子尤其是从连续域零阶保持离散化出来的。如果代码里混用了不同来源的系数而它们的 T 定义不一致输出会莫名其妙地差一个常数倍数。我现在的做法是每一个系数数组都配一份注释写清楚采样周期和来源别嫌啰嗦。freqz 的 worN 参数。默认的 512 点在窄带滤波器上经常太粗糙看不出谐振峰的细节也看不出通带波纹。我一般直接给 4096 或者 8192代价只是几毫秒的计算时间。单位圆上的角度和实际频率的换算。ω 2πf/f_sf_s 是采样频率。这个换算我见过至少三次算错最后结果差了一倍或者十倍。养成先写单位、再代数入值的习惯。零相位滤波的适用边界。前后各滤一遍filtfilt确实能得到零相位响应但它的平方幅频特性意味着等效阶数翻倍过渡带会变陡通带波纹也会被平方放大。用它之前先确认原滤波器的通带波纹足够小。滤波器阶数不是越高越好。阶数越高过渡带越陡但群延迟越大、数值越敏感、定点实现越危险。很多时候一个四阶滤波器加上过采样效果远好过一个十六阶滤波器硬扛。我现在的取舍原则是先看能不能用采样率换阶数能换就换。最后分享一个我个人用了很多年的自检流程三个步骤不到一分钟拿到任何一个离散系统先算极点模长看稳定性再用初值定理验 x[0] 或者直流增益把 z1 代进去看是不是期望的增益最后用长除法或者递推算前五个采样值。这三步走完绝大多数低级错误都会现形。Z变换这套东西纸面上的优雅固然迷人但真正让人少加班的是这些琐碎但硬核的核对动作。