
我第一次在一张入门级FPGA板上把Remote ID的发射端和接收端同时跑起来的时候最直观的感受是无线调制解调这件事看起来是算法问题做起来全是时序、位宽和状态机问题。你在MATLAB里画出来的GFSK波形再漂亮放进FPGA以后也要面对DDS相位累加器位宽选多大、高斯滤波器系数怎么量化、解调侧残余频偏怎么消除这类具体到不能再具体的工程问题。这篇内容就是围绕“Remote ID的调制解调FPGA实现”这个项目把基于BLE 5.0物理层的GFSK调制解调链路从发送到接收完整拆开从调制器设计、解调器结构、帧同步状态机到验证调试手段逐段说明白。适合正在做无人机识别、无线数传或者想在FPGA上入门软件无线电基带的工程师参考也适合那些卡在“仿真能通、上板不通”问题上的朋友对照排查。1. Remote ID对调制解调方案的约束比你想的更多1.1 信号到底长什么样Remote ID要解决的事情本质上是在一定范围内周期性地广播飞行器的身份、位置、速度等信息。现在行业里主流的传输载体之一就是BLE 5.0原因是它这个物理层本身就是为低速率、低功耗、中等距离广播设计的而且几乎所有移动终端都具备接收能力接收门槛很低。BLE 5.0的1M PHY物理层核心参数并不多符号速率1Msps使用GFSK调制BT值为0.5调制指数标称0.5对应的峰值频偏约250kHz。这个参数组合意味着信号本身的频谱效率不算高但对接收机的要求比较友好即便在移动终端那种低成本射频前端下也能解调。我在这里用“GFSK”而不是“GMSK”是因为严格来说GMSK是GFSK的一种特指调制指数为0.5、高斯滤波后的连续相位频移键控。GMSK的术语更多出现在GSM、LoRa等系统中而BLE规范里明确写的是GFSK。两者在基带处理上非常类似都是对基带比特先做高斯脉冲成形再用成形后的信号控制载波瞬时频率。FPGA实现时你完全可以借鉴GMSK那套经典的“高斯滤波器DDS频控”结构只是把频偏系数按照BLE的参数重新计算即可。从数据链路看一个典型的BLE广播包由前导码、访问地址、协议数据单元PDU和CRC组成。广播通道的访问地址固定为0x8E89BED6PDU里再承载Remote ID的应用层数据。调制解调器解决的问题就是用物理层方式把这一串比特可靠地搬移到空中并且在接收端从被噪声干扰的波形中恢复出原始比特。应用层的什么位置、什么高度、什么ID其实跟基带解调没有直接关系你只需要保证MAC层拿到的是完整无误的比特流。1.2 为什么不在MCU或者DSP里做有人会问Remote ID的符号速率只有1Msps一些高端MCU也能通过定时器加DMA的方式模拟GFSK生成为什么要用FPGA我的看法是能“生成”和能“可靠收发”是两码事。MCU的缺点在于实时性不稳定中断抖动和任务调度会导致符号边界出现微小的时间偏移这个偏移在经过射频前端和空中信道后会被放大接收端定时恢复会变得非常痛苦。DSP则更多受限于并行度一个通道还好如果以后要支持多通道接收、多频点同时扫描DSP的负担就会直线上升。FPGA的优势在于调制解调的每一条数据通路都是独立的硬件逻辑符号定时是时钟驱动的不存在“任务被打断”的问题。发射机可以把数据包做成严格的时隙调度接收机可以在同一个FPGA里跑多个解调通道分别监听不同的广播频点。这对于Remote ID这种需要周期广播、并且可能同时接收多路信号的场景很合适。另外FPGA内部的逻辑延迟是可预测的这给基带延迟控制带来很大方便调制器能在精确的时刻把前导码发送出去接收机也能通过硬件计数器确认每一个比特到达的时间。多提一句FPGA实现调制解调并不意味着要把整个软件无线电协议栈都放进去。以我的项目为例MAC层和App层我仍然放在一个软核处理器里跑FPGA只负责物理层的调制解调、比特同步和线路编解码。这种软硬划分既保留了FPGA对时序和并行处理的优势又让上层逻辑可以用C语言快速迭代整体开发效率高很多。2. 调制侧在FPGA里生成一张能过无线的GFSK波形2.1 高斯滤波器的设计与量化GFSK调制器在FPGA里的实现路径非常固定原始比特先做NRZ映射也就是比特1对应正电平、比特0对应负电平然后通过一个高斯低通滤波器对电平序列进行成形用滤波后的信号去控制DDS的瞬时频率。这里最关键的就是高斯滤波器。高斯滤波器的冲激响应由BT值决定BT值越小信号带宽越窄但码间串扰越大。BLE规定BT0.5这是一个比较折中的选择既保证了频谱占用在2MHz通道内又让接收端的差分检测不至于因为码间串扰太严重而失效。在FPGA里做高斯滤波器我不会直接设计一个高阶FIR而是根据发射机的升采样率预先计算好滤波系数再加载到ROM表中。比如系统内部升采样率为16即每个符号周期内输出16个采样点那么对称FIR滤波器所需的系数就是16乘以滤波器span。取span为3个符号周期就得到48个滤波器系数。这48个系数可以离线用firpm或者gaussdesign计算再量化成16位定点数存储到ROM里。为什么用16位因为实测下来10位系数量化在部分频偏条件下会带来约0.8dB的解调灵敏度损失而12位以上就基本看不到差别了。综合考虑资源16位是零风险的稳定选择。滤波器的工作方式有两种。第一种是直接法输入比特流按其速率进入一个较短的移位寄存器每个符号周期计算一次完整的48阶FIR输出然后以16倍符号速率输出。这种方式逻辑资源稍多一些但结构直观。第二种是多相结构把48个系数分成16个子滤波器每个子滤波器只有3个系数在符号速率时钟下完成计算再把16个并行的结果按顺序读出。多相结构最省乘法器适合资源紧张的芯片。我实际项目用的是多相结构因为FPGA要同时跑调制和解调DSP乘法器资源不够。2.2 DDS调制器的主频率字高斯滤波器输出的是一个带限的基带控制信号接下来就进入DDS部分。DDS的经典结构是相位累加器加波形查找表。GFSK本质上是频率调制所以不需要直接控制相位而是把滤波后的信号映射为频率控制字累加器不断累加生成连续变化的相位再通过查找表得到正弦和余弦波形。这里频率控制字的计算值得仔细看。假设FPGA工作时钟是48MHzDDS累加器位宽取32位那么频率分辨率是48MHz除以2的32次方大约0.011Hz远好于我们需要的250kHz精度。BLE标称频偏250kHz那么在48MHz时钟下对应的相位累加步进是[ \Delta\phi \frac{250\mathrm{kHz}}{48\mathrm{MHz}} \times 2^{32} ]算一下大概是22369621也就是0x01555555附近。这个数很有特点因为250kHz和48MHz之间存在一个友好的比例。当输入比特为1时累加步进等于载波中心频率对应的步进加上正频偏步进为0时减去。实际实现中不需要每个采样点都调用乘法器。高斯滤波后的信号可以预先量化成一个小位宽的查找表索引比如8位而每种索引值对应的频率控制字增量都预先算好存入ROM。这样调制器最关键路径上只剩加法和累加器时序很容易满足。相位累加器输出的高位作为正弦查找表的地址通常取高12位作为波形表地址因为12位相位分辨率能保证SFDR大约在-70dB以上对2MHz带宽的信号足够。我还做了另一个处理在调制器输出端加了一级2倍插值半带滤波器把内部数据率从16Msps提升到32Msps以后再送DAC。这样做有两个好处一是DAC镜像抑制更好二是后级的抗混叠模拟滤波器设计压力小很多。FPGA里做半带滤波器代价很低只需要少量乘法器但射频指标改善明显值得加。2.3 发射数据链路安排调制器本身只是把比特流变成波形但你不能把一个字节一个字节随便丢进去。GFSK是一个有记忆的调制方式符号间隔之间是关联的必须保证比特按照符号周期的节奏进入高斯滤波器。所以在调制器前端一定要做一个发射状态机负责把前导码、访问地址、PDU和CRC这几部分按顺序拼接成完整的比特流并且以一个稳定的符号时钟逐bit送入调制器。一个容易犯的错误是把要发送的数据放在异步FIFO里之后读取时没有严格对齐符号时钟。FIFO读侧如果由一个和系统时钟同源的时钟驱动看上去没问题但如果你用FIFO几乎空标志来触发发送实际上会产生不确定的延迟。我后来把发送调度做得非常“死板”一个符号计数器从0计到包长度每个符号周期从固定的RAM地址取bit取完就发发完就停。这种方式的优点是每一帧从第一个前导码bit开始到最后一个CRC bit结束时间都是确定的接收端做时隙统计和同步都非常方便。3. 解调侧ADC之后IQ流变比特流的三道关3.1 低中频还是零中频先想清楚接收机的射频前端方案会直接决定FPGA里解调器的整体架构所以这个问题必须先定下来再动手。零中频方案射频前端输出的是I/Q两路模拟基带信号ADC直接采样I/QFPGA拿到的是以0Hz为中心的二相基带信号。这种方法实现起来直观但对射频前端的镜像抑制要求很高直流偏置问题也明显GFSK信号本身是双边带调频对直流偏置容忍度稍好但依然需要做偏置校正。低中频方案射频前端把信号搬到一个几兆赫兹到几十兆赫兹的中频ADC采样后FPGA里通过数字混频器把中频搬回基带。低中频方案对直流偏置不敏感对射频前端的镜像抑制要求也下降不少。FPGA实现时需要额外付出一个数字混频器和一个抗镜像滤波器的资源代价。我在项目里选的是低中频方案因为这是一块通用SDR板卡中频频率取10.7MHzADC采样率80Msps。FPGA内部做的第一级数字混频本质上就是乘上(e^{-j2\pi f_{IF} nT})。混合后的信号包含期望信号和2倍中频的镜像分量后面再接一个抽取滤波器。设计抽取滤波器时我建议先做CIC滤波器把数据率从80Msps降到16MspsCIC后接补偿FIR把带内平坦度修回来。为什么先CIC因为80Msps下的FIR阶数如果太高资源开销会非常吓人CIC可以在零乘法器的情况下完成大倍数抽取非常划算。CIC级联4级微分延迟取1在16Msps输出下能提供足够阻带抑制。3.2 差分相位鉴别与频偏补偿GFSK信号里的信息是调制在瞬时频率上的也就是相位的导数。因此一个最直接的解调思路是差分检测用当前采样点的I/Q值乘以前一个采样点的共轭得到的复数相角就是相邻采样点之间的相位增量。在FPGA里这个复数乘法的实部其实就是[ I[n]I[n-1] Q[n]Q[n-1] ]虚部是[ Q[n]I[n-1] - I[n]Q[n-1] ]取虚部除以实部的反正切就得到相位差。但在FPGA里用CORDIC做反正切资源消耗不小而且相位差很小直接算反正切对噪声比较敏感。我实际项目里简化了一步由于相邻采样点间隔很短相位差远小于1弧度此时相位差可以直接用虚部近似省掉CORDIC。当然在做判决之前需要一个归一化操作来消除信号幅度的影响否则AGC没收敛时虚部幅度会随着输入信号强度波动。这里必须处理一个比想象中更麻烦的问题频率误差。射频前端本振和发射机之间存在最大可达几十ppm的频率偏差2.4GHz频段上30ppm就是72kHz频率误差。这个误差加到250kHz峰值频偏信号上会导致差分检测结果整体抬升一个固定量直接导致比特1和比特0的判决边界偏移。不消除的话即使信噪比很高误码率也会很高。FPGA里做频偏估计的常用方法是利用前导码已知的0101序列做累加平均。前导码的频偏模式是正负交替的把所有正相位差加起来减去所有负相位差得到2倍的频偏估计值。收完前导码后把估计的频偏对应到差分检测判据里就可以对后续payload做有效补偿。需要注意的是这个估计只适用于慢变频偏如果收端本振漂移比较快则需要在payload中间周期性插入已知序列或者采用判决反馈方式持续校准。对于Remote ID这种短数据包前导码估计一次够用。3.3 符号定时恢复做完差分检测和频偏补偿得到的是一串带有符号信息的波形但要决定在每个符号周期的哪个点上去采样这就是符号定时同步。如果我们只是做板级自环发射机和接收机共用一个时钟定时问题可以靠固定计数解决。可一旦接上真实的射频前端、经过空中信道或者从一个独立的信号源收发采样时钟和符号时钟之间虽然来自同一个标称频率但微小偏差会逐符号累积一定是需要动态调整的。我用的是Gardner定时恢复算法。它的好处是对GFSK这种有一定脉冲成形的信号依然有效而且不依赖前导码可以实现盲定时恢复。Gardner算法计算方式是这样的每个符号周期有两个采样点一个是最佳判决点(y[k])另一个是两个判决点之间的中间点(y[k-1/2])定时误差为[ e (y[k] - y[k-1]) \cdot y[k-1/2] ]这个误差信号会通过一个环路滤波器转换为小数插值器的分数间隔更新。在FPGA里我用了一个Farrow结构的立方插值器来完成分数延迟调整插值器系数直接用定点数表示。GFSK信号因为经过高斯滤波波形比NRZ圆滑Gardner算法收敛稍慢所以我把环路带宽设置得比较低这样虽然锁定时间变长但稳态抖动更小。符号速率1Msps环路带宽设置在几百赫兹实测几毫秒内能完成锁定。数字配方的定点数也要注意。Gardner环路滤波器里的比例项和积分项系数非常小如果直接用整数运算很容易被截位吞掉。我把误差信号先左移若干位环路滤波器全用32位定点最后输出给插值器时再截断。这个细节决定了定时环在小频偏下能否长时间稳定。4. 协议层比特流怎么变成一包可用的Remote ID4.1 前导检测与访问地址相关解调器输出的是连续比特流但解调器不知道数据包从哪个比特开始。所以接收端必须有前导检测机制。BLE的前导码是01010101或者10101010这种交替模式在解调后的比特流中表现为周期性的特征前导检测电路要做的工作就是对这个特征做实时相关。前导检测的实现在FPGA里有几种办法。最简单的就是移位寄存器和相关器保留最近的32个bit与本地模板做匹配超过阈值就认为检测到前导。相关器的阈值设定很关键设得太低噪声引起的误触发会非常高后面状态机会被假前导带跑设得太高真实前导在信噪比较低时检测不到。我采用的方案是对前导检测输出加一个“连续确认”机制第一次相关超过阈值后不立刻进入解包状态而是在下一个比特时再对照理论值做一次确认两次一致才认定同步。这样虽然损失半个符号周期的响应时间但误触发率显著降低。访问地址相关比前导检测更严格。BLE广播通道的访问地址固定为0x8E89BED632比特。在解调器完成前导检测后后续的32个比特会进入一个访问地址比较器只有精确匹配才允许继续接收payload。这里用到了前面定时恢复的质量如果符号定时抖动了半个周期相关峰就会明显下降。实际调试中访问地址匹配率是最能反映解调器质量的一个硬指标匹配率上不去几乎可以断定问题在定时恢复或者频偏补偿环节。4.2 CRC校验与整体状态机访问地址确认之后后续数据进入payload缓存区同时实时计算CRC。BLE使用的CRC24多项式为(x^{24}x^{10}x^9x^6x^4x^3x1)FPGA实现就是一个24位线性反馈移位寄存器。每收一个bit就送入LFSR计算等最后一个payload bit收完后把收到的CRC字段与本地计算值比较。整个包的状态机可以定义为IDLE等待前导PREAMBLE检测前导SYNC核对访问地址PAYLOAD收payload数据并计算CRCCRC_CHECK比较CRC最后回到IDLE等待下一包。状态跃迁必须考虑异常路径比如在PAYLOAD阶段预设一个最大超时计数器如果收到一半没有后续数据超时强制回到IDLE这样可以防止一个虚假前导把接收状态机卡死。这个CRC状态机在FPGA里属于典型的时序逻辑难度不大但有一个细节常常踩坑CRC计算的使能信号和有效比特对齐。如果计算使能和收数据使能之间差了一个周期那么每一个bit都错位计算最后CRC永远对不上。我调试时花费了大量时间排查这类“看似算法错误、实则是时序错位”的问题后来统一用一个bit_valid信号贯穿数据通路才从根上解决。4.3 解调器输出到MAC层的时钟域处理解调器和上层处理器通常运行在不同的时钟域。解调器输出也许是16MHz采样率每个符号在固定位置提取出一bit这bit要交给一个工作在其他频率的MCU总线去读取中间必须做跨时钟域处理。很多人在这里直接用一个异步FIFO理论上是正确的但实际中经常因为FIFO读写指针的Gray码转换有问题导致偶发丢包。更稳妥的做法是接收侧不再用逐bit中断去骚扰CPU而是把完整的一包数据包括payload和CRC校验结果放在一个双口RAM中一口气写入写完后产生一个短暂高电平脉冲通知CPU来读取。CPU读完后写一个清除位。这样跨时钟域交互被缩短到一个脉冲和一个状态位race条件出现的概率大幅降低。我在Vivado里验证过这个设计即使跑上连续一整晚也不会出现丢包错包的情况。5. 验证调试与资源优化怎样才算真的“通了”5.1 板级回环先把数字底座打稳接到射频、拉天线之前我强烈建议先做板级回环。所谓板级回环就是把FPGA内部发射机的数字基带输出直接送到接收机的数字基带输入或者通过DAC-ADC做一个模拟回环。这个测试能验证调制器和解调器之间的算法是否匹配、定时恢复能否收敛、帧同步状态机是否正确把问题限制在数字逻辑范围内。我做数字回环时用了一个简单的信号注入方式发射机输出的匹配滤波后波形经过一个可调增益器和一个加法器注入高斯噪声噪声由FPGA内部的LFSR生成。通过调节噪声方差可以快速测量不同信噪比下的误码率曲线。这里有个容易忽视的点注入噪声的带宽要和信号带宽一致不能把噪声加在16Msps采样率下然后只做通带滤波否则解调器看到的等效信噪比和你预期的完全不一样。板级回环通过之后再过渡到DAC-ADC回环。这时要关注的是通道增益、直流偏置、DAC和ADC的采样时钟是否同源。我踩过一个坑发射和接收各自使用独立的晶振时即便都是标称16MHz实际频率偏差也有几个ppm这个微小差异会让符号定时环路一直追虽然Gardner能扛住但误码率会比同源时钟高不少。建议回环测试用同源时钟验证算法正确性再换独立时钟验证定时同步裕量。5.2 上射频之前的检查清单真正接上射频前端以后问题就从数字域转向模拟域。我总结几个必查项第一发射功率和频谱是否正常。用频谱仪看GFSK信号时如果看到明显的离散谱线多半是高斯滤波没有生效或者符号时钟抖动太大。好的GFSK信号频谱轮廓平滑没有明显毛刺。第二接收AFC是否正确工作。搜索一个已知信号源看解调后的频偏估计值是否稳定在真实偏差附近。如果估计值反复横跳说明前导码相关窗口和数据bit没有对齐。第三前导相关阈值是否合理。通过日志记录误触发次数如果误触发过多适当调高阈值如果丢包严重就适当调低阈值。这两者之间需要根据现场环境找一个平衡点。第四天线位置和遮挡对多径的影响。Remote ID是低速率广播多径会造成频率选择性衰落在走廊或者室内测试时会出现偶发的深度衰落。FPGA解调器里如果加了一个简单的均衡器会比较稳妥但如果短期没精力做均衡可以在接收端用双天线分集接收Altera和Xilinx的FPGA资源都足够支持两路解调。5.3 资源优化与后续扩展整个调制解调链路在主流中端FPGA上比如Xilinx Artix-7或Intel Cyclone 10 GX资源消耗大约是DSP乘法器60到80个BRAM 20到30个逻辑单元约8K到12K。如果你有多个接收通道需求资源会线性增加这时候就需要优化。一个很有效的优化点是把多通道解调分成两段第一段是共享的DDC和CIC抽取第二段才是每通道独立的差分检测和定时恢复。因为DDC和CIC处理的是整个目标频段各通道的数据来自同一个宽带数字化结果没必要每个通道都复制一份混频器和抽取滤波器。这个结构我在资源受限的板子上验证过3通道接收的额外开销只相当于单通道的1.6倍左右。另一个可以优化的点是波形查找表。如果要同时兼顾正弦和余弦输出直接用两个48x16位查找表BRAM开销不小。更好的做法是只存1/4周期正弦表通过地址变换生成完整正弦和余弦。地址位宽取14位输出16位占用的BRAM约28Kb比双完整表省一半。最后提一个扩展思路现在做的是单一GFSK解调但Remote ID场景里还存在其他调制模式。FPGA的好处是可以把多种解调器做成模块池通过配置寄存器选择当前的解调模式。我在项目里预留了一个QPSK解调器接口未来切换时不用重新综合整个工程只需要例化对应模块就可以了。现在回到我的体会。每次有人问我Remote ID调制解调到底难不难我总觉得单纯讨论算法难度是没意义的GFSK调制解调原理课本上几页纸就写完了但真正让一个项目变得复杂的地方全在那些算法课本不讲的工程细节里。比如采样率选16M还是32M决定滤波器的阶数和资源消耗比如差分检测时候的频偏补偿精度直接决定接收机能不能在低成本晶振下稳定工作再比如前导检测阈值定多少决定了误触发率和灵敏度之间如何取舍。这些细节一旦处理好FPGA的调制解调链路其实非常可靠。如果这篇文章的某个细节能帮你在调试中少走一次弯路那它就算没白写。