我想用一个低成本的USB声卡和GNU Radio(一个开源软件开发工具包)来尝试模拟和数字通信。在动手之前,我得先弄清楚它会如何影响信号。这就引出了一个概念:扫频信号。
这张图片由艺术家 fiolisaviy 绘制。
我受到 Qasim Chaudhari 的 无线通信课程 的启发,他在课程中使用 GNU Radio 和声卡演示了射频通信。但如果我的便宜声卡失真严重,他的演示就无法正常进行。一种测量失真的方法是使用脉冲波形(图1A)。脉冲波形难以生成且处理起来很麻烦,因为大量的能量需要被压缩到很短的时间间隔内。我更愿意将脉冲分解成它的构建模块——正弦波(图1B),并用它们来工作。每个正弦波以独特的频率振荡,提供关于声卡在该频率下如何失真声音的信息。
图1:(A)一个由有限数量的正弦波组成的脉冲。(B)当这些正弦波相加时会生成脉冲。并非所有的正弦波都被显示出来。(C)橙色的双箭头是为了在(D)中将脉冲的高度与正弦波的振幅进行比较的参考。所有的橙色双箭头高度都一样。
将大振幅脉冲分解为小振幅正弦波并依次发送这些正弦波,可以降低我的测量系统的传输幅度需求。图1中,图中所有橙色的双箭头表示相同的振幅高度。脉冲(图1C)的振幅是单个正弦波(图1D)的振幅的许多倍。如果我知道声卡如何失真每个正弦波,我就能预测声卡如何失真任何波形(即信号)。
来创建一个 chirp 声音吧但是这种解决方案还有一些遗留问题。当正弦波一个接一个地发送时,它们的末端可能无法连接。这种效果在图2A中更加突出了,其中不连续之处在图2B中用黑色箭头标出。这些不连续性会导致失真。这些不连续性可以通过让相邻的正弦波在同一位置开始和结束来减少。现在要解决的是减少正弦波之间频率变化的问题,因为这种变化会产生不良影响。可以通过平滑增加每个时间步长中的正弦波频率来最小化这种频率变化。如果这种平滑变化呈线性,则称为线性啁啾(图2C)。术语“线性”是因为频率随时间变化的值是常数。在图2D中,这种恒定的变化表现为线性啁啾的频谱图中的一条线的斜率。
图2:从脉冲组织正弦波的几种方式。(A)将正弦波堆叠在一起,但是这里仅示例显示了三个。(B)在末端出现的不连续性将导致失真。(C)通过随时间线性增加信号频率来平滑组合,这种组合方式被称为线性啁啾。(D)线性啁啾的频谱图中频率变化表现为一条直线。频率的变化斜率是常数,并且是时间的线性关系。
接下来的文章将介绍在GNU Radio中生成线性 chirp 的四种方法。我将使用GNU Radio自带的构建模块,并定性地比较这些方法的结果。在后续的文章中,我们将使用通过 make_chirp_for_GNURadio_conf.ipynb 创建的参考 chirp 信号来进行定量分析。
这四个实现通过调整相位增量来控制正弦波的频率。低频正弦波使用较小的相位增量,因此每个周期的时间更长(图3A)。高频正弦波使用更大的相位增量,用更少的样本完成一个周期(图3B)。
图3:在固定采样率条件下对比低频和高频生成。(A) 低频生成的相位步长较小。(B) 高频生成的相位步长较大,且样本较少。
如图2C所示,线性啁啾可以通过随时间线性增加相位步长来实现。这将导致频率平滑变化,对应于增加的相位步长(图4A和图4B)。相位步长也可以被视作圆周上的一段弧,其中整个圆的周长代表一个完整正弦波的相位。更大的弧对应更大的每样点相位步长,从而生成更高的频率的正弦波。
在本文的其余部分中,我将把线性啁啾简称为 chirp。
图4:生成具有平滑增加相位增量的线性啁啾。(A) 相位增量随时间线性增加(红点),对应频率也呈线性增长。(B) 相位增量在旋转圆圈上以红色弧线表示。较大的红色弧线表示每个样本的相位增量更大,从而产生更高的频率。(C) 在(C)中,较大的红色弧线显示了频率的增加。图顶部的数字代表样本编号。跳过一些样本使图形更易于理解。
在 GNU Radio 中生成啁啾信号我用来生成啁啾的四种方法分别是,它们主要区别在于如何从线性变化的相位中创建啁啾。
- 数字积分器啁啾信号生成器。
- 相位步长平方啁啾信号生成器。
- 浮点相位增量啁啾信号生成器。
- 基于整数的相位增量啁啾信号生成器。
我将从数字积分型 chirp 生成器开始,因为它最贴近 chirp 的解析定义。
数字 chirp生成器这种方法利用锯齿波形生成器(图5A)来输出线性增加的相位步长,然后通过递归滤波器(图5B)将它们相加(积分)以计算啁啾的总相位值。这个总相位值被用来作为“幅度和相位到复数”块的输入来生成正弦波(图5)。数学推导在此链接中:https://github.com/potto216/tds-tutorials/blob/master/course/tds-200/week_01/notebooks/TDS_Part_1-chirp_basics.ipynb,图5中的示例源文件为:chirp_with_integrator.grc。虽然这种方法遵循了数学原理,但它也有一些局限性。
- 数值溢出问题:总相位不断累积,可能会导致溢出——尽管在浮点数运算中不会发生这种溢出,但在整数或定点数实现中,溢出是一个潜在的风险。
- 精度问题:在浮点数实现中,相对较大的总相位值,较小的相位增量可能因浮点数精度限制而无法正确更新。
为了缓解这些限制,锯齿波发生器可以从负相位步长开始(图5A),以防止总相位积累到过大,或者可以定期重置积分器(图5B)。如果都不采取这两种方法,总相位将无限增加,最终会导致数值溢出,或者如果使用浮点数,相位增量与总相位的比率会变得小于浮点数所能表示的最小步长,因此增量过程将停止。Eli Hughes 在他的三部曲视频系列《固定点数学》中对此问题做了很好的解释:《固定点数学》系列视频(https://www.youtube.com/watch?v=bbFBgXndmP0&list=PLWM8NW5LEukizzSBHNYUk1fo8Rhg1ALMF)。
图5:(这不是一个实际实现。)使用锯齿波发生器和数字积分器实现啁啾波。(A) 锯齿波发生器通过线性增加的相位步长来生成啁啾波。(B) 数字积分器累加相位增量以生成一个相位值,该值在“Magnitude and Phase to Complex”模块中转换为波形。(C) 水平图用于可视化啁啾波随时间变化的频率。(D) 随时间变化的相位值,可用于调试。(E) 啁啾波的时间域波形可视化。
为了防止总相位变得太大,我启动了锯齿波发生器(图5A),其负相位增量与正相位增量(图5D和图6D)相互抵消,这样总相位在不同啁啾之间就不会累积增长(图5B)。负相位增量会产生负频率(图6C)的副作用,因此需要完美平衡,以防止偏差不会导致总相位值在多次啁啾间逐渐增加。运行GNU Radio脚本时,图6中的输出啁啾可视化图由图5C和图5E中的可视模块生成。图6C中的频谱瀑布图的时间轴(图6A)和频率轴(图6B)与图2D中的频谱图相对比是颠倒的。此外,图6D中总相位随时间增加的线性图与图4A和图2D中的图类似,且与频率之间通过一个缩放常数和偏移量关联。
图6:数字积分器方法得到的啁啾信号的可视化。使用负相位步长来消除表现为负频率的相位缠绕现象。(A)瀑布频谱图中,时间轴是垂直方向的。(B)频率沿水平方向显示。(C)显示了四个完整啁啾的瀑布频谱图。(D)相位值(以弧度为单位)可能会变得非常大,从而导致浮点数值累积误差。(E)啁啾信号在时域中的复数值表示,包括实部和虚部。
接下来的方法通过不使用积分器避免了相关问题。相反,该方法直接实现了积分函数,即相位步阶的平方。
平方相位步进啁啾信号生成器可以直接对来自锯齿波发生器(图7A)的相位步长(图7B)进行平方,从而消除之前的集成块,以获得总的相位值。平方后的相位步长直接获得了总的相位值(图7)。该方法的数学推导可以在Jupyter笔记本中的方程5看到。图7中的示例文件chirp_with_square.grc。
当比较图7和图5时,进行积分的IIR滤波器(图5B)已被相位步长平方的积分函数(图7B)所取代,该函数输出总相位。相位步长源(图7A)不需要有负相位步长来保持总相位在多个啁啾信号中平衡,因此其相位步长的偏移为零。
图7:通过使用锯齿波生成器和相位增量的平方来生成总相位值,从而实现啁啾。 (A) 锯齿波生成器通过线性增加的相位步长生成啁啾。 (B) 乘法器将相位步长平方,生成相位值,然后在“幅度和相位到复数”块中将其转换成波形。 (C) 瀑布图用于可视化啁啾随时间变化的频率。 (D) 可视化随时间变化的相位值。 (E) 时域啁啾波形可视化。
如图8所示,平方技术的输出与图6中积分技术的输出相类似。不过有一点不同,现在啁啾的最小频率为零(图8C),如图8B所示。
图8:相位增量平方法产生的啁啾信号的可视化。当相位值增大时,不会累积误差。(A)瀑布频谱图中的频率沿水平轴显示,而不是垂直轴。(B)时间沿垂直方向显示。(C)瀑布频谱图展示了三个完整的啁啾信号和两个部分啁啾信号。(D)通过相位增量平方计算的相位值。(E)啁啾信号的复时域表示,包括实部和虚部。
虽然平方有效,但它存在动态范围的限制,较大的值可能导致数值不稳。不过,它避免了积分法中常见的漂移和溢出问题(见图8)。
平方方法解决了积分器方法中的漂移和溢出问题,但平方方法必须在整个啁啾过程中跟踪总相位值,这会导致相位值变得非常大。这可能会给浮点数的表示带来问题,也可能在基于整数的系统中可能导致溢出。实际上,跟踪啁啾的总相位是不必要的,因为啁啾由正弦波组成,每个正弦波周期在360度或2π弧度处重复,因此可以在每个正弦波的开始重置总相位。接下来的方法就是这样做的。
浮点相位增量调频生成器浮点相位增量啁啾信号发生器通过保持相位值在0到2π弧度之间,避免处理过大的总相位值。我在一次演讲中详细介绍了这种方法,演讲题目是《通过累加生成啁啾》(Generating a chirp by addition)。简单来说,该方法通过锯齿波生成器(图9A)为啁啾生成器块(图9B)提供相位增量,后者用于生成正弦波。啁啾生成器块是运行Python代码的一个GNU Radio模块。图9的源文件在这里:chirp_float_wrap.grc。
图9:使用锯齿波生成器来提供相位增量,该增量被输入到执行相位求和的模块中,该模块对相位值进行指数运算。(A)锯齿波生成器生成线性增加的相位增量,以实现 chirp。(B)chirp 生成器块将相位增量作为输入,并将其添加到当前相位值,用于生成该相位的指数分量。
在chirp生成器块中使用的Python代码(如下)用于生成chirp信号,该代码会取一组相位增量值,并将其与之前运行的累积相位和初始相位值相加,这保证了相位值在不同运行之间的一致性。然后将总相位值输入到复指数函数中,该函数生成一个正弦和余弦值的向量,我称之为正弦波³,然后输出。最终的相位值将作为下次运行的初始相位值。
# 角度增量
# 确保每个块的开始角度包括结束角度,以保持 chirp 连续
input_angle_delta_rad[0] = self.start_angle_rad + input_angle_delta_rad[0]
# 累加增量以得到实际角度
input_angle_rad = np.cumsum(input_angle_delta_rad)
# 计算复指数函数
output_values = np.exp(1j * input_angle_rad)
# 防止角度超出范围
self.start_angle_rad = (input_angle_rad[len(input_angle_rad)-1] + np.pi) % (2 * np.pi) - np.pi
此方法可以通过使用整数来适应没有浮点运算功能的系统。
基于整数相位增量的啁啾信号生成器对于需要整数运算的系统,比如低成本嵌入式平台,可以通过整数来实现相位增量和总相位值的计算。此方法将相位步长映射到整数,并通过模运算对这些整数求和并归一化(见图10)。现在,锯齿波生成器(见图10A)输出的是相位步长的整数。这些步长由“整数积分器”模块(见图10B)进行累加,该模块运行Python代码(代码如下),并通过模块的“Integer_Module”参数(例如图10中的4,096)重置累加和。可以将图10中的当前值除以4096,然后乘以2π来转换为弧度,得到当前的相位步长。图10的源文件为chirp_integer_wrap.grc,可以在该链接中查看。
图10:使用锯齿波发生器为相位累加块提供相位增量来实现一个啁啾信号,该块累积相位增量。所有值都是整数。(A) 锯齿波发生器生成线性增加的相位步长,由整数值255提供,从而得到256个相位步长的啁啾频率分辨率。(B) 啁啾信号发生器模块将整数相位增量作为输入,并将其加到具有4096个计数的当前相位上,该值并通过乘以常数块转换为弧度相位。(C) 幅度到相位模块根据相位值生成复指数。
下面的代码与之前的浮点版本功能类似,但使用了整数。该代码块的输出是整数形式的相位,在转换为“幅度和相位到复数”模块之前,需要将其转换为弧度单位。这通过乘以常数 (2π/4,096) 来实现。
in0 = input_items[0] # 整数输入
out = output_items[0] # 浮点输出
# 对输入信号进行累加
for i in range(len(in0)):
self.accumulator += in0[i]
self.accumulator = self.accumulator % self.integer_modulus
out[i] = self.accumulator
结论
通过这些方法,我现在有了声卡频率响应的扫频工具。下一步是用声卡发送啁啾信号,并用同一声卡录制相同的啁啾信号。通过比较发送和接收的啁啾信号,我可以测量失真及延迟。我期待看到这四种方法的结果如何对比。
要了解更多关于线性啁啾信号理论和应用的内容,可以看看:
-
关于频率调制连续波(也称为啁啾)的讲座。这是我学习啁啾时看的讲座,讲得很棒。
- 我关于啁啾的另一场视频演讲,更侧重于设计。
备注:写作时用了ChatGPT 4。
1[1] 存在权衡。依次传输构成带限信号的所有正弦波会大大延长测量的持续时间。此外,这种方法无法捕捉到大振幅失真或随时间变化的特性。
[2] 不同频率的两个正弦波在末端相连时,频率差的跳变会使得该区域的瞬时带宽范围变大,从而导致分析中出现误差。
[3] 在本文中,“幅度和相位到复数”和“Chirp生成器”这两部分生成复指数信号,但在本文中,我将复指数信号称为正弦波。如果有需要,可以提取复指数的虚部来得到正弦波。