Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions content-zh/about_author.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@
专长于 SDR(软件定义无线电),机器学习,LTE/5G-NR,以及频谱感知。
他是马里兰大学的兼职教授,在那里他创立了一门课程,该课程成为了他编写这本教科书的基础。
他的课程是一个面向对 SDR/DSP(数字信号处理)感兴趣的计算机科学本科生的四年级选修课。
这门课程使他更好地了解了如何深入浅出地吸引并教会那些编程能力很强但几乎不了解物理层知识的学生
这门课程使他更好地了解了如何深入浅出地吸引并教会那些编程能力很强但几乎没有无线通信和信号处理背景的学生
在马克的课程中遇到迷你黑客马拉松并不奇怪,学生们必须利用他们最近学到的知识来找到或解码(由 Marc 发送)的隐藏的信号。

Marc 也是 `GNU Radio项目 <https://www.gnuradio.org/>`_ 的主要负责人之一。
GNU Radio 是一个开源的 SDR 框架,在学术界和国防相关研究中被广泛应用。
虽然 Python 非常适合学习、快速实验和开发,但它并不适合大型和计算复杂的应用程序。
GNU Radio 能够实现更高级的 DSP 应用程序,而且使用 GNU Radio 开发的 App 或单独的 Block 非常容易与他人共享。
GNU Radio 可以用来实现更高级的 DSP 应用程序,而且使用 GNU Radio 开发的 App 或单独的 Block 非常容易与他人共享。

Marc 目前和他的妻子 Lindsey 以及他们的多只猫和狗一起住在华盛顿特区地区
他的爱好包括木工制作、激光切割、演奏单簧管/萨克斯风、帆船、园艺、制作/操纵无人机、制作/骑行电动滑板,以及钻研高级溜溜球技巧
Marc 目前和他的妻子以及他们的多只猫和狗一起住在华盛顿特区地区
他的爱好包括木工制作、机加工、激光切割、单簧管/萨克斯风、帆船、园艺和弹球机

邮箱: marc@pysdr.org

Expand Down
25 changes: 12 additions & 13 deletions content-zh/digital_modulation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@

注意,图中无线信号的纵坐标平均值为零。在讨论调制时,人们往往喜欢这样展现数据和绘图。

我们可以使用多于两个幅度级别,从而让每个符号携带更多比特。以下是 4-ASK 的示例。
我们可以使用多于两个幅度级别,从而让每个符号携带更多比特。以下是 4-ASK 的示例(幅度为 0 也是四种级别之一)
在这种情况下,每个符号携带 2 比特的信息。

.. image:: ../_images/ask2.svg
Expand Down Expand Up @@ -133,7 +133,7 @@

上图中,顶图展示的是由红色点表示的离散采样点(即我们生成的数字信号),底图展示的是调制后的真正能在空中发出的信号。
在真实的通信系统中,载波的频率通常远远高于符号变化的频率:
在这个例子中,每个符号包含载波(正弦波)的三个周期,但在实际中可能包含数千个周期,具体取决于载波频率有多高。
在这个例子中,每个符号包含载波(正弦波)的 2.5 个周期,但在实际中可能包含数千个周期,具体取决于载波频率有多高。如需了解更多关于 ASK 的信息,推荐参阅 `这篇资料 <https://ez.analog.com/ez-blogs/b/engineering-mind/posts/digital-signal-modulations-with-ask-rf-modulation-schemes-part-3-of-7>`_

************************
相移键控(PSK)
Expand Down Expand Up @@ -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 %
Expand Down Expand Up @@ -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
Expand All @@ -302,9 +301,8 @@ FSK 是相当容易理解的---我们在 N 个频率之间切换,每个频率
在设计 FSK 时,你一定会遇到一个关键问题:相邻频率之间的频谱间距应该是多少?
我们通常把这个间距用 :math:`\Delta f` 表示(单位是 Hz)。
我们希望在频域中避免频率重叠,这样接收机才能根据不同的频率辨别符号,所以 :math:`\Delta f` 必须足够大。
而每种频率的宽度则取决于我们的符号速率:每秒需要传输的符号越多,每个符号的持续时间越短,其频率宽度就越宽(回忆一下时间和频率之间的反比关系)。
而每种频率的宽度则取决于我们的符号速率以及所应用的脉冲整形滤波器:每秒需要传输的符号越多,每个符号的持续时间越短,其频率宽度就越宽(回忆一下时间和频率之间的反比关系)。
那么相应的,:math:`\Delta f` 就需要越大,以避免不同频率之间出现重叠。
我们暂时不会在本教材中深入讨论 FSK 的设计细节。

IQ 图无法展示不同的频率,它们展示的是幅度和相位。
虽然在时域中展示 FSK 是可能的,但是超过 2 个频率仍然会使符号之间的区分变得困难:
Expand All @@ -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 调制后的载波在时域上的示例动画。

在本教材中,我们主要关注数字信号调制方法。
Expand Down
46 changes: 36 additions & 10 deletions content-zh/frequency_domain.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 单位。

上述傅里叶变换的方程是连续形式,其实你只会在数学问题中看到它。离散形式的方程才更接近于它在代码中实现的形态:

Expand Down Expand Up @@ -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}

因此,在射频信号处理中,我们通常使用复指数而不是余弦和正弦函数。

****************************
时域中的顺序并不重要
****************************
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion content-zh/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
参与贡献
***************

如果你从 PySDR 中获得了帮助,别忘了与你的同事、学生和其他可能对这些资料感兴趣的终身学习者分享。你还可以通过在 PySDR 的 `Patreon <https://www.patreon.com/PySDR>`_ 上捐款来表达你的支持,你的名字将会显示在每章下面的章节列表的左侧。
如果你从 PySDR 中获得了帮助,别忘了与你的同事、学生和其他可能对这些资料感兴趣的终身学习者分享。你还可以通过在 PySDR 的 `Patreon <https://www.patreon.com/PySDR>`_ 上捐款来表达你的支持,你的名字将会显示在每章下面的章节列表的左侧。此外也可以通过 `一次性捐款 <https://www.paypal.com/donate/?hosted_button_id=FH3LQCJRUVPWL>`_ 的方式支持本项目。

此外,欢迎你通过 marc@pysdr.org 向我发送问题、评论或者建议,你将为这本教科书的完善做出贡献!
你还可以直接在教科书的 `GitHub页面 <https://github.com/777arc/PySDR/tree/master/content>`_ 上编辑源代码,欢迎你提交 Issue 或 Pull Request。
Expand All @@ -77,5 +77,7 @@
- `mrbloom <https://github.com/mrbloom>`_ 将 PySDR 翻译为 `乌克兰语 <https://pysdr.org/ukraine/index-ukraine.html>`_
- `Yimin Zhao <https://github.com/doctormin>`_ 将 PySDR 翻译为 `简体中文 <https://pysdr.org/zh/index-zh.html>`_
- `Eduardo Chancay <https://github.com/edulchan>`_ 将 PySDR 翻译为 `西班牙语 <https://pysdr.org/es/index-es.html>`_
- John Marcovici
- `Vishwaksen Reddy Dhareddy <https://www.linkedin.com/in/vishwaksen-/>`_ 贡献了检测章节中的实时数据包检测部分

以及所有 `PySDR Patreon <https://www.patreon.com/PySDR>`_ 支持者!
12 changes: 7 additions & 5 deletions content-zh/iq_files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,26 @@ 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
:scale: 70 %
: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 代码示例
Expand Down
Loading
Loading