搓一个FPGA FM收音机–从仿真到实现

前情提要

前段时间我在b站发了这篇专栏:玩玩八毛钱的有源混频器:IAM81008

文章末尾提到了接下来将转入数字域,完成信号解调。那今天就把这坑给填上

做这块板子的根本目的还是为了完成我的课设,同时学习一些数字信号处理知识。

另外这也是我本人第一次做射频相关的板级设计,其中设计有很多不足。不过第二版已经在规划了,希望不久后能做一个关于此的完整视频。

这也是我第一次在emoe.xyz上发文,有点小激动捏,文章在b站专栏也是同步发出

开源地址和参考资料地址都在文末。废话少说,直接开始。

硬件设计

在做硬件设计时,我首先确立了几个目标(需要注意的是,这些目标的设立是以学习为第一目的,在实际工程中不一定是最优解)

1.射频前端部分使用经典的中频架构,将RF信号下变频到IF,再进行数字化。这一部分主要是为了学习射频前端。

2.数字部分使用IQ架构完成解调,这一部分是学习数字信号处理和FPGA

3.在完成以上任务的前提下,使用的硬件足够便宜(tips:可以上x宝购买拆机芯片),数字部分(FPGA)使用尽量少的资源(LUT)。

下面是我选用的元器件:

天线放大器/中频放大器:BGA2869

1-LNA.png

一个很便宜的小玩意,5V单电源供电,32dB功率增益。现在看来,选这玩意不是很好的选择,主要是增益有点太大了,本地的FM信号十分强劲,容易导致饱和。

电路中使用了部分5V供电的射频元器件是本次设计失误之一,因为这部分电源我是直接从USB口引入,然而USB开关电源的噪声很容易使敏感的射频前端饱和失真,测试中也明显观察到了这种噪声。

通过某些手段可以很大程度上改善5V噪声问题,不过在本次设计中并没有加入这类措施

混频器:IAM81008

2-MIXER.png

上古混频器,性能不算突出,但好处是便宜,覆盖的频率范围大。

前端滤波器/中频滤波器:普通的LC滤波

因为处理的信号是100MHz以下的FM广播信号,以及10MHz以下的中频信号,使用普通的LC滤波器足够了。

滤波器设计使用的是LC Filter Design Tool这个网页工具,下图是我使用的参数

3-bpf1.png

FM广播的带通滤波器,单独做一个小板上了。下图实测,还是比较接近的

4-bpf2.png

中频滤波器参数如下

5-lpf.png

这个没有实测图,因为集成到信号链里了,前有混频后有LNA两面夹击,测量不大方便。但考虑到ADC的采样率为20MSa/s,这个截止为7MHz的低通滤波器也是足够的。

本振发生器:ADF4350/ADF4531

6-pll.png

推荐使用ADF4351,因为可以覆盖100MHz附近的FM频段。我使用的是ADF4350,最低频率只能到140MHz。所以在测试中,FM电台混频是使用了外置信号源产生LO。

模数转换器:AD9200

7-adc.png

普普通通10bit 20MSa/s的ADC,单3.3供电,单端输入,2Vpp最大摆幅,并行接口

用这玩意没有啥特殊原因,完全就是我在看拆机片店铺时随手买来玩玩。

FPGA:LCMXO2-2000

8-fpga.png

普普通通的小规模FPGA,可单3.3V供电(单供电的FPGA到今天居然这么少见)。规模不够的话还可以加点钱上4000LUT版本。

使用这玩意的原因很简单:之前随手买了一片一直没用上。

小FPGA的缺点我认为还是调试比较困难,内置的逻辑分析仪(ILA)采不了一会就满了。超过80%的时间都是只能在modelsim+matlab里仿真,然后祈祷着上板能正常工作。

输出DAC:LCMXO2-2000

没错,输出级不用任何额外的DAC,只需要通过FPGA输出PWM波,并加入一个RC低通滤波器,即可播放音频。

请注意,RC滤波器需要外接,板子只输出PWM音频信号

PCB设计

这部分的资料在文末有,这里只简单放个截图

可能需要解释的就是为啥这板子的上方和下方有一排接口,原因在前一篇文章提到了,原本这是给某个开发板做的扩展配件,但因为软件上的一些问题,暂时还没实现,所以就先拿来顶一下我的课设了。

9-sch.png

10-pcb.png

信号链调试(信号链RTL仿真+matlab分析)

前文提到,由于FPGA的资源限制,直接上板调试并不是最佳选择。另外,学会对信号链的RTL仿真以及分析对今后进行较为复杂的FPGA开发时,也是很有帮助的。

我的主要开发流程如下:

第一步:在matlab中生成量化的CW/FM信号,作为仿真输入(方法见文末参考链接)

第二步:编写/修改/复制别人的RTL代码,用modelsim进行仿真

第三步:将modelsim仿真结果重新导入matlab,进行分析

重复以上第二第三步,直到完成整个信号链的RTL代码编写

第四步:对设计进行适当调整,生成比特流,上板验证

下面的内容不会有繁杂的数学公式,主要是讲述如何将IF信号混频、解调,并生成PWM音频信号,最后通过耳机播放出来。也就是更加关注实际的工程问题,而非纯理论研究。

IF混频产生IQ信号

首先让我们来解决本振(LO)的问题,在数字域里,想产生本振有很多方法,如果用的是比较高级的FPGA,可能有现成DDS或者CORDIC的IP核。但是咱一个2K LUT、连DSP都没的低端小FPGA就别想那么多了。

这里使用的是查表法,我用了Lattice的Sin-Cos Table IP核,其实这玩意自己写也是很简单的。

11-ipcore.png

首先将0~2pi值均匀的分成2的N次方份,然后把这些值输入sin和cos计算,将结果量化为需要的比特数,就得到了一个表。可以看出N越大,分块越细对频率的控制就越精确。

如上图,这个表被分为了256份。如果我想获取一个5MHz的数字本振信号应该如何做?

假设时钟输入为20MHz,我们需要的,就是让这个SinCos表每4个时钟周期内循环一遍。这里的“循环一遍”不代表我们要遍历整个表的数值,而是从这256份中均匀的抽出4个数值(也就是第0、64、128、192份数据),不停地循环。这样就有了5MHz的sin和cos信号。

12-digitalLO.png

(可以导入matlab里看FFT,确实就是5MHz的信号)

解决了本振以后就是混频环节。在数字域中我们只需要一个乘法器就可以完成

13-multi.png

唯一需要注意,也是最需要注意的问题就是FPGA中的数字符号问题。在本设计中,除了ADC输入为无符号数,其他环节的处理均使用到了有符号数。

因此,在乘法器需要设置正确的符号。外加所有符号数相关的wire和reg数据,都需要加上signed的声明,否则会导致后续环节不正常。

下图是一个4MHz的单频信号与5MHz的数字本振,通过乘法器混频后,产生结果的FFT结果

14-mixerfft.png

非常好非常干净,也能清晰的看到因为混频产生的上差信号。

完成了混频,还有一步需要进行,而且是之后每一次处理完成后都要进行的,就是截位操作。

从乘法器出来的数据宽度为20bit,但如果我们仔细的看看出来的数据👀

15-detail1.png

没错,低8位都是空的。意味着我们可以只取上面有效的12位来进行后续处理。

而且哪怕低位不是空的,我们也必须截位,因为多出来的位数在很多时候并不能显著提升接收机的性能,但会浪费大量资源。数字接收机中截位是十分常见的操作,以在性能和资源间取得最佳平衡。

但截位也是一门学问,很多时候不能简单的只取高x位或者低x位,必须根据实际情况,确定最佳截位范围,否则会造成信号的严重失真,在本文中也将看到这一现象。

对截位感兴趣的朋友可以自行学习相关算法。

CIC滤波

完成混频后,数据速率依然很高(20MHz时钟速度),但我们的信息已经被搬移到了低频区域,并且信号本身的带宽很低(此处是FM广播信号,国内的调制带宽为75KHz,一般而言)。此时就可以用CIC滤波器对信号进行滤波和抽样,从而将数据速率降低至可接受范围内。

CIC滤波器的原理网上一搜就有,我这里也是参考(指直接复制修改)别人的代码,参考链接放文末了。

尽管CIC滤波器相比其他数字滤波器已经是非常节约资源的了,但是在仅有2K个LUT的情况下,还是需要在性能和资源做出取舍。我最后选定的参数为4级,抽样100倍,也就是将数据速率降低至200KHz,理论截止带宽应该在100KHz附近,但是Matlab算出来是50K附近(其实是好事,因为正好让FM信号落在了整个通带范围内,同时对带外信号抑制更强了)

16-cic_cal.png

确定了CIC滤波器的参数,落实到工程上,还需要确定一个参数,就是积分器的宽度。这里我用了一个经典公式:Bout=Bin+N*log2(D*M),Bout为积分器最小宽度,Bin为输入的数据宽度,N为积分器(滤波器)级数,D为抽样系数,这里默认单个滤波器阶数M为1。

Bin=12,N=4,D=100,计算结果为38.5,按理说向上取整到40即可。

但为啥我工程里是48位呢,emmmmm,这个得问问当时我是咋想的了。但是为了文章的连续性和完整性,就这样留着吧。或许是个可以优化的点,有后续我会在评论区贴出来。

下面两图分别为4.99MHz的IF信号与5MHz的LO混频产生的信号的FFT,以及该信号通过上面的CIC滤波器后的结果

17-cic1.png

18-cic2.png

可以看到CIC滤波器很好的完成了任务,但也带来了一个新的问题,就是输出的48位数据实在是太大了,需要对其进行截位。

把cic输出的数据按位展开,如下图:

19-cic3.png

这里就呼应了上文为啥不能乱截位的原因,假如在这里我们只取了高12位,那得到的信号只有统一的0和1,有效信息完全丢失。正确的截位应该从第35位(作符号位)开始向下截取,这样才能保留有效信息。截位效果如下

20-cic4.png

其FFT如下

21-cic5.png

至此,我们的信号完成了混频和滤波,接下来就是解调操作。

FM信号的IQ解调

普通的FM IQ解调需要用到除法,而这个除法的除数又是不定的(由I和Q当前值决定),让这种类型的除法运算在小规模FPGA上运转就是奢望。

不过好在凡事都可以近似,这一部分主要参考了电子发烧友论坛的一篇文章,里面提到了一种只需要加减乘的FM近似解调算法,文章链接在文末参考部分。

22-fm1.png

其实和普通的FM解调算法十分相近,只是给除法去掉了。因此工程上的实现就变得十分简单。

23-fm2.png

首先对输入的I和Q信号做双重缓冲,从代码中容易看出,delay2信号比delay1信号晚一个周期,这就得到了公式中的当前信号和延迟一个周期的信号。

接着就是调用乘法器IP,以及完成公式中的减法,最后按照上文的截位方法进行截位

24-fm3.png

输入一个载波5MHz,频偏75KHz,调制3KHz正弦波的FM信号,然后我们就可以解调得到一个完美的正弦…wait what:

25-fm4.png

这也是在整个项目里我纠结最久部分,这个波形,看上去很像是某种数据溢出导致的位翻转。但在仿真中,即使将数据宽度开到256,每个环节都使用全精度进行数据传递,依然是这样。

我排查了很久,无奈,我只能将解调出的信号导入matlab,从频域看看咋回事

26-fm5.png

🤔我们的3KHz信号确实是在那,但伴随着巨大的谐波分量,似乎给了我一些眉目。这会不会是因为我们使用的近似算法带来的副作用?

为了验证这一点,我将通过滤波器后的I和Q信号导出,在matlab里做解调算法的对比,首先是近似算法解调得到的结果频谱:

27-fm6.png
(可以看到我给后面的除法注释掉了)

28-fm7.png

非常相似的结果!

然后是加了除法的(较为)精确解调:

29-fm8.png

谐波很小,对于收音机音频而言已经是足够干净。

因此可以下结论,近似解调的算法和结果都没有问题,这就是他应该有的表现。

为了解决这个问题,我们可以在音频信号后再加入一个数字滤波器,由于此时数据速率足够低,使用高阶数字滤波器在资源上是可行的。

不过这里还有个更简单的方法,外挂物理滤波器,也就是我们解调的最后一环:PWM音频输出。

PWM音频输出

1bit采样已经不是什么新东西了,当采样速度足够快的时候,1bit的AD或者DA也能有惊人的精度,无论是理论还是实践,1bit ADDA的相关资料十分丰富。原理在FPGA4FUN网站上一篇名为“PWM DAC 3 – One-bit DAC”的文章有详细解释

不过很显然24bit的hifi音质并不是这里的目标,收音机只要能响就行了。在这里我直接借用了1bitSDR项目的pwm音频代码,短短几行就能让FPGA的IO口唱歌了

30-fm9.png

理论上这个时候就可以给耳机接上FPGA听声音了,但是为了保护我们的耳朵,以及提升听感,可以加入一个简单的RC低通滤波器,截止频率在3~5KHz左右即可。

硬件调试(射频前端测试+数字信号链实测)

上面总算给数字域的信号链打通了,不过还有前端到ADC这一部分的信号链没测试呢,这也是接下来要做的事情。

混频器测试

这部分的内容在开头提到的文章里已经写过了,这里只做简单的说明,下图的信号幅度只有几百mv是正常的,测试混频器时还没有加入前端和IF的LNA。

ADF4350出200M -1dBm,信号源195MHz -5dBm,下变频,IF经过7阶无源7M低通滤波,CW单音

31-mixer1.png

FPGA出100MHz 8mA驱动输出,信号源95MHz -5dBm,下变频。CW单音,IF滤波

32-mixer2.png

可以看到混频器工作正常

加入LNA后接天线测试

在展示结果之前,需要先展示一下整套系统的连接方法以及说明测试目标:

33-sys_overview.png

EXT_LO:外部本振输入,连接到信号源上

IF_OUT:中频输出,连接到示波器上。

需要指出的是,由于LNA输出阻抗为50欧,而ADC输入阻抗在10K欧以上,这里我给LNA输出端口并入了一个50欧姆电阻(IF_OUT接口上方)进行匹配。

RF_IN:射频输入,连接到天线,天线如下图

34_ant.png

严格来说,这种环状天线并不是很适合这次的接收(阻抗不匹配+调谐点不在100MHz附近),但由于本地FM台的信号实在是强劲,哪怕只插一根焊锡丝都能响,所以天线的选择并不是那么关键。

另外,上文提到的天线带通滤波器也在这里接入。设计之初是作为有源放大+滤波器使用的,但因为信号强度很大,这里使用两根跳线绕过LNA,只使用了滤波部分。该滤波器的实测参数在上文硬件部分已经给出。

首先是不接入天线,不接入LO,观察一下中频的噪底:

35-nf1.png

汗流浃背了老弟,哪怕给RF和LO输入接上一个50欧负载,这个信号依然存在。

其实就是上文所说的一个设计失误,直接使用USB输入的5V电源给射频器件供电(在看本文的射频工程师是不是已经气晕过去了,我为此道歉),各种噪声进入混频器,又进入LNA,导致了这一现象。

不过好在,这种现象在使用充电宝而不是墙插充电器时得到了大幅缓解(尽管噪声依旧很大)

36-nf2.png

我猜测那些尖尖应该是所谓的“共模噪声”,感兴趣的可以观看Lopz-老师翻译的evblog视频

37-eev.png

所以接下来的测试都使用充电宝进行供电。

值得一提的是,USB供电线的质量也会显著影响噪底,这与线缆的屏蔽严密程度有关,如果用的是一块钱一条的地摊货,环境中的噪声会耦合进供电线。在这里我用手机原装快充线,对环境噪声屏蔽效果最佳。

对当地FM电台下混频到5MHz:

Note:因为我使用的PLL芯片不能产生低于140MHz的信号,所以接下来所有的测试结果,都使用外置信号源作为本振输入。

中国之声—–发射频率96.4MHz

设置本振频率为91.4MHz,理论上可以在示波器FFT上看到一个5MHz的FM信号,试试看👀

38-station1.png
芜湖,成功了,不仅看到5MHz处的模拟载波,甚至还能清晰看到一旁的数字载波(左右两侧的方块突起)

陕西农村广播——104.9MHz

本振设置99.9MHz,IF输出如下

39-station2.png

也可以清晰看到信号,只不过这个电台只有模拟载波。

到这里,我已经等不及要听听最终效果了,快点端上来罢(急切)。我知道很急,但先别急,咱FPGA还没烧bitstream呢

FPGA最后的微调

FPGA时钟由ADC提供,两者共用一个20MHz有源晶振。引脚分配如下

40-pinplan.png

板子设计的时候需要注意使用特定的时钟引脚,不然布线会报错

最后综合资源利用率如下

41-resource.png

时序报告

42-drc.png

还有几百个LUT可以使用,用这剩下的LUT来搓一个控制接口应该不是很难,下次一定,下次一定。

效果测试(附视频)

43-full.png

最后整套玩意拼起来就像这样,很糙。左边那块板子是我之前做的另一个收音机,不过没成,这里只是借用一下上面的耳机口和PWM滤波器,其余的处理完全由右边的板子承担。

试听视频

版权问题我查过了,这种应该是属于教育目的,而且我的文章和视频不能给我带来收益,因此属于“合理合法的免版权使用”范畴。

44-ohno.png

视频里可以看到频谱上有一排间隔相同的谱线,这个是因为FPGA的PWM输出通过线缆后,线缆作为天线给发射出去了,尽管能量很小,但在LNA的作用下依然十分明显。最佳方式应该还是将这部分电路直接做到同一块板子上,同时使环路最小。

总结

作为我个人第一次射频板级设计,我对这个结果感到十分满意(它能响!)。

但它是一个合格的FM收音机吗?显然不是,噪声巨大,失真严重,而且还不能用旋钮调台(这个实在是无法忍受)

所以第二版的制作已经提上日程了,并且计划做一个有关的完整视频。

(另外再挖个坑:搓一个1bit的GPS信号接收机)

开源地址:https://github.com/BellssGit/FPGA-SDR-FM-RADIO

参考链接

1.FPGA 设计FIR滤波器处理MATLAB激励信号

https://zhuanlan.zhihu.com/p/357638188

2.FPGA + 3 R + 1 C = MW and SW SDR Receiver

https://hackaday.io/project/170916-fpga-3-r-1-c-mw-and-sw-sdr-receiver

3.Verilog CIC 滤波器设计

https://www.runoob.com/w3cnote/verilog-cic.html

4.Matlab FFT

https://ww2.mathworks.cn/help/matlab/ref/fft_zh_CN.html

5.简易FM信号解调的FPGA实现过程讲解

https://www.elecfans.com/pld/2144995.html

6.PWM DAC 3 – One-bit DAC

https://www.fpga4fun.com/PWM_DAC_3.html

发表回复

这篇文章有一个评论

  1. 第 必过特洛夫斯基页

    大佬牛逼啊!