diff --git a/content-zh/about_author.rst b/content-zh/about_author.rst index a32f2baa..9129f666 100644 --- a/content-zh/about_author.rst +++ b/content-zh/about_author.rst @@ -8,16 +8,15 @@ 专长于 SDR(软件定义无线电),机器学习,LTE/5G-NR,以及频谱感知。 他是马里兰大学的兼职教授,在那里他创立了一门课程,该课程成为了他编写这本教科书的基础。 他的课程是一个面向对 SDR/DSP(数字信号处理)感兴趣的计算机科学本科生的四年级选修课。 -这门课程使他更好地了解了如何深入浅出地吸引并教会那些编程能力很强但几乎不了解物理层知识的学生。 +这门课程使他更好地了解了如何深入浅出地吸引并教会那些编程能力很强但几乎没有无线通信和信号处理背景的学生。 在马克的课程中遇到迷你黑客马拉松并不奇怪,学生们必须利用他们最近学到的知识来找到或解码(由 Marc 发送)的隐藏的信号。 Marc 也是 `GNU Radio项目 `_ 的主要负责人之一。 GNU Radio 是一个开源的 SDR 框架,在学术界和国防相关研究中被广泛应用。 -虽然 Python 非常适合学习、快速实验和开发,但它并不适合大型和计算复杂的应用程序。 -GNU Radio 能够实现更高级的 DSP 应用程序,而且使用 GNU Radio 开发的 App 或单独的 Block 非常容易与他人共享。 +GNU Radio 可以用来实现更高级的 DSP 应用程序,而且使用 GNU Radio 开发的 App 或单独的 Block 非常容易与他人共享。 -Marc 目前和他的妻子 Lindsey 以及他们的多只猫和狗一起住在华盛顿特区地区。 -他的爱好包括木工制作、激光切割、演奏单簧管/萨克斯风、帆船、园艺、制作/操纵无人机、制作/骑行电动滑板,以及钻研高级溜溜球技巧。 +Marc 目前和他的妻子以及他们的多只猫和狗一起住在华盛顿特区地区。 +他的爱好包括木工制作、机加工、激光切割、单簧管/萨克斯风、帆船、园艺和弹球机。 邮箱: marc@pysdr.org diff --git a/content-zh/digital_modulation.rst b/content-zh/digital_modulation.rst index 97adf0f2..ffdd9862 100644 --- a/content-zh/digital_modulation.rst +++ b/content-zh/digital_modulation.rst @@ -100,7 +100,7 @@ 注意,图中无线信号的纵坐标平均值为零。在讨论调制时,人们往往喜欢这样展现数据和绘图。 -我们可以使用多于两个幅度级别,从而让每个符号携带更多比特。以下是 4-ASK 的示例。 +我们可以使用多于两个幅度级别,从而让每个符号携带更多比特。以下是 4-ASK 的示例(幅度为 0 也是四种级别之一)。 在这种情况下,每个符号携带 2 比特的信息。 .. image:: ../_images/ask2.svg @@ -133,7 +133,7 @@ 上图中,顶图展示的是由红色点表示的离散采样点(即我们生成的数字信号),底图展示的是调制后的真正能在空中发出的信号。 在真实的通信系统中,载波的频率通常远远高于符号变化的频率: -在这个例子中,每个符号包含载波(正弦波)的三个周期,但在实际中可能包含数千个周期,具体取决于载波频率有多高。 +在这个例子中,每个符号包含载波(正弦波)的 2.5 个周期,但在实际中可能包含数千个周期,具体取决于载波频率有多高。如需了解更多关于 ASK 的信息,推荐参阅 `这篇资料 `_ 。 ************************ 相移键控(PSK) @@ -227,7 +227,7 @@ IQ 图/星座图 现在,让我们暂时回到 ASK 方案。 我们可以像展示 PSK 一样在 IQ 图上展示 ASK。 -以下是双极(Bipolar) 2-ASK、4-ASK 和 8-ASK,以及单极(Unipolar) 2-ASK 和 4-ASK 的 IQ 图。 +以下是双极(Bipolar) 2-ASK、4-ASK 和 8-ASK,以及单极(Unipolar) 2-ASK 和 4-ASK 的 IQ 图。在这里,双极意味着调制信号可以取正和负的幅度值,而单极 ASK 只使用正幅度。 .. image:: ../_images/ask_set.png :scale: 50 % @@ -286,13 +286,12 @@ FSK 是相当容易理解的---我们在 N 个频率之间切换,每个频率 因为我们是在调制载波,所以实际上是在载波频率上加减这 N 种频率。 例如,我们可能在 1.2 GHz 的载波频率上切换以下四个频率: -1. 1.2005 GHz -2. 1.2010 GHz -3. 1.1995 GHz -4. 1.1990 GHz +1. 1.2001 GHz +2. 1.2003 GHz +3. 1.1999 GHz +4. 1.1997 GHz -上面这个例子就是一个 4-FSK, 每个符号携带 2 比特的信息。 -在频域中,一个 4-FSK 信号可能看起来像这样: +上面这个例子就是一个 4-FSK,每个符号携带 2 比特的信息。频率间距为 200 kHz,整个信号的带宽略大于 600 kHz。这个 4-FSK 信号在基带频域中(对多个符号进行 FFT 后)可能看起来像这样: .. image:: ../_images/fsk.svg :align: center @@ -302,9 +301,8 @@ FSK 是相当容易理解的---我们在 N 个频率之间切换,每个频率 在设计 FSK 时,你一定会遇到一个关键问题:相邻频率之间的频谱间距应该是多少? 我们通常把这个间距用 :math:`\Delta f` 表示(单位是 Hz)。 我们希望在频域中避免频率重叠,这样接收机才能根据不同的频率辨别符号,所以 :math:`\Delta f` 必须足够大。 -而每种频率的宽度则取决于我们的符号速率:每秒需要传输的符号越多,每个符号的持续时间越短,其频率宽度就越宽(回忆一下时间和频率之间的反比关系)。 +而每种频率的宽度则取决于我们的符号速率以及所应用的脉冲整形滤波器:每秒需要传输的符号越多,每个符号的持续时间越短,其频率宽度就越宽(回忆一下时间和频率之间的反比关系)。 那么相应的,:math:`\Delta f` 就需要越大,以避免不同频率之间出现重叠。 -我们暂时不会在本教材中深入讨论 FSK 的设计细节。 IQ 图无法展示不同的频率,它们展示的是幅度和相位。 虽然在时域中展示 FSK 是可能的,但是超过 2 个频率仍然会使符号之间的区分变得困难: @@ -318,9 +316,10 @@ IQ 图无法展示不同的频率,它们展示的是幅度和相位。 不同于 FSK 中不同符号所使用的离散频率,FM 广播使用连续的音频信号来调制载波频率。 下面是调频(FM)和调幅(AM)的示例,顶部的 “信号” 是需要调制到载波上的音频信号。 -.. image:: ../_images/Carrier_Mod_AM_FM.webp +.. image:: ../_images/am_fm_animation.gif :align: center - :target: ../_images/Carrier_Mod_AM_FM.webp + :scale: 75 % + :target: ../_images/am_fm_animation.gif :alt: AM 和 FM 调制后的载波在时域上的示例动画。 在本教材中,我们主要关注数字信号调制方法。 diff --git a/content-zh/frequency_domain.rst b/content-zh/frequency_domain.rst index 58b24e3e..7a9385ec 100644 --- a/content-zh/frequency_domain.rst +++ b/content-zh/frequency_domain.rst @@ -150,23 +150,23 @@ 为信号 :math:`x(t)` 我们可以使用下面的公式得到其频域版本 :math:`X(f)` 。 我们将以 :math:`x(t)` 或 :math:`y(t)` 来表示函数的时域版本, 相应的以 :math:`X(f)` 和 :math:`Y(f)` 来表示其频域版本。 -注意“ :math:`t` ”代表时间,而“ :math:`f` ”代表频率。“ :math:`j` ”只是虚数单位而已。 -你可能在高中数学课上见过用“ :math:`i` ”来表示它。 -在工程和计算机科学中使用“ :math:`j` ”,因为在这些领域中“ :math:`i` ”通常指电流,并且在编程中常常作为循环变量使用。 +注意 :math:`t` 代表时间,而 :math:`f` 代表频率。 :math:`j` 只是虚数单位而已, +你可能在高中数学课上见过用 :math:`i` 来表示它。 +在工程和计算机科学中使用” :math:`j` “,因为在这些领域中” :math:`i` “通常指电流,并且在编程中常常作为循环变量使用。 -要从频域返回到时域几乎是一样的,除了多了一个缩放因子和一个负号: +要从频域返回到时域几乎是一样的,除了一个负号: .. math:: - x(t) = \frac{1}{2 \pi} \int X(f) e^{j2\pi ft} df + x(t) = \int X(f) e^{j2\pi ft} df -请注意,许多教科书和其他资源使用 :math:`w` 代替 :math:`2\pi f`。:math:`w` 是以弧度每秒为单位的角频率, +请注意,许多教科书和其他资源使用 :math:`w` 代替 :math:`2\pi f`,其中 :math:`w` 是以弧度每秒为单位的角频率, 而 :math:`f` 是以 Hz 为单位。你只需要知道: .. math:: \omega = 2 \pi f -即使它在许多方程中增加了一个 :math:`2 \pi` 项,在实际中我们更倾向于使用频率的 Hz 单位。 -最终,你在 SDR 应用中使用的将是 Hz 单位。 +即使它在许多方程中增加了一个 :math:`2 \pi` 项,在实际中我们更倾向于使用频率的 Hz 单位, +因为在大多数 SDR 和射频信号处理应用中我们使用的都是 Hz 单位。 上述傅里叶变换的方程是连续形式,其实你只会在数学问题中看到它。离散形式的方程才更接近于它在代码中实现的形态: @@ -356,6 +356,30 @@ FFT 令人疑惑的一点在于,其输出总是在频域中的,所以当我 虽然接收信号的中心频率被调到了 100MHz,但是在接受结果中,97.5 MHz 的信号实际会显示为 -2.5 MHz,负频率出现了! 但是实际上,仅仅是因为它低于中心频率而已。当我们学习更多关于采样的知识并且积累了 SDR 设备的使用经验后,你将彻底明白为什么会发生这个转换。 +从数学角度来看,通过观察复指数函数 :math:`e^{2j \pi f t}` 可以理解负频率。如果我们代入一个负频率,可以看到它是一个沿相反方向旋转的复正弦波。 + +.. math:: + e^{2j \pi f t} = \cos(2 \pi f t) + j \sin(2 \pi f t) \quad \mathrm{\textcolor{blue}{blue}} + +.. math:: + e^{2j \pi (-f) t} = \cos(2 \pi f t) - j \sin(2 \pi f t) \quad \mathrm{\textcolor{red}{red}} + +.. image:: ../_images/negative_freq_animation.gif + :align: center + :scale: 75 % + :target: ../_images/negative_freq_animation.gif + :alt: Animation of a positive and negative frequency sinusoid on the complex plane + +之所以用复指数来解释,是因为单独的 :math:`cos()` 或 :math:`sin()` 其实同时包含正频率和负频率成分,这可以从欧拉公式应用于频率为 :math:`f` 的正弦波看出来: + +.. math:: + \cos(2 \pi f t) = \underbrace{\frac{1}{2} e^{2j \pi f t}}_\text{positive} + \underbrace{\frac{1}{2} e^{-2j \pi f t}}_\text{negative} + +.. math:: + \sin(2 \pi f t) = \underbrace{\frac{1}{2j} e^{2j \pi f t}}_\text{positive} - \underbrace{\frac{1}{2j} e^{-2j \pi f t}}_\text{negative} + +因此,在射频信号处理中,我们通常使用复指数而不是余弦和正弦函数。 + **************************** 时域中的顺序并不重要 **************************** @@ -586,14 +610,16 @@ FFT 窗口大小设置 for i in range(num_rows): spectrogram[i,:] = 10*np.log10(np.abs(np.fft.fftshift(np.fft.fft(x[i*fft_size:(i+1)*fft_size])))**2) - plt.imshow(spectrogram, aspect='auto', extent = [sample_rate/-2/1e6, sample_rate/2/1e6, 0, len(x)/sample_rate]) + # 时间从顶部开始向下递增,例如 x[0] 会作为显示的第一行(最顶行)的一部分 + plt.imshow(spectrogram, aspect='auto', extent = [sample_rate/-2/1e6, sample_rate/2/1e6, len(x)/sample_rate, 0]) plt.xlabel("Frequency [MHz]") plt.ylabel("Time [s]") plt.show() 该操作应会生成以下结果,这算不上一个很有趣的时频谱,因为没有频率时变行为。 它有两个音调是因为我们模拟了一个真实信号,而真实信号总是有一个与正侧相匹配的负功率谱密度(PSD)。 -想看更多有趣的时频谱示例,请访问 https://www.IQEngine.org! +请注意,在这种实现方式中,最顶行对应的是信号的起始部分。 +想看更多有趣的时频谱示例,请访问 https://www.IQEngine.org ! .. image:: ../_images/spectrogram.svg :align: center diff --git a/content-zh/intro.rst b/content-zh/intro.rst index 233e06e2..7216ea64 100644 --- a/content-zh/intro.rst +++ b/content-zh/intro.rst @@ -56,7 +56,7 @@ 参与贡献 *************** -如果你从 PySDR 中获得了帮助,别忘了与你的同事、学生和其他可能对这些资料感兴趣的终身学习者分享。你还可以通过在 PySDR 的 `Patreon `_ 上捐款来表达你的支持,你的名字将会显示在每章下面的章节列表的左侧。 +如果你从 PySDR 中获得了帮助,别忘了与你的同事、学生和其他可能对这些资料感兴趣的终身学习者分享。你还可以通过在 PySDR 的 `Patreon `_ 上捐款来表达你的支持,你的名字将会显示在每章下面的章节列表的左侧。此外也可以通过 `一次性捐款 `_ 的方式支持本项目。 此外,欢迎你通过 marc@pysdr.org 向我发送问题、评论或者建议,你将为这本教科书的完善做出贡献! 你还可以直接在教科书的 `GitHub页面 `_ 上编辑源代码,欢迎你提交 Issue 或 Pull Request。 @@ -77,5 +77,7 @@ - `mrbloom `_ 将 PySDR 翻译为 `乌克兰语 `_ - `Yimin Zhao `_ 将 PySDR 翻译为 `简体中文 `_ - `Eduardo Chancay `_ 将 PySDR 翻译为 `西班牙语 `_ +- John Marcovici +- `Vishwaksen Reddy Dhareddy `_ 贡献了检测章节中的实时数据包检测部分 以及所有 `PySDR Patreon `_ 支持者! diff --git a/content-zh/iq_files.rst b/content-zh/iq_files.rst index 8ab12c52..0384c306 100644 --- a/content-zh/iq_files.rst +++ b/content-zh/iq_files.rst @@ -19,13 +19,13 @@ IQ 文件与 SigMF 以上数据对应 [I+jQ, I+jQ, I+jQ, I+jQ, I+jQ, I+jQ, I+jQ, ...] 当我们要将复数保存到文件中时,我们会使用 IQIQIQIQIQIQIQIQ 这样的格式进行保存。 -也就是说,我们按顺序存储了一系列浮点数。在读取它们时,我们必须将其重新分离成 [I+jQ, I+jQ, ...] 的形式。 +也就是说,我们按顺序存储了一系列整数或浮点数。在读取它们时,我们必须将其重新分离成 [I+jQ, I+jQ, ...] 的形式。 虽然我们可以将这一长串复数存储在文本文件或 CSV 文件中,但我们更倾向于将它们保存在所谓的 “二进制文件(Binary File)” 中以节省空间。 毕竟在高采样率下,你所记录的信号文件可能轻松超过多个 GB。 如果你直接在文本编辑器中打开一个二进制文件,它看起来可能和下面的截图差不多。 二进制文件包含了一系列字节,所以你必须自己按照约定的格式解析,但是二进制文件通常是存储数据最高效的方式(同时还有各种压缩算法可用)。 -由于我们的信号通常是随机序列般的一串浮点数,所以我们通常不会对其进行压缩。当然,二进制文件也被用于许多其他事情,例如编译过的程序。 +由于我们的信号通常是随机序列般的一串整数或浮点数,所以我们通常不会对其进行压缩。当然,二进制文件也被用于许多其他事情,例如编译过的程序。 用于保存信号时,我们称它们为二进制的 “IQ 文件”,使用文件扩展名 :code:`.iq` 。 .. image:: ../_images/binary_file.png @@ -33,10 +33,12 @@ IQ 文件与 SigMF :align: center 在 Python 中,默认的复数类型是 :code:`np.complex128`,它使用两个 64 位浮点数(:code:`'float64'`)来表示一个复数。 -但是在 DSP/SDR 领域,我们倾向于使用 32 位的浮点数(:code:`'float32'`), +但是在 DSP/SDR 领域,我们倾向于使用 16 位整数或 32 位浮点数, 毕竟我们的 SDR 设备上的 ADC 硬件并不能提供高达 :code:`'float64'` 的精度。 -因此在 Python 代码中,我们实际使用的是 **np.complex64** ,即用两个 :code:`'float32'` 来表示一个复数。 -其实在写代码时,复数到底是哪种类型并不重要。重要的是当你把数据保存到文件时,请确保它是以 :code:`np.complex64` 类型的数组存储的。 +事实上,大多数 SDR 使用 12 位 ADC,因此我们可以通过存储为 16 位整数(Python 中的 :code:`np.int16`)来最小化存储空间, +这意味着每个 IQ 采样点占用 4 字节,射频录制文件的大小(字节)将是采样率的 4 倍,这被称为 "Sean 的 4 倍法则"。 +在下面的 Python 示例中,我们将使用 **np.complex64** ,即用两个 :code:`'float32'` 来表示一个复数,因为 Python 没有原生的复数整数类型(但这并不妨碍我们将 IQ 数据以整数格式保存到文件中,后文会有说明)。 +其实在写代码处理信号时,复数到底是哪种类型并不重要。重要的是当你把数据保存到文件时,请确保它是以 :code:`np.complex64`(或交织 IQ 的 :code:`np.int16`)类型的数组存储的。 ************************* Python 代码示例 diff --git a/content-zh/noise.rst b/content-zh/noise.rst index 0c23be0d..ecd598f0 100644 --- a/content-zh/noise.rst +++ b/content-zh/noise.rst @@ -1,12 +1,15 @@ .. _noise-chapter: -################## -噪声与分贝(dB) -################## +########################## +噪声与随机变量 +########################## 本章将详细讨论噪声的相关主题,特别是噪声在无线通信系统中的建模和处理方式。 涉及的概念包括加性高斯白噪声(AWGN)、复数噪声、信噪比(SNR)、信干噪比(SINR)。 同时,我们还会介绍在无线通信和软件定义无线电(SDR)中广泛使用的分贝(dB)单位。 +最后,我们将深入探讨随机变量和随机过程的基本概念,这些概念对于理解噪声、信道效应以及无线通信中的许多信号处理技术至关重要。 +我们将涵盖概率分布、期望、方差,以及随机过程如何随时间演变。 +这些概念构成了分析噪声以及 SDR 和 DSP 中许多其他主题的数学基础。 ************************ 高斯噪声 @@ -17,6 +20,7 @@ .. image:: ../_images/noise.png :scale: 70 % :align: center + :target: ../_images/noise.png 在时间域中,图上的纵坐标的平均值为零。 如果平均值不为零,我们可以减去平均值来得到一个偏置,从而使剩余部分的平均值为零。 @@ -63,6 +67,7 @@ :scale: 70 % :align: center :alt: Depiction of why it's important to understand dB or decibels, showing a spectrogram using linear vs log scale + :target: ../_images/linear_vs_log.png 给定一个值 x,我们可以使用以下公式将 x 转换为 dB: @@ -77,7 +82,7 @@ Python 代码: 你可能会发现在其他领域中,表达式中的 :code:`10 *` 可能需要改为 :code:`20 *` 。 当处理代表功率的量时,常用系数 10,但当处理代表非功率量如电压或电流时,更适合使用系数 20。 -在DSP领域,我们通常处理代表功率的量。实际上,在本书中我们没有使用过 20,而始终使用 10。 +在 DSP 领域,我们通常处理代表功率的量。 我们使用以下方法将 dB 转换回线性数值(普通数值): @@ -104,6 +109,7 @@ dB 的对数刻度使我们能够在表达数字或绘制图形时拥有更大 .. image:: ../_images/db.png :scale: 80 % :align: center + :target: ../_images/db.png 还需要记住,dB 并不是严格意义上的 “单位”。 一个 dB 的数值本身是无单位的,就像说某物体是 “2 倍大” 一样,在上下文告诉你单位之前,它本身是没有单位的。 @@ -157,6 +163,7 @@ dB 是一个相对的概念。在音频处理中,当人们说 dB 时,他们 :scale: 110 % :align: center :alt: AWGN in the time domain is also Gaussian noise in the frequency domain, although it looks like a flat line when you take the magnitude and perform averaging + :target: ../_images/noise_freq.png 从下方的频谱图可以看到,所有频率上的功率谱密度都大致相同且相对平坦。 这证明,高斯噪声在频域中也是呈高斯分布的。 @@ -179,13 +186,14 @@ dB 是一个相对的概念。在音频处理中,当人们说 dB 时,他们 plt.plot(np.real(X), '.-') plt.show() -请注意,默认情况下,:code:`randn()` 生成的数据符合标准正态分布(均值为 0,方差为 1)。 +请注意,默认情况下, :code:`randn()` 函数生成的数据符合标准正态分布(均值为 0,方差为 1)。 代码生成的两张图像看起来都会类似这样: .. image:: ../_images/noise_python.png :scale: 100 % :align: center :alt: Example of white noise simulated in Python + :target: ../_images/noise_python.png 只需对上面的 FFT 输出取对数并进行平均,你就可以复现上文来自 GNU Radio 的那张平坦的 PSD 图像。 我们生成并进行 FFT 的信号是实信号(而不是复信号),任何实信号的 FFT 都会有相抵的负数和正数部分,所以我们只保存了 FFT 输出的正部分(第二部分)。 @@ -233,6 +241,7 @@ dB 是一个相对的概念。在音频处理中,当人们说 dB 时,他们 :scale: 80 % :align: center :alt: Complex noise simulated in Python + :target: ../_images/noise3.png 从上图可以看出来,实部和虚部是互相独立的。 @@ -249,6 +258,7 @@ dB 是一个相对的概念。在音频处理中,当人们说 dB 时,他们 :scale: 60 % :align: center :alt: Complex noise on an IQ or constellation plot, simulated in Python + :target: ../_images/noise_iq.png 它的形状符合预期:一个以原点为中心的随机斑点分布,即 0+0j。为了增加趣味性,让我们尝试在一个 QPSK 信号中引入噪声,并观察 IQ 图的变化。 @@ -256,12 +266,15 @@ dB 是一个相对的概念。在音频处理中,当人们说 dB 时,他们 :scale: 60 % :align: center :alt: Noisy QPSK simulated in Python + :target: ../_images/noisey_qpsk.png 当噪声更强时,会发生什么呢? .. image:: ../_images/noisey_qpsk2.png :scale: 50 % :align: center + :alt: Noisy QPSK with stronger noise simulated in Python + :target: ../_images/noisey_qpsk2.png 我们逐渐领悟到了无线数据传输的复杂性。 在追求高效率的同时,我们也不得不面对噪声干扰所带来的问题。 @@ -273,7 +286,7 @@ AWGN AWGN (Additive White Gaussian Noise,加性高斯白噪声)是 DSP 和 SDR 领域中经常能听到的缩写。 GN 指的是高斯噪声,我们之前已经讨论过了。Additive (加性)表示噪声是被添加到接收信号中的。 -White (白)在频域上意味着我们整个观测频带上的频谱是平坦的,在实践中,它几乎总是白噪声,或者是近似白噪声。 +White (白)在频域上意味着我们整个观测频带上的频谱是平坦的,在实践中,它几乎总是白噪声,或者近似白噪声。 在本教材中,当处理通信链路和链路预算等问题时,我们将只考虑 AWGN 作为唯一形式的噪声。 非 AWGN 噪声往往是一个专门的课题。 @@ -309,19 +322,363 @@ SNR 在实践中通常以分贝(dB)表示。在无线通信的模拟实验 干扰的构成取决于具体应用和场景,但通常是另一个信号干扰了我们感兴趣的信号(Signal Of Interest,缩写为 SOI), 并且在频率上与 SOI 重叠、或者由于某种原因无法被滤除。 -************************* -参考资料 -************************* +********************************* +深入理解随机变量 +********************************* + +到目前为止,我们一直避免过于数学化,但现在我们需要退后一步,介绍随机变量的概念以及它们在无线通信和 SDR 中的应用。 **随机变量** 是一个数学概念,它将随机实验的结果映射到数值上。随机变量表示那些在被观察或测量之前其值是不确定的量,比如我们的噪声采样点。设想掷一个六面骰子,在掷之前你不知道会出现什么数字。我们可以定义一个随机变量 :math:`X` 来表示掷骰子的结果。 :math:`X` 的值是 {1, 2, 3, 4, 5, 6} 中的一个,但在实际掷之前我们不知道是哪一个。 + +在无线通信和 SDR 的场景中,随机变量无处不在: + +* 接收机中的热噪声在每个时刻都可以建模为随机变量 +* 受多径衰落影响的接收信号的幅度是随机的 +* 变化信道引入的相位偏移可以建模为 :math:`0` 到 :math:`2\pi` 之间的随机变量 +* 甚至我们传输的数据比特也可以被视为随机变量 + +**单个样本 vs. 多个样本** + +这是一个至关重要的区别,经常引起混淆: + +* 随机变量的 **单次实现** 或 **单个样本** 只是一个数字——随机实验的一个结果 +* 要描述一个随机变量的特征(找到其均值、分布范围等),我们需要 **大量实现** ——即多个结果 + +例如,如果你在 Python 中调用 ``np.random.randn()`` 不带任何参数,它返回一个从高斯分布中抽取的随机数。这单个数字几乎无法告诉你分布本身的任何信息。但如果你调用 ``np.random.randn(10000)`` 生成 10,000 个样本,你就可以估计分布的均值和方差等属性了。 + +.. code-block:: python + + import numpy as np + + # 单个样本 - 只是一个数字 + x_single = np.random.randn() + print(x_single) # 可能是 0.534, -1.23, 或其他任何值 + + # 大量样本 - 现在我们可以描述分布的特征 + x_many = np.random.randn(10000) + print(np.mean(x_many)) # 会接近 0 + print(np.var(x_many)) # 会接近 1 + +联合分布 +#################### + +到目前为止,我们关注的是单个随机变量。当同时处理两个或更多随机变量时,我们使用 **联合分布(Joint Distribution)** 。 + +对于连续随机变量 :math:`X` 和 :math:`Y` ,联合分布由 **联合概率密度函数(Joint PDF)** 描述: + +.. math:: + f_{X,Y}(x,y) + +联合概率密度函数告诉我们 :math:`X` 取值 :math:`x` *同时* :math:`Y` 取值 :math:`y` 的可能性。 + +从联合概率密度函数中,我们可以计算: + +* 边缘概率密度函数(例如 :math:`f_X(x)` 或 :math:`f_Y(y)` ) +* 期望值,如 :math:`E[XY]` +* 协方差和相关性 +* 涉及两个变量的概率 + +例如,:math:`X` 的边缘概率密度函数可以通过对 :math:`Y` 积分得到: + +.. math:: + f_X(x) = \int_{-\infty}^{\infty} f_{X,Y}(x,y)\,dy + +联合分布是理解随机变量之间依赖性、相关性和独立性的数学基础。 + + +概率分布 +######################### + +**概率分布** 描述了随机变量取不同值的可能性。对于连续随机变量,我们使用 **概率密度函数(Probability Density Function,PDF)** ,记为 :math:`f_X(x)` 。PDF 告诉我们随机变量取不同值的相对可能性。 + +在 SDR 和通信中最重要的分布是 **高斯(正态)分布** 。均值为 :math:`\mu` 、方差为 :math:`\sigma^2` 的高斯随机变量 :math:`X` 的 PDF 为: + +.. math:: + f_X(x) = \frac{1}{\sqrt{2\pi\sigma^2}} e^{-\frac{(x-\mu)^2}{2\sigma^2}} + +这就是你可能见过的著名 "钟形曲线"。该分布完全由两个参数确定: + +* **均值** :math:`\mu` :分布的中心 +* **方差** :math:`\sigma^2` :分布的展宽程度(标准差 :math:`\sigma` 是方差的平方根) + +在 Python 中,``np.random.randn()`` 生成的样本来自 :math:`\mu = 0` 、 :math:`\sigma^2 = 1` 的 **标准高斯** 分布。我们可以这样可视化: + +.. code-block:: python + + import numpy as np + import matplotlib.pyplot as plt + + # 从标准高斯分布生成 10,000 个样本 + x = np.random.randn(10000) + + # 创建直方图以可视化分布 + plt.hist(x, bins=50, density=True, alpha=0.7, edgecolor='black') + plt.xlabel('Value') + plt.ylabel('Probability Density') + plt.title('Gaussian Distribution (μ=0, σ²=1)') + plt.grid(True) + plt.show() + +.. image:: ../_images/gaussian_histogram.png + :scale: 80% + :align: center + :alt: Histogram of Gaussian distributed samples + :target: ../_images/gaussian_histogram.png + +期望(即均值) +######################### + +随机变量的 **期望** 或 **期望值** ,记为 :math:`E[X]` 或 :math:`\mu` ,表示其在大量实现中的平均值。对于具有 PDF :math:`f_X(x)` 的连续随机变量,期望为: + +.. math:: + E[X] = \int_{-\infty}^{\infty} x \cdot f_X(x) \, dx + +在实践中,当我们有从分布中抽取的 :math:`N` 个样本 :math:`x_1, x_2, \ldots, x_N` 时,我们使用 **样本均值** 来估计期望: + +.. math:: + \hat{\mu} = \frac{1}{N} \sum_{n=1}^{N} x_n + +期望是一个 **线性算子** ,这意味着: + +* :math:`E[aX + b] = aE[X] + b` (其中 :math:`a` 和 :math:`b` 为常数) +* :math:`E[X + Y] = E[X] + E[Y]` (对于任意两个随机变量) + +这种线性性质在信号处理中非常有用! + +方差与标准差 +############################### + +随机变量的 **方差** ,记为 :math:`\text{Var}(X)` 或 :math:`\sigma^2` ,衡量其值围绕均值的分散程度。它被定义为偏离均值的平方的期望值: + +.. math:: + \text{Var}(X) = E[(X - \mu)^2] = E[X^2] - (E[X])^2 + +当我们有 :math:`N` 个样本时,我们使用以下公式估计方差: + +.. math:: + \hat{\sigma}^2 = \frac{1}{N} \sum_{n=1}^{N} (x_n - \hat{\mu})^2 + +**标准差** :math:`\sigma` 就是方差的平方根::math:`\sigma = \sqrt{\sigma^2}` 。 + +请注意上面公式中 :math:`\sigma` 和样本均值上方的 :math:`\enspace \hat{} \enspace` 符号(称为 "hat")。这个帽子符号表示我们是在 *估计* 均值/方差,估计值不一定精确等于真实的均值/方差,但随着样本数量的增加,它会越来越接近真实值。 + +**关键性质:** 如果 :math:`X` 是方差为 :math:`\sigma^2` 的随机变量,则: + +* 缩放::math:`\text{Var}(aX) = a^2 \text{Var}(X)` +* 平移::math:`\text{Var}(X + b) = \text{Var}(X)` (加上常数不改变分散程度) + +相应地,标准差 :math:`\sigma` 的性质为: + +* 缩放::math:`\sigma(aX) = a\sigma(X)` +* 平移::math:`\sigma(X+b) = \sigma(X)` + +.. image:: ../_images/gaussian_transformed.png + :scale: 80% + :align: center + :alt: Scaling and shifting the Gaussian Distribution. (notice the scales on x and y axes) + :target: ../_images/gaussian_transformed.png + +对高斯分布进行缩放和平移(注意 x 轴和 y 轴的刻度变化) + +**方差与功率** + +在信号处理中,对于 **零均值** 信号(均值约为 0),方差等于 **平均功率** 。这就是为什么我们经常交替使用这两个术语: + +.. math:: + P = \text{Var}(X) = E[X^2] \quad \text{(当 } E[X] = 0\text{)} + +这个关系对于分析噪声功率、信噪比(SNR)和链路预算至关重要。 + +.. code-block:: python -关于AWGN,SNR和方差的更多资料可参考: + noise_power = 2.0 + n = np.random.randn(N) * np.sqrt(noise_power) + print(np.var(n)) # 会约等于 2.0 -1. https://en.wikipedia.org/wiki/Additive_white_Gaussian_noise -2. https://en.wikipedia.org/wiki/Signal-to-noise_ratio -3. https://en.wikipedia.org/wiki/Variance +协方差 +########## +两个随机变量 :math:`X` 和 :math:`Y` 之间的 **协方差(Covariance)** 定义为: +.. math:: + \text{Cov}(X,Y) = E[(X - E[X])(Y - E[Y])] + +一个等价且通常更方便的形式是: + +.. math:: + \text{Cov}(X,Y) = E[XY] - E[X]E[Y] + +协方差衡量两个变量如何共同变化: + +* 正协方差:它们趋向于一起增大或一起减小 +* 负协方差:一个趋向于在另一个减小时增大 +* 零协方差:它们是不相关的 + +如果两个变量都是零均值的,则简化为: + +.. math:: + \text{Cov}(X,Y) = E[XY] + +协方差有单位(它不是归一化的),这就是为什么在实践中我们经常使用 **相关系数** (Correlation Coefficient): + +.. math:: + \rho_{XY} = \frac{\text{Cov}(X,Y)}{\sigma_X \sigma_Y} + +相关系数是一个介于 -1 和 +1 之间的无量纲值。 + +变量之和的方差 +############################### + +在信号处理中,我们经常处理随机变量之和,比如信号加噪声: + +.. math:: + Z = X + Y + +这个和的方差取决于 :math:`X` 和 :math:`Y` 是否独立(或更一般地说,是否相关)。 + +完整的一般形式为: + +.. math:: + \text{Var}(X + Y) = \text{Var}(X) + \text{Var}(Y) + 2\,\text{Cov}(X,Y) + +其中 :math:`\text{Cov}(X,Y)` 是 :math:`X` 和 :math:`Y` 之间的 **协方差** 。 + +**独立的情况** + +如果 :math:`X` 和 :math:`Y` 是独立的(或仅仅是不相关的),则表达式简化为: + +.. math:: + \text{Var}(X + Y) = \text{Var}(X) + \text{Var}(Y) + +这个结果在通信中极其重要。例如,如果接收信号为: + +.. math:: + R = S + N + +其中 :math:`S` 是信号,:math:`N` 是独立的噪声,那么总功率就是信号功率和噪声功率之和。 + +这就是为什么 SNR 计算如此简单直接。 + +************************ +复数随机变量 +************************ + +在 SDR 中,我们大量使用 **复数值信号** ,这意味着我们也需要处理复数随机变量。复数随机变量的形式为: + +.. math:: + Z = X + jY + +其中 :math:`X` 和 :math:`Y` 都是实数值随机变量,分别代表同相(I)和正交(Q)分量。 + +**复高斯噪声** + +在无线通信中最常见的复数随机变量是 **复高斯噪声** ,其中 :math:`X` 和 :math:`Y` 都是具有相同方差的独立高斯随机变量。 +例如,如果 :math:`X \sim \mathcal{N}(\alpha_1, \sigma_1^2)` 和 :math:`Y \sim \mathcal{N}(\alpha_2, \sigma_2^2)` 是独立的,那么复数随机变量 :math:`Z = X + jY` 具有: + +* 均值::math:`E[Z] = E[X] + jE[Y] = \alpha_1 + j\alpha_2` +* 方差(功率)::math:`\text{Var}(Z) = \text{Var}(X) + \text{Var}(Y) = \sigma_1^2 + \sigma_2^2` + +.. image:: ../_images/gaussian_IQ.png + :scale: 80% + :align: center + :alt: Complex Gaussian noise visualized as two independent Gaussian random variables on the I and Q axes + :target: ../_images/gaussian_IQ.png + +这就是为什么当我们创建单位功率(方差 = 1)的复高斯噪声时,我们使用: + +.. code-block:: python + + N = 10000 + n = (np.random.randn(N) + 1j*np.random.randn(N)) / np.sqrt(2) + print(np.var(n)) # ~ 1 + +除以 :math:`\sqrt{2}` 确保了总功率(I 和 Q 方差之和)等于 1。 + +.. code-block:: python + + # 不做归一化的情况: + n_raw = np.random.randn(N) + 1j*np.random.randn(N) + print(np.var(np.real(n_raw))) # ~ 1 + print(np.var(np.imag(n_raw))) # ~ 1 + print(np.var(n_raw)) # ~ 2 (总功率) + + # 做归一化的情况: + n_norm = n_raw / np.sqrt(2) + print(np.var(n_norm)) # ~ 1 (单位功率) + +**************** +随机过程 +**************** + +到目前为止我们讨论的是随机变量——某个单一时刻的随机值。 **随机过程** (也称为 **随机过程** ,Stochastic Process)是一组按时间索引的随机变量: + +.. math:: + X(t) \quad \text{或} \quad X[n] \text{(离散时间)} + +在每个时刻 :math:`t` ,:math:`X(t)` 都是一个随机变量。可以把随机过程想象成一个随时间随机演变的信号。 + +在无线通信中的例子: + +* 接收机处的噪声::math:`N(t)` 或 :math:`N[n]` +* 经历时变衰落的信号::math:`H(t)S(t)` +* 来自 SDR 的采样数据:每批数据都是随机过程的一次实现 + +**平稳过程** + +如果一个随机过程的统计特性不随时间变化,则称其为 **平稳** 的。特别地,一个 **广义平稳(Wide-Sense Stationary,WSS)** 过程具有: + +* 恒定均值:对所有 :math:`t` ,:math:`E[X(t)] = \mu` +* 自相关仅依赖于时间差::math:`E[X(t)X(t+\tau)]` 仅依赖于 :math:`\tau` ,而不依赖于 :math:`t` + +无线系统中的许多噪声源近似为平稳的,这大大简化了分析过程。 + +**白噪声** + +**白噪声** 是一种在不同时刻的样本之间不相关的随机过程,且其功率谱密度在所有频率上是恒定的。加性高斯白噪声(AWGN)同时具有以下两个特性: + +* **白** :时间上不相关,频谱平坦 +* **高斯** :每个样本都服从高斯分布 + +当我们在 Python 中使用 ``np.random.randn(N)`` 生成噪声时,:math:`N` 个样本中的每一个都是独立的高斯随机变量,共同构成一个白噪声过程。 + + +独立性与相关性 +############################# + +如果知道一个随机变量的值不能提供关于另一个的任何信息,则这两个随机变量 :math:`X` 和 :math:`Y` 是 **独立** 的。数学上,它们的联合 PDF 可以分解为: + +.. math:: + f_{X,Y}(x,y) = f_X(x) \cdot f_Y(y) + +独立性是一个很强的条件。一个较弱的条件是 **不相关** ,即: + +.. math:: + E[XY] = E[X]E[Y] + +对于高斯随机变量,不相关意味着独立(这是高斯分布的一个特殊性质)。 + +在复高斯噪声中,I 和 Q 分量是独立的: + +.. code-block:: python + N = 10000 + I = np.random.randn(N) + Q = np.random.randn(N) + + # 通过相关性检验独立性 + correlation = np.corrcoef(I, Q)[0, 1] + print(f"Correlation between I and Q: {correlation:.4f}") # ~ 0 + +*************************** +拓展阅读 +*************************** + +1. Papoulis, A., & Pillai, S. U. (2002). *Probability, Random Variables, and Stochastic Processes*. McGraw-Hill. +2. Kay, S. M. (2006). *Intuitive Probability and Random Processes using MATLAB®*. Springer. +3. https://en.wikipedia.org/wiki/Random_variable +4. https://en.wikipedia.org/wiki/Normal_distribution +5. https://en.wikipedia.org/wiki/Stochastic_process +6. https://en.wikipedia.org/wiki/Additive_white_Gaussian_noise +7. https://en.wikipedia.org/wiki/Signal-to-noise_ratio diff --git a/content-zh/pulse_shaping.rst b/content-zh/pulse_shaping.rst new file mode 100644 index 00000000..2da00459 --- /dev/null +++ b/content-zh/pulse_shaping.rst @@ -0,0 +1,263 @@ +.. _pulse-shaping-chapter: + +####################### +脉冲整型 +####################### + +在本章中我们将介绍脉冲整形(Pulse Shaping),符号间干扰(Inter-Symbol-Interference, ISI),匹配滤波器(Matched Filter)和升余弦滤波器(Raised-Cosine Filter)。 +最后我们将使用 Python 为 BPSK 符号添加脉冲整形。 +你可以把本章节视为滤波器章节的后续章节,在此我们将深入讨论脉冲整形。 + +********************************** +符号间干扰(ISI) +********************************** + +在 :ref:`filters-chapter` 章节中,我们学习到了方块形状的符号/脉冲会占用相当宽的频谱,我们可以通过"整形"我们的脉冲来大大减少使用的频谱。然而,你不能使用任何低通滤波器,否则可能会出现符号间干扰(Inter-Symbol-Interference,ISI),即符号相互干扰。 + +当我们传输数字符号时,我们是连续地逐个传输它们的(而不是在符号之间等待一段时间)。当你施加脉冲整形滤波器时,它会在时域中拉长脉冲(以便在频域中压缩),从而导致相邻符号彼此重叠。只要你的脉冲整形滤波器满足以下准则,这种重叠就不会有问题:除了其中一个脉冲之外,所有脉冲在符号周期 :math:`T` 的每一个整数倍处的叠加值必须为零。通过下面这幅图可以最直观地理解这个概念: + +.. image:: ../_images/pulse_train.svg + :align: center + :target: ../_images/pulse_train.svg + :alt: A pulse train of sinc pulses + +如图所示,在 :math:`T` 的每一个整数倍处,只有一个脉冲存在峰值,而其余所有脉冲的值都为 0(它们穿过了 x 轴)。当接收机对信号进行采样时,它恰好在完美的时刻(即脉冲的峰值处)进行采样,这意味着只有那个时间点才真正重要。通常,接收机中会有一个符号同步模块来确保在峰值处对符号进行采样。 + +********************************** +匹配滤波器 +********************************** + +在无线通信中,我们使用的一个技巧叫做匹配滤波(Matched Filtering)。要理解匹配滤波,你首先需要理解以下两点: + +1. 上面讨论的那些脉冲只需要在 *接收端* 采样之前完美对齐即可。在那之前,即使存在 ISI 也没关系,也就是说,信号可以带着 ISI 在空中传播,这是完全没问题的。 + +2. 我们希望在发射机中使用低通滤波器来减少信号占用的频谱。但接收机同样需要一个低通滤波器来尽可能消除信号旁边的噪声/干扰。因此,我们在发射机(Tx)端有一个低通滤波器,在接收机(Rx)端也有一个低通滤波器,而采样发生在这两个滤波器(以及无线信道的影响)之后。 + +在现代通信中,我们会将脉冲整形滤波器均等地拆分到 Tx 和 Rx 两端。虽然它们 *不一定* 非得是相同的滤波器,但从理论上讲,在 AWGN 环境下最大化 SNR 的最优线性滤波器就是在 Tx 和 Rx 使用 *相同* 的滤波器。这种策略被称为 "匹配滤波器" 的概念。 + +另一种理解匹配滤波器的方式是:接收机将接收到的信号与已知的模板信号进行相关运算。这里的模板信号本质上就是发射机发送的脉冲,与施加在其上的相位/幅度偏移无关。回忆一下,滤波是通过卷积来完成的,而卷积基本上就是相关运算(事实上,当模板是对称的时候,它们在数学上是完全等价的)。将接收信号与模板进行相关运算,能让我们最大限度地恢复所发送的内容,这也是为什么它在理论上是最优的。打个比方,想象一个图像识别系统,它使用一个人脸模板并通过 2D 相关运算来寻找人脸: + +.. image:: ../_images/face_template.png + :scale: 70 % + :align: center + +********************************** +将滤波器拆分为两半 +********************************** + +我们具体要怎么把一个滤波器拆成两半呢?卷积运算具有结合律,即: + +.. math:: + (f * g) * h = f * (g * h) + +假设 :math:`f` 是我们的输入信号,而 :math:`g` 和 :math:`h` 是两个滤波器。先用 :math:`g` 滤波 :math:`f` ,再用 :math:`h` 滤波,等效于使用一个为 :math:`g * h` 的滤波器进行滤波。 + +另外,回忆一下时域卷积等于频域相乘: + +.. math:: + g(t) * h(t) \leftrightarrow G(f)H(f) + +要将一个滤波器拆分为两半,你可以对其频率响应取平方根。 + +.. math:: + X(f) = X_H(f) X_H(f) \quad \mathrm{where} \quad X_H(f) = \sqrt{X(f)} + +下图展示了一个简化的发射和接收链路图。其中升余弦(RC)滤波器被拆分为两个根升余弦(RRC)滤波器:发射端的一个是脉冲整形滤波器,接收端的一个是匹配滤波器。它们共同作用,使得解调器处的脉冲看起来就像经过了单个 RRC 滤波器进行脉冲整形一样。 + +.. image:: ../_images/splitting_rc_filter.svg + :align: center + :target: ../_images/splitting_rc_filter.svg + :alt: A diagram of a transmit and receive chain, with a Raised Cosine (RC) filter being split into two Root Raised Cosine (RRC) filters + +********************************** +具体的脉冲整形滤波器 +********************************** + +我们已经知道我们需要做到: + +1. 设计一个能降低信号带宽(以减少频谱占用)的滤波器,同时要求在每个符号间隔处,除了一个脉冲之外,其余脉冲的叠加值均为零。 + +2. 将该滤波器拆分为两半,一半放在 Tx,另一半放在 Rx。 + +让我们来看看一些常用的脉冲整形滤波器。 + +升余弦滤波器 +######################### + +最常用的脉冲整形滤波器似乎是 "升余弦" 滤波器。它是一个很好的低通滤波器,能够限制信号占用的带宽,同时它还具有在 :math:`T` 的整数倍处求和为零的特性: + +.. image:: ../_images/raised_cosine.svg + :align: center + :target: ../_images/raised_cosine.svg + :alt: The raised cosine filter in the time domain with a variety of roll-off values + +请注意,上图是时域图,描绘的是滤波器的脉冲响应。参数 :math:`\beta` 是升余弦滤波器唯一的参数,它决定了滤波器在时域中衰减到零的速度,而这与其在频域中衰减的速度成反比: + +.. image:: ../_images/raised_cosine_freq.svg + :align: center + :target: ../_images/raised_cosine_freq.svg + :alt: The raised cosine filter in the frequency domain with a variety of roll-off values + +之所以称之为升余弦滤波器,是因为当 :math:`\beta = 1` 时,其频域响应是一个半周期的余弦波,被抬升到 x 轴上。 + +升余弦滤波器的脉冲响应的数学表达式为: + +.. math:: + h(t) = \mathrm{sinc}\left( \frac{t}{T} \right) \frac{\cos\left(\frac{\pi\beta t}{T}\right)}{1 - \left( \frac{2 \beta t}{T} \right)^2} + +关于 :math:`\mathrm{sinc}()` 函数的更多信息可以参阅 `这里 `_ 。你可能在其他地方看到包含 :math:`\frac{1}{T}` 缩放因子的公式;这个因子使滤波器具有单位增益,即输出信号的功率与输入信号相同(这是设计滤波器时的常见做法)。然而,我们要将其应用于由符号组成的脉冲序列(例如 1 和 -1),我们不希望这些符号的幅度在脉冲整形后发生变化,因此我们省略了这个缩放因子。当我们深入到 Python 示例并绘制输出时,这一点会更加清晰。 + +记住:我们要将这个滤波器均等地拆分到 Tx 和 Rx。接下来介绍根升余弦(RRC)滤波器! + +根升余弦滤波器 +######################### + +根升余弦(Root Raised-Cosine,RRC)滤波器才是我们实际在 Tx 和 Rx 中实现的滤波器。正如前文所述,两者组合起来就构成了一个完整的升余弦滤波器。因为将滤波器拆分为两半涉及到频域上的开平方运算,所以脉冲响应看起来会稍微复杂一些: + +.. image:: ../_images/rrc_filter.png + :scale: 70 % + :align: center + +好在这是一个非常常用的滤波器,有大量现成的实现可以使用,包括 `Python 实现 `_ 。 + +其他脉冲整形滤波器 +########################### + +其他滤波器包括高斯滤波器(Gaussian Filter),其脉冲响应类似高斯函数。还有 sinc 滤波器,它等效于 :math:`\beta = 0` 时的升余弦滤波器。sinc 滤波器更接近理想滤波器,即它能够在几乎没有过渡区域的情况下消除不需要的频率。 + +********************************** +滚降因子 +********************************** + +让我们仔细研究一下参数 :math:`\beta` 。它是一个介于 0 到 1 之间的数值,称为 "滚降因子(Roll-off Factor)",有时也被称为 "过量带宽(Excess Bandwidth)"。它决定了滤波器在时域中衰减到零的速度。回忆一下,要用作滤波器,脉冲响应必须在两侧衰减到零: + +.. image:: ../_images/rrc_rolloff.svg + :align: center + :target: ../_images/rrc_rolloff.svg + :alt: Plot of the raised cosine roll-off parameter + +:math:`\beta` 越小,所需的滤波器抽头数就越多。当 :math:`\beta = 0` 时,脉冲响应永远不会完全衰减到零,因此我们试图将 :math:`\beta` 设置得尽可能低,同时又不会引发其他问题。滚降因子越低,对于给定的符号速率,我们能够在频率上把信号压缩得越紧凑,这一点始终很重要。 + +一个常用的公式用于估算给定符号速率和滚降因子下的带宽(单位为 Hz): + +.. math:: + \mathrm{BW} = R_S(\beta + 1) + +:math:`R_S` 是符号速率(单位为 Hz)。在无线通信中,我们通常选择 0.2 到 0.5 之间的滚降因子。作为经验法则,一个使用符号速率 :math:`R_S` 的数字信号将占用略多于 :math:`R_S` 的频谱,这里包括正频率和负频率部分。一旦我们将信号上变频并发射出去,两侧的频谱都很重要。如果我们以每秒 100 万个符号(MSps)的速率传输 QPSK 信号,它将占用大约 1.3 MHz 的频谱。数据速率将是 2 Mbps(回忆一下 QPSK 每个符号携带 2 比特),其中包括信道编码和帧头等开销。 + +********************************** +Python 练习 +********************************** + +作为 Python 练习,让我们来对一些脉冲进行滤波和整形。我们将使用 BPSK 符号,因为它更容易可视化——在脉冲整形步骤之前,BPSK 就是传输 1 或 -1,其 "Q" 分量等于零。由于 Q 为零,我们只需绘制 I 分量即可,这样看起来更简单。 + +在这个仿真中,我们将使用每个符号 8 个采样点,并且不使用看起来像方波的 1 和 -1 信号,而是使用由脉冲(冲激)组成的脉冲序列。当你把一个脉冲通过滤波器时,输出就是脉冲响应(这也是 "脉冲响应" 这个名称的由来)。因此,如果你想要一系列脉冲,就应该使用中间填充零的冲激序列,从而避免产生方形脉冲。 + +.. code-block:: python + + import numpy as np + import matplotlib.pyplot as plt + from scipy import signal + + num_symbols = 10 + sps = 8 + + bits = np.random.randint(0, 2, num_symbols) # Our data to be transmitted, 1's and 0's + + x = np.array([]) + for bit in bits: + pulse = np.zeros(sps) + pulse[0] = bit*2-1 # set the first value to either a 1 or -1 + x = np.concatenate((x, pulse)) # add the 8 samples to the signal + plt.figure(0) + plt.plot(x, '.-') + plt.grid(True) + plt.show() + +.. image:: ../_images/pulse_shaping_python1.png + :scale: 80 % + :align: center + :alt: A pulse train of impulses in the time domain simulated in Python + +此时我们的符号仍然是 1 和 -1。不要纠结于我们使用了冲激这件事。实际上,*不要* 去可视化冲激响应,而是把它当作一个数组来理解可能会更简单: + +.. code-block:: python + + bits: [0, 1, 1, 1, 1, 0, 0, 0, 1, 1] + BPSK symbols: [-1, 1, 1, 1, 1, -1, -1, -1, 1, 1] + Applying 8 samples per symbol: [-1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ...] + +我们将使用 :math:`\beta` 为 0.35 的升余弦滤波器,并将其设为 101 个抽头长度,以给信号足够的时间衰减到零。虽然升余弦方程需要我们提供符号周期和时间向量 :math:`t` ,但我们可以假设 **采样** 周期为 1 秒来 "归一化" 我们的仿真。这意味着我们的符号周期 :math:`Ts` 为 8,因为我们有每个符号 8 个采样点。于是我们的时间向量将是一个整数列表。根据升余弦方程的特性,我们希望 :math:`t=0` 位于中心。我们将生成从 -51 到 +51 的长度为 101 的时间向量。 + +.. code-block:: python + + # Create our raised-cosine filter + num_taps = 101 + beta = 0.35 + Ts = sps # Assume sample rate is 1 Hz, so sample period is 1, so *symbol* period is 8 + t = np.arange(num_taps) - (num_taps-1)//2 + h = np.sinc(t/Ts) * np.cos(np.pi*beta*t/Ts) / (1 - (2*beta*t/Ts)**2) + plt.figure(1) + plt.plot(t, h, '.') + plt.grid(True) + plt.show() + + +.. image:: ../_images/pulse_shaping_python2.png + :scale: 80 % + :align: center + +注意输出确实衰减到了零。我们使用每个符号 8 个采样点这一设定决定了这个滤波器看起来有多窄以及它衰减到零的速度有多快。上面的脉冲响应看起来就像一个典型的低通滤波器,我们实际上无法仅从外观上判断它是专门的脉冲整形滤波器还是其他普通的低通滤波器。 + +最后,我们可以用该滤波器对信号 :math:`x` 进行滤波并查看结果。不要过分关注代码中引入的 for 循环,我们会在代码块之后讨论它存在的原因。 + +.. code-block:: python + + # Filter our signal, in order to apply the pulse shaping + x_shaped = np.convolve(x, h) + plt.figure(2) + plt.plot(x_shaped, '.-') + for i in range(num_symbols): + plt.plot([i*sps+num_taps//2,i*sps+num_taps//2], [0, x_shaped[i*sps+num_taps//2]]) + plt.grid(True) + plt.show() + +.. image:: ../_images/pulse_shaping_python3.svg + :align: center + :target: ../_images/pulse_shaping_python3.svg + +这个结果信号是由许多脉冲响应叠加而成的,其中大约一半的脉冲响应先乘以了 -1。虽然看起来可能很复杂,但我们会一起来分析。 + +首先,由于滤波器和卷积运算的特性,数据前后会存在一些瞬态采样点。这些额外的采样点会包含在我们的传输中,但它们并不包含真正的脉冲 "峰值"。 + +其次,竖直线是在 for 循环中创建的,用于辅助可视化。它们旨在标示 :math:`Ts` 间隔出现的位置,这些间隔代表接收机对信号进行采样的位置。观察可以发现,在每个 :math:`Ts` 间隔处,曲线的值恰好为 1.0 或 -1.0,这使得它们成为理想的采样时刻。 + +如果我们要将这个信号上变频并发射出去,接收机就需要确定 :math:`Ts` 的边界在哪里,例如使用符号同步算法。这样接收机才能 *精确地* 知道何时采样以获得正确的数据。如果接收机采样稍早或稍晚,由于 ISI 的存在,它会看到略有偏差的值;如果偏差太大,接收到的就会是一堆奇怪的数字。 + +下面是一个使用 GNU Radio 创建的示例,它展示了在正确和错误的时刻采样时 IQ 图(即星座图)的样子。原始脉冲的比特值已标注在图上。 + +.. image:: ../_images/symbol_sync1.png + :scale: 50 % + :align: center + +下图代表理想的采样时刻以及对应的 IQ 图: + +.. image:: ../_images/symbol_sync2.png + :scale: 40 % + :align: center + :alt: GNU Radio simulation showing perfect sampling as far as timing + +与之对比,下图展示了最差的采样时刻。注意星座图中出现了三个聚类。我们恰好在每两个符号的正中间进行采样,因此采样值会有很大偏差。 + +.. image:: ../_images/symbol_sync3.png + :scale: 40 % + :align: center + :alt: GNU Radio simulation showing imperfect sampling as far as timing + +这是另一个不良采样时刻的例子,它介于理想情况和最差情况之间。注意此时出现了四个聚类。在高 SNR 下,我们或许勉强能用这个采样时刻,但这并不可取。 + +.. image:: ../_images/symbol_sync4.png + :scale: 40 % + :align: center + +请记住,时域图中没有显示 Q 值,因为它们近似为零,所以 IQ 图仅在水平方向上展开。 diff --git a/content-zh/sampling.rst b/content-zh/sampling.rst index 40065c79..1f72c9e7 100644 --- a/content-zh/sampling.rst +++ b/content-zh/sampling.rst @@ -18,7 +18,7 @@ IQ 采样是软件定义无线电(SDR)以及许多数字接收机(和发 麦克风是一种传感器,它将声波转换成电信号(电压)。这个电信号被 ADC(模数转换器)转换成声波的数字表示。 简化来说,麦克风捕捉声波,将声转换成电,然后将电转换成数字。可以看出,ADC 是模拟领域和数字领域之间的桥梁。 SDR 与麦克风惊人地相似,只不过使用天线而不是麦克风接收信号,它的内部也使用了 ADC。 -在两种情况中,电压水平都是由 ADC 进行采样的。你可以把 SDR 设备想象成以无线电波而不是声波为输入的麦克风。 +在两种情况中,电压水平都是由 ADC 进行采样的。你可以把 SDR 设备想象成以无线电波为输入、数字为输出的麦克风。 无论是处理声波还是无线电波,如果我们想要数字化捕捉、处理或保存一个信号,就必须对其进行采样。 采样这个过程看似简单,实际上还挺复杂。 diff --git a/content-zh/usrp.rst b/content-zh/usrp.rst index f8659772..7eec9153 100644 --- a/content-zh/usrp.rst +++ b/content-zh/usrp.rst @@ -43,9 +43,15 @@ Python 玩转 USRP .. code-block:: bash - sudo apt-get install git cmake libboost-all-dev libusb-1.0-0-dev python3-docutils python3-mako python3-numpy python3-requests python3-ruamel.yaml python3-setuptools build-essential # 译者注:这里可以提前配置 APT 源为清华源等镜像以加速下载 + sudo apt update + sudo apt install git cmake libboost-all-dev libusb-1.0-0-dev build-essential # 译者注:这里可以提前配置 APT 源为清华源等镜像以加速下载 + sudo pip install pybind11[global] + pip install numpy==1.26.4 docutils mako requests ruamel.yaml setuptools cd ~ git clone https://github.com/EttusResearch/uhd.git # 译者注:这里需要注意网络环境 + cd uhd + git checkout v4.8.0.0 + cd host mkdir build cd build cmake -DENABLE_TESTS=OFF -DENABLE_C_API=OFF -DENABLE_PYTHON_API=ON -DENABLE_MANUAL=OFF .. @@ -325,6 +331,12 @@ Python 玩转 USRP 为了 Debug,你可以通过检查 :code:`usrp.get_mboard_sensor("ref_locked", 0)` 的返回值来验证 10 MHz 信号是否传递到了 USRP。而对于 PPS 信号而言,如果它没有传递到 USRP,那么上面代码中的第一个 while 循环将永远不会结束。 +********************************************** +多台 B210 的相位相干同步(用于 MIMO) +********************************************** + +为了执行到达方向(DOA)估计和相控阵数字波束成形等操作,通常需要所有接收通道实现相位相干,即接收通道之间的相对相位保持恒定且可以通过校准消除。B200 和 B210 USRP 基于 AD9361 射频集成电路,其本振(LO)由芯片内部生成,无法外部馈入 LO。因此即使你为 USRP 提供了 10 MHz 参考信号和 PPS,也只能实现频率和采样时钟的同步,而无法实现相位同步。这是因为每次设备开机或改变频率时,VCO/PLL 链路中的分频器都会引入一个新的随机相位偏移,更多信息请参见 `此页面 `_ 。实现相位同步的一种方法是增加硬件,将校准信号(可以是 USRP 自身生成的信号、宽带噪声源或单音信号)分路后馈入所有接收端口,每次 USRP 开机或重新调谐时进行一次快速校准。请注意,更改增益也会导致相位偏移,但只要 B210 保持相同的增益,相位差不应发生显著变化。 `Techtile 项目 `_ 提供了关于此主题的更多信息,包括可能允许多台 B210 同步重新调谐以保持同步的自定义固件镜像,但每次无线电开机时可能仍需要使用外部硬件进行校准。 + **** GPIO **** diff --git a/index-zh.rst b/index-zh.rst index 80c5a042..52ff3505 100644 --- a/index-zh.rst +++ b/index-zh.rst @@ -21,4 +21,5 @@ content-zh/channel_coding content-zh/iq_files content-zh/multipath_fading + content-zh/pulse_shaping content-zh/about_author