ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

CD74HC4067扩展ADC的多路采集笔记:通道串扰与上电复位调试

CD74HC4067扩展ADC的多路采集笔记:通道串扰与上电复位调试 做嵌入式最怕的不是功能实现不了而是功能看起来实现了数据却在悄悄骗你。前段时间做一块多路环境监测板需要采集32路模拟信号MCU内部ADC只有3个通道最后选了CD74HC4067把单路ADC扩展成16路。板子跑起来之后采集值时灵时不灵通道之间还会“串味”加上一个藏得更深的上电初始化问题折腾了整整一个周末。这篇笔记把完整方案和两个最折磨人的调试坑一次性说清楚给正在做多路模拟采集的朋友一个参考。先说结论CD74HC4067是模拟多路复用器4根地址线控制16路模拟开关的通断把一路ADC物理上扩展成16路。方案本身不复杂但坑藏在信号建立时间和复位瞬间的引脚悬空里这两件事数据手册不会替你操心。1. 为什么我用CD74HC4067而不是换芯片重画板1.1 通道不够用时的三条路当时的情况是板子已经画完MCU选型已定3路ADC通道要覆盖32路外部模拟量通道数完全不够。摆在面前的无非三条路第一换一颗ADC通道更多的MCU。这是最彻底的方案但意味着重新选型、重画PCB、重写底层驱动项目周期直接多出两到三周对于已经定型的板子来说代价太大。第二外挂一颗多通道ADC芯片比如ADS11154路16位、ADS12568路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根地址线的二进制编码翻译成“接通第几号模拟开关”。比如S00、S10、S20、S30时COM端与C0通道导通S01、S10、S20、S30时COM端与C1通道导通。16个通道对应16种组合用4个GPIO的二进制编码全部覆盖这就是“4线控16路”的秘密。S3S2S1S0导通通道0000C00001C10010C2...............1111C15另外还有一个EN引脚低电平有效。EN接低电平芯片正常工作EN接高电平所有开关全部关断COM端呈高阻态。这个引脚看似多余实际上是我第二个调试坑的救命稻草后面细说。有了这个逻辑结构采集16路信号的核心算法就很简单把通道号拆成4个二进制位输出到S0~S3等信号稳定启动一次ADC转换读回结果然后切下一路。2.2 从数据手册里挤出来的关键参数有一说一CD74HC4067的数据手册并不厚几页纸但参数全在字缝里得自己挤。以下是实际工作中最关键的几个导通电阻RonVCC3.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输入引脚如PA1S0~S3MCU任意4个GPIO如PA0~PA3ENMCU任意1个GPIO如PA4通过10kΩ上拉到3.3VVCC3.3V对地并联10μF0.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切换到通道5S0变化和S1变化之间可能有几十纳秒的时间差这时候芯片短暂选中了某个中间通道虽然时间极短但在高内阻场景下可能引入毛刺。3.3 稳定延时的计算思路稳定延时到底设多少这个问题很多人直接抄教程写个10μs实际上应该算出来。我当时的温度采集通道NTC热敏电阻和分压电阻的等效内阻约5kΩ4067导通电阻按100Ω计COM端对地有0.1μF滤波电容如果输入端还加了RC滤波这个电容会更大。那么切换后信号的建立时间常数就是τ R_total × C_load (5kΩ 100Ω) × 0.1μF ≈ 510μsRC一阶系统要稳定到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引脚直接接地图省事少用一个GPIOS0~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两路并行采集不同通道采样周期缩短一半。这种改动只涉及硬件接线和软件抽象层采集逻辑不需要大改。最后说一个我自己的调试习惯多路模拟开关电路出问题时示波器比万用表重要得多。万用表只能告诉你“现在电压是多少”但示波器能告诉你“这个电压是怎么爬上去的”也就是能看到开关切换的完整暂态过程。很多看似“玄学”的串扰问题波形一出来原因基本就摆在眼前了。希望这篇笔记里的两个坑能帮你少走点弯路。
返回列表