1/3
2/3
3/3
Pi Pico Rx-一台数字时代的晶体管收音机?
win小邵
编辑于 2024年03月20日 04:56
收录于文集
共1篇

原文地址 https://101-things.readthedocs.io/en/latest/radio_receiver.html

Github地址 https://github.com/dawsonjon/PicoRX

百度网盘链接 https://pan.baidu.com/s/1Cw-SaGbAzdAEwMZwowG7IA?pwd=qy1v

  • 前言

    第一次带我走进电子世界的是一台晶体管收音机,就像这样:

crystal radio

    之前,我从来没有想过能自己组装一台收音机,所以我不指望他能正常工作。但当我戴上耳机,我非常惊讶地听到了一些微弱的声音。我简直不敢相信,制造一个收音机会如此简单,而且更好的是它不需要电池。这个小小的经历激发了我的电子的兴趣。

    时光飞逝,在今天,我们正处于一个电子爱好者的黄金时代。回到上世纪80年代,我从来没有想象过我的零钱能买一台算力与房间一样大的计算机相当的设备。

    我常常想,如何还能体会到第一次制作晶体管收音机时的那种敬畏和兴奋感。我还能创造出这样简单而迷人的东西吗?

    Pi Pico Rx - 可能是这个问题的答案。虽然它的看起来不像晶体管收音机那么直观,但Pi Pico Rx的结构非常简单,只有一个树莓派Pico、一个模拟开关和一个运算放大器。现在我们能制作一个强大的SDR接收机,覆盖LW、MW和SW波段,甚至能够接收来自地球另一边的信号。我不禁想,年轻时的自己一定会被深深打动!

原理图,详见Github或度盘链接

  • 特点

  • 0~30MHz的接收范围

  • 250KHz带宽

  • CW/SSB/AM/FM 接收

  • OLED屏幕

  • 频谱显示

  • 耳机/扬声器 输出

  • 500个频道存储

  • 3x AAA电池供电

  • 电流消耗低于50mA

  • Pi Pico Rx

Pi Pico Rx

    Pi Pico Rx是一个基于树莓派Pico的SDR接收机。它使用了由Dan Tayloe推广的“Tayloe”正交采样检波器 (QSD),其被用于许多HF波段的SDR设计中。这种简单的设计允许使用一个廉价的模拟开关来实现一个高质量的混频器。

    利用RP2040的PIO设计了一个正交振荡器。这就不需要使用外部可编程振荡器。RP2040在不超频的情况下可以输出最大约30MHz的频率,能够覆盖LW、MW和SW波段。

    QSD的I/Q输出通过一个高速低噪声的运放进行放大。I和Q通道由内置的ADC采样,可达到250 kHz的带宽。双核ARM Cortex M0处理器实现了对数字信号的计算,以解调AM、FM、SSB和CW信号,产生音频输出。

    音频输出是通过PWM和低通滤波器实现的。起初,我使用的是LM386音频放大器,但后来发现,有了合适的限流电阻,IO引脚可以很容易地驱动一副耳机甚至一个小扬声器。

    一个简单拼凑的样机证明了使用Pi Pico、模拟开关、运算放大器和一些离散器件构建HF波段SDR是可能的。

  • 其他SDR接收机

    上述设计在SDR中不算什么新东西。有的人使用PC声卡,有的人使用嵌入式MCU,uSDX项目甚至使用8位单片机进行DSP处理!我列出了一些启发了我的项目的链接列表。每一个项目都有一些新的想法和创新。我希望通过介绍自己制作Pi Pico Rx的过程来激励其他人做自己的项目。

  • softrock SDR https://www.wb5rvz.org/

  • QRP Labs https://www.qrp-labs.com/receiver.html

  • mcHF http://www.m0nka.co.uk/

  • elecraft KX3 https://elecraft.com/products/kx3-all-mode-160-6-m-transceiver

  • usdx https://github.com/threeme3/usdx

  • (tr)uSDX https://dl2man.de/

  • uSDR-pico https://github.com/ArjanteMarvelde/uSDR-pico

  • compact-si5351-based-SDR https://circuitsalad.com/2020/01/06/compact-si5351-based-SDR/

  • qex https://www.arrl.org/files/file/Technology/tis/info/pdf/020708qex013.pdf

  • Teensy-Convolutional SDR https://github.com/DD4WH/Teensy-ConvolutionSDR

    本着知识分享的精神,我将继续在这个wiki中描述软硬件的细节,从Pi Pico Rx引入的新功能的开始。这些设计是我以前在其他项目中从未见过的。

  • 使用ADC交替采样I/Q数据

    我面临的挑战之一是使用一个单通道ADC对I/Q信号进行采样。它可以配置为round-robin模式,交替采样I/Q数据。我担心这可能会导致I/Q通道之间的相位不一致。事实证明我不用担心,有一种简单的方法可以在500KSPS下交替采样I和Q来复原具有250 kHz带宽的混合信号。

    诀窍是对I/Q数据进行低通滤波,使250 kHz的带宽保留到-125kHz~125 kHz。QSD检波器本身构成了一个低通滤波器,这很容易通过在运放中选择合适的电容值来实现。ADC配置为交替采样I/Q数据(从I开始)。在软件中,“缺失”的值可以用零代替。

频谱中的镜像部分

    此时得到了一个采样率为500KSPS的混合信号。其中我们想要的频谱为-125kHz到125 kHz。从-250kHz到-125kHz和125 kHz到250 kHz的频谱包含了中央部分的镜像。为了恢复原始的频谱,我们只需要对信号进行低通滤波,以便保留中心部分。然后我们可以将采样率降低到250 kHz。在此应用中,我们只过滤掉包含所需信号频谱中的一小部分,不需要采取其他步骤来去除镜像信号。

  • 为什么可以这样采样

    为了理解这种方法的可行性,我们可以思考如何将信号转换为真实信号(而不是复杂的I/Q信号),并使用单通道ADC对其进行采样。这是我最初考虑采取的一种方法之一。当我解决了这个问题后,我才意识到存在一种更简单的方法。这就是我的思考过程。

每步操作对应的频谱

    为了满足奈奎斯特采样的要求,我们需要过滤掉多余的部分,使得所有信号都在-125kHz到125kHz之间。然后,我们可以将频谱向上移动125kHz,从而使频谱在0到250kHz之间。频谱移动Fs/4可以通过在每个样本中旋转信号1/4圈来实现,无需使用乘法运算符,只需添加负号。

    由于我们的信号现在只包含正的频率,所以信号的虚数(Q)部分不包含任何有用的信息,我们可以把它扔掉。一个只包含实(I)值的信号具有对称的频谱,丢弃假想的采样会引入正频率信号的负频率镜像。

    实际信号现在可以用单通道ADC以500KSPS进行采样。频移可以在硬件中使用一个简单的混频器来实现,但我们只需要I和Q采样交替进行,所以我们可以使用ADC来交替采样I和Q,并在用软件实现混频,必要时可以翻转I和Q。

    一旦我们在软件中得到了实际信号,我们可能会想把实际信号转换回一个复杂的IQ信号。我们可以使用希尔伯特变换,这将过滤掉负频率,留下一个复杂的信号,不对称频谱只包含0到250 kHz的正频率。

    另一种方法是将频率降低125kHz,使原始频谱从-125kHz变为125kHz,现在镜像存在于频谱的外半部分,它们可以用低通滤波器去除。我们可以对Fs/4频移采取相同的方法,这次将每个样本在相反的方向旋转1/4。

    查看频谱,我们可以看到频率下移已经抵消了我们在频率上移期间导致的翻转,得到我们最初的交替I/Q采样结果。

    事实证明,仅需交替采样I/Q数据即可满足频谱的中间部分,“缺失”的采样只影响我们已经过滤掉的部分。

  • 使用PIO实现正交振荡器

    pi pico基于RP2040单片机,而PIO是RP2040的一个新特性。可编程状态机(类似于小型的逻辑电路)可以被配置为独立于软件的IO功能。配置一个PIO状态机来在2个IO引脚上输出一个正交振荡器是相当简单的。一旦配置好,振荡器将自行运行,无需软件控制,不会给CPU施加额外负载。

PIO的编程非常简单:

代码块
JavaScript
自动换行
复制代码
.program nco
set pins, 0
set pins, 1      ; Drive pin low
set pins, 3      ; Drive pin high
set pins, 2      ; Drive pin low
复制成功

    NCO的频率可以使用PIO时钟分频器进行编程。它有一个16位的整数和一个8位的小数部分。通过一个125MHz的时钟输入,NCO可以从几百Hz编程到30MHz以上。很适合LW、MW和SW的接收。

    在低频时,这可以实现较好的分辨率,但在高频时,调节步长将超过100 kHz。然而,对于250 kHz的带宽来说,这仍然足以在频率范围内实现连续调节。为了补偿振荡器的误差,在软件中实现了一个高分辨率的移频器。(32位相位累加器的理论分辨率为略高于0.0001 Hz,这应该足够了。)

  • 硬件设计

    硬件的设计目标是使设计尽可能简单和廉价,而不太影响性能。我设计了一个PCB,它拥有一些基本的元件,包括一个前置放大器和一组低通滤波器。要查看详细信息,您可以查看pdf格式的完整原理图 :

https://github.com/dawsonjon/PicoRX/raw/master/PCB/pico_rx/pipicorx.pdf

但我仍将在这里介绍一些设计上的细节。

  • Raspberry Pi Pico

    接收机的核心是一个树莓派Pico。内置500KSPS采样率的ADC给了我们250 kHz的带宽。12位ADC的理论动态范围为72 dB,但实际上达不到。鉴于一个SSB信号只需要2.5 kHz的带宽。我们可以通过牺牲多余的带宽来增加动态范围,提高灵敏度。100倍的过采样给我们增加了20 dB信噪比,相当于增加3位。这使得SSB模式下的理论动态范围为92 dB。ADC的输入电压范围为0~3.3V。没有前置放大的情况下,这代表了-78 dBm到14 dBm的范围。

RP2040

    树莓派Pico板载一个电源切换模块,这允许Pico轻松地由电池供电。这种设计使用了3xAAA电池。通过为树莓派Pico添加其他元件,使其可以由电池或USB电源供电。为了保证其便携性,可以通过USB直接烧录Flash。为了避免添加其他的元件,我选择从pico中删除D1。这防止了电池供电与USB供电冲突。

  • 用户界面

    UI没什么特别之处。我使用了一片基于SSD1306的I2C接口的12864 OLED屏幕,它取代了HD44780成为了更常用的廉价屏幕。I2C接口有助于减少针脚数。出于成本考量,我想使用一对按键来代替旋转编码器,但我又觉得这太激进了。如果没有旋钮,它就不像收音机了。在设想中,我会使用一些更小巧的东西,比如一个装在侧面的拨轮开关,但它似乎很难买到。轨迹球也吸引了我的注意,但最终成本战胜了我,我用了一个普通的编码器。

显示、按键电路

  • PWM音频

    起初,我考虑使用一个LM386(或类似的)音频放大器来驱动耳机或小扬声器。事实证明,PWM信号完全能够直接驱动耳机或小扬声器。用100uF电容耦合信号,电容越大,低频越好,但这里使用100uF可能会过载。RP2040的最大驱动电流为12 mA。100欧姆电阻用作限制电流并构成一半的低通滤波器。峰值电压为1.65v,假设内阻约为40欧姆,接入32欧姆负载时的最大电流为1.65/(100+40+32)=9.5 mA,8欧姆负载为1.65/(100+40+8)=11.1 mA。

PWM转音频电路

    如果要更好的外放效果,TPA2012看起来是LM386的理想替代品,并且是电池供电的好选择,也可以很好地搭配PC扬声器输出。但注意驱动器输出的峰峰值明显高于常见的100 mV 。

  • QSD检波器(Tayloe检波器)

    该设计使用了由Dan Tayloe推广的“泰勒”正交采样检波器(QSD)。它被用于许多SDR接收机中,这是有充分理由的。在我的设计中,由树莓派Pico驱动模拟开关进行输入选择,RP2040的PIO特性能够实现在不需要软件控制的情况下产生一个频率高达30MHz的正交振荡器。通过选择电阻值以获得1000倍或60 dB的增益。这时QSD的理论输入范围达到了-138 dBm到-46 dBm。选择滤波电容值以达到约60 kHz的截止频率和120 kHz的带宽。因为有QSD作为抗混叠滤波器,所以一定程度的过采样对提高接收效果有帮助。需要高速运放以满足增益和带宽要求,LT6231是这种SDR的主流选择,因为它的高速低噪声,可以满足设计中的带宽需求。新的设计推荐用更好更新的LTC6227。

“Tayloe”正交采样检波器 (QSD)

    这种设计的一个潜在问题是ADC采样可能混叠。这对于使用声卡或音频adc的SDR来说不是问题,它们通常拥有非常好的数字方波滤波器。一个可行的改进将是添加一个有源低通滤波器。这可以利用一个更基本的(和更便宜)的)运放。此外,我们还可以通过级联几个廉价的运算放大器来共享它们之间的增益来节省成本,每级的增益带宽积可以低得多,而后端运放的噪声性能也不那么重要。

  • 低通滤波器

    Tayloe检波器使用了一个开关,而不是一个模拟混频器,这给出了类似于混合入射射频与方波的行为。这意味着QSD对基波的奇次谐波很敏感,调谐频率的3次谐波是最强的。这种设计采用了低通滤波器来强衰减奇数谐波。5个滤波器覆盖频率范围从1MHz到30MHz。每个频带覆盖一个八度,在1MHz到2 MHz的通频带中,2 MHz的截止频率衰减了可能在3MHz和6MHz之间的三次谐波。随着频率的增加,频带的宽度可以加倍,因此从1MHz到30MHz的范围可以被5个滤波器覆盖。为了覆盖完整的LW和MW范围,我需要至少需要3个滤波器,考虑到这部分频谱的站点数量有限,这似乎太浪费了,所以我决定容忍在这个范围内可能存在的奇次谐波干扰。对这些波段感兴趣的用户需要添加一个额外的滤波器,它甚至可以内置到磁环或铁氧体天线中。

低通滤波电路

    在实测中,强本地AM电台会引起干扰,因为这些信号往往在较低的频率,低通滤波器对它们的作用很小。带通滤波器会有更好的性能。我发现安装一个外置AM带阻滤波器大大提高了SW频段的性能。

    我用 nanovna测量了滤波器的响应曲线。这排除了设计中的许多不确定性。我直接测量了PCB上的滤波器电路。

nanovna测量滤波器参数

    这个截止频率为4MHz的滤波器在阻带上的衰减超过60 dB。在通带中,插入损耗约为10dB。这给出了理论功率范围约为-128 dBm到-36 dBm。

截止频率为4MHz的滤波器阻抗曲线

  • 前置放大器

    在LW/MW/SW波段,有高水平的大气噪声。可以说,一个前置放大器是不必要的。如果我们能实现-128 dBm到-36 dBm的理论范围的话,这将满足我们的需要。在实际应用中,ADC的内部噪声约为20 dB。因此一个略大于-100dBm的MDS可能是一个更实际的数字。

    一个很好的经验法则是,接收机应该能够“听到”天线噪声,以达到分辨微弱信号的能力。测试这一点的一个好方法是在连接天线时寻找噪声水平上升的一个s点。

    连接上阁楼上的一根天线,我发现接收机足够灵敏。然而,对于便携式使用,我们需要一个更紧凑的天线。我使用youloop天线时接收效果很好,但我需要添加一个低噪声放大器来得到这么好的效果。我在原型中使用了一个经典的基于MMIC的20dB LNA。我考虑过使用像MAR6 https://www.minicircuits.com/pdfs/MAR-6+.pdf 这样的MMIC放大器。最终,我选择使用LTC6226(QSD中使用的LTC6227运放的单通道版本)。该低噪声放大器具有足够的GBP以在30MHz带宽上提供20dB增益。该放大器采用具有50欧姆输入阻抗的反相配置。运放反馈网络包括一个电容和电阻器,以构成一个截止频率为30MHz的低通滤波器。

前置放大电路

    加上LTC6226前置放大器,我能用youloop天线收到很多微弱的信号,但我又发现,强大的本地AM电台会使接收过载,从而导致严重的削波失真。可能可以通过调整前置放大器的增益,以找到一个合适的值,从而接收更多信号。也许也可以增加一个可调衰减器,使接收机更加适用。

  • 外壳

    外壳往往是电子项目中最昂贵的部分之一。然而,现在可以通过丝印来以非常低的成本制造各种颜色的PCB,它们可以被精确地加工,并且非常坚固。简而言之,它们是理想的前、后面板。我选择了PCB以三明治式的结构来搭建一个便宜、坚固且看起来相当时尚的外壳。

  • 软件设计

    树莓派Pico包含一个双核处理器。第一核心处理用户界面,包括驱动屏幕、编码器、按钮、刷新界面。第二个核心专注于DSP功能。核心使用控制和状态结构体进行通信,这些结构体由互斥锁保护。控制和状态数据周期性的在两个核之间传递。

  • ADC接口

    ADC接口配置为循环模式。两个DMA通道用于将4000次采样的块从ADC传输到内存。4000次采样的选择相当随意,当遇到某次计算时间明显长于平均值时(以牺牲额外内存为代价),较多采样的块会提供额外的余量。DMA通道是以乒乓方式配置DMA链接的。当一个DMA传输数据完成时,另一个DMA通道自动启动。DMA链接允许ADC采样后自动传输数据,不会占用CPU。

  • 实时处理

    当每个DMA传输完成时会触发函数。函数输入一组I/Q数据,并输出一组解码后的音频数据。当采样率为500KSPS时,处理每组数据的时限为8 ms。当CPU频率为125MHz时,每组数据恰好有100万个时钟周期。计算完成后,计时器记录空闲时间,直到下一组数据传输完成。CPU利用率可以计算为,在开发期间监视CPU利用率很有用,可以方便观察每次改动的影响。函数是软件中唯一对时间敏感的部分,这部分软件使用定点算法,并从内存运行以最大化性能。该软件的其他部分并不是特别重要,所以它可以从Flash中运行,浮点运算可以自由使用内存。

  • DC偏置

    第一个任务是去除DC分量,这是通过对每个块中的数据进行平均来实现的,平均值代表DC值,然后从下一组数据中减去这个值。这比使用直流阻塞滤波器稍微快一些。在DSP链的这一点上,DC去除过程并不那么关键。接收机使用一个低IF,所以想要的信号总是从直流偏移几个kHz。一旦我们对信号进行了频移,任何剩余的DC都在通带之外,并被抽取滤波器去除。首先,我从原始的(无符号的0到4095)ADC采样中减去2048,以得到一个有符号的值(-2048到2047)。事实证明,这个过程是多余的,我们可以忽略减法,在减去DC偏置的过程中把它看作是额外的2048的DC偏置,并删除它即可。

  • 频移

    在对采样数据进行频移之前,我们需要将I/Q数据分离。轮询ADC交替采样I/Q数据,所以偶数次的采样是I,奇数次的采样是Q。分离复合的采样后需要将“缺失”样本用零代替。

代码块
JavaScript
自动换行
复制代码
int16_t i = (idx&1^1)*raw_sample; //even samples contain i data
int16_t q = (idx&1)*raw_sample; //odd samples contain q data
复制成功

    由于RP2040可以在一个时钟周期内执行一个乘法,因此,将采样乘以1或0比使用三目运算 更快。这在其他MCU上可能不是如此。一旦信号是复数形式,我们就可以用复频乘以固定频率将所需的信号转移到频谱中心。

    频率偏移有两个分量,第一个是补偿正交振荡器有限的频率分辨率(我们想要的频率和我们得到的频率之间的差异)。另一个分量是我们故意引入的低中频偏移,以使想要的信号远离直流电。直流附近往往会有很多干扰,如漏电、市电嗡嗡声等。应用频率偏移可以滤除这种干扰。

    我们需要创建一个复杂的音调来“擦除”频率偏移。我们无法以足够快的速度计算sin和cos,以满足实时操作,因此我们计算了一个完整周期的2048个值的查找表。通过对sin和cos值使用相同的查找表,可以节省一些内存,cos是通过对索引应用pi/2相移从sin表计算出来的。这些值被缩放为15个比特位,大小略小于1,以充分利用可用的16个比特位,而不会导致溢出。

代码块
JavaScript
自动换行
复制代码
//pre-generate sin/cos lookup tables
float scaling_factor = (1 << 15) - 1;
for(uint16_t idx=0; idx<2048; idx++)
{
    sin_table[idx] = sin(2.0*M_PI*idx/2048.0) * scaling_factor;
}
复制成功

    对于每个样本,32位相位累积了一个样本的相位变化值(频率)。32位的相位和频率值被缩放,以便 0~(2^32)-1 表示 0~(几乎)2*pi 的范围。相位累加器的高11位被用作查找表的索引。虽然只有11位被用于查找表索引,但相位会累积到一个更高的分辨率。由低21的数据位所引起的误差会导致短期的相位抖动,但这将在后续的周期中得到补偿,为我们提供长期的非常精确的平均频率。

代码块
JavaScript
自动换行
复制代码
const uint16_t phase_msbs = (phase >> 21);
const int16_t rotation_i =  sin_table[(phase_msbss+512u) & 0x7ff]; //32 - 21 = 11MSBs
const int16_t rotation_q = -sin_table[phase_msbs];
phase += frequency;
复制成功

    然后,可以通过复数乘法将该音调应用于信号,从而使所需信号移至频谱中心。乘法结果现在有多出的15位小数部分需要去除。截断会造成大约1/2LSB的负偏置,这在后续信号处理过程中可能会造成问题(特别是对于CW信号,我们故意将直流移至可听范围)。在这里,我们可以用四舍五入方法来更好的消除偏置,但这会在软件的关键部分消耗大量的CPU周期。一种更为高效的方法是估计每一步处理引入的偏置,然后在后续过程中补偿总偏置,在降采样之后去除偏置可以大大减少所需的计算周期数量。

  • 降采样

    此时,我们仍然在以500KSPS的采样率工作,这远远超过了我们的需要。我们试图处理的最高带宽信号是一个具有9 kHz带宽的FM信号。在这个阶段,我们可以大幅降低采样率,这将在之后的过程中减少计算量。在这个设计中,轮询IQ采样在频谱的外半部分引入信号。这些信号也会在降采样过程中被删除。

    降采样是通过CIC和半带滤波器的组合来实现的,这样我们就得到了一个狭窄的频谱。

不同滤波器的频率响应曲线

    CIC是一种非常有效的滤波器设计,但它没有非常锋利的边缘,这导致边缘频谱的混叠。这些混叠使用前半带滤波器去除,然后进一步降采样2倍。第二个和最后一个半带滤波器删除了残留在频谱边缘的任何混叠,最后的半带滤波器是一个高阶滤波器,用于实现更清晰的边缘。在最后阶段不进行降采样,以免引入其他的混叠。

    在这种设计中,降采样因子根据模式进行调整,从而产生不同的最终采样率和带宽。这是一种非常简单有效的方法来改变最终滤波器的带宽。

滤波器参数

  • 解调AM

    在这个项目中,AM解调是通过取复样本的幅值来实现的。为了避免使用平方根,我们计算了一个更有效的大小近似值。这是使用最小/最大近似计算的,基于我在这里找到的近似幅度的方法 http://dspguru.com/dsp/tricks/magnitude-estimator/。

代码块
JavaScript
自动换行
复制代码
uint16_t rectangular_2_magnitude(int16_t i, int16_t q)
{
  //Measure magnitude
  const int16_t absi = i>0?i:-i;
  const int16_t absq = q>0?q:-q;
  return absi > absq ? absi + absq / 4 : absq + absi / 4;
}
复制成功

    AM载波现在看起来像一个大的直流偏置,使用直流滤波器滤波。

代码块
JavaScript
自动换行
复制代码
int16_t amplitude = rectangular_2_magnitude(i, q);
//measure DC using first-order IIR low-pass filter
audio_dc = amplitude+(audio_dc - (audio_dc >> 5));
//subtract DC component
return amplitude - (audio_dc >> 5);
复制成功

    这是AM解调的最简单的方法之一,实现同步调幅检测器应该能提高性能。

  • 解调FM

    解调FM与解调AM类似。这次,我们取一个样本到另一个样本的相位变化。我在相同的地方找到了相位近似和幅度近似,并针对这个应用修改了它。我缩放了输出以使用16位整数的全范围。这样,我就可以从16位数字中获得可能的最佳分辨率,而当整数溢出时,相位卷绕就会出现。

代码块
JavaScript
自动换行
复制代码
int16_t rectangular_2_phase(int16_t i, int16_t q)
{

   //handle condition where the phase is unknown
   if(i==0 && q==0) return 0;

   const int16_t absi=i>0?i:-i;
   int16_t angle=0;
   if (q>=0)
   {
      //scale r so that it lies in the range -8192 to 8192
      const int16_t r = ((int32_t)(q - absi) << 13) / (q + absi);
      angle = 8192 - r;
   }
   else
   {
      //scale r so that it lies in the range -8192 to 8192
      const int16_t r = ((int32_t)(q + absi) << 13) / (absi - q);
      angle = (3 * 8192) - r;
   }

   //angle lies in the range -32768 to 32767
   if (i < 0) return(-angle);     // negate if in quad III or IV
   else return(angle);
}
复制成功

    实际输出与理想输出非常吻合  。

实际输出与理想输出拟合曲线

    现在,通过将每个样本的相位与前一个样本的相位进行比较来解调FM信号是非常简单的。

代码块
JavaScript
自动换行
复制代码
int16_t phase = rectangular_2_phase(i, q);
int16_t frequency = phase - last_phase;
last_phase = phase;
return frequency;
复制成功

    这是最简单的解调FM方法之一。

  • 解调SSB

解调SSB步骤框图

    在采样器的输出端,我们有一个具有5kHz带宽的混合信号,覆盖了从-2.5kHz到+2.5kHz的频率范围。正频率表示上边带,负频率包含下边带。我们只需要其中一个边带。相反的边带可能包含另一个信号或干扰,所以我们想要过滤掉它。

    过滤SSB信号的一种有效方法是用复数乘法器Fs/4对频率进行上移,并用只保留负频率分量的对称半带滤波器对信号进行滤波。然后频率被Fs/4下移,只留下较低的边带。

    选择Fs/4是因为它易于实现。频率为Fs/4的复正弦波仅由0、1和-1组成。与0、1或-1的乘法可以使用简单的算术运算实现,不需要任何乘法或三角运算。

    选择一个半频带滤波器-Fs/4到Fs/4可以进一步提高效率。半带滤波器的核心是对称的,这可能会减少一半的乘法运算,或者减少一半需要存储的数据。除此之外,大约有一半的值是0,同样也大约占乘法的一半。总的来说,这个滤波操作减少了所需的乘法数,仅为原来的1/4。

    如图所示的结构留下了信号的下边带部分。可以通过先将频谱下移,再上移来产生上边带信号。

代码块
JavaScript
自动换行
复制代码
if(mode == USB)
{
  ssb_phase = (ssb_phase + 1) & 3u;
}
else
{
  ssb_phase = (ssb_phase - 1) & 3u;
}

const int16_t sample_i[4] = {i, q, -i, -q};
const int16_t sample_q[4] = {q, -i, -q, i};
int16_t ii = sample_i[ssb_phase];
int16_t qq = sample_q[ssb_phase];
ssb_filter.filter(ii,  qq);
const int16_t audio[4] = {-qq, -ii, qq, ii};
return audio[ssb_phase];
复制成功

    一旦我们过滤掉了另一半的边带,我们就只剩下2.5 kHz的带宽。我们现在可以丢弃假想的分量,留给我们一个真实的音频信号

  • 解调CW

    CW信号比语音信号占用更少的带宽,许多CW信号可以被容纳在一个语音信号的带宽内。为了提取单个信号,我们需要一个更窄的滤波器。为了实现这一点,我们使用相同设计的第二个抽取滤波器。这次CIC的抽取率为10,然后是两个半带滤波器,最终带宽为150Hz。产生的信号位于直流附近,所以听不到。为了将信号转换为可听的音调,我们需要通过与CW侧音调混合来移频。这使用了与上述相同的移频技术。使用相同的正弦查找表,并将新的相位累加器调谐到侧音频率。因为我们打算去掉信号的虚数(Q)部分,所以我们一开始就不必去计算它。

代码块
JavaScript
自动换行
复制代码
if(cw_decimate(ii, qq)){
    cw_i = ii;
    cw_q = qq;
}
cw_sidetone_phase += cw_sidetone_frequency_Hz * 2048 * decimation_rate * 2 / adc_sample_rate;
const int16_t rotation_i =  sin_table[(cw_sidetone_phase + 512u) & 0x7ffu];
const int16_t rotation_q = -sin_table[cw_sidetone_phase & 0x7ffu];
return ((cw_i * rotation_i) - (cw_q * rotation_q)) >> 15;
复制成功

  • 音频AGC

    AM或SSB信号的响度取决于接收信号的强度。非常弱的信号比非常强的信号音量小得多。FM信号的振幅与信号的强度无关,而是与频率差有关。因此,宽带FM信号的声音将比窄带FM信号的更大。在一般情况下,AGC缩放输出,以给出一个相似的响度,无论信号强度或带宽。

    这可能有点棘手,在语音中,单词之间发音有间隔。如果AGC反应太快,那么会放大发音间隔的噪声。相反,如果AGC反应太慢,那么突然增大的音量将导致输出饱和。UHSDR https://github.com/df8oe/UHSDR/wiki/Automatic-Gain-Control-(AGC)有很好的设计方案,OpenXCVR也使用了类似的设计。

    AGC的第一步是估计信号的平均幅值,它是通过一个漏电流最大保持电路实现的。当输入信号大于估计值时,电路的反应是迅速增加估计值(触发周期)。当输入小于幅度估计时,在响应之前等待一段时间(挂起周期)。挂起周期过后,电路通过缓慢降低估计幅度(衰减)来响应。触发周期总是相当快,但挂起和延迟周期是可编程的,并由AGC速率设置控制。该图显示了幅值估计如何响应不断变化的输入幅值。

AGC响应曲线

    在估计了幅值之后,通过将期望幅值除以估计幅值来计算增益。算出增益后,我们简单地将信号乘以增益来对输出音量进行缩放。在信号幅度迅速增加,AGC没有时间做出反应的情况下,我们需要防止信号溢出。这是通过软限幅和硬限幅相结合来实现的。高于软限幅阈值的信号逐渐减小输出,高于硬限幅的信号被箝位到最大值。

代码块
JavaScript
自动换行
复制代码
static const uint8_t extra_bits = 16;
int32_t audio = audio_in;
const int32_t audio_scaled = audio << extra_bits;
if(audio_scaled > max_hold)
{
  //attack
  max_hold += (audio_scaled - max_hold) >> attack_factor;
  hang_timer = hang_time;
}
else if(hang_timer)
{
  //hang
  hang_timer--;
}
else if(max_hold > 0)
{
  //decay
  max_hold -= max_hold>>decay_factor;
}

//calculate gain needed to amplify to full scale
const int16_t magnitude = max_hold >> extra_bits;
const int16_t limit = INT16_MAX; //hard limit
const int16_t setpoint = limit/2; //about half full scale

//apply gain
if(magnitude > 0)
{
  int16_t gain = setpoint/magnitude;
  if(gain < 1) gain = 1;
  audio *= gain;
}

//soft clip (compress)
if (audio > setpoint)  audio =  setpoint + ((audio-setpoint)>>1);
if (audio < -setpoint) audio = -setpoint - ((audio+setpoint)>>1);

//hard clamp
if (audio > limit)  audio = limit;
if (audio < -limit) audio = -limit;

return audio;
复制成功

  • 音频输出

    音频输出是通过PWM实现的。输出滤波使用一个非常简单的低通RC滤波器。PWM频率需要权衡。频率越高,纹波越低,频率越低,分辨率越高。我发现500kHz的PWM频率是一个很好的折中。这提供了大约8位的音频分辨率,同时将纹波降低到可接受的水平且不在听觉范围内。由于我们只需要几千赫的带宽,因此应该可以通过在输出上使用高阶低通滤波器来实现更大的分辨率。然而,所选的PWM频率提供了比我预期更好的音频质量,使用非常简单和经济的硬件,并且在较低的音量设置下不会明显下降。

PWM音频使用2个DMA通道交替地从ADC DMA传输数据。ADC DMA、运行在核心1上的 函数和PWM DMA形成了一个流水线。在任何时候,这三个进程都并行处理一个块。

音频DMA数据流

  • 数据捕获

    用户界面提供了一个简单的频谱图。虽然频谱图的大部分计算是在核心0上的用户界面中执行的,但在每个块的处理期间需要捕获数据。数据在频移后被捕获到捕获缓冲区。不需要在每个块期间捕获数据,只需要以屏幕的刷新率进行更新。捕获缓冲区由互斥锁保护,但重要的是,获得对互斥锁的访问永远不会延迟信号处理。因此,只有当互斥量可以使用时,才会捕获数据。一旦捕获了一定缓冲区容量的数据,就会释放互斥量。

  • 捕获电池电压和CPU温度

    虽然不是必要的,但监测电池电压和CPU温度是很好的功能,树莓派Pico可以使用ADC监测这些数据。不幸的是,ADC被I/Q采样占用了。解决方法是中断几个周期的I/Q采样,以捕获电压和温度数据。虽然每分钟左右丢失这些采样对音频质量的影响不大,但我们必须在正确的时间进行I/Q采样。要想可靠地实现这一点,就需要完全停止接收机过程。DMA通道都停止刷新。然后,ADC可以在单次采样模式下捕获电压和温度,然后再重新配置为轮询模式并重新启动接收机。

  • 操作界面

    操作界面非常简单,只包括一个旋转编码器,按钮和一个小的OLED屏幕。接收机可以使用菜单设置。PIO特性大大简化了旋转编码器的实现。PIO可以独立于软件实现触发,而不需要经常检测编码器位置的变化。

  • 频谱图

    OLED屏幕有足够的空间来显示一个简单的频谱图。这是通过对捕获缓冲区加窗和执行FFT来实现的。由于采用循环采样的方法,频谱的外半部分不包含任何有用信息,因此只捕获256个点中的中心128个点。为了降低噪声水平,对多个FFT的结果进行了非相干合并。频谱被缩放到128像素宽,64像素高。幅度被自动调整为64格。屏幕不允许单独改变像素的亮度,所以不能实现瀑布图。但是,如果使用了其他类型的屏幕,那么实现瀑布图应该相对简单。

  • Flash接口

    接收机包括一个512通道存储器,其中每个存储器可以保存单个频率或感兴趣的频段。这些通道在编译时被写入预设值,但是用户可以通过菜单重写这些内存。虽然使用外部I2C EEPROM设备实现这个功能相当简单,但可以通过访问未使用的flash区域来模拟这个功能。虽然一个合适的EEPROM相当便宜,但降低硬件的复杂性(和成本)是这个项目的主要目标之一。

    从Flash读取相当简单,可以通过在flash中读取一个常量数组。写入Flash是另一回事,Flash数据只能逐个扇区进行读写。同样重要的是,软件在写入Flash时不能读取Flash。将单个通道写入flash的过程为:

  • 复制整个扇区到RAM

  • 在内存副本中,更新保存内存通道的扇区部分

  • 暂停接收机,终止所有DMA传输

  • 停止核心1

  • 禁用所有中断

  • 擦除Flash扇区

  • 将扇区的RAM副本写回flash

  • 重新启用中断

  • 恢复核心1

  • 恢复接收机,重新启动DMA传输

    这个过程执行起来相当复杂,但值得付出这些努力。这一切都是在用户一眨眼之间发生的。它能让接收机保存设置,如音量,静噪等。音量对于使用耳机的用户来说尤其重要。该闪存预计将有大约100,000次的擦写寿命。如果设置为每次更改设置时写入,这可能会限制设备的寿命,特别是当每次使用编码器更改频率时存储频率值。为了延长闪存的使用寿命,这些设置被存储在一个包含512个通道的块中。每次保存一个设置时,都将使用下一个空闲通道。一旦所有通道耗尽,所有通道都被擦除,第一个通道被覆盖。每次接收机通电时,设置将从最后一个要写入的通道恢复。这有效地使闪光灯的使用寿命延长了512倍,达到了5120万次读写。这将允许设置每秒存储一次,连续使用超过一年。在实际情况下,闪存的寿命将远远超过接收机的寿命。

  • 制作

  • PCB的完整原理图在这里https://github.com/dawsonjon/PicoRX/raw/master/PCB/pico_rx/pipicorx.pdf

  • 共有3块PCB,前面板和后面板由PCB构造成一个三明治外壳。PCB的Gerber文件可以在这里https://github.com/dawsonjon/PicoRX/raw/master/PCB/pico_rx/gerbers/gerbers.zip这里https://github.com/dawsonjon/PicoRX/raw/master/PCB/front_panel/gerbers/gerbers.zip和这里https://github.com/dawsonjon/PicoRX/raw/master/PCB/back_panel/gerbers/gerbers.zip

  • 物料清单在这里https://github.com/dawsonjon/PicoRX/raw/master/PCB/pico_rx/BOM.ods

  • 使用USB烧录的固件在这里https://github.com/dawsonjon/PicoRX/raw/master/build/picorx.uf2

  • 测试

测试全程使用一个普通的 youloop天线。

法语短波电台(6145KHz)

航空气象台(5505KHz)

40米波段单边带电台“Rag Chewing”(7158KHz)

40米波段上的CW信号

授权转载,由 win小邵翻译