CD74HC4067扩展ADC的多路采集笔记:通道串扰与上电复位调试
2026/9/8 22:04:04 网站建设 项目流程

做嵌入式最怕的不是功能实现不了,而是功能看起来实现了,数据却在悄悄骗你。前段时间做一块多路环境监测板,需要采集32路模拟信号,MCU内部ADC只有3个通道,最后选了CD74HC4067把单路ADC扩展成16路。板子跑起来之后,采集值时灵时不灵,通道之间还会“串味”,加上一个藏得更深的上电初始化问题,折腾了整整一个周末。这篇笔记把完整方案和两个最折磨人的调试坑一次性说清楚,给正在做多路模拟采集的朋友一个参考。

先说结论:CD74HC4067是模拟多路复用器,4根地址线控制16路模拟开关的通断,把一路ADC物理上扩展成16路。方案本身不复杂,但坑藏在信号建立时间和复位瞬间的引脚悬空里,这两件事数据手册不会替你操心。

1. 为什么我用CD74HC4067而不是换芯片重画板

1.1 通道不够用时的三条路

当时的情况是:板子已经画完,MCU选型已定,3路ADC通道要覆盖32路外部模拟量,通道数完全不够。摆在面前的无非三条路:

第一,换一颗ADC通道更多的MCU。这是最彻底的方案,但意味着重新选型、重画PCB、重写底层驱动,项目周期直接多出两到三周,对于已经定型的板子来说代价太大。

第二,外挂一颗多通道ADC芯片,比如ADS1115(4路16位)、ADS1256(8路24位)这类。精度确实高,还自带PGA可编程增益放大器,但需要I2C或SPI通信,驱动代码量大不少,而且多路高速采样时吞吐率受限,用在低速传感器采集场景有点杀鸡用牛刀。

第三,就是本文的方案:用CD74HC4067做模拟开关,把MCU自带的那一路ADC通过时分复用扩展到16路。每片不到两块钱,接线不超过10根,代码只需要控制4个GPIO电平,就能把一路ADC“变成”16路。

我的选择是第三种。原因很朴素:改动最小,逻辑最简单,用来采集NTC温度、电池电压、电位器位置这类更新率低于每秒几十次的信号,完全够用。

1.2 CD74HC4067在方案对比中的真实位置

为了说清楚这个芯片的定位,我做了一张选型对比表,覆盖当年纠结过的几个候选方案:

方案通道数通信方式精度附加成本适合场景
换高通道MCU视型号换料+改板项目早期,方案未定型
ADS1115等外置ADC4~16I2C/SPI高,带PGA通道少、要求高精度
CD74HC4067模拟开关16/片并行GPIO控制受限于后级ADC通道多、速度要求低
多片4067级联16×N并行GPIO+片选受限于后级ADC通道非常多、成本敏感

4067的优势一句话就能概括:它不是在“最高精度”方向上竞争,而是在“用最少资源扩展最多通道”这个诉求上做到极致。它本身不参与模数转换,只是把16路模拟信号按顺序“切”给后级的一路ADC,转换精度完全取决于MCU内部ADC。

1.3 一个关键边界:它不是万能的

同时必须说清楚4067的边界,否则后面调试起来容易误判。

首先,它是单端模拟开关,不是差分开关。每个被测信号都得有一个公共地参考,如果信号是差分输出或者浮地,必须先把信号转成单端再接进来。

其次,模拟输入电压范围不能超过芯片电源范围。CD74HC4067的VCC我用的是3.3V,那所有通道输入电压必须落在GND到3.3V之间。如果通道上接了12V电平的信号,内部ESD保护二极管的漏电路径会被导通,轻则读数严重偏移,重则芯片损坏。

再者,它有导通电阻(Ron),典型值在几十到一百多欧姆,这会在信号链路里引入分压误差和建立时间延迟。处理办法后面会详细讲,但选型时就要有这个概念:它适合连接输出阻抗偏低的信号源,不适合直接接高阻信号。

2. 这块芯片到底是怎么工作的

2.1 内部逻辑:4根线控制16个开关

CD74HC4067的内部结构可以拆成两部分:一个4-16线译码器,加上16个模拟开关(MOSFET传输门)。数字逻辑部分不参与模拟信号的路径选择,它只负责把S0~S3这4根地址线的二进制编码翻译成“接通第几号模拟开关”。

比如S0=0、S1=0、S2=0、S3=0时,COM端与C0通道导通;S0=1、S1=0、S2=0、S3=0时,COM端与C1通道导通。16个通道对应16种组合,用4个GPIO的二进制编码全部覆盖,这就是“4线控16路”的秘密。

S3S2S1S0导通通道
0000C0
0001C1
0010C2
...............
1111C15

另外还有一个EN引脚,低电平有效。EN接低电平,芯片正常工作;EN接高电平,所有开关全部关断,COM端呈高阻态。这个引脚看似多余,实际上是我第二个调试坑的救命稻草,后面细说。

有了这个逻辑结构,采集16路信号的核心算法就很简单:把通道号拆成4个二进制位输出到S0~S3,等信号稳定,启动一次ADC转换,读回结果,然后切下一路。

2.2 从数据手册里挤出来的关键参数

有一说一,CD74HC4067的数据手册并不厚,几页纸,但参数全在字缝里,得自己挤。以下是实际工作中最关键的几个:

导通电阻Ron,VCC=3.3V时实测在80~120Ω左右,手册给的是4.5V供电时典型70Ω。注意Ron会随供电电压下降而上升,3.3V系统里按100Ω估算比较稳妥。

开关切换时间一般只有几百纳秒,但这只是“开关动作完成”的时间,不等于“信号稳定”的时间。信号在开关闭合后还要经过源阻抗、导线寄生电容、后级滤波电容组成的RC网络慢慢爬升,这个“稳定时间”才是决定延时大小的关键,我第一个调试坑就栽在这里。

未选中通道的漏电流很小,常温下nA级别,但悬空的未选中通道引脚就像一根小天线,会通过寄生电容把噪声耦合到COM端。所以硬件上最好给每路输入一个到地的偏置电阻,比如100kΩ,让悬空通道有个确定的电位,防止噪声累积。

还有一点容易被忽略:CD74HC4067是多路复用开关,不是继电器,它不能隔离。开关断开时是“高阻态”而不是真正“断路”,高阻态和断路在绝大多数情况下效果一样,但耐压、漏电特性完全不同。如果你用它来切换高压信号,务必确认断开状态下的耐压等级足够。

2.3 关于导通电阻和悬空通道的认知误区

很多教程会告诉你4067有导通电阻,建议选低阻信号源,但没说为什么。我来解释一下:假设某路信号源是电池电压经过100kΩ和100kΩ分压得到的2.5V,信号源等效内阻就是两个电阻的并联值,50kΩ。这个50kΩ串联在信号路径上,和4067的100Ω导通电阻、后级ADC输入阻抗构成分压网络。如果后级ADC输入阻抗是1MΩ,那ADC实际采到的电压只有2.5V × 1MΩ / (50kΩ + 100Ω + 1MΩ) ≈ 2.38V,直接偏了5%。

更麻烦的不是分压误差,而是建立时间。高内阻信号源与后级寄生电容、滤波电容组成的一阶RC低通,时间常数大得惊人。如果你切换到这一路后立刻启动ADC,采到的根本不是一个稳定值,而是上一路电压向当前电压爬升过程中的一个中间值,这就是“串扰”的根源。

所以绕开认知误区的做法有两个:一是降低信号源内阻,重要通道在进4067之前加一个运放电压跟随器;二是软件上留足稳定延时,让RC网络把电压爬到足够接近最终值再采样。

3. 硬件接线和采集时序的实际做法

3.1 电路连接的最简骨架

先给出一份可以直接照抄的接线表,MCU端我以STM32F103为例,其他MCU同理:

CD74HC4067引脚接法
C0~C1516路模拟输入信号
COMMCU的ADC输入引脚,如PA1
S0~S3MCU任意4个GPIO,如PA0~PA3
ENMCU任意1个GPIO,如PA4,通过10kΩ上拉到3.3V
VCC3.3V,对地并联10μF+0.1μF去耦电容
GND系统地

几点实际操作经验:VCC和GND之间必须加去耦电容,电源纹波会直接影响模拟开关的导通特性;信号线尽量短,远离数字信号线,避免数字边沿通过寄生电容耦合到模拟通道;如果需要采集的信号数量不到16路,空闲通道用100kΩ电阻下拉到GND,不要让引脚悬空。

3.2 软件切换与ADC读取的时序设计

采集一路信号的软件流程可以拆成四个步骤:设置地址线电平、等待信号稳定、启动ADC转换、读取转换结果。写成流程串就是:

/* 伪代码:切换并读取一路 */ void MUX_SelectChannel(uint8_t ch) { /* ch范围0~15,拆成二进制位输出到地址线 */ HAL_GPIO_WritePin(GPIOA, MUX_S0_PIN, (ch & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MUX_S1_PIN, (ch & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MUX_S2_PIN, (ch & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOA, MUX_S3_PIN, (ch & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); /* 留足稳定时间,后面细说这个值怎么来的 */ delay_us(5000); } static uint16_t ADC_ReadOnce(void) { HAL_ADC_Start(&hadc); HAL_ADC_PollForConversion(&hadc, 10); return (uint16_t)HAL_ADC_GetValue(&hadc); }

地址线电平设置顺序没有硬性要求,但建议用位操作一次性赋值而不是逐根线修改,因为逐根线修改会在切换过程中产生中间状态。比如从通道3切换到通道5,S0变化和S1变化之间可能有几十纳秒的时间差,这时候芯片短暂选中了某个中间通道,虽然时间极短,但在高内阻场景下可能引入毛刺。

3.3 稳定延时的计算思路

稳定延时到底设多少?这个问题很多人直接抄教程写个10μs,实际上应该算出来。

我当时的温度采集通道,NTC热敏电阻和分压电阻的等效内阻约5kΩ,4067导通电阻按100Ω计,COM端对地有0.1μF滤波电容(如果输入端还加了RC滤波,这个电容会更大)。那么切换后信号的建立时间常数就是:

τ = R_total × C_load = (5kΩ + 100Ω) × 0.1μF ≈ 510μs

RC一阶系统要稳定到0.1%精度,理论上需要约6.9倍时间常数,也就是3.5ms。我当时软件只给了100μs,差了35倍,采到的信号还处在指数爬升的早期阶段,数值自然不对。

后面我把这个延时改成5ms,同时把采样结果连读三次取中值,问题立刻缓解了一大半。后来在硬件上给温度通道加了运放跟随器,把源内阻降到几十欧,整个时间常数缩小到几十微秒级别,延时改成200μs就绰绰有余了。

所以延时不是一个“拍脑袋”的参数,它取决于信号源等效内阻、通道负载电容和你对精度的要求。严谨的做法是拿示波器看波形,量出实际稳定时间,再留出一到两倍余量。

4. 调试坑一:通道串扰的完整排查链路

4.1 现象:13路里只有8路正常

板子上用了两片CD74HC4067扩展32路采集,第一片接的是电池电压、电流、环境温度等信号,工作正常;第二片接了8路NTC温度和几路开关量电平,结果这8路温度普遍偏高,且温度显示会随着电池电压的波动而上下跳动,最高时偏了3度。

一开始我还以为温度传感器本身有问题,换了传感器、重新标定、检查分压电阻,都没找到毛病。

4.2 排查过程:怀疑顺序逐级排除

第一个怀疑目标是ADC参考电压。我往4067的第13通道接了一个标准1.5V电压源,读回来的值是1.53V,偏高但不离谱,而且抖动明显。我又把同一个1.5V直接接到MCU的ADC引脚(绕过4067),读回来是稳定的1.50V,由此排除ADC和参考电压的问题。

第二个怀疑目标是芯片损坏。我把两片4067对调了一下位置,结果故障还是出现在原来的通道位置上,也就是说这是“位置性”问题,不是“芯片性”问题,芯片本身是好的。

第三个怀疑目标落到了信号源阻抗和建立时间上。我用示波器探到第13通道对应的COM引脚,在切换瞬间能看到一条毫秒级的指数充放电曲线,信号爬升的过程清晰可见。这时候才彻底明白,问题出在“开关切换了,但信号远没有稳定”。

4.3 根因拆解:70Ω导通电阻和寄生电容组成了一阶低通

把这条链路展开看就一目了然:NTC温度传感器的等效源内阻约5kΩ,加上4067的导通电阻约100Ω,再碰上输入端滤波电容和走线寄生电容构成的约0.1μF负载,整个就是一个截止频率仅几百赫兹的低通滤波器。切换到该通道后,信号不是瞬间跳变到目标电压,而是要按指数曲线慢慢爬升。我在爬升到三分之一的时候就启动了ADC采样,读到的自然是偏低且受前一路残留电压影响的错误值。

至于为什么第一片4067上的8路都正常,原因也很简单:那些通道的信号都经过运放缓冲,源内阻只有几十欧姆,同样100nF电容的时间常数只有几微秒,100μs的延时早就绰绰有余。同一个延时参数,低阻信号源和高阻信号源的表现天差地别。

4.4 解决与验证:硬件低阻化加上稳定延时

解决方案分两步,软件硬件同时改:

第一步是软件延时,切换通道后的稳定延时从100μs直接加到5ms,并且对同一通道连采三次,排序取中位数,剔除ADC转换中偶然出现的毛刺。这一步做完,温度读数偏移从3度降到不到0.5度。

第二步是硬件低阻化。在每路NTC温度信号进入4067之前,加一级运放电压跟随器,把几十kΩ的高阻信号转成低阻信号。我用的是LMV321这一类的轨到轨单运放,3.3V供电下输出摆幅接近满量程,一片四路运放就能覆盖四路温度信号。硬件改完再把延时调回200μs,全链路正常。

项目修改前修改后
切换延时100μs5ms(硬件低阻化后可回到200μs)
采样策略单次采样三次取中值
信号源内阻约5kΩ约几十Ω(加跟随器后)
温度通道最大偏差约3度小于0.1度

这个坑的价值在于:它说明多路开关加高阻信号源是天然的“串扰放大器”,只要源阻抗不降下来,软件调参只能是拆东墙补西墙。

5. 调试坑二:上电瞬间的随机选通

5.1 现象:设备复位后偶发采集超限报警

第二个坑比第一个更隐蔽,它只在上电和复位的瞬间出现。设备每次上电后,第一个采集周期偶尔会有通道读数超过报警阈值,系统误报一次“超限”,之后所有通道恢复正常,再也抓不到异常。故障概率大概在20%到30%,而且报警通道不固定,今天报第3通道,明天报第7通道,完全没有规律。

这种偶发问题最麻烦,因为它是“瞬态”的,一旦系统跑起来,一切又都正常,常规打断点、看日志的手段都很难定位。

5.2 排查过程:从触发条件反推

我先用串口调试助手把首周期16路的原始ADC值全部打印出来,对比正常启动和异常启动的差异。结果发现,异常启动时首周期里会有一路ADC值明显偏高,几乎是满量程,后面所有周期恢复正常。

再结合“报警通道随机”这个特征,我判断问题出在“上电瞬间4067选到了一个随机通道”。但当时的硬件设计是EN引脚直接接地(图省事少用一个GPIO),S0~S3由MCU的GPIO直连。问题就出在这里——MCU的GPIO在复位期间是浮空输入,没有任何确定的电平输出,S0~S3上的电压完全取决于上电顺序和引脚内部的寄生状态,4067就会随机选通某个通道。如果恰好选到一直接着外部电压的通道(比如电池电压分压后的3V信号),这个电平就直接通过COM端出现在了MCU的ADC引脚上,触发了首周期的超限报警。

为了验证这个推断,我用示波器同时抓MCU的3.3V电源轨和S0引脚的波形,上电瞬间确实能看到S0在VCC爬升过程中出现了一段无规则抖动,而不是干净的0V或3.3V。这就是随机选通的直接证据。

5.3 根因:GPIO复位态悬空,4067随机选通

根因总结起来就是一句话:MCU从上电复位到执行main函数里GPIO初始化代码之间,存在几十毫秒的时间窗口,这个窗口内GPIO寄存器还没有配置,引脚处于高阻浮空状态。如果外设的“默认状态”依赖MCU引脚来保证,那这几十毫秒就是失控窗口。CD74HC4067的EN如果直接接地,芯片在上电瞬间就处于“使能且随机选通”的状态,任何输入信号都可能被直接串到ADC引脚上。

更危险的是,如果被随机选中的通道恰好接的是高于3.3V的外部电源,在MCU尚未配置好ADC引脚的过压保护时,这个电压可能对引脚造成实质性的过压冲击。我当时采样的是分压后的信号,没有超出3.3V,所以只是误报警,没有损坏芯片,但隐患是实打实的。

5.4 修复:EN上拉、地址线下拉、软件先置电平再操作

对症下药,硬件和软件各改一处:

硬件上,EN引脚加一颗10kΩ上拉电阻到3.3V,保证芯片在上电和复位期间默认处于禁用状态,所有开关断开,COM端与任何通道都不导通。S0~S3每根地址线加一颗10kΩ下拉电阻到GND,让复位期间地址线被钳位在低电平,即使以后某次忘了配置GPIO,芯片也只会“选择通道0”,而不是随机选通。

软件上,main函数最开头、任何外设初始化之前,就先配置4067相关的GPIO为推挽输出,并立即输出一个确定电平:S0~S3全部输出低电平,EN输出高电平(禁用状态)。全部配置完成后再把EN拉低,使能芯片。这样MCU代码接管引脚的控制权之后,芯片才被允许工作,中间的失控窗口被完全覆盖。

改完后的验证方式是连续上电20次、按复位键20次,用串口监控首周期采集值,没有再一次出现超限报警。示波器复测复位瞬间的S0~S3波形,也已经是干净的低电平,EN引脚在MCU接管之前始终为高。

修改项修改前修改后
EN引脚直接接地10kΩ上拉到3.3V,由MCU控制
S0~S3直连MCU GPIO,无默认电平各加10kΩ下拉到GND
软件初始化顺序先初始化ADC,后操作GPIOmain函数最先强制GPIO输出确定电平
上电首周期误报约20%~30%概率0

这个坑的通用教训是:外设的默认态必须由硬件电阻保证,不能依赖软件的初始化速度。代码执行前那几十毫秒,所有悬空引脚都是隐患。

6. 再往后可以做的几个优化

6.1 高阻抗信号源前务必加缓冲

第一个坑已经充分说明了问题:4067的导通电阻只是串扰的一部分,真正的放大器是高阻抗信号源本身。凡是要接NTC、光敏电阻、电位器这类高阻传感器,设计阶段就在前端加上运放跟随器,比事后调参省心一百倍。选运放时注意轨到轨输出和足够的带宽,LMV321、SGM8531这类微功耗轨到轨运放在3.3V系统里都很好用。

6.2 轮询策略与自检通道

16路通道轮询时,建议不要按固定顺序从头到尾刷,而是把变化频繁的重要通道(比如电池电压)放在前面,更新周期短的通道优先采集。这样即使系统忙到来不及刷新全部通道,关键数据也不会过期太久。

另一个实用技巧是:留出一个空闲通道接一个精密基准电压源(比如TL431输出的2.5V),周期性采集一次并与标定值比对。如果偏差超过阈值,就能提前发现4067的开关阻抗漂移、ADC参考电压变化或焊点老化问题。这个小功能在长期运行的项目里价值极高,用几行代码换一份可靠性保障。

6.3 还能不能继续扩展通道?

如果你觉得16路还不够,可以用多片4067做级联。最简单的做法是:所有芯片的S0~S3并联,共用一个4位地址总线,每个芯片的EN引脚由MCU的独立GPIO控制。访问时先选通对应芯片的EN,再输出通道地址。N片4067就能扩展成16×N路,成本依然很低。

如果想提高整体采集吞吐率,还有一种思路:MCU如果有两个独立的ADC,可以用两片4067分别接ADC1和ADC2,两路并行采集不同通道,采样周期缩短一半。这种改动只涉及硬件接线和软件抽象层,采集逻辑不需要大改。

最后说一个我自己的调试习惯:多路模拟开关电路出问题时,示波器比万用表重要得多。万用表只能告诉你“现在电压是多少”,但示波器能告诉你“这个电压是怎么爬上去的”,也就是能看到开关切换的完整暂态过程。很多看似“玄学”的串扰问题,波形一出来,原因基本就摆在眼前了。希望这篇笔记里的两个坑能帮你少走点弯路。

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

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

立即咨询