CD74HC4067多路复用扩展ADC采集:STM32 16路模拟量采集实战与调试
2026/9/4 10:50:25 网站建设 项目流程

1. 为什么普通MCU的ADC不够用,以及多路复用方案的选型逻辑

嵌入式项目里"引脚不够用"是比"内存不够用"更折磨人的问题,尤其是做模拟量采集的时候。一次做设备改造,需要采集16路0-5V的电压信号,MCU选的是STM32F103系列,片内ADC一共只有十几个通道,扣除已经分配给其它功能的引脚,真正能用的ADC输入通道只剩五六路,缺口很大。换一个大封装或者更高端的型号当然可以,但意味着重新画板、重新调BSP、交期可能直接泡汤。

这种情况下最务实的方案就是"模拟多路复用"。思路和电话交换机一样:一批输入信号通过开关矩阵,分时复用到同一个测量端口,ADC本身不用变,只加一颗多路选择芯片就能把采集通道数乘好几倍。这个方法在工业采集板、电池管理系统、环境监测终端里都很常见,属于"用时间换引脚"的标准玩法。

选择CD74HC4067而不是其它方案,主要考虑三点。

第一点是通道数量。这颗芯片是单端16通道,对应4根地址线(S0-S3),用二进制编码选择通道,正好匹配"16路采集"的需求。如果只差几路,可以用CD4051(8通道)或者CD4066(4路独立开关),但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.3V,GND接系统地。注意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.3V,2.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选中通道
0000IO0
0001IO1
0111IO7
1000IO8
1111IO15

我在驱动代码里把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×100nF=1ms降到10Ω×100nF=1μ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翻转的瞬间差太大。比如从通道0(0000)切到通道7(0111),S1、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的CD74HC4067SM96(SOIC-16封装)。国内有不少替代型号,比如华冠、圣邦微都有类似规格的模拟多路开关,管脚兼容,电气参数接近,价格可能更低。但要注意看导通电阻、泄漏电流、逻辑电平和ESD等级是否满足要求,尤其是"通道间串扰"这个参数,劣质芯片可能在高速切换时串扰更严重。

如果只是临时做实验,用HEF4067BT或M74HC4067也可以,管脚定义一样,但CD74HC系列在3.3V下导通电阻控制得更好一些,因此优先推荐前者。

8.2 更大规模的通道扩展:两级级联

如果16路还不够用,比如要64路、128路采样,可以用多颗4067级联。最简单的做法是用一颗4067当中层选择器,它的16个IO口分别接后续16颗4067的COM端,总共256路。当然这要求每次采样先选中层再选中里层,时序上多一级切换,速度和代码复杂度都会增加。做大规模采集系统时,这种"树形两级多路复用"是经典方案。

8.3 差分输入场景:改用CD74HC4052/4053

4067是单端开关,只能对单端信号做选择。如果采集的是差分信号(比如电桥输出、电流检测电阻),需要同时切换正负两极,这时候就该用CD74HC4052(双4通道)或者CD74HC4053(三2通道),它们内部有多个独立开关,可以同步切换差分对的两条线,保证信号完整性。这一点和4067完全不同,选型时要格外注意。

9. 在项目收尾阶段,关于这套采集方案的综合评价与维护经验

最后聊聊这套方案生命周期里积攒下来的一些体会。

第一,CD74HC4067扩展ADC采集的本质是"带宽换通道"。它的价值在低速、多路、中等精度的场景里能得到充分发挥。反过来,如果要求16路信号每路都能做到100kSPS以上的采样率,那模拟开关方案就不合适,这时候老老实实上多ADC内核同步采样或者外部多通道同步采样芯片才对。

第二,硬件上加了滤波电容反而拖慢了信号建立,这个反直觉的坑,建议大家在做方案设计时就提前评估。不是不能加滤波电容,而是加了滤波电容后必须同步考虑信号源内阻和开关切换时间,否则你为了滤除干扰加的电容,会变成数据串扰的元凶。

第三,GPIO速度配置这类参数,属于"平时不惹事、出事就是大事"的类型。排查随机性BUG时别只盯着数据通路,控制信号本身的质量同样关键。手边常备一个逻辑分析仪,在碰到这类问题时能省下大量无谓的调参时间。

第四,从软件架构的角度,多路复用采集的驱动层和上层应用一定要解耦。接口设计成adc_read_channel_voltage(ch)之后,上层完全不用关心底层是单ADC还是多ADC,是4067还是别的芯片。后期从16路扩展到32路,只需要改驱动层,业务代码一行不动。

这套采集方案在我后续的几个项目里也一直在复用,硬件结构基本不变,软件换一下HAL库版本或者改一下引脚映射就能跑起来。对于"中等速度、多路模拟量采集"这一类型的需求,它确实是一个值得长期保留的架构方案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询