做高速采集这行十来年,被问得最多的一个问题就是标题这句。上个月还有个做超声前端的朋友拿着需求单来找我,第一行写着 16bit、250MSPS、并行 LVDS 输出,瞬时数据率 500MB/s。他盯着手上那颗主频 480MHz 的 MCU 反复确认:这块 MCU 能不能直接扛?是不是必须再挂一片 FPGA?这个问题没有标准答案,但有一个相对清晰的判断链条——把 500MB/s 这个数字真正拆成位宽、时钟、占空比三个维度看清楚,再对照 MCU 侧接口和总线的真实吞吐能力,答案往往就浮出来了。这篇就把我自己走过的路、算过的账、踩过的坑完整写一遍,从接口物理层一直讲到软件层的缓存策略,给正在做并行接口高速采集选型的同行一个可对照的参考。
1. 先把 500MB/s 这个数字拆开看,它到底是什么
很多人一看 500MB/s 就条件反射地想到"这是高速总线才有的量级",然后直接跳到 FPGA 方案。但 500MB/s 只是一个速率标签,它背后至少能对应四五种完全不同的物理形态,而这些形态对接收端的要求差别极大。
1.1 500MB/s 在不同位宽和时钟组合下的物理形态
把 500MB/s 换算成更接近硬件直觉的表达式:500MB/s = 4Gbps(按 8bit 一字节算)。再往下拆,就是位宽乘时钟的关系:
| 数据位宽 | 时钟频率 | 边沿方式 | 等效字节率 | 差分对数量(LVDS 常见) |
|---|---|---|---|---|
| 8 bit | 500 MHz | 单边沿 SDR | 500 MB/s | 4 对数据 + 1 对时钟 |
| 8 bit | 250 MHz | 双边沿 DDR | 500 MB/s | 4 对数据 + 1 对时钟 |
| 16 bit | 250 MHz | 单边沿 SDR | 500 MB/s | 8 对数据 + 1 对时钟 |
| 16 bit | 125 MHz | 双边沿 DDR | 500 MB/s | 8 对数据 + 1 对时钟 |
| 12 bit | 333 MHz | 单边沿 SDR | 约 500 MB/s | 6 对数据 + 1 对时钟 |
注意:这里的"字节率"是按 ADC 有效数据位凑整到字节估算的,实际传输时还会带上帧同步位、溢出标志位等额外开销,真实线速率往往还要再高 10% 到 15%。
这张表信息量很大。同样叫 500MB/s,选 16bit/250MHz/SDR 的方案,接收端要处理的是 250MHz 的源同步时钟,多个数据位的建立保持窗口只有 2ns 左右;而选 8bit/250MHz/DDR,时钟还是 250MHz,但每个边沿都要采一次数据,对输入延迟和走线匹配的敏感度反而更高。区别在于,前者是"宽而慢",后者是"窄而快"。宽总线对 MCU 更友好,因为 MCU 的并行接口通常就是宽总线结构;窄而快的 DDR 总线对纯逻辑器件更友好,因为 FPGA 内部有 ISERDES、IDDR 这类专用原语,天然就是干这个的。
我自己做过的选择是:只要前端 ADC 允许配置输出格式,优先选宽位宽 + 低时钟的组合。16bit/125MHz/DDR 比 8bit/500MHz/SDR 好接太多了——前者时钟 125MHz,走线等长控制在 ±20mil 就能稳;后者 500MHz 时钟,那基本就得按射频板来做,成本翻倍。
1.2 峰值带宽、持续带宽、占空比:三个完全不同的概念
这是整个选型里最容易被忽略、也最容易决定成败的一点。需求方给的"500MB/s"几乎从来指的都是峰值带宽,而真正压垮系统的是持续带宽和占空比。
举个具体场景:某雷达回波采集,ADC 250MSPS、16bit,数据率 500MB/s,但系统是脉冲工作制——发射一个 20μs 的脉冲,然后静默 5ms,等回波返回时再采 100μs。这时候占空比是多少?100μs / 5.1ms ≈ 2%。也就是说,500MB/s 只持续 100μs,一次采下来总共 50KB 数据。
50KB 是什么概念?STM32H7 的 FMC 接口按 16bit 位宽、100MHz 时钟跑,理论 200MB/s,实际稳定 130MB/s 左右,写 50KB 只需要约 385μs。就算 MCU 在采集期间什么都不干,只负责把前端 FIFO 里的数据搬进内部 SRAM 或外部 SDRAM,这点数据和这点时间完全够用。
反过来,如果是连续波工作制,ADC 一直在采,500MB/s 是持续带宽,MCU 就彻底没戏了——不是接不住,是根本存不下。500MB/s 连续采 1 秒就是 500MB,采 10 秒是 5GB,任何片上存储都装不住,必须往主机或者大容量存储搬,这时候瓶颈早就不是 MCU 接口,而是整个数据链路。
所以我在评估任何方案之前,一定会先向需求方确认三件事:数据是连续流还是突发流、单次突发的最大时长是多少、两次突发之间的最小间隔是多少。这三个数一确认,方案空间的边界基本就画出来了。
1.3 MCU 侧到底能吞吐多少,几个主流平台的真实上限
光看厂商手册上的接口最高频率是不够的,手册给的通常是接口时钟的极限,不是数据吞吐的极限。下面这张表是我自己在几块平台上实测或者可信同行实测下来的持续吞吐数据,测试条件都是顺序读写、无并发竞争的场景:
| 平台 | 接口 | 位宽 | 接口时钟 | 理论峰值 | 实测持续吞吐 |
|---|---|---|---|---|---|
| STM32H743 | FMC 接外部 FIFO | 16 bit | 100 MHz | 200 MB/s | 约 150 MB/s |
| STM32H743 | FMC 接 SDRAM | 16 bit | 100 MHz | 200 MB/s | 约 95 MB/s |
| STM32H750 | FMC 接外部 FIFO | 16 bit | 100 MHz | 200 MB/s | 约 145 MB/s |
| i.MX RT1062 | SEMC 接 SDRAM | 16 bit | 166 MHz | 332 MB/s | 约 120 MB/s |
| STM32H7 DCMI 直采 | 并行 DVP | 8 bit | 80 MHz | 80 MB/s | 约 70 MB/s |
| STM32F429 | FSMC 接 SDRAM | 16 bit | 90 MHz | 180 MB/s | 约 70 MB/s |
几个关键结论:
第一,FMC 接外部 FIFO 比接 SDRAM 快不少。原因不难理解——SDRAM 有刷新周期、行激活、预充电这些开销,随机访问时有效带宽会掉到理论值的 50% 左右;而外部同步 FIFO 内部是双端口 SRAM,读写可以完全流水,只要时序对了就能接近满速。
第二,即便按最好的 150MB/s 持续吞吐来算,距离 500MB/s 也还差三倍多。这个差距不是靠优化代码能填平的,是硬件结构决定的。MCU 的 FMC 时钟一般来自 HCLK 分频,最高也就 100MHz 到 133MHz 这个量级,16bit 位宽下天花板就在 200MB/s 到 266MB/s 之间。
第三,DCMI 这类专用并行接口本来是为摄像头设计的,8 到 14bit 位宽、PIXCLK 一般不超过 80MHz 到 100MHz,而且它只能接收、不能主动停止,遇到内部缓存满的时候只能靠背压(把时钟拉低)或者丢帧,用于采集场景限制较多。
提示:厂商手册里的最高接口频率通常是"接口能跑到的时钟上限",和"你能持续搬多少数据"是两码事。做方案估算时,建议把手册数字打对折,再留 20% 余量,比较接近真实可获得带宽。
2. 为什么大家第一反应是 FPGA,它到底强在哪
说了这么多 MCU 的局限,也得公平地讲讲 FPGA 强在哪里,不然就成了为否定而否定。FPGA 在这个场景里的优势不是"主频高",而是"确定性",这一点经常被误解。
2.1 源同步接口的捕获,拼的是确定性而不是主频
250MHz 的源同步并行总线,数据位和时钟一起从 ADC 出来。接收端要做的动作是:用随路时钟去采每一根数据线,并且保证采样点落在数据眼图的中心。
在 MCU 里,这个动作是由 FMC 外设硬件完成的,采样点的位置由寄存器配置的建立时间和保持时间决定,配置粒度通常是 HCLK 的整数倍。HCLK 是 200MHz 的话,一个周期 5ns,你能调的最小步进就是 5ns。而 250MHz 时钟的半个周期才 2ns,也就是说——MCU 根本没有足够的调节精度去对准眼图中心。这不是 MCU 设计得不好,而是它的定位就是接异步或者半速总线,没打算处理这么高的源同步接口。
FPGA 的解法完全不同。它内部有 IDELAY 原语,可以按每级 78ps 或者 150ps 的精度微调输入延迟;有 ISERDES 可以把 DDR 数据直接解成 SDR;有 MMCM 可以对输入时钟做任意倍频和相位偏移。这一套组合下来,即使数据眼图只有几百皮秒,也能稳定采到中心位置。更关键的是,这些调节是确定性的——今天调好了,明天上电、换个温度、换个批次,行为还是一样。
我做过的对比实验很能说明问题:同样一条 16bit/250MHz 的并行总线,用 FPGA 接,误码率测试跑 10 小时零错误;用 MCU 的 FMC 直接接,误码率在 10 的负 6 次方量级波动,而且随温度漂移。这就是确定性带来的差距。
2.2 MCU 的 DMA 和总线仲裁,看不见的带宽黑洞
很多人算 MCU 吞吐时只看 FMC 时钟,忽略了内部总线的竞争。STM32H7 这类芯片内部是多层 AXI 总线加多个主设备——CPU、DMA1、DMA2、MDMA、以太网 DMA、USB DMA、LTDC 都在抢总线带宽和 SRAM 端口。
实测过一组数据:STM32H7 的 FMC 单独跑 DMA,能到 150MB/s;同时开着 LTDC 刷 800x480 的屏幕,FMC 吞吐掉到 110MB/s;再叠加上以太网收包,掉到 80MB/s 以下。这些损耗在数据手册里是看不到的,只有实际跑起来才会暴露。
FPGA 这边不存在这个问题,因为它内部的数据通路是你自己搭的,从输入引脚到内部 RAM 是一条专用的、你可以完全掌控的路径,没有别的设备跟你抢。这也是为什么在确定性要求高的场合,工程师宁可多花几百块钱上 FPGA。
2.3 FPGA 的真实代价:开发周期、功耗、BOM 和人员
但 FPGA 从来不是免费的。我统计过手上做过的几个项目,从零开始做一块 FPGA 采集卡(含电源、配置电路、DDR3 缓存、接口逻辑),一个熟练工程师大概需要 3 到 5 周才能出第一版能跑通的原型;如果要做到稳定交付,还要再加上 2 到 3 轮的调试和时序收敛。加上 DDR3 需要做等长、需要做阻抗控制,PCB 层数从 4 层跳到 6 层甚至 8 层,板子成本直接翻倍。
功耗也是实打实的。一片中等规模的 FPGA 加上 DDR3 控制器,静态功耗加上动态功耗,轻松跑到 1.5W 到 3W;而一颗 MCU 全速跑也就 200mW 到 500mW 这个量级。对于电池供电的便携设备,这个差距是决定性的。
还有一个经常被忽略的软成本:人员。会写 MCU 固件的人遍地都是,能把 FPGA 时序收敛、把源同步接口调通的人,数量少一个数量级,人力成本也差得远。如果你的团队里没有 FPGA 工程师,那上 FPGA 这个决定的代价还要再乘上招聘和培养的时间。
3. 不一定需要 FPGA:四条能落地的替代路线
讲完两边的能力边界,就能看出真正的问题不是"MCU 行不行",而是"能不能想办法把 500MB/s 的峰值变成 MCU 能承受的持续速率"。围绕这个思路,我整理出四条实际落地过的路线。
3.1 路线 A:触发式突发采集,把峰值变成短脉冲
这是最简单也最有效的一招。核心思路是:前端始终在采,但只把符合触发条件的那一段数据交给 MCU。
实现方式一般是在前端放一个高速 FIFO 芯片,让它以 500MB/s 的速率持续写入,同时用一个可配置深度的预触发计数器决定保留多少历史数据。触发信号到来后,采集窗口持续固定时长,窗口结束时 FIFO 停止写入,MCU 再把这段数据慢慢读走。
这条路线对 MCU 的要求一下子就降下来了:MCU 只需要在"窗口结束"后,用 FMC 从 FIFO 里把数据读走,读的速率只要 150MB/s 就够,而且这段时间前端不产生新数据,没有任何竞争。
适合的场景:雷达回波、超声脉冲回波、激光测距、瞬态信号记录。这些场景的共同特征是——关心的信号是短时的,剩下的时间都是在等。
注意:这条路线能不能用,取决于一个硬条件:前端 FIFO 必须能承受 500MB/s 的持续写入速率。普通同步 FIFO 的写时钟上限一般到 200MHz 左右,位宽 18bit,折算下来理论 450MB/s,实际要选更快的器件或者用 DDR 结构的 FIFO。
3.2 路线 B:前端降速,用抽取或者数字下变频把数据率压下来
如果 ADC 本身带数字下变频(DDC)或者抽取滤波器,那事情就简单了:让 ADC 内部把数据率降下来,输出一个 50MB/s 或者更低的并行流,MCU 直接接。
这不是偷懒,在很多应用里这恰恰是正确的做法。比如做频谱监测,你关心的是窄带信号,ADC 采 250MSPS 之后直接数字混频到基带,再抽取 8 倍,输出 31.25MSPS 的复数数据,两路 I/Q 加起来也就 31.25 乘 2 乘 2 字节 = 125MB/s,再抽 16 倍就是 62.5MB/s,MCU 轻松接下。
有些 ADC 支持"平均模式"或者"低通抽取模式",把相邻的 N 个采样点做一次平均再输出,等效采样率降到 1/N,但位宽可能增加。这种方式对 MCU 极其友好,代价是丢失了高频信息,只适合低速变化的信号。
如果 ADC 不带这些功能,还可以在模拟端解决——加一级抗混叠滤波器,把带宽限制在目标频段,然后用更低的采样率去采。这个方法最土,但也最省钱。
3.3 路线 C:专用 FIFO 和解串芯片顶替前端逻辑
有些厂商专门做了"并行 LVDS 转单端并行"的芯片,或者"高速串行转并行"的解串器。这类芯片把最难的源同步捕获和串并转换做掉,输出一个速率较低、时序宽松的并行接口,直接喂给 MCU 就行。
典型的产品思路是:输入 8 对 LVDS,时钟 250MHz DDR,输出 16bit 单端并行、时钟 125MHz SDR,附带一个 FIFO 做速率缓冲。这时候 MCU 面对的是一条 125MHz、16bit 的普通同步总线,FMC 完全能接,而且因为有时钟随路输出,MCU 甚至可以用外部时钟模式来做同步采样。
这条路线的代价是灵活性差——芯片的功能是固定的,你能调的参数有限。但换来的好处也很明显:不用写 FPGA 代码,不用做时序收敛,开发周期从几周缩短到几天。
3.4 路线 D:小 CPLD 做数据泵,MCU 只管搬运
如果前三条路线都不适用,还有一种折中方案:用一片小规模的 CPLD 或者低端 FPGA 做"数据泵",只负责最难的时序捕获和数据去复用,不做任何算法处理,把结果通过一个简单的同步接口交给 MCU。
关键在于选型和分工。CPLD 这里要做的事很纯粹:接收 LVDS 数据、做 IDDR 解串、按行写入内部或外部的 FIFO、产生一个"数据就绪"信号给 MCU。整个逻辑可能只需要几百个宏单元,用一片高云或者 Lattice 的入门级器件就够了,成本可以压到几十块钱。
这种分工的好处是把"确定性时序"和"复杂控制"拆开:CPLD 只管它最擅长的时序,MCU 只管它最擅长的流程控制和后续处理。两边各司其职,开发难度都降下来了。我在几个项目中用过这个思路,实测下来比纯 FPGA 方案省了将近一半的开发时间。
3.5 四条路线的横向对比
| 路线 | 前端复杂度 | MCU 负担 | 开发周期 | 参考成本增量 | 适用场景 |
|---|---|---|---|---|---|
| A 触发式突发 | 中(需高速 FIFO) | 低 | 1-2 周 | 中 | 脉冲/瞬态采集 |
| B 前端降速 | 低 | 极低 | 几天 | 低或无 | 窄带信号、频谱监测 |
| C 专用解串芯片 | 低 | 低 | 几天 | 中高 | 通用、批量产品 |
| D 小 CPLD 数据泵 | 中 | 中 | 2-3 周 | 低 | 需要一定灵活性的产品 |
| 纯 FPGA | 高 | 极低 | 3-6 周 | 高 | 需要实时算法、极高确定性 |
4. 一个真实项目的落地过程:16bit/250MSPS 采集卡
光讲路线还是太抽象,把去年做的一块板子完整拆一遍,细节都在里面。
4.1 硬件架构选型与器件确定
需求是:16bit、250MSPS ADC,LVDS 并行输出,采集窗口最长 200μs,两次采集间隔不小于 2ms,采集完的数据需要在本地做简单累加平均后上传。
数据率算下来:16bit × 250MSPS = 500MB/s 峰值。单次窗口 200μs,单次数据量 100KB。
方案选择上,先排除了纯 MCU 直采——250MHz 源同步时钟,MCU 接不住。也排除了纯 FPGA——需求里的"累加平均"完全可以在 MCU 里做,上 FPGA 属于杀鸡用牛刀。
最后定的是路线 A 加路线 D 的混合:前端用一片 CPLD 做 LVDS 接收和 FIFO 写入控制,FIFO 用一片 18bit 位宽、512K 深度的同步 FIFO 芯片,MCU 用 STM32H743,通过 FMC 接 FIFO 的输出端口。
CPLD 的任务清单:接收 8 对 LVDS 数据和 1 对随路时钟,用 IDDR 把 DDR 数据解成 SDR,凑成 16bit 后连同有效标志写入 FIFO,写时钟用随路时钟的二分频(125MHz)。当接收到的数据量达到预设的窗长时,拉高一个 done 信号,同时停止写入。FIFO 的读侧由 MCU 控制,MCU 检测到 done 后,启动 FMC 连续读 100KB(因为 FIFO 是 18bit 位宽,实际地址空间按 16bit 对齐访问),读完给出复位脉冲。
器件选型上,CPLD 选的是入门级的低功耗器件,宏单元数足够覆盖 IDDR 加 FIFO 控制逻辑,电源电压 1.2V 内核加 3.3V IO,静态功耗不到 50mW。FIFO 选的是 18bit × 512K 的同步 FIFO,写时钟上限 166MHz,读时钟上限 133MHz,符合我们的速率要求。
4.2 MCU 侧 FMC 与 DMA 的配置和参数计算
FMC 这块的关键是时序配置。STM32H743 的 FMC 时钟来自 HCLK3,我们配置成 100MHz(HCLK 400MHz 四分频)。因为 FIFO 的读侧是同步接口,用 FMC 的同步模式(PSRAM/SRAM 模式)来配。
几个关键寄存器的计算过程:
地址建立时间(ADDSET):FIFO 的读地址建立要求最小 5ns,100MHz 下一个周期 10ns,取 1 个周期刚好满足,留裕量取 2 个周期 = 20ns。
数据建立时间(DATAST):FIFO 的输出有效延迟最大 4.5ns,从时钟上升沿算起,取 1 个周期够了,留裕量取 2 个周期。
但实际配的时候发现 4 个周期下来总周期数太多,吞吐掉到了 80MB/s。后来把 ADDSET 减到 1、DATAST 减到 1,总周期 2 个 HCLK = 20ns,吞吐到了 100MB/s,跑误码测试 24 小时无错误。时序裕量到底能压到多少,只能靠实测,手册上的最小值都留了很大余量。
总线周转(BusTurnAround):读操作之间的周转时间,配成 0。
时钟分频:FMC 输出给 FIFO 的读时钟,配成 HCLK 二分频,即 50MHz,一个访问周期 2 个时钟周期,等效 100MB/s。
DMA 配置上用的是 MDMA(因为要配合 FMC 的高带宽),双缓冲模式。缓冲区 A 的地址指向 FMC 数据端口映射的内存区域,缓冲区 B 在内部 DTCM 或者 AXI SRAM。缓冲区大小各 32KB,DMA 一次搬 32KB,搬完触发中断,软件切换缓冲并处理上一块。
4.3 时序收敛的实测过程和调试方法
第一次上电,数据全是乱的。排查过程记录下来:
第一步,先用静态测试确认物理连接。让 CPLD 输出一个递增计数器而不是 ADC 数据,MCU 读回来一看,发现每隔 8 个数据错一位——典型的位对齐问题。原因是 CPLD 里的 IDDR 解出来的数据位顺序和 MCU 侧的字节序不一致,把 CPLD 输出端的位顺序翻转一下就好了。
第二步,位对齐解决后,发现偶发的数据跳变。用示波器抓 FIFO 的写使能和写时钟,看到写使能和写时钟之间存在约 1.5ns 的偏斜,在某些批次器件上触发了建立时间违例。解决方案是在 FIFO 的写侧加一个输出寄存器,让数据、使能和时钟都从同一级寄存器输出,把偏斜压到 300ps 以内。
第三步,FIFO 读侧偶尔出现第一个数据错。这个是典型的读延迟问题——FIFO 从"空"到"有数据"之后,第一个读出的数据需要额外的建立周期。解决方式是在开始读之前先做一次空读,把第一个数据丢弃。
整个过程花了三天,大部分时间是在区分"是数据链路的哪一级出的问题"。这一点很重要:分级测试,逐级确认,不要一上来就怀疑最复杂的那部分。
4.4 软件层面的双缓冲和丢点保护
DMA 双缓冲是标配,但真正防丢点还需要几个配套动作。
内存位置的选择很关键。AXI SRAM 的带宽是共享的,会有其他主设备竞争;DTCM 是 CPU 专用的紧耦合内存,但 DMA 访问不了。最后选的是把 DMA 目标缓冲区放在 AXI SRAM 的一个独立分区,同时通过 MPU 配置成非缓存,避免 Cache 一致性开销。
MPU 的配置是必须做的。Cortex-M7 有 D-Cache,如果不配 MPU 把 DMA 缓冲区标记为 Device 或者 Write-Through 类型,会出现 CPU 读到的数据和 DMA 写进去的数据不一致的情况,而且这种现象是间歇性的,极难排查。
丢点检测上,在 CPLD 里加了一个计数器,记录从前端接收到的总字节数;MCU 侧在读完之后也统计读到的字节数,两者做比对。如果不等,说明 FIFO 溢出了或者 MCU 读漏了,这个时候要给出明确的状态标志,而不是静默地接受错误数据。这个设计后来救过我们一次——某次批量生产里有一块板的 FIFO 电源纹波偏大导致偶发溢出,因为计数器对不上,直接在上电自检阶段就被拦下来了。
5. 踩过的坑和常见问题速查
5.1 数据错位、丢点这一类问题的排查顺序
这类问题最忌讳的是东查一下西查一下。我总结的顺序是:先物理层,再协议层,最后软件层。
物理层要看三样东西:随路时钟和数据线的相位关系、走线等长是否满足、电源纹波是否过大。前两项用高带宽示波器加差分探头就能看出来,第三项要特别注意——高速并行接口的电源噪声会直接调制到信号眼图上,让本来够用的时序裕量凭空缩小。
协议层要确认的是位序、字节序、帧结构。这类问题通常表现为"数据整体偏移一个固定量"或者"某些位固定错",规律性很强,好识别。
软件层的问题最隐蔽,因为它是间歇性的。典型的例子是 DMA 缓冲切换的中断延迟抖动,某次中断被更高优先级的中断挤掉了几微秒,导致前一块缓冲区还没处理完就被覆盖了。这类问题的解法是把处理逻辑做得足够快,或者把缓冲区开得足够大,用空间换时间。
5.2 带宽达不到标称值的几个常见原因
实测带宽只有理论值一半,这个问题太常见了,原因通常在这几处:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 带宽约为理论 50% | 访问周期配置过大,多等了 1-2 个时钟 | 逐周期压缩 ADDSET/DATAST,每次压 1 个周期跑稳定性测试 |
| 带宽随温度下降 | FMC 时序裕量不足 | 高低温测试,看误码率随温度的变化趋势 |
| 同时开其他外设时带宽骤降 | 内部总线仲裁竞争 | 逐个关闭其他主设备,定位竞争者 |
| 带宽波动大 | SDRAM 刷新或者行切换开销 | 换成外部 FIFO 或者调整访问的页面局部性 |
| 只有理论值 30% | Cache 一致性处理方式不对,每次访问都穿透到外部 | 检查 MPU 配置,确认缓冲区没有配成 Strongly Ordered |
最后一行这个原因特别值得说。把缓冲区配成 Strongly Ordered 类型,会让每次 CPU 访问都直接打到外部总线上,性能下降极其严重。正确的做法是配成 Normal、Non-cacheable,或者 Device 类型。
5.3 Cache 与 DMA 的一致性处理
这个话题值得单独讲,因为它坑过太多人。Cortex-M7 的 D-Cache 默认是打开的,DMA 直接写内存,CPU 读的时候可能读到 Cache 里的旧数据,因为 Cache 不知道这块内存被 DMA 改过了。
三种处理方式:
第一种,把 DMA 缓冲区所在的 MPU 区域配置成 Non-cacheable。简单粗暴,性能略有损失但最稳妥。我自己的项目基本都是这么做的,因为调试时间比那点性能重要得多。
第二种,保持 Cache 打开,在 DMA 传输前后手动做 Cache 维护操作(clean/invalidate)。性能最好,但每个传输点都要记得做,漏一次就出问题,而且这类问题往往是偶发的。
第三种,用硬件的一致性管理器。STM32H7 没有这个,其他一些平台有,比如带 CCI 的器件。用起来最省心,但器件选择受限。
提示:如果一定要用第二种方式,建议写一个封装函数,把所有 DMA 传输都走这个函数,不要裸调用 HAL 库的接口。这样至少保证维护操作不会被漏掉。
5.4 常见问题速查表
| 症状 | 优先怀疑 | 快速验证 |
|---|---|---|
| 数据全 0 或全 FF | 电源、复位、时钟未到位 | 测电源和时钟引脚,看复位是否释放 |
| 数据规律性错位 | 位序、字节序 | 用递增计数器代替真实数据源 |
| 偶发单点错误 | 时序裕量不足、电源噪声 | 降速测试,看错误率是否下降 |
| 整块数据丢失 | DMA 缓冲被覆盖、FIFO 溢出 | 检查缓冲切换逻辑和溢出标志 |
| 长时间运行后出错 | 温度漂移、Cache 一致性问题 | 高低温循环测试,检查 MPU 配置 |
| 读第一个数据总是错 | FIFO 读延迟 | 做一次空读丢弃首字 |
6. 到底什么时候该上 FPGA,一张决策表加我的经验判断
6.1 决策维度评分表
下面这张表是我实际选型时会逐项打分用的,每项按 1 到 5 分评估,分数越高越偏向 FPGA:
| 评估维度 | 偏向 MCU 的取值 | 偏向 FPGA 的取值 |
|---|---|---|
| 数据占空比 | 低于 20% | 接近 100% 连续 |
| 接口时钟频率 | 低于 100 MHz | 高于 200 MHz 源同步 |
| 是否需要实时算法 | 不需要,事后处理即可 | 需要每点实时处理 |
| 延迟确定性要求 | 容忍毫秒级抖动 | 要求微秒级确定性 |
| 单次数据量 | 小于 1 MB | 大于 100 MB 持续流入 |
| 团队能力 | 只有 MCU 工程师 | 已有 FPGA 工程师 |
| 功耗预算 | 严格受限,百毫瓦级 | 可接受瓦级 |
| 产量与成本 | 大批量,成本敏感 | 小批量,性能优先 |
我的判断经验是:占空比和接口时钟这两项是决定性的。只要这两项都落在 MCU 一侧,其余项再偏向 FPGA,也大概率能用 MCU 方案解决;反过来,只要接口时钟超过 200MHz 的源同步,其余项再偏向 MCU,也得老老实实上逻辑器件。
6.2 成本和周期的真实账
把账算清楚,做决定会容易很多。以一个中等规模的采集项目为例,逐项对比:
| 项目 | MCU 方案 | FPGA 方案 |
|---|---|---|
| 主控器件成本 | 50-120 元 | 150-600 元 |
| 缓存器件 | 外部 FIFO,30-80 元 | DDR3,30-60 元 |
| PCB 层数 | 4 层 | 6-8 层 |
| PCB 制板成本(100 片) | 约 8000 元 | 约 20000 元 |
| 开发周期 | 2-4 周 | 4-8 周 |
| 需要的工程师技能 | MCU 固件 | FPGA 逻辑 + 硬件 |
| 功耗 | 200-600 mW | 1.5-3 W |
这两条路各有各的适合区间。如果产品批量大、成本敏感、需求相对固定,MCU 方案的优势非常明显;如果是小批量、高性能、需求还在变化,FPGA 的灵活性值那个钱。
6.3 我的几条经验判断
做了这些年,有几条体会是反复被验证的。
第一条:先问占空比,别问带宽。需求方张口就是 500MB/s,但他的系统十有八九是脉冲工作制。把这个问清楚,一半以上的"必须上 FPGA"结论都会松动。
第二条:能降速就降速,降速是最便宜的方案。前端抽取、模拟滤波、触发采集,这些手段的成本远低于加一片 FPGA。先穷尽所有降速的可能,再考虑换器件。
第三条:分工比单干好。如果确实需要逻辑器件,优先考虑"小 CPLD 做时序 + MCU 做控制"的分工,而不是把所有活儿都压给一片大 FPGA。这样两边的开发难度都降下来了,团队里的 MCU 工程师也能参与进来,人力瓶颈缓解很多。
第四条:留一条后路。PCB 设计时,如果接口速率处在 MCU 和 FPGA 的中间地带,可以在板上同时留出两种器件的焊盘(虽然不焊接),万一 MCU 方案压不下去,改板成本就是焊接费和调试费,而不是重新投板。这个做法我在两个项目里用过,省了不少钱。
第五条:别信手册的峰值数字。无论是 MCU 的接口频率还是 FPGA 的收发器速率,手册给的都是条件最优下的极限值。做方案评估时,把这些数字打对折,再往上加 20% 的裕量,得到的才是能稳定工作的数字。按这个标准去判断,很多"看起来够用"的方案会原形毕露,而很多"看起来不够用"的方案反而留有空间。
最后再分享一个实操上的小技巧。在评估阶段,不要急着做完整方案,先用一块开发板搭一个最小验证系统——前端数据源用信号发生器或者 CPLD 模拟,MCU 侧只跑最简的 DMA 搬运循环,测量连续搬运一小时的数据完整率。这个验证大概两三天就能做完,但它的结论比任何理论计算都可靠。我在几个项目里都是先做这个验证,有一次就在验证阶段发现某款 MCU 在 DMA 满速运行时,如果同时有调试器连接,吞吐会掉 40%,这个坑要是留到量产才发现,代价就大了。