前阵子给一款便携收音机做固件用的是杰理AC69xx平台的方案。产品经理扔过来一句“FM搜台要又快又准”结果手动搜台一切正常自动搜台一跑就露馅市区里该搜的台少搜两三个郊区反而把噪声当台存进列表更崩溃的是偶尔会无限循环搜不停。那几天基本就是在“搜台、删台、重搜”的循环里折腾最后才意识到问题根本不在于“扫得快不快”而是对“到底什么算一个有效电台”的判断方式出了问题。这篇就把杰理系列芯片做FM搜台这件事从头到尾捋清楚射频链路上搜台到底在搜什么、SDK里的调用流程怎么组织、RSSI和SNR这些参数怎么标定以及漏台、串台、重复台和死循环这些经典问题到底出在哪。适合正在用杰理芯片做收音机产品、或者想在蓝牙音频方案里塞FM功能的开发者参考。1. 搜台不是“扫频率”是“判断信号可信度”1.1 频率扫描只是看起来简单FM搜台的表面逻辑任何人看一遍都能懂在合法频段内从下限频率到上限频率按固定步长比如100kHz一个点一个点地走每到一个频点就让接收机稳定下来读信号强度、信噪比判断这个频点是不是一个“可听的台”是就记下来不是就继续走。但真正把这一步做到位问题就来了。FM接收链路是这样的天线进来的射频信号经过低噪声放大、混频降到中频或者直接零中频再经过信道滤波器、限幅放大、解调最后恢复出音频。搜台时我们关心的信号强度RSSI是在解调之前的射频端读出来的而信噪比SNR是解调之后对音频质量的度量。一个描述“信号的绝对强度”一个描述“信号相对噪声干不干净”两者不能互相替代。我见过不少开发者直接在代码里写一句“RSSI大于30就存台”这在信号环境简单的实验室里能跑一拿到真实环境就废。原因在于FM信号是恒包络调制没有电台时背景噪声同样能让RSSI读数飘得很高市区底噪动辄30dBμV以上这时单纯看RSSI噪声点就会被当成电台存进去。反过来遇到强台时自动增益控制AGC会压缩中频增益RSSI读数反而掉下来用固定阈值又容易把好台漏掉。所以搜台必须靠RSSI和SNR双维度判断这是第一个要建立的认知。1.2 信号强度的“假象”与信噪比的“真实感”打个比方。RSSI好比远处有人举着手电筒照你手电筒很亮但中间隔了一层雾你还是看不清来的人是谁。SNR则是你“看清来的人是谁”的程度。FM解调的特殊之处在于射频端噪声再大只要信号压过噪声一个量级解调器就能把信号“钳”出来音频里几乎听不到噪声而如果信号强度不够解调出来的就是一片沙沙声。所以SNR其实更接近人耳的真实听感。实操中我一般这样看数据安静郊区下没台时RSSI可能只有10~15dBμVSNR低得没法看一旦有台RSSI通常会跳到25dBμV以上SNR也维持在20dB以上。市区相反没台时RSSI可能已经30dBμV但SNR很难超过10dB因为那只是宽带噪声被检波成“强度”并不是一个真正的载波出现一个弱台时SNR可能到15dB左右虽然信号不强但人耳已经能听清内容。这几个典型数据可以作为判台的第一感觉场景典型噪声底RSSI有效台RSSI有效台SNR安静郊区10~15dBμV25~40dBμV20~40dB市区环境25~40dBμV30~50dBμV15~25dB临界弱台接近底噪20dBμV左右10~15dB纯噪声20~35dBμV不稳定通常小于8dB这个表的意义在于当你准备设阈值时不要拍脑袋。先把设备拿到目标场景用串口把RSSI和SNR打印出来观察“能听清的台”和“纯噪声”两组数据的分界线落在哪然后去找一个中间值这样标出来的阈值才有说服力。2. 杰理SDK中FM搜台的主流程与接口调用拆解2.1 搜台前要把状态“摆正”在杰理平台上做FM搜台第一步不是直接调搜索函数而是把系统状态摆正。顺序大概是初始化FM模块、选择频段制和步进、关闭不必要的音效处理、把音量调到基准值、然后打开软静音。为什么要先静音再搜台因为频率切换的瞬间PLL锁相环重新锁定、中频滤波器切换音频输出会有明显的“咔哒”声连续扫描时就是一连串机械噪声。搜台期间把音频静音掉是最基本的体验保障。有些平台SDK里有专门的“扫描模式”接口会自动处理静音和信号判定如果没有就要手动做。另外搜台前要把I2C或者芯片内部寄存器访问总线空出来尤其是杰理这种FM和蓝牙共用一个SoC的方案。如果此时蓝牙正在传音频先暂停或降低蓝牙任务优先级否则搜台过程中的寄存器读写会被打断导致频率还没稳定就跑去读RSSI读回来的数当然不准。2.2 逐频点扫描与有效性判断的标准流程下面是杰理FM模块搜台的主流程示意。接口命名在不同的SDK版本里会变但调用逻辑基本是这套// 示意流程具体接口以当前SDK的fm头文件为准 fm_band_set(FM_BAND_CN); // 设置87.5~108MHz频段 fm_step_set(FM_STEP_100K); // 设置100kHz步进 fm_mute(1); // 扫描全程静音 for (uint16_t freq start_khz; freq end_khz; freq step_khz) { fm_set_freq(freq); // 切到目标频点 delay_ms(30); // 等待PLL锁定、信号稳定 uint8_t rssi fm_get_rssi(); uint8_t snr fm_get_snr(); if (is_valid_station(rssi, snr)) { uint8_t confirm_cnt 0; // 连续多次采样去抖确认 for (uint8_t i 0; i 5; i) { delay_ms(10); if (is_valid_station(fm_get_rssi(), fm_get_snr())) { confirm_cnt; } } if (confirm_cnt 3) { channel_save(freq); // 频率入表 } } } fm_mute(0);这段流程里最核心的是is_valid_station这个判据函数。我在工程里的写法是bool is_valid_station(uint8_t rssi, uint8_t snr) { if (rssi RSSI_THRESHOLD snr SNR_THRESHOLD) { return true; } return false; }有人说这也太简单了后文你会看到阈值本身怎么定才决定这个函数是宝贝还是摆设。2.3 找到电台之后的锁定与播放动作上面是“全频段扫描”的逻辑适用于用户手动触发“搜全台”把所有有效台一次全找出来存进列表。还有另一种交互是“自动搜台”用户按一下键从当前频率向上或向下找最近的一个有效台找到就停下来播放。这种逻辑差异体现在循环上不是遍历整个频段而是一旦找到有效台立即break退出循环然后取消静音、恢复音量。无论哪种模式找到台后都要注意恢复时序。取消静音之前先确认音频通路已经切换好避免把上一个频点的残余数据播出来恢复音量建议用一二百毫秒的渐变不要直接跳变否则会有“砰”的爆音。这些细节不做用户按一次搜台键就觉得“滋啦”一下好感度直接减半。3. 决定搜台体验的关键参数RSSI、SNR与去抖标定3.1 RSSI会被AGC“骗”标定时要正视这一点很多做FM搜台优化的开发者卡得最久的往往不是流程而是“为什么同一个频点清晨能搜到中午就搜不到”。这在城市环境下大概率是AGC和底噪两者共同作用的结果。FM接收机为了兼顾强弱信号会在天线端到中频端分布自动增益控制。信号很强的时候前端增益被压低于是射频端读到的RSSI反而不会太高。这时候如果阈值盲目设低强台不会丢但如果信号强弱突然交替AGC起控时间跟不上RSSI读数会来回跳临界频点就可能被误判。所以我在实际项目里很少把RSSI当成唯一判据而是把它当“门槛”而不当“质量”。只要RSSI超过一个基础值比如20dBμV接下来主要看SNR。3.2 SNR是更接近人耳感受的质量标尺FM信号有“捕获效应”当存在一个较强的FM信号时干扰信号会被抑制解调输出基本只有强台的内容。这种效应让SNR成为一个很“硬”的指标。实测下来SNR在15dB以上时人耳已经能听清绝大部分语音和音乐20dB以上时噪声明显可闻但不难受超过30dB就是很干净的声音了。需要特别注意的是立体声。立体声解码对信号质量要求更高同样的频点单声道听感不错切到立体声就可能出现嘶嘶声。如果你的产品支持立体声我建议判台时对立体声模式单独加5dB左右的SNR余量。也就是说单声道模式SNR阈值可以设在12~15dB立体声模式建议设在18~20dB。3.3 去抖采样是“防误判”的最后一道防线信号经常是抖动的尤其在频点刚切换完的瞬间。PLL锁定的时间、信道滤波器的建立时间、AGC的稳定时间叠加起来频率切过去之后的头20ms内读数都是不可信的。我的做法是切频后先等30ms再进行连续采样。每次采样间隔10ms连续采样5次至少有3次满足判据才认定这是台。这样每个候选频点的总耗时大概是80ms左右。全频段从87.5到108MHz以100kHz步进一共205个频点满打满算扫一遍要16秒多确实偏慢。想加快可以做两级扫描先用快速采样每个频点只采一次阈值放宽扫一遍把可能电台的候选频率记下来再对每个候选点做慢速精确确认总耗时能压到七八秒。3.4 一套可参考的阈值起点下面是几个环境场景下我实测过起点值注意这是起点到具体产品上还需要用样机现场微调应用场景RSSI阈值SNR阈值去抖次数安静郊区、弱台优先18dBμV12dB3/5一般城市环境25dBμV16dB3/5强干扰商圈/写字楼30dBμV20dB3/5立体声强制要求30dBμV20dB4/5阈值设定的总原则是宁可多搜到一个弱台也不要存一堆噪声。台上去了可以删噪声存进去用户会直接质疑产品坏了。4. 实战排障漏台、串台、重台与循环不停止的根因4.1 漏台先查步进和稳定时间别急着改阈值漏台是最常见的投诉。用手动搜台能收到的台自动搜台就是搜不到。我遇到过的案例超过一半是下面三个原因之一。第一个是步进设置错误。有些地方存在100kHz间隔的邻频台如果SDK默认步进是200kHz从低到高扫描时那些落在100kHz上的台就会从扫描网格中间漏过去。排查方法很简单把步进强制改成100kHz再扫一遍漏台立刻减少。第二个是稳定等待时间太短。有些开发者把切频后的等待时间砍到5ms来追求“搜得快”结果PLL还没锁住就去读RSSI弱台读数偏低被过滤。测量方法是在固定频点上反复切进切出用逻辑分析仪或者串口打印连续RSSI值看看从发出切频指令到读数稳定大概需要多久然后留出余量。第三个才是阈值问题。如果你确认步进正确、时间足够还是漏台就先调SNR阈值而不是RSSI。因为弱台卡在RSSI边界上很常见但SNR一旦够好说明信号确实“解出来了”人耳基本能听。我习惯把SNR阈值从18dB往下调到12dB试试弱台就能重新出现。4.2 串台与邻频干扰通常是“把噪声当台存了”串台的表现有两种。一种是搜台中途在一个没有台的地方停下来播放时能听到隐隐约约的隔壁台声音另一种是明明应该有台的频率点附近却连续存了两个频率。第一种情况根源是邻频强台的带外泄漏和镜像干扰。FM射频前端的滤波器选择性有限一个强台可能在它实际频率之外好几个步进处仍然产生足够高的RSSI如果只查RSSI不查SNR误判就不可避免。解决思路就是回到双阈值判据并且对候选频点做频率微调验证如果这个频点算有效台那么在它附近微调频率时RSSI会围绕中心先升后降如果只是泄漏微调后读数变化没有明显峰值特征。第二种情况通常需要做“邻频合并”。两个频率间隔不超过2个步进对于100kHz步进就是200kHz的候选台基本可以判断是同一个电台或者其镜像保存时只保留RSSI更高的那个。这个去重逻辑放在台站表写入前简单有效。4.3 重台和循环不停止扫描边界与回卷逻辑的坑搜台停不下来和重复存台的根因多半出在边界处理上。先说重复存台。很多团队直接按“从87.5到108MHz扫一遍”写循环却忽视了频谱两端的效应。FM频段边界外的噪声和微弱信号同样可能越过阈值被当成台存在列表里。我的做法是扫描范围比实际的合法频段向外各扩展两个步进但对落在合法范围外的候选结果直接丢弃。同时如果扫描起点不是频段下限比如用户从89MHz开始向上搜扫到108MHz后要回卷到87.5MHz继续搜这时候如果不做“已经访问过该频率”的标记就会把同一个台重复存两次。循环不停止则是回卷条件写错了。常见写法是do { freq step_khz; if (freq end_khz) freq start_khz; } while (found_count max_count freq ! original_freq);注意这个freq ! original_freq比较一定要放在扫描结束之后判断如果放在扫描中途一旦回卷点恰好在某个已存台的频率上循环可能提前退出。反过来如果忘记加“回到起点”的终止条件搜台就会无限跑。我在调这类bug时习惯在调试固件里加一个“本次搜索访问过的最大频率数”计数器超过频段内步进数2就强制终止并打印卡住的位置比死盯屏幕高效得多。4.4 搜台过程的杂音与爆音静音时序的细节搜台时出现杂音往往不是RF问题而是静音时序没做好。要点有三条搜索开始前静音搜索过程中保持静音搜索到台之后延时50ms左右再取消静音。延时是为了让音频通路稳定下来避免在第N个频点的残留解码数据刚播出去时突然开音。另一个容易忽略的点是FM和蓝牙共用音频通路搜台结束后要确保音频路由切回FM否则会出现“搜到了台但没有声音”的诡异现象。5. 搜台只是开始台站表、制式适配与体验优化5.1 台站表的数据结构与存储策略搜完台如果不落盘每次上电重新搜一遍用户体验就很差。台站表我通常用固定数组管理结构大概是typedef struct { uint16_t freq_khz; uint8_t rssi; uint8_t snr; uint8_t is_valid; } fm_preset_t; fm_preset_t preset_list[FM_PRESET_MAX]; // 通常预留20~40个数组按频率升序排列写入前先做去重和排序。存储介质用EEPROM还是Flash取决于平台资源我建议在用户发起“保存电台”操作或者关机时一次性写入不要在搜台过程中频繁擦写否则寿命和功耗都受不了。5.2 区域制式适配别让一批货在海外变成废品FM频段和步进不是全球统一的。中国和欧洲大部分地区是87.5~108MHz美国是87.9~107.9MHz奇数频率日本则是76~90MHz且步进一般是50kHz。如果产品要出口台站表的上下限、步进、甚至搜索起点都要做成配置项而不是写死在代码里。我踩过的坑是只改频段上下限忘了步进某一版固件在日本用50kHz步进搜台结果一台机器生生搜出80多个“台”一半是噪声。后来配置表里加入step_khz和stereo_threshold字段不同区域读不同配置问题才算根治。5.3 更进一步的体验优化RDS、恢复播放与信号稳定性基础搜台跑通之后真正拉开产品体验差距的是这些后续优化。一是RDS无线数据广播。如果能收到RDS数据可以在台站表里同时存电台名称UI上显示“中国交通广播”而不是一个光秃秃的频率观感完全不同。注意RDS解码要在音频通路稳定的情况下进行不要在搜台扫描过程中解析否则丢帧严重。二是开机恢复上次播放。上电先读台站表直接跳到上次关机前的频率播放比什么都搜不到让用户手动找台强得多。三是搜台过程中的任务调度。杰理这类双模芯片上搜台是I2C密集型操作期间蓝牙如果还在跑协议栈注意留足带宽。我实际项目里出现过搜台时蓝牙断连的情况原因是FM搜台循环里连续做了太多delay阻塞把协议栈的调度饿死了。解法是把搜台放到低优先级任务里切频和读数的间隔之间主动让出CPU。最后说一个我自己的习惯调试搜台时永远在代码里保留一级“强制打印每个频点的RSSI和SNR”的调试开关。平时关着现场出问题就打开让测试人员对着实际收音环境把数据打出来回传。很多时候你以为的“信号差”其实是某个局部频点的底噪异常没有这些原始数据光靠猜阈值不知道要猜到什么时候。搜台这种功能看着简单真正把它调到“又快又准”靠的就是对每一个频点的理解和对细节的死磕。
阅读完成 · 觉得有帮助?