
最近在调一颗 STM32C5项目里要把单通道 ADC 的采样率从五百多 Ksps 干到 1Msps 以上搜了一圈资料发现 C5 这颗芯片的 ADC 交错采样interleave mode完整配置流程讲得真的不多尤其是新版 CubeMX 里应该怎么点、生成代码之后还缺哪些东西很多帖子都停留在老架构上。这篇文章把我从原理到配置、再到实测踩坑的完整过程整理出来给同样想用 STM32C5 做高速采样的朋友一条能直接走通的路。先说清楚这篇文章适合谁如果你手上的应用是数字电源、电机控制、音频采集或者任何“单个 ADC 采样率不够、但又不是非要上外部高速 ADC”的场景那你来对了地方。我默认你用过 HAL 库、看过 CubeMX 生成的代码但对交错采样这个概念还处在“听说过、没见过”的阶段。下面我会把 ADC interleave mode 的原理、CubeMX 配置步骤、生成代码后的改动点以及实测中碰到的问题全部过一遍。1. 先说清楚 ADC 交错模式到底解决什么问题1.1 为什么单 ADC 采样率会卡住很多人在 STM32 上做 ADC 采样第一反应就是“直接把 ADC 时钟拉满采样周期调到最小”。这个思路没错但你会很快撞到一面墙ADC 转换时间 采样时间 转换周期12 位分辨率下转换周期固定是 12.5 个 ADC 时钟周期采样周期即使压到最小 1.5 个周期一次完整转换也要 14 个 ADC 时钟周期。如果你把 ADC 时钟配置到 125MHz那么单 ADC 最大有效采样率大概就是 125M / 14 ≈ 8.93Msps听起来不低对吧但很多应用场景对采样率的需求是“多多益善”比如电机控制里要做高频注入、数字电源里要抓开关纹波8Msps 看着够实际上留给你的裕量很有限。问题是主频就那么多ADC 时钟分频比也是固定档位你再想往上提单 ADC 这条路就堵死了。这时候要么外挂一颗高速 ADC 芯片成本、PCB 面积、通信接口全都要重新考虑要么就看看这颗 MCU 本身还有没有潜力。STM32C5 这类新一代 MCU 上双 ADC 交错采样就是挖掘潜力的最直接办法。1.2 交错模式的核心原理其实就是俩 ADC 轮流干活交错采样interleaved sampling的原理用一句话说让两个 ADC 交替对同一个通道采样一个采奇数拍一个采偶数拍然后把两组数据在时间轴上拼接起来等效采样率翻倍。具体到 STM32 上ADC1 和 ADC2 共享同一个触发源——比如同一个定时器的 TRGO 事件。硬件会保证 ADC1 在触发沿立即开始转换而 ADC2 经过一个可配置的延迟TwoSamplingDelay之后再开始。这个延迟通常配置为半个采样周期也可以按实际需求微调。这样一来ADC1 和 ADC2 的采样时刻在时间上错开了半个周期最终 DMA 搬运出来的数据流在时间轴上就是均匀间隔的连续采样序列。打个比方你原来只有一个人值班每 14ns 能处理一件事现在你叫来第二个人两人错开时间上班同一时刻就只有一个人在忙但单位时间内处理掉的事变多了。交错模式就是这个思路不是把单次转换加速而是让两个转换器并行、交替干活。这里有一个关键点需要理解交错模式下两个 ADC 的模拟输入通道必须是同一个否则你采到的就是两个不同管脚、不同信号的数据拼在一起没有任何意义。1.3 什么时候选交错模式什么时候老老实实用多通道扫描我见过不少人在做方案选型的时候把“ADC 多通道扫描循环采样 DMA”和“ADC 交错模式”混为一谈以为交错模式能把多通道采样也一起提速。这两者解决的根本不是同一个问题应用场景推荐方案原因单通道高速连续采样双 ADC 交错 DMA采样率翻倍满足高频信号捕捉多通道周期巡检温度、电压慢变量单 ADC 扫描 DMA通道切换本身会占用时间交错模式帮不上忙多通道 每组通道都需要高速采样多 ADC 独立触发 DMA每个 ADC 各管一路或几路避免扫描与交错的混乱对采样时序确定性要求极高的场景定时器触发 注入通道注入通道可以打断规则组转换响应更及时我的建议是先搞清楚你的瓶颈是“单个通道不够快”还是“通道太多来不及轮询”。前者找交错模式后者找扫描 DMA。强行把两个特性叠在一起用大概率是数据错乱加调试地狱后面我会讲我踩过的具体坑。2. CubeMX2 里的配置路线与关键选型2.1 先花十分钟确认你的 ADC 模块架构STM32C5 和老的 F1/F4 在 ADC 外设上有明显差异我一开始就默认 F4 的老思路去配结果在 CubeMX 里找了半天没找到对应的选项。建议你在动手配置之前先把你手上那颗芯片的参考手册翻到 ADC 章节确认三件事第一芯片有几个 ADC 实例是否支持双 ADC 组合模式第二两个 ADC 之间是否有硬件延迟补偿机制也就是 TwoSamplingDelay 的具体档位第三DMA 在多模式下搬运数据时每个数据项是多少位宽——这直接决定你配置 DMA 时选半字还是字。C5 系列的 ADC 模块和 H7/G4 的架构思路更接近都支持多 ADC 组合模式。但不同型号、不同封装下引脚分配可能不同所以最靠谱的方法还是以手里那份参考手册为准。这个过程也就十分钟但能帮你省下后面一整天的无用功。2.2 ADC1 和 ADC2 的基础参数配置打开新版 CubeMX创建一个 STM32C5 工程之后先别急着点生成代码。在 Pinout 视图里把 ADC1 和 ADC2 的同一个输入通道分别分配到你需要的引脚上。这里有个细节如果这颗芯片内部已经把两个 ADC 的某几个输入通道绑到了不同引脚你就要选一个两个 ADC 都能连到的那一路。接下来在 ADC 的 Parameter Settings 里我用的配置参数如下参数项配置值说明Resolution12-bit根据你的精度需求和转换周期权衡位数越高转换周期越长Scan Conversion ModeDisabled交错模式只做单通道采集Scan 必须关掉Continuous Conversion ModeDisabled用定时器触发不依赖连续模式Data AlignmentRight alignment常规右对齐数据处理最简单External Trigger SourceTimer 1 Trigger Out同一个触发源同时驱动两个 ADCExternal Trigger EdgeRising edge触发沿的选择要和定时器配置一致EOC SelectionSingle conversion每个单独转换产生一次 EOCDMA Continuous RequestsEnabled让 DMA 持续搬运不然转换完一轮就停了ADC2 的配置基本照抄 ADC1但有一个点特别容易忽略很多例程里只在 ADC1 上开了 DMAADC2 不配置 DMA。这是正确的因为双 ADC 交错模式下数据由主 ADC 统一搬运从 ADC 的转换结果会通过内部总线合并到主 ADC 的读取路径上。你在 CubeMX 里给 ADC2 也开了 DMA反而可能造成冲突。2.3 使能交错模式的位置藏在多模式配置里CubeMX 的 ADC 配置界面里有一个你可能不会注意到的选项卡叫 Multi-mode 或者类似的名字。我在新版 CubeMX 里找到的是 ADC1 的配置面板里会有 Mode 下拉框。这个下拉框展开之后能看到 Disabled、Independent、Dual regular simultaneous、Dual interleaved 等选项。选 Dual interleaved 就是我们要的交错模式。选完之后界面会多出几个参数比较重要的是 Two Sampling Delay 档位。这个参数决定了 ADC2 相对 ADC1 的采样延迟单位是 ADC 时钟周期。我的建议是先选一个折中值比如 5 个周期后面实测的时候再根据波形微调。如果这个值设得太小两个 ADC 的采样保持开关有可能互相影响设得太大采样时刻的偏移就不是理想的半周期重建出来的波形会出现相位不均匀。这里要提醒一句CubeMX 生成的代码里多模式配置不一定直接体现在 ADC1_Init 函数里它可能生成到单独的 HAL_ADCEx_MultiModeConfigChannel 调用中。如果你在主函数里找不到这行代码搜一下 MultiMode 关键字或者看 ADC 外设初始化之后的系统时钟配置部分。2.4 DMA 配置方案这是最容易踩坑的地方交错模式真正难的不是 ADC 配置是 DMA。我在 H7 上用过类似功能很清楚 DMA 数据宽度选错会发生什么。首先DMA 的请求来源选 ADC1因为主 ADC 负责搬运。方向是外设到内存。最关键的一点数据宽度必须设置为 Word32 位。原因很简单双 ADC 交错模式下每次搬出来的数据不是两个 16 位值而是一个合并后的 32 位值高 16 位来自一个 ADC低 16 位来自另一个 ADC。如果你按 Half Word16 位搬数据流的边界就全乱了解析出来的两个通道数据相互穿插基本没法用。DMA 模式选 Circular这样缓冲区满了之后自动回绕配合半传输中断和传输完成中断可以实现无间隔的连续采集。缓冲区大小建议至少是 2 的整数次幂后面做数据解析和 FFT 扩展都比较方便。我常用的配置是 2048 个 32 位单元这样一个缓冲区能装下 2048 个采样点即 ADC1、ADC2 各 2048 个转换结果正好满足一次 FFT 对点数 2 的幂次要求。另外那个 DMA 通道的优先级实测下来 Medium 以上比较稳妥。如果系统里还有其他 DMA 业务给 ADC 分配高优先级不然在总线繁忙的时候ADC 转换结果不能及时搬走就会触发 Overrun数据流出现空洞。3. 生成代码之后的关键改动与完整实现3.1 CubeMX 生成的初始化代码缺了什么CubeMX 生成代码之后你会看到类似下面这样的 ADC1 初始化函数。注意我强调了“类似”——不同 CubeMX 版本和不同 HAL 库版本生成的代码格式会有差异但关键参数基本一致。static void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV2; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T1_TRGO; hadc1.Init.ExternalTrigConvEdge ADC_EXTERNALTRIGCONVEDGE_RISING; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; hadc1.Init.DMAContinuousRequests ENABLE; hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; hadc1.Init.Overrun ADC_OVR_DATA_OVERWRITTEN; if (HAL_ADC_Init(hadc1) ! HAL_OK) { Error_Handler(); } }ADC2 的初始化结构和 ADC1 几乎一样只是实例换成了 ADC2。这里有一个新手容易犯的错误以为两个 ADC 都要配置 DMA 通道。实际上只有主 ADC 需要挂 DMA从 ADC 的数据会通过内部合并路径过来在 DMA 那边变成 32 位数据。如果你给两个 ADC 都接了 DMA轻则多占一条 DMA 通道重则数据竞争。3.2 把两个 ADC “合体”的关键代码初始化完成之后CubeMX 一般不会自动生成多模式配置需要你手动调用。我通常在 main 函数里的 MX_ADC1_Init 和 MX_ADC2_Init 之后加上这一段ADC_MultiModeTypeDef multimode_config {0}; multimode_config.Mode ADC_DUALMODE_INTERL; multimode_config.DMAAccessMode ADC_DMAACCESSMODE_32_10_BITS; multimode_config.TwoSamplingDelay ADC_TWOSAMPLINGDELAY_5CYCLES; if (HAL_ADCEx_MultiModeConfigChannel(hadc1, multimode_config) ! HAL_OK) { Error_Handler(); }注意这个调用传入的是 hadc1也就是主 ADC 的句柄。多模式配置是以主 ADC 为视角进行的别传成 hadc2。DMAAccessMode 选择 32_10_BITS 而不是老的 32_10_BITS 还是别的具体看你的 HAL 库支持哪些枚举值核心思想就是让 DMA 按 32 位方式组合搬运两个 ADC 的结果。校准这一步也不能漏。STM32 的 ADC 上电后最好做一次校准对于交错模式来说两路 ADC 的校准情况直接影响拼接后的波形质量。我在初始化启动 DMA 之前会调用HAL_ADCEx_Calibration_Start(hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); HAL_ADCEx_Calibration_Start(hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);如果两块 ADC 的偏移校准不准确交错拼接后的信号会出现固定频率的杂散分量这个后面我会在问题排查章节展开讲。3.3 启动多模式 DMA 传输所有初始化做完之后启动代码是uint32_t adc_buffer[ADC_BUFFER_SIZE]; // ADC_BUFFER_SIZE 建议定义为 2 的幂如 2048 HAL_ADCEx_MultiModeStart_DMA(hadc1, adc_buffer, ADC_BUFFER_SIZE); HAL_TIM_Base_Start(htim1); // 启动定时器开始输出触发脉冲启动的顺序有讲究。我习惯先启动 DMA再启动定时器。原因是定时器一旦开始输出触发信号ADC 就会立刻开始转换并把结果往 DMA 里塞如果你的 DMA 还没有准备好第一批数据就可能丢失或者未定义。先启 DMA、后开定时器能保证数据流从第一拍开始就是完整的。如果你用的是 HAL_ADC_Start_DMA 而不是 MultiMode 版本的函数多半是哪里配置漏了或者你的 HAL 库版本里这个函数的枚举名不一样。去 HAL 库里搜一下 MultiModeStart确认当前版本支持哪些函数。定时器的配置这里也给一个参考。比如 ADC 时钟配置为 125MHz触发频率想要 1MHz那么定时器 TIM1 的 ARR 和 PSC 就需要让触发事件每 1μs 产生一次。假设 TIM1 的时钟是 125MHz那么PSC 125MHz / 1MHz - 1 124也就是分频到 1MHzARR 1MHz / 1MHz - 1 0也就是每个时钟周期触发一次这个配置比较激进实际使用中我留了一些裕量比如把触发频率设成 500kHz让总采样率落在 1Msps 附近给 CPU 留出处理时间。3.4 数据拼接与通道对应DMA 搬运的数据里每个 32 位数据项合并了两个采样值。具体哪个是高 16 位、哪个是低 16 位取决于芯片设计不能一概而论。我用的 C5 上是低 16 位为 ADC1 结果高 16 位为 ADC2 结果。解析代码很简单for (uint32_t i 0; i ADC_BUFFER_SIZE; i) { uint16_t adc1_sample (uint16_t)(adc_buffer[i] 0xFFFF); uint16_t adc2_sample (uint16_t)((adc_buffer[i] 16) 0xFFFF); sample_index 2 * i; final_sequence[sample_index] adc1_sample; final_sequence[sample_index 1] adc2_sample; }拼接完成之后final_sequence 就是一条采样率为定时器触发频率两倍的连续数据流可以直接送到 FFT 或者做其他后处理。这里要注意缓冲区大小和数组越界问题。如果 ADC_BUFFER_SIZE 是 2048final_sequence 的大小就要是 4096。3.5 用信号验证交错模式是否真的生效跑起来之后第一件事不是看数据波形而是验证“采样率翻倍”这个效果是否真实。我的方法是用信号源输入一个已知频率的正弦波比如此时设置信号源输出 100kHz而定时器触发频率是 1MHz交错后有效采样率是 2Msps。把拼接后的数据丢进 Python 或者 MATLAB 里做一次 FFT正常情况下你应该在 100kHz 处看到主峰。如果交错模式没有生效你会看到主峰出现在 100kHz 没错但伴随明显的镜像分量或者主峰位置偏移。如果两路 ADC 的偏移和增益差异很大你会看到在采样率的一半处也就是 1MHz 处出现杂散。这种频域特征可以快速帮你判断问题出在相位还是幅值。4. 实测中踩过的坑和排查办法4.1 DMA 读到的数据和手动轮询值对不上热搜词里有“adc的dma模式与手动轮询值不一样”看到这个词我就知道又有人掉进同一个坑了。我第一次调交错模式时也干过这事手动读 ADC1 的数据寄存器然后对比 DMA 缓冲区里的数据发现数值差距很大一度以为是 DMA 配置错了后来才想明白手动轮询读的是单 ADC 的 16 位寄存器而 DMA 缓冲区里是合并后的 32 位数据低 16 位才是 ADC1。你拿 32 位值整个去比较自然对不上。数据来源数据宽度内容手动读 ADC1 数据寄存器16 位只有 ADC1 的结果DMA 缓冲区32 位高 16 位 低 16 位拼接解决办法解析时先拆出低 16 位再比较或者干脆不用手动轮询验证直接看 DMA 数据。如果你现在就在调这个赶紧检查一下你是在拿哪个数值和哪个数值比。4.2 交错模式下多通道扫描数据乱飞如果你在 CubeMX 里同时开了 Scan Conversion Mode 和双 ADC 交错模式实测数据大概率是混乱的。交错模式的硬件设计基础是“两个 ADC 轮流采同一个通道”当你开启扫描模式后两个 ADC 各自要跑一串通道序列触发的时序就会完全失控出来的数据不仅通道对应不上时间关系也是乱的。这个问题没有太好的软件解法唯一的办法是回到应用层想清楚你到底需要多通道采集还是单通道高速采集如果是多通道用独立模式 DMA 扫描数据稳定、代码也好写如果必须高速且多通道建议用两路 ADC 实例分别跑各自的触发采集不要试图在交错模式下硬塞多通道。4.3 采样率没有翻倍甚至比单 ADC 还慢有不少朋友配置完交错模式测了一下发现采样率不但没翻倍还变慢了第一个念头是芯片不支持。先别急着怀疑芯片按这个顺序排查第一检查定时器触发频率是不是真的比你单 ADC 模式的触发频率高。交错模式只是把每个 ADC 的采样机会翻倍但触发源频率如果没变你的总采样率就是双份的触发频率这和单 ADC 模式下的“连续转换”相比不一定有明显优势。很多人配置完交错模式忘了调定时器频率自然看不到翻倍效果。第二检查 ADC 时钟分频。交错模式的总采样率 触发频率 × 2但前提是每个 ADC 都有足够的转换时间完成一次完整转换。我实际计算过ADC 时钟 125MHz、12 位分辨率、采样周期 1.5 时单 ADC 最大转换速率是 8.93Msps交错模式下理论上限是 17.86Msps。如果你的 ADC 时钟分频太大比如分到 12.5MHz那交错出来的采样率也就 1.78Msps反而可能不如你原来单 ADC 配置下的高频时钟效果。第三检查 TwoSamplingDelay 是否过大。延迟越长两个 ADC 的实际采样时刻越疏等效采样率计算时要考虑这个额外的时间开销。比如你设的是 20 个 ADC 时钟周期那总共一个采样周期就是 14 20 14 48 个 ADC 时钟周期相当于等效采样率是 125M/48 ≈ 2.6Msps连单 ADC 的最大值都达不到。TwoSamplingDelay 不是越大越好要匹配你的转换时间不是特殊情况不建议超过转换周期的四分之一。4.4 底噪偏大、出现周期性毛刺热搜词里有“adc测试中降低板卡底噪的办法”这个问题我在交错模式下遇到得更明显。单 ADC 采集时底噪问题相对温和一旦切换到交错模式两路 ADC 轮流工作模拟部分的电源和地线上的瞬态电流变化更频繁底噪会反过来抬升。硬件层面的处理我做了三件事第一ADC 的模拟电源引脚和参考电压引脚单独走线加 LC 滤波电感选 1μH 到 10μH 的功率电感电容用 10μF 100nF 组合第二参考电压尽量用独立 LDO 或者基准芯片不和数字电源共用第三PCB 上模拟区域和数字区域做单点接地避免地环路。这些是通用做法但在交错模式下收益显著。软件层面也有辅助手段。采样周期不是越小越好适当加大采样时间可以降低输入源的等效阻抗要求减少采样保持电容充电不充分造成的误差。我在最终方案里把采样周期从 1.5 周期调到了 5.5 周期底噪有了可观察到的下降代价是最大采样率略降但 1Msps 的指标完全满足。4.5 DMA 中断不触发或数据整体错位出现这种情况先检查 DMA 缓冲区的大小。在双 ADC 交错模式下DMA 的传输单位是 32 位而不是 16 位。如果你把缓冲区大小定义成了“采样点数”那实际最后写入的数据量只有你预期的一半半传输中断的触发位置自然也不对。另外我遇到过一次 DMA 数据整体偏移一个 32 位字的问题。排查后发现是 DMA 配置里的数据宽度设成了 Half Word而我在代码里按 Word 解析。DMA 每次搬 16 位数据在缓冲区里是交错排列的 16 位块用 Word 解析等于所有数据都错位了。这种问题看代码很难看出来建议直接查 DMA 的寄存器配置或者拿调试器看缓冲区前几个数据的字节序。5. 几个提升采样质量的额外经验5.1 时钟树里的 ADC 时钟分频值得花时间调很多人的第一版代码用的是 CubeMX 默认的 ADC 时钟分频大概率不是最优解。我在 C5 上调交错模式时专门把 RCC 配置打开看了 ADC 时钟的来源和分频链。ADC 的时钟上限是有限的如果超过上限虽然代码能跑但转换结果不会更精确反而可能引入额外误差。我的建议是先查手册确认这颗料 ADC 时钟最大值然后在 CubeMX 的 Clock Configuration 页面里把 ADC 时钟拉到接近上限但不超限的档位。这样即使你后续调整了采样周期也能保证交错模式的总采样率还有足够的裕量。实测下来这个步骤对最大采样率的影响非常直接值得花十分钟调好。5.2 两路 ADC 的偏移失配校准交错模式最大的敌人不是噪声而是两路 ADC 的失配。如果 ADC1 和 ADC2 的增益或偏移不一致输入一个直流信号时交错拼接后的数据会交替出现两个不同的值在频谱上表现为采样频率一半处的杂散。这个问题在 FFT 里非常显眼第一次看到时我还以为是哪根信号线松了。软件校准的思路不复杂输入端接地和接一个已知精确电压源分别记录 ADC1 和 ADC2 的读数算出各自的偏移和增益系数。然后在数据处理时对每一路做线性校准比如 adc1_sample 乘 1.003 再加 2adc2_sample 乘 0.998 再减 1具体系数按实测来。这个校准量在系统启动时做一次就行之后直接套用。要我选的话校准后频谱改善非常明显比单纯加大采样周期效果更直接。5.3 软件滤波不要照搬单 ADC 的方案交错模式的数据流在时间上是均匀的但它的“均匀”建立在两路 ADC 匹配良好的前提下。如果你直接套用单 ADC 的滑动平均滤波可能会引入额外的问题。我在实际项目里用的是“去极值滑动平均”先对连续 5 个点排序去掉最大最小剩下 3 个做平均。这种滤波方式对偶发毛刺抑制效果好也不会因为两路 ADC 的微小失配带来明显的波形畸变。如果数据要送到控制环路里还可以考虑在滤波之后叠加一个一阶低通截止频率按你的控制带宽来设计。这个组合处理在数字电源和电机控制的电流采样里表现都不错简单、可靠、没有复杂的系数整定。5.4 后续可以往哪个方向扩展如果单双 ADC 交错之后采样率还是不够不妨看看这颗芯片是否支持更多路的组合模式。ST 的高端系列里有三 ADC 交错甚至四 ADC 交错的先例原理和双 ADC 完全一致只是配置枚举和 DMA 合并位宽不同。C5 的具体支持情况要看参考手册但思路是一样的。另一个扩展方向是结合注入通道。交错模式擅长规则组的连续高速采样如果你还希望在某些特定时刻插入一次高优先级采样可以把注入通道配置为定时器事件触发转换结果走独立的数据寄存器不影响规则组的数据流。这个用法在电机控制里很常见既能保证高速采样又能在过流保护点做精确捕捉。最后的一点实际操作体会折腾了大半个月我的体会是 ADC 交错模式在 STM32C5 上完全可行而且它带来的采样率提升不是“纸面参数”实测波形、FFT 结果都能对得上。最容易让人崩溃的反而不是 ADC 本身而是 DMA 的数据宽度和缓冲区的对应关系以及两路 ADC 的校准一致性。如果你也卡在某个环节出不来把时钟树配置、DMA 数据宽度、TwoSamplingDelay 这三个地方重新捋一遍多半问题就浮出水面了。另外一个小技巧调试时先把信号源的频率设置得很低比如几十千赫兹确认数据流没问题了再把信号频率往上提这个习惯能帮你省下大量排查时间。