1. 为什么普通MCU的ADC不够用以及多路复用方案的选型逻辑嵌入式项目里引脚不够用是比内存不够用更折磨人的问题尤其是做模拟量采集的时候。一次做设备改造需要采集16路0-5V的电压信号MCU选的是STM32F103系列片内ADC一共只有十几个通道扣除已经分配给其它功能的引脚真正能用的ADC输入通道只剩五六路缺口很大。换一个大封装或者更高端的型号当然可以但意味着重新画板、重新调BSP、交期可能直接泡汤。这种情况下最务实的方案就是模拟多路复用。思路和电话交换机一样一批输入信号通过开关矩阵分时复用到同一个测量端口ADC本身不用变只加一颗多路选择芯片就能把采集通道数乘好几倍。这个方法在工业采集板、电池管理系统、环境监测终端里都很常见属于用时间换引脚的标准玩法。选择CD74HC4067而不是其它方案主要考虑三点。第一点是通道数量。这颗芯片是单端16通道对应4根地址线S0-S3用二进制编码选择通道正好匹配16路采集的需求。如果只差几路可以用CD40518通道或者CD40664路独立开关但16路这种规模直接用4067最省事。第二点是模拟开关的导通电阻。4067的导通电阻典型值在70Ω左右相比老旧的CD4067版本已经优化了不少在小信号采集场景下配合高输入阻抗的ADC前端电压跌落可以控制在可接受范围。这个后面会重点算一笔账。第三点是兼容性和采购难度。CD74HC4067是TI的经典型号HC系列是CMOS电平3.3V供电下可以直接由STM32的GPIO驱动不需要电平转换。市面上还有HEF4067、M74HC4067等兼容型号管脚定义完全一致货源充足价格大概一两块钱替换成本极低。当然任何方案都有取舍。模拟开关最大的局限是不能同时采集所有通道必须轮询切换这就引入了时间片的概念。每次切换通道后不能立即读取ADC值必须等模拟开关稳定、信号线上残余电荷泄放完毕才能采样否则读到的可能是上一个通道的残留值。这个特性正是后面两个调试坑的根源先记在这里。我最后选定的器件组合是CD74HC4067负责通道扩展STM32F103的ADC1工作在扫描连续模式配合DMA搬运结果控制引脚用4个普通GPIO去拨动S0-S3地址线。整个方案的核心诉求是软件上对16路采集要做到透明化上层逻辑拿到的是一份连续的数组不需要关心底层哪一路正在被采样。这也是后面驱动代码的设计原则——封装细节提供干净的接口。2. 硬件电路搭建16路模拟输入的连接细节以及参考电压和信号调理的关键参数2.1 引脚分配与基本接线CD74HC4067的引脚可以分成三组电源和地VCC、GND、地址选择S0-S3、信号通路COM公共端、IO0-IO15十六路输入输出这两组在物理上是对称的可以互换使用。具体接线我有几个建议COM端接MCU的ADC输入引脚。这是公共通道所有输入信号最终都会从这里拐进ADC。选STM32上支持ADC功能的引脚比如PA0或PA1。S0-S3接四个GPIO。建议选同一个端口的连续引脚比如PB12-PB15方便软件上用一段位操作直接拼地址值。如果GPIO不够用74HC595这种串转并芯片也可以但会引入额外的SPI时序不推荐在采集场景里自找麻烦。VCC接3.3VGND接系统地。注意4067的VCC范围很宽2V-6V但控制逻辑电平和VCC挂钩如果VCC接5V那S0-S3也得按5V电平驱动STM32虽然大多支持5V容忍但为了简单起见统一用3.3V供电。连接完成后的硬件拓扑大致是这样模拟信号源 x16 → CD74HC4067 的 IO0-IO15 | COM 端口 | MCU ADC 引脚 | STM32F103 ADC1这是典型的多对一结构。16路模拟信号从左边进入多路开关右边只有一个出口地址线决定哪一路和出口导通。2.2 参考电压和信号量程的匹配问题4067本身不改变信号幅度它是直通的但前提是你的信号电压不能超过芯片的供电范围。在3.3V供电下4067的模拟信号输入范围大约是0V到VCC也就是0-3.3V。如果你的传感器输出的是0-5V标准信号比如一些工业变送器直接把信号怼到IO口上轻则削顶失真重则通过内部的静电保护二极管往VCC灌电流时间长了可能烧芯片。所以硬件上一定要在信号进4067之前做好调理。我常用的方案有两种电阻分压。适合信号源内阻较小的场景比如运放输出或者参考电压源。两颗电阻把5V压到3.3V以内比例大约是3:2。分压的同时还要考虑信号源的驱动能力如果内阻太大分压后信号会变形。运放衰减。适合信号源内阻较大的场景比如一些pH电极、热电偶。用运放搭一个同相比例放大器把增益调到0.66既完成了衰减又做了阻抗隔离不影响信号源。这次项目里的信号是板内自产的电压量输出阻抗非常低而且信号范围最大只有2.5V左右所以没有加额外的调理电路直接进4067。如果ADC的参考电压用内部的3.3V那ADC满量程就是0-4095对应0-3.3V2.5V信号读回来大概是3100左右的原始值余量充足。但如果对精度有较高要求建议用外部基准电压源比如REF3030把参考电压做稳定避免VDDA上的纹波直接折算成采样误差。4067本身不影响参考电压但电源噪声会通过COM端耦合进ADC所以4067的VCC附近一定要放一个100nF去耦电容靠近电源脚摆放。2.3 信号地和模拟地纯数字电路可以不区分地和模拟地但混合信号采集必须注意地平面设计。4067的GND、信号源的参考地、ADC的模拟地应该尽量保持在同一电位面上避免形成地环路。如果板上信号源离得远建议用单点接地的方式把所有模拟部分的地汇到一个点上再和数字地单点连接。当时画板时把4067放在了模拟区域的中心位置尽量避免数字信号线平行穿过模拟信号下方。对于低速采样场景布线的要求没有射频那么苛刻但基本的不要用地线环路串扰模拟信号这个底线还是得守住。3. 软件驱动实现GPIO地址切换、ADC配置与16通道轮流采集的完整逻辑3.1 GPIO驱动层用一行代码切换通道地址选择是40467的核心控制逻辑。S0-S3四根线组成一个4位二进制数对应0-15号通道比如S3S2S1S0选中通道0000IO00001IO10111IO71000IO81111IO15我在驱动代码里把S0-S3定义为一个端口连续的GPIO组然后直接用寄存器操作一次写完地址值。HAL库的GPIO_WritePin逐位操作虽然可读性好但切换时间是按微秒计的对于快速轮询来说不太合适。用寄存器写法可以做到纳秒级切换而且代码也不复杂。// STM32F103 寄存器方式切换通道 // PB12-S0, PB13-S1, PB14-S2, PB15-S3 #define MUX_S0_PIN GPIO_PIN_12 #define MUX_S1_PIN GPIO_PIN_13 #define MUX_S2_PIN GPIO_PIN_14 #define MUX_S3_PIN GPIO_PIN_15 void mux_select_channel(uint8_t ch) { if (ch 15) ch 15; // 清掉现有地址位 GPIOB-BRR MUX_S0_PIN | MUX_S1_PIN | MUX_S2_PIN | MUX_S3_PIN; // 写入新地址 if (ch 0x01) GPIOB-BSRR MUX_S0_PIN; if (ch 0x02) GPIOB-BSRR MUX_S1_PIN; if (ch 0x04) GPIOB-BSRR MUX_S2_PIN; if (ch 0x08) GPIOB-BSRR MUX_S3_PIN; }GPIO模式要配置成推挽输出速度建议设为50MHz。这里有人会踩坑觉得这是控制逻辑线速度低一点无所谓实测发现GPIO速度配置过低会导致地址切换瞬间的上升沿变缓让4067的通道建立时间变长间接拉低整个系统的轮询频率。关于这一点后面的调试坑部分会详细复盘。3.2 ADC初始化单通道多次采样配合DMA取平均值ADC部分我复用了一个通道比如ADC1_IN0对应PA0然后在软件上轮询。这样配置最简单而且可以避免多通道扫描DMA模式下的缓冲区错位问题。既然硬件上每次只选通一路ADC自己不需要知道外面到底接了几路信号它只需要老老实实地把当前引脚上的电压采回来。ADC参数建议这样设置ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; static void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode DISABLE; // 单通道模式 hadc1.Init.ContinuousConvMode DISABLE; // 不用连续转换靠软件触发 hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); }关键点在于每次切换4067通道后要发起一次ADC转换并且把结果读回来。为了降低噪声我做了每通道连采8次取平均的策略这个策略在后来的调试坑2中救了命。DMA在这种软件轮询模式下不是必须的。如果用的是单通道采样、软件触发直接调HAL_ADC_PollForConversion同步等待也可以一次转换大约几十微秒16路全扫一遍在1ms左右足以覆盖大多数慢速传感器。如果采样率要求更高可以考虑DMA定时器触发的方式但那是另一个复杂度层级前期不建议一上来就直接上DMA。3.3 16路轮询时序分配与通道切换轮询的核心逻辑是切换通道 → 延迟等待模拟开关稳定 → 连续采样N次取平均 → 保存结果 → 切下一路。伪代码如下uint16_t adc_results[16]; void adc_read_all_channels(void) { for (uint8_t ch 0; ch 16; ch) { mux_select_channel(ch); delay_us(100); // 等待4067完全导通以及电荷泄放 uint32_t sum 0; for (uint8_t i 0; i 8; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); sum HAL_ADC_GetValue(hadc1); } adc_results[ch] sum / 8; } }关于延迟时间100μs多说两句。4067数据手册上的导通时间典型值是几十纳秒但那是纯开关时间不包含外部电路的建立时间。实际系统中信号源可能带电容负载、走线可能有寄生电容ADC采样保持电容也需要时间充电所以不能照搬手册的开关时间要给足裕量。100μs这个数值是在项目里实测过的留了比较大的余量。从示波器波形上看切换通道时COM引脚的电压在几微秒内就能完成跳变但此时读ADC值仍然有概率读错。换到100μs之后读出来的数据纹波明显下降说明这个延迟是值得的。3.4 接口封装屏蔽底层细节为了上层代码干净我做了两层封装。第一层是单路读取接口第二层是全通道扫描接口。float adc_read_channel_voltage(uint8_t ch) { mux_select_channel(ch); delay_us(100); uint32_t sum 0; for (int i 0; i 8; i) { sum adc_single_read_raw(); } return (sum / 8) * (3.3f / 4095.0f); }返回的电压值是浮点如果考虑性能可以改成定点数或者直接返回原始ADC码让上层自己换算。项目里我倾向于返回原始码因为每次换算浮点乘法在16路全扫时会有额外的CPU开销虽然不大但能省则省。上层需要电压值的时候再统一乘一个比例系数心算、维护都方便。4. 调试坑一通道切换后读到上一个通道的残留值排查思路与根因分析4.1 原始现象数据错位而且看起来像有规律的乱跳第一版代码跑起来printf打印16路ADC原始值发现一个非常诡异的规律相邻通道的数据有交叉污染。具体表现是通道1输入的是500mV通道2输入的是3.0V打印结果里通道1显示的是2.95V左右通道2正常显示3.0V。把通道1的电压改成1.2V通道2变成1.25V左右。也就是说前一通道的电压泄漏到了后一通道上。一开始我还怀疑是ADC配置问题比如扫描模式下的序列顺序没设对。但单通道模式下没有序列顺序概念每次都是同一个ADC_IN0通道所以问题必然出在4067这一侧。4.2 排查过程从软件时序到硬件寄生电容的逐步倒推排查分了三步走。第一步先看软件时序是否正确用逻辑分析仪抓S0-S3的波形确认通道切换指令确实在采样前执行了。波形显示GPIO翻转正常地址线切换瞬间是正确的没有错位。第二步怀疑是GPIO驱动能力不足导致4067通道开关没有完全导通。4067是个CMOS模拟开关地址线输入是高阻的理论上需要的驱动电流极小不至于推不动。但保险起见在S0-S3上各加了一个10k上拉电阻到VCC想看看是不是电平不够导致的开关不完全。结果问题依然存在。第三步开始怀疑寄生电容。4067的COM端和未选通的IO通道之间会存在一个很小的沟道电容大概是几pF量级信号源、走线、滤波电容加起来可能还有几十pF的容性负载。当通道从CH1切到CH2时这些电容上残留的CH1电荷如果来不及泄放就会被ADC读走表现为CH2读到了CH1的电平只是幅度被分压电阻衰减了一部分所以呈现的是百分比混合的效果。为了验证这个判断我把通道切换后的延迟从100μs改成了1ms再跑一遍数据发现串扰幅度明显下降从偏差1V左右降到了200mV以内。继续加大延迟到5ms偏差进一步降低到50mV以下说明了问题确实和电荷泄放时间强相关。4.3 根因确认模拟开关的先断后通特性决定了残留必然存在查4067内部原理它用的是传输门结构沟道导通和截止之间存在一个微秒级的过渡期。更关键的是当一路断开、另一路接通的瞬间COM节点上的电压不会瞬间跳到新输入的值而是先保持一段时间旧值然后以RC时间常数向新值过渡。这个RC时间常数由两部分决定COM节点上的总等效电容芯片输出电容 走线寄生电容 ADC输入采样电容和等效通路电阻信号源内阻 4067导通电阻。我用的信号源内阻是10k、4067导通电阻约70Ω并联后的等效电阻基本就是10k总电容粗算50pF那时间常数τ 10k × 50pF 500ns。理论上5倍τ也就是2.5μs后就应该稳定了为什么100μs延迟还出现问题答案是我低估了外部滤波电容的贡献。硬件上为了滤除高频干扰在每条模拟输入线上都加了一个100nF的滤波器电容到地。这个电容在通道导通时被充电到当前信号电压切换通道后它要放完电新通道的信号才能把它重新充到新电平。充放电的等效回路是信号源内阻4067导通电阻10kΩ的源内阻串联100nF时间常数直接变成1ms级别。这就完美解释了为什么把延迟加到1ms甚至5ms后才有效——它不是在等4067本身建立而是在等外部滤波电容完成充放电。4.4 解决方案硬件加缓冲、软件调时序双管齐下治本的方法是降低信号源内阻。最简单有效的办法是在每路模拟输入上加一个电压跟随器运放把信号源的高内阻变成低内阻通常小于10Ω。这样即使滤波电容还有100nF充放电时间常数也从10k×100nF1ms降到10Ω×100nF1μs延迟100μs就绰绰有余了。治标的方法是调整代码时序一是把通道切换后的延迟加大到1ms以上二是采样前多读几次丢弃前两次结果让电荷平衡后再取有效值。我先把这两个软件改动加上数据串扰问题立刻消失16路数据全部恢复正常。后续版本再考虑加运放缓冲。这个坑其实特别典型因为很多人只盯着MCU这边的时间参数忽略了模拟开关之外的RC网络才是真正的时间瓶颈。建议所有做多路复用采集的朋友在画板阶段就考虑信号源内阻的问题如果信号源本来就很弱直接在4067前面加一级运放缓冲省得后面调参调得焦头烂额。5. 调试坑二GPIO速度配置导致4067通道切换异常一个隐蔽的硬件驱动陷阱5.1 现象16路里总有那么一路读数不对而且是随机出现的第二个坑更像玄学。代码跑了一会儿16路数据的绝大部分时刻是正常的但每隔几次扫描就会出现某一通道的读数突然跳到另一个值去持续时间只有几十毫秒然后又自己恢复。用万用表去测板子上对应通道的电压信号源本来就是这个值说明信号源没问题换能换的手动切换通道固定地址线ADC数据始终稳定。只有程序自动轮询的时候才会偶发异常。5.2 定位过程Matplotlib曲线对比逻辑分析仪抓时间片我用串口把每次扫描的16路数据全部打出来用曲线画到上位机上发现异常数据出现的位置没有规律今天通道3出问题明天可能通道11出问题而且错的幅度不等有时候是看起来完全像是上一通道的值。这时候怀疑通道切换的瞬间时序出问题了。用逻辑分析仪抓S0-S3四根线波形显示绝大多数情况下地址线切换是整齐的但在异常发生的那个时刻某根地址线上出现了一个毛刺——一个很窄的低电平脉冲宽度大约100ns。因为4067是电平触发型译码这个100ns的毛刺足以让内部译码器短暂跳到别的通道ADC在毛刺期间采样自然就把另一个通道的电压读进来了。这个毛刺哪来的按理说GPIO输出翻转不会有毛刺除非两个GPIO翻转的瞬间差太大。比如从通道00000切到通道70111S1、S2、S3需要同时从低变高如果三个引脚的寄存器写入速度不一致或者GPIO本身的压摆率不够就会出现中间状态让4067暂时译码到一个中间通道虽然时间极短但恰好和ADC采样重叠就会出问题。进一步查看GPIO配置发现我把S0-S3的GPIO速度配成了2MHz低速模式。STM32的GPIO速度配置本质上是控制输出级的压摆率2MHz模式下的上升沿会比50MHz模式慢很多几根线的翻转时间不一致性被放大了中间状态持续时间更长。5.3 修复方案提升GPIO速度等级增加切换后的软件延迟保护修复非常简单把S0-S3的GPIO速度从2MHz改成50MHz。修改后重新跑逻辑分析仪毛刺消失再跑一整天压力测试异常数据没有再次出现。从这里得到的经验是GPIO速度配置不仅影响功耗和EMI还直接影响对外设的时序控制。低速GPIO确实能减少开关噪声但数字控制信号宁愿陡峭一点也不要含糊陡峭沿才能保证开关瞬间的确定性。另外一个辅助手段是在切换通道后加一个保护延时确保地址线稳定后再启动ADC采样。我用的延时本来是100μs看起来非常充裕但毛刺是纳秒级的再长的软件延时也救不了毛刺。所以这类问题只能靠硬件信号的干净度解决软件兜底是兜不住的。// 修复后的切换逻辑先翻转地址线再插入一段稳定等待 void adc_switch_channel_safe(uint8_t ch) { GPIOB-BRR MUX_S0_PIN | MUX_S1_PIN | MUX_S2_PIN | MUX_S3_PIN; if (ch 0x01) GPIOB-BSRR MUX_S0_PIN; if (ch 0x02) GPIOB-BSRR MUX_S1_PIN; if (ch 0x04) GPIOB-BSRR MUX_S2_PIN; if (ch 0x08) GPIOB-BSRR MUX_S3_PIN; delay_us(10); // 额外等待GPIO稳定再让给ADC逻辑 }5.4 复盘这个坑为什么难查难查的原因在于现象的随机性。数据大部分时间对偶发错误会让人倾向于怀疑接触不良、电源干扰很少会想到是GPIO速度配置这种看起来无关的参数。没逻辑分析仪的话这个坑可能要调很多天。所以做嵌入式调试逻辑分析仪或至少一个示波器是必需品碰到随机问题第一件事不是改代码而是抓时序波形。顺便补充一点STM32的GPIO速度配置对输出信号上升沿的影响在低速外设如I2C、UART上不明显因为这些协议本身容忍时间长但在高速数字控制线如多路复用器地址线上就会变成实打实的时序问题。建议涉及这类快速切换的数字控制信号时一律用最高速度档不会有副作用。6. 数据处理层ADC原始值的滤波方法和增益校准策略6.1 为什么单次ADC值不可信从毛刺到噪声的视角转换即使两个坑都填平了单次采集的原始ADC值依然会存在一定的随机波动幅度大约在±5个LSB左右。对于大多数项目这个波动可以忽略但如果信号本身比较小比如0.1V量级波动就会被放大成明显的百分比误差。项目里我采用的滤波策略是每通道连续采样8次取平均。这个方案不能滤除全部噪声但能把高斯白噪声的幅度降低到原来的1/sqrt(8)≈0.35倍效果肉眼可见。如果对噪声要求更高可以改用中值滤波采样3-5次取中位数对付尖峰脉冲干扰很有效更讲究的做法是滑动平均限幅滤波的组合先限制变化率再求平均工程上更稳健。// 滑动窗口平均窗口大小可调 #define FILTER_WINDOW 8 uint32_t filter_buf[FILTER_WINDOW]; uint8_t filter_idx 0; uint32_t filter_sum 0; uint16_t adc_filtered_read(void) { uint16_t raw adc_single_read_raw(); filter_sum - filter_buf[filter_idx]; filter_buf[filter_idx] raw; filter_sum filter_buf[filter_idx]; filter_idx (filter_idx 1) % FILTER_WINDOW; return (uint16_t)(filter_sum / FILTER_WINDOW); }6.2 零点和增益校准4067本身不影响信号的精度它就是个开关但整条信号链路里还有别的误差源ADC的失调误差、基准电压源的实际电压偏差、分压电阻的精度等。如果项目要求采集误差小于1%就必须做校准。常用的两点校准法输入0V记录ADC零偏值输入一个已知的满量程电压比如3.000V记录增益值然后软件里做线性修正float calibrated_voltage(uint16_t raw) { // offset_raw 和 gain_raw 通过已知输入标定得到 float volt (raw - offset_raw) * (3.0f / (gain_raw - offset_raw)); return volt; }这个思路不限于4067任何ADC采集链路都适用。校准参数可以烧录到Flash里也可以每次开机进入校准模式重新标定取决于项目对长期稳定性的要求。7. 实测数据与性能评估轮询周期、采集精度、资源占用的情况7.1 轮询周期实测配置参数ADC时钟12MHz、采样时间239.5周期、每通道8次采样、切换延迟100μs。实测下来完整扫一遍16路大约需要35ms折算单通道约2.2ms其中等待时间占了大头。对于温度、液位、压力这些缓慢变化的物理量35ms刷新一次完全够用甚至有富余。如果想提速有几个方向缩短切换延迟。如果硬件上做了运放缓冲信号源内阻低切换延迟可以缩到10μs甚至更低轮询周期能降到10ms以内。减少每通道平均次数。从8次减到4次噪声幅度上升约40%但扫描时间几乎减半。改用DMA定时器触发让ADC连续采样时自动切换通道CPU几乎不参与可以做到每通道约几十微秒的轮询间隔。7.2 精度对比用同一路信号分别做直接ADC采集和经过4067采集的对比测试结果显示经过4067后读数偏差在0.2%以内和直接采集的差异主要在4067的导通电阻引起的微小分压损失上。如果信号源内阻很低例如运放输出这个偏差会进一步缩小如果信号源内阻高就必须考虑导通电阻和源内阻的分压效应。信号源类型源内阻4067引入偏差估算运放输出10Ω0.05%传感器直出1kΩ~0.7%高内阻源10kΩ~7%需缓冲从表里能看出低内阻信号源是直接接4067的前提高内阻信号必须加一级运放缓冲否则精度完全没法接受。这是选型时最容易忽略的一点。7.3 资源占用评估软件方案占用的MCU资源非常少4个GPIO引脚1个ADC通道约600字节Flash驱动滤波代码几乎没有RAM开销缓冲数组很小CPU占用在轮询期间约5%左右72MHz主频。对于项目里还要同时跑显示、按键、通信的MCU这个占用率完全不是问题。对比之下用外部独立ADC芯片比如ADS1115、ADS1263的方案虽然精度更高、通道隔离更好但成本和代码复杂度都上升一个层级对于这种数量多、精度要求中等、速度要求不高的场景4067方案的性价比优势非常明显。8. 国产替代与选型延伸CD74HC4067之外的几点补充8.1 兼容型号与国内货源CD74HC4067最常用的是TI的CD74HC4067SM96SOIC-16封装。国内有不少替代型号比如华冠、圣邦微都有类似规格的模拟多路开关管脚兼容电气参数接近价格可能更低。但要注意看导通电阻、泄漏电流、逻辑电平和ESD等级是否满足要求尤其是通道间串扰这个参数劣质芯片可能在高速切换时串扰更严重。如果只是临时做实验用HEF4067BT或M74HC4067也可以管脚定义一样但CD74HC系列在3.3V下导通电阻控制得更好一些因此优先推荐前者。8.2 更大规模的通道扩展两级级联如果16路还不够用比如要64路、128路采样可以用多颗4067级联。最简单的做法是用一颗4067当中层选择器它的16个IO口分别接后续16颗4067的COM端总共256路。当然这要求每次采样先选中层再选中里层时序上多一级切换速度和代码复杂度都会增加。做大规模采集系统时这种树形两级多路复用是经典方案。8.3 差分输入场景改用CD74HC4052/40534067是单端开关只能对单端信号做选择。如果采集的是差分信号比如电桥输出、电流检测电阻需要同时切换正负两极这时候就该用CD74HC4052双4通道或者CD74HC4053三2通道它们内部有多个独立开关可以同步切换差分对的两条线保证信号完整性。这一点和4067完全不同选型时要格外注意。9. 在项目收尾阶段关于这套采集方案的综合评价与维护经验最后聊聊这套方案生命周期里积攒下来的一些体会。第一CD74HC4067扩展ADC采集的本质是带宽换通道。它的价值在低速、多路、中等精度的场景里能得到充分发挥。反过来如果要求16路信号每路都能做到100kSPS以上的采样率那模拟开关方案就不合适这时候老老实实上多ADC内核同步采样或者外部多通道同步采样芯片才对。第二硬件上加了滤波电容反而拖慢了信号建立这个反直觉的坑建议大家在做方案设计时就提前评估。不是不能加滤波电容而是加了滤波电容后必须同步考虑信号源内阻和开关切换时间否则你为了滤除干扰加的电容会变成数据串扰的元凶。第三GPIO速度配置这类参数属于平时不惹事、出事就是大事的类型。排查随机性BUG时别只盯着数据通路控制信号本身的质量同样关键。手边常备一个逻辑分析仪在碰到这类问题时能省下大量无谓的调参时间。第四从软件架构的角度多路复用采集的驱动层和上层应用一定要解耦。接口设计成adc_read_channel_voltage(ch)之后上层完全不用关心底层是单ADC还是多ADC是4067还是别的芯片。后期从16路扩展到32路只需要改驱动层业务代码一行不动。这套采集方案在我后续的几个项目里也一直在复用硬件结构基本不变软件换一下HAL库版本或者改一下引脚映射就能跑起来。对于中等速度、多路模拟量采集这一类型的需求它确实是一个值得长期保留的架构方案。