ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32三重ADC交错采样:原理、配置与数据重组详解

STM32三重ADC交错采样:原理、配置与数据重组详解 如果你已经调过 STM32 的 ADC八成会撞到同一个瓶颈单颗 ADC 转换 12 位数据再快也需要十几个 ADCCLK 周期想把采样率往上提要么缩小采样时间导致精度下降要么换更高速的芯片。真正做过电机控制、数字电源、音频采集的人最后基本都会盯上同一个方案——多个 ADC 交错采样。STM32 的部分型号内置了三颗 ADC把这些 ADC 按固定相位差“排班”去采同一个信号等效采样率能直接翻倍甚至接近三倍。这篇内容就是围绕 STM32 三重 ADC 交错采样从原理、硬件门槛、代码配置到数据重组和调试的全过程展开的内容偏实操适合已经开始接触寄存器或 HAL 库、想把手头项目的 ADC 吞吐率再往上抬一截的开发者。三重交错采样不是什么黑魔法它解决的核心问题就一个单颗 ADC 存在转换死区转换期间没法采下一个点那就在时间上错开让 ADC2 和 ADC3 去补位。这个思路在高速示波器、射频采样前端里非常成熟STM32 里虽然没有专用并行采样前端但依靠多 ADC 模式和内置的延迟控制照样能玩出不错的效果。下文会以最常见的 F103 为例讲透“规则同步 时滞”这种实现方式再补充 F4 原生三重交错模式的区别争取让你看完就能在自己的板子上动手试。1. 为什么需要三重ADC交错采样1.1 单颗ADC的采样周期拆开看ADC 完成一次转换并不是瞬间完成的。拿 STM32F103 来说12 位分辨率下一次完整的采样转换时间大致等于采样时间加转换时间。采样时间是给内部采样电容充电的时间转换时间固定是 12.5 个 ADCCLK 周期采样时间则可以配置成 1.5、7.5、13.5、28.5、41.5、55.5、71.5 或 239.5 个周期。举个实际数字如果 ADCCLK 配置为 12MHz采样时间选 7.5 周期那么一次转换就是T_total (12.5 7.5) / 12MHz 1.667us也就是这颗 ADC 每秒最多能完成约 60 万次转换。单通道采集这个速度对很多应用已经够了但你一旦想同时采三相电流、又想去监控母线电压或者想把一个几十 kHz 的正弦波还原得像样一点60 万次的瓶颈立刻卡脖子。另一种更常见的受害者是电机控制和数字电源。PWM 频率一高你需要在固定的开关周期里采电流、采电压、算环路留给 ADC 的时间窗口非常窄。单颗 ADC 往往在忙完第一个通道后第二个通道排队等着最终整个控制环路的带宽被 ADC 拖死。1.2 “三个ADC轮流采样”到底解决了什么交错采样的基本思想特别朴素一颗 ADC 在转换的时候干不了别的那就放三颗 ADC让它们错开时间同时开工。这样在同一个时间窗口里你能采到 3 个不同时刻的样本等效采样率自然就上去了。以规则同步加时滞为例一次触发后ADC1、ADC2、ADC3 分别在不同的 ADCCLK 延迟点开始转换。外部看起来就像有一个“超级 ADC”在高速采样只是这个“超级 ADC”把一次结果拆成了 3 个数据寄存器输出。这个方案和“扫描模式多通道”有本质区别。扫描模式是一颗 ADC 排队转换多个通道结果虽然也连续来但每个通道的采样点之间隔着完整转换时间它并不能提高对单一信号的采样率三重交错则是 3 颗 ADC 同时对同一个信号做时间切片真正把吞吐量堆上去了。1.3 什么场景该用、什么场景不该用先说清楚适用边界避免有人拿它到处乱套。适合用的场景首先是需要连续监测单一信号、且这个信号频率明显高于单 ADC 能力上限的情况。比如音频级信号采集、振动分析、电源环路动态响应记录、电机相电流波形重建这些场景对“同一时刻附近的多个连续点”有硬需求。但如果你只是采集几个缓慢变化的传感器电压、温度、电池电压那完全没必要用三重交错。因为三重模式会占用 3 个 ADC 的全部资源你本来可以同时采 3 路不同的信号现在变成了 3 颗 ADC 采同一路这属于资源置换。还有一些场景在三重交错下反而会更难受比如需要同时锁存三相电流的“同一时刻值”。三相电机控制里你希望三相电流是同一时刻采样的这时候应该用规则的“规则同步、无延迟”模式让三个 ADC同时触发而不是搞交错。交错是牺牲同时性换采样密度必须想清楚业务需求再选模式。2. 三重ADC模式原理与硬件门槛2.1 三种容易混淆的多ADC模式STM32 的 ADC 多模式并不是只有一种“三重模式”打开参考手册你会发现一堆名字规则同步、注入同步、规则交替、注入交替、备用模式、交错模式。很多新手一上来就被这些名字绕晕我建议只抓住三个最关键的第一种是规则同步模式。三个 ADC 共享同一组触发源同时对各自的规则通道进行转换。这种模式的特点是“同一时刻快照”适合三相电流这种需要同步采样的场景。缺点是对单一信号的采样率并没有提升因为三个 ADC 是同时采的不是轮流采。第二种是规则同步加时滞模式。这就是本文要重点讲的三重交错采样基础。三个 ADC 仍然共享触发源但 ADC2 和 ADC3 的启动时刻会分别延迟若干个 ADCCLK 周期。这样一次触发你能得到 3 个不同相位的样本把三个样本按时间顺序排好就形成了对同一信号的密集采样。第三种是规则交替模式。这个模式下三个 ADC 是真正轮流做转换一个转换完立刻接下一个一般用于把三个 ADC 拼成一个大吞吐量的采集系统。F4 系列的原生三重交错模式就是典型代表。F1 上也有类似概念但配置方式和数据读取方式不完全一样。2.2 时滞规则同步是怎么工作的用一句话概括三个 ADC 都听同一个触发信号但 ADC1 先开工ADC2 等几个 ADC 时钟周期再开工ADC3 再等同样的时间开工。这个等待时间由 ADC_CR1 寄存器里的 DELAY 字段控制单位是 ADCCLK 周期可配置范围一般是 0 到 15。假设你设置了 DELAY 2ADCCLK 为 12MHz那么ADC1 在 t0 开始采样 ADC2 在 t0 2/12MHz t0 167ns 开始采样 ADC3 在 t0 4/12MHz t0 333ns 开始采样结果是外部触发一次你就拿到了 3 个相隔 167ns 的采样点。如果你的触发信号来自 PWM 定时器且周期设置为略大于 3 颗 ADC 完成转换所需的总时间那这个采样系统就等价于一个以 3 倍频率采样的超级 ADC。重点在于 DELAY 并不是越大越好DELAY 太大会让总时间窗口拉长降低等效采样率DELAY 太小则三个样本之间的时间间距太近如果信号变化不快信息冗余就很高。实际工程中我会根据信号频率和 ADC 时钟综合取舍稍后给出具体计算过程。2.3 芯片选型与硬件连接注意不是所有 STM32 都有 3 颗 ADC。F103 系列里只有高密度和超高密度型号比如 C8T6 这种 64KB Flash 的荔枝派网红芯片只有 2 颗 ADC你硬着头皮配 ADC3 是配不出来的。必须选 ZET6、RCT6、VET6 这类带有“C/D/E”后缀或 256KB 以上 Flash 的型号才能启用 ADC3。选型确认后还要确认引脚映射。STM32F103 的 ADC1 和 ADC2 有 16 个外部通道ADC3 同样有 16 个外部通道但 ADC3 内部没有温度传感器和 VREFINT 通道所以如果你想把内部参考电压也一起采就不能用三重模式完整覆盖。硬件连接上有个非常隐蔽的坑既然要三个 ADC 采同一个信号最自然的做法就是把信号直接接到三个 ADC 共用的 GPIO 引脚上。表面看没错但你要意识到这颗引脚内部其实连接到了三个 ADC 各自的采样电容。如果信号源输出阻抗很高比如用电位器分压或者直接从运放输出端拉一根长线过来三个采样电容在同时采样时会互相“抢电流”导致采样值不稳定、偏小、甚至有波动。我常用的做法是在信号源和引脚之间加一级运放缓冲再用一个低阻值电阻并联一个电容做 RC 滤波保证进入三个 ADC 的源阻抗足够低。这一点在后面排查采样值抖动时会再次提到。下表是几个常见工程的硬件权衡参考因素单ADC三重交错对单一信号的采样率1倍最高接近3倍对多通道同时采样能力支持扫描通常会牺牲引脚要求1个模拟引脚1个模拟引脚共用DMA占用1个通道3个通道或1个32位通道调试复杂度低中高3. 实操F103三重规则同步时滞的完整配置3.1 CubeMX 工程配置要点我习惯先用 CubeMX 搭好基础工程再在生成的代码上补寄存器配置因为三重模式里 CubeMX 的图形配置并不能把 DUALMOD 和 DELAY 全部暴露出来最终还是免不了动寄存器。CubeMX 里需要做这么几件事ADC1、ADC2、ADC3 都打开选择同一个通道比如 IN0对应 PA0 引脚。三者都配置成单端输入、单次转换模式采样时间我推荐从 7.5 周期开始试如果信号源阻抗很低、前端驱动能力足够再切到 1.5 周期去追求速度。为每个 ADC 单独配置 DMA。在 DMA Settings 里给 ADC1、ADC2、ADC3 分别添加一个 DMA 请求模式选 Circular数据宽度都选 Half Word内存地址递增打开。三个 DMA 通道分别对应不同的缓冲区方便后续独立读取和重组。定时器配置。选择 TIM1 作为触发源将 TRGO 设置为 Update Event并配置一个合适的周期。触发频率取决于你要的等效采样率可以先从 200kHz 开始验证之后按公式调整。生成代码后你需要在初始化函数最后面补上三重模式的寄存器配置因为 CubeMX 生成的多 ADC 代码默认还是独立模式。3.2 寄存器代码ADC初始化、DELAY、DMA下面这段代码是在 F103 上配置三重规则同步加时滞的核心逻辑我尽量把注释写到位。#define ADC_BUF_LEN 1024 static volatile uint16_t adc1_buf[ADC_BUF_LEN]; static volatile uint16_t adc2_buf[ADC_BUF_LEN]; static volatile uint16_t adc3_buf[ADC_BUF_LEN]; void MX_ADC_TripleInterleave_Init(void) { // 三个 ADC 的时钟在 CubeMX 里已经打开这里只补关键配置 // 1. 配置 PA0 为模拟输入 GPIOA-CRL ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0); GPIOA-CRL | GPIO_CRL_CNF0_0; // 2. 统一规则组每个 ADC 只转换一个通道IN0 ADC1-SQR1 0; ADC1-SQR2 0; ADC1-SQR3 0; // SQ1 对应通道 0 ADC2-SQR1 0; ADC2-SQR2 0; ADC2-SQR3 0; ADC3-SQR1 0; ADC3-SQR2 0; ADC3-SQR3 0; // 3. 采样时间选 7.5 ADCCLK 周期SMPx 位的 001 对应 7.5 ADC1-SMPR2 | ADC_SMPR2_SMP0_0; ADC2-SMPR2 | ADC_SMPR2_SMP0_0; ADC3-SMPR2 | ADC_SMPR2_SMP0_0; // 4. 配置 ADC1 作为主 ADC外部触发来自 TIM1_TRGO // EXTSEL 0b100 表示 TIM1_TRGO ADC1-CR2 | ADC_CR2_EXTTRIG | (0b100 17); // 5. 设置三重模式规则同步 DELAY 时滞 // DUALMOD 0b001 表示规则同步模式 // DELAY 2 表示 ADC2 比 ADC1 晚 2 个 ADCCLKADC3 再晚 2 个 ADC1-CR1 ~(ADC_CR1_DUALMOD_Msk | ADC_CR1_DELAY_Msk); ADC1-CR1 | (0b001 ADC_CR1_DUALMOD_Pos); ADC1-CR1 | (2 ADC_CR1_DELAY_Pos); // 6. 使能 ADC 并执行校准 ADC1-CR2 | ADC_CR2_ADON; ADC2-CR2 | ADC_CR2_ADON; ADC3-CR2 | ADC_CR2_ADON; // 下面这段校准代码要连续三个 ADC 各做一次 // 校准前先等待稳定然后 RSTCAL - CAL - 等待完成 ADC1-CR2 | ADC_CR2_RSTCAL; while (ADC1-CR2 ADC_CR2_RSTCAL); ADC1-CR2 | ADC_CR2_CAL; while (ADC1-CR2 ADC_CR2_CAL); ADC2-CR2 | ADC_CR2_RSTCAL; while (ADC2-CR2 ADC_CR2_RSTCAL); ADC2-CR2 | ADC_CR2_CAL; while (ADC2-CR2 ADC_CR2_CAL); ADC3-CR2 | ADC_CR2_RSTCAL; while (ADC3-CR2 ADC_CR2_RSTCAL); ADC3-CR2 | ADC_CR2_CAL; while (ADC3-CR2 ADC_CR2_CAL); // 7. 使能三个 ADC 各自的 DMA 请求 ADC1-CR2 | ADC_CR2_DMA; ADC2-CR2 | ADC_CR2_DMA; ADC3-CR2 | ADC_CR2_DMA; // 8. 配置 DMA1 的 Channel 1/2/3 分别对应 ADC1/2/3 RCC-AHBENR | RCC_AHBENR_DMA1EN; DMA1_Channel1-CPAR (uint32_t)ADC1-DR; DMA1_Channel1-CMAR (uint32_t)adc1_buf; DMA1_Channel1-CNDTR ADC_BUF_LEN; DMA1_Channel1-CCR DMA_CCR_MINC | DMA_CCR_CIRC | DMA_CCR_PSIZE_0 | DMA_CCR_MSIZE_0 | DMA_CCR_TCIE | DMA_CCR_HTIE; DMA1_Channel1-CCR | DMA_CCR_EN; // Channel 2 和 Channel 3 按同样方式配置 // 注意把外设地址换成 ADC2-DR 和 ADC3-DR缓冲区也对应好 }这里有几个地方容易写错。第一F103 的 DMA 配置里DIR 位默认是 0表示从外设读数据到内存千万不要为了“看起来对”去置 DIR 位。第二三个 ADC 的采样时间寄存器 SMPR2 要各自独立配置不能只配置 ADC1。第三DELAY 是写在 ADC1 的 CR1 里的ADC2 和 ADC3 的 CR1 不需要重复配置多模式下主 ADC 统一管。3.3 数据重组与触发启停配置好之后三个 DMA 会持续把转换结果写入各自的缓冲区。因为配置的是 Circular 模式缓冲区满了之后会自动回卷所以你必须在 DMA 中断里及时处理数据否则新数据会覆盖旧数据。我在 F103 上推荐用半传输中断加传输完成中断把缓冲区切成前后两半来处理。这样每半段数据都有一片连续的 512 个样本可用。void DMA1_Channel1_IRQHandler(void) { if (DMA1-ISR DMA_ISR_HTIF1) { DMA1-IFCR | DMA_IFCR_CHTIF1; process_interleaved_data(0); // 处理前半段 } if (DMA1-ISR DMA_ISR_TCIF1) { DMA1-IFCR | DMA_IFCR_CTCIF1; process_interleaved_data(1); // 处理后半段 } }数据重组的原则是同一索引 i 的adc1_buf[i]、adc2_buf[i]、adc3_buf[i]对应的是同一次外部触发生成的三个样本。所以重组时按顺序把它们写入输出缓冲区static void process_interleaved_data(uint8_t half) { uint32_t start half ? ADC_BUF_LEN / 2 : 0; uint32_t end half ? ADC_BUF_LEN : ADC_BUF_LEN / 2; for (uint32_t i start; i end; i) { interleaved_out[out_index] adc1_buf[i]; interleaved_out[out_index] adc2_buf[i]; interleaved_out[out_index] adc3_buf[i]; } }interleaved_out就是你最终拿到的高速采样序列后面做 FFT、波形显示、闭环控制都用这组数据。启动时我建议先把定时器触发关闭等 ADC 和 DMA 全部就绪再打开定时器。不然系统刚上电、DMA 还没准备好时第一次外部触发可能已经让缓冲区里混入一个不完整的数据包。3.4 F407原生三重交错模式补充F407 这类芯片在三重交错上做得更直接。数据手册里的“三重交错模式”下三个 ADC 会轮流对同一个通道进行转换数据按固定格式打包进一个 32 位的 ADC_CDR 寄存器然后由一次 DMA 传输读走。DMA 外设地址直接指向ADC_CDR内存缓冲区用 32 位无符号整数数组一次 DMA 传输就拿到三个样本volatile uint32_t adc_triple_buf[ADC_BUF_LEN]; // 伪代码示意具体 MULTI 编码值以参考手册为准 ADC1-CR1 | (三重交错模式编码 ADC_CR1_MULTI_Pos); ADC1-CR2 | ADC_CR2_DMA; // DMA 外设地址指向 ADC_CDR内存缓冲区指向 adc_triple_buf读取时按位拆包一份 32 位数据里同时包含三个 ADC 的结果顺序和你在参考手册里看到的 CDR 位域定义一致uint16_t adc1_val adc_triple_buf[i] 0xFFF; uint16_t adc2_val (adc_triple_buf[i] 12) 0xFFF; uint16_t adc3_val (adc_triple_buf[i] 24) 0xFFF;F4 这种模式的优点是 DMA 只占一个通道中断处理也简单缺点是你能控制的只有芯片预设好的交错顺序灵活性比 F103 的独立三缓冲区方案略低。如果你用的芯片是 F4 系列前面 F103 的代码框架仍然可以借鉴但寄存器细节要对换成对应系列参考手册。4. 等效采样率、失真校正与数据分析4.1 等效采样率到底怎么算等效采样率不是简单地把三个 ADC 的速率相加你需要把触发周期、DELAY、转换时间都算进去。假设 ADCCLK 为 12MHz采样时间为 7.5 周期DELAY 为 2 个 ADCCLK单次转换时间 T_conv (12.5 7.5) / 12MHz 1.667us 两次触发之间的最小间隔 T_min T_conv 2 * DELAY / 12MHz 1.667us 2 * 2 / 12MHz 1.667us 333ns 2.0us在 T_min 内你会得到 3 个样本所以等效采样率F_eff 3 / T_min 1.5MSps对比单颗 ADC 的 600ksps这个配置把采样率提升了约 2.5 倍。如果你再把采样时间压到 1.5 周期则T_conv (12.5 1.5) / 12MHz 1.167us T_min 1.167us 333ns 1.5us F_eff 3 / 1.5us 2MMsps所以优化方向很明显想要更高的等效采样率一是缩短采样时间二是减小 DELAY三是提高 ADCCLK。但这三者都有代价缩短采样时间会让采样电容充电不够充分低阻抗源才能扛得住DELAY 太小会让三个采样点过于集中ADCCLK 超过芯片规格则可能采出完全错误的数据。4.2 增益/偏移失配校准三重交错采样最大的敌人不是噪声而是三颗 ADC 的不一致性。每颗 ADC 的 offset 和 gain 都有细微差别如果你直接把三个 ADC 的数据拼成连续波形就会在频谱上看到明显的杂散成分这是因为三组数据不是同一条直线而是三条互相平行的线在跳跃。解决办法也很传统标定。给 ADC 输入端接一个稳定的直流电压分别读取三颗 ADC 的平均值得到 offset再换一个接近满量程的电压读取三颗 ADC 的平均值算出 gain然后做一次一阶校正。typedef struct { float offset; float gain; } adc_cal_t; // 标定后再用的校正函数 float adc_correct(uint16_t raw, adc_cal_t *cal, float ref_offset) { return (raw - cal-offset) * cal-gain ref_offset; }实际操作时我习惯把三颗 ADC 在 0V 和 1.65V 两个点的值都记录下来然后以 ADC1 为基准把 ADC2 和 ADC3 的结果都校正到 ADC1 的标尺上。这个过程不用每次上电都做只要供电电压和温度变化不太剧烈标定参数可以一直复用。4.3 让数据变成好看的波形数据拿到之后最简单的验证方式是串口上传配合 VOFA 或者匿名上位机画波形。先接一个频率很低的三角波或正弦波比如 1kHz 左右看三个 ADC 重组后的曲线是否连续。如果不连续、有台阶大概率是增益失配或排序顺序标错了。更严格一点的验证是做 FFT。把重组后的序列用串口传回上位机在 Python 或 MATLAB 里画频谱。如果输入是一个单频正弦波频谱上除了主峰以外在特定频率处出现明显杂散往往就是三个 ADC 之间的时间间隔不一致导致的相位失配。如果要做更高级的相位补偿可以测量三个 ADC 实际采样时刻的精确间隔然后用线性插值或 cubic 插值把样本重采样到均匀时间网格上。这个话题展开讲又是一篇文章实际工程中多数场景先用一阶校正就够了。5. 调试中遇到的坑与排查技巧5.1 DMA数据错位和启动丢点三重交错采样里最常见的现象是数据整体看起来“乱跳”波形像被切碎了一样。排查思路很简单先用固定电压输入比如直接接 GND观察三个缓冲区里是不是都应该是 0 附近的值。如果有一个缓冲区明显不是说明那个 ADC 的 DMA 通道配置错了。再输入一个稳定的 1V 电压看三个缓冲区是否都读到同一个换算值。启动丢点是另一种常见问题。系统上电瞬间ADC 内部状态还没有完全稳定DMA 可能已经抓到了一批垃圾数据。我的习惯是启用定时器触发之前先把三个 DMA 缓冲区全部清零并且在软件里忽略前 64 个样本。5.2 ADC3没有内部通道别踩如果你试图让 ADC3 去采样温度传感器或者内部参考电压你会发现结果永远不对。F103 的 ADC3 只支持外部通道 0 到 15内部温度传感器和 VREFINT 是 ADC1 的专属通道。三重交错模式下三个 ADC 必须共享同一个外部模拟输入所以你要找的信号必须是真实存在的物理引脚。还有一种情况是 ADC3 的某些外部通道在特定封装上没有引出来这时候即使代码配置没问题硬件上也接不到信号。我踩过这个坑之后学乖了选型时先把最新数据手册里的 ADC 通道表翻出来确认三个 ADC 都覆盖了目标引脚。5.3 采样值抖动、转换结果漂移采样值抖动先怀疑硬件再怀疑软件。硬件上最常见的是信号源阻抗太高三个 ADC 内部的采样电容争抢电荷导致每次充电的最终电压都差一点。解决方法是在信号源和引脚之间加一个 RC 滤波时间常数要远小于你的采样周期同时用运放缓冲降低阻抗。软件上的抖动通常来自 ADC 时钟不稳定或者参考电压波动。F103 的 ADC 时钟最高不要超过规格书的上限超频使用不仅数值不稳长期还会损伤模块。参考电压 VREF 的引脚应该用低阻抗的基准源供电而不是直接从 LDO 的输出拿。漂移问题则多半和校准有关。每次上电我都坚持对三个 ADC 都执行一遍完整的校准流程不要只校准 ADC1 就以为 ADC2 和 ADC3 也自动校准了。这个细节非常容易忽略但影响却很明显。5.4 收个尾一个实用的查错流程最后分享一个我实际调项目时一直在用的查错顺序。先准备一个稳定的直流电压源和一个可以看波形的上位机然后按下面这个顺序排查第一步不接输入信号直接把引脚接地观察三个 ADC 的原始值是否都在个位数附近。如果不是查引脚配置、ADC 通道号、DMA 通道号。第二步接一个 1/2 VREF 的直流电压比如 1.65V。观察三个 ADC 的原始值是否一致。如果偏差超过 1%考虑做增益和偏移校准。第三步接一个已知频率的正弦波比如 10kHz观察重组序列的波形是否连续。如果波形有台阶再检查你的数据重组顺序是否和采样时序一致。第四步最后才去优化参数比如缩短采样时间、调整 DELAY追求更高采样率。千万不要一上来就把参数拉满否则信号源阻抗的影响会被放到最大你会得到一堆看起来像噪声、其实是前端采样电路缺陷的数据。三重 ADC 交错采样这个功能初看像是参考手册里一个不起眼的小章节真正调通之后对项目的采样能力提升是非常明显的。无论你是在做数字电源、电机控制还是单纯的音频采集实验只要按上面的思路把硬件电源处理好、触发时序理清楚、 DMA 数据重组做对再用一阶校准把三颗 ADC 的差异抹平这套方案是可以稳定跑起来的。
返回列表