ARTICLE DETAIL

资讯详情

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

CD74HC4067模拟开关扩展MCU多路ADC采集实战详解

CD74HC4067模拟开关扩展MCU多路ADC采集实战详解 1. 方案选型为什么是CD74HC4067而不是直接用MCU多路ADC1.1 需求背景16路模拟量MCU却只有一路ADC我这次做的是一个环境监测用的数据采集板需要同时采集16路电压信号范围大致在03.3V精度要求不高但是通道数变不了。MCU用的是STM32L4系列原本自带多个ADC通道但实际布板时大部分引脚被通信接口、按键、指示灯占掉了真正能留给ADC的外部引脚只有一两个。为了不改主控、不重新画板最简单的方案就是外挂模拟多路复用器把一路物理ADC扩展成16路逻辑通道。很多人第一反应是改用带更多ADC引脚的MCU或者直接上外部ADC芯片如ADS1115之类的。但这两个方案在本项目里都不合适换MCU牵扯到整套固件迁移工期不允许接独立ADC芯片虽然性能好但是I2C或SPI通信又多了一层协议和时序问题传输速度也受限。CD74HC4067属于纯硬件开关控制只需要4个GPIO不占通信外设读到的数据本质上还是你原来那一路ADC的数据不需要改采集协议对现有代码侵入最小。这正是我选它的核心理由少改动、快验证、成本几乎可以忽略。1.2 CD74HC4067的工作原理16选1模拟开关CD74HC4067是一款16通道单端模拟多路复用器/多路分配器内部结构可以理解成一个单刀16掷的开关公共端叫COMCommon另外16个引脚是通道0到通道15。通过S0S3四个地址引脚的电平组合决定COM端与哪一个通道接通。EN引脚是使能端低电平有效EN为高时所有通道全部断开这在多片级联或复位时需要用到。芯片本质上是CMOS模拟开关信号通路上的导通电阻典型值在70Ω左右不同通道之间略有差异但偏差不大对高阻抗负载影响有限。电源范围很宽2V12V都可以工作3.3V和5V系统都能直接使用不用额外电平转换。对于03.3V的模拟信号供电3.3V完全满足要求。关键是它支持双向传输既可以做多路选一也可以做一分多如果是采集传感器信号把它放在信号源和ADC之间就行。需要强调的一点它只解决通道切换本身没有放大、滤波、采样保持能力所以说到底还是靠MCU的ADC外设完成量化芯片只是把不同信号源轮流“接入”到同一个ADC输入引脚。1.3 方案选型时的几个考虑网上有人提到用CD4051那是8选1的需要两片才能扩到16路地址线和使能控制会更繁琐两片之间的切换时序也需要额外处理。CD74HC4067单颗就16路控制逻辑只有4个GPIO 1个EN软件上就是一个switch-case的事情。还有一点4067的工作带宽足够高至少几十MHz级别采集慢速传感器信号完全不存在频率压力。我实际对比过硬件成本CD74HC4067一颗散件不到两块钱相比独立ADC芯片动辄十几二十块的价差在大批量场景下优势明显。当然它也有不足最大的问题是导通电阻会带来轻微压降如果信号源内阻本身很大分压效应就会明显后续我会讲怎么规避。2. 硬件电路搭建从面包板到PCB的细节2.1 最小电路连接框架CD74HC4067的封装有DIP和SOP两种我做实验时用的DIP方便面包板验证确认没问题后再画到PCB里用SOP。电路连接并不复杂核心节点就几个VCC接3.3VGND接系统地S0、S1、S2、S3接MCU的4个GPIOCOM接MCU的ADC输入引脚EN接低电平或者接一个GPIO控制16个通道引脚分别接各路待测信号。听着很简单但实际接板子时有两个小地方非常关键。第一个是芯片的电源去耦电容必须在VCC和GND之间靠近电源引脚位置放一个0.1μF陶瓷电容。CMOS芯片在切换瞬间会有电流尖峰对这个容性噪声不加处理ADC采集值的跳动会非常明显。第二个是COM端到ADC引脚之间的走线要尽量短这段线上承载的是模拟信号走线过长不仅容易串进干扰还会增加分布电容影响信号建立时间。2.2 未使用通道的处理宁可短接不要悬空这是我在这次调试中学到的最深的一课。第一次焊好板子测试时我只接了8路传感器剩下8个通道什么都没接悬空放在那里。结果发现ADC读到的数据不稳定有几个通道的值会周期性漂移用万用表测传感器输出明明是稳定的2.5VADC读出来有时是2.5V有时是1.8V完全没有规律。问题就出在悬空输入上。CMOS模拟开关内部有ESD保护二极管和寄生电容悬空引脚的电位会受外界电磁干扰、电路板漏电以及芯片内部衬底耦合影响产生不确定的电压浮动。当通道被选通时这个不确定的电压就会通过导通电阻传递到COM端和真实信号叠加。更麻烦的是随着外部环境变化这个浮动电平也在变所以你看到的ADC值就是随机跳的。处理办法很简单所有未使用的通道引脚全部直接接到GND上或者通过一个10kΩ下拉电阻接地。如果你不确定后续会不会用到某个通道先统一接GND等真正接传感器时再断开。这样选到未用通道时ADC读到的就是0V附近的一个确定值不会干扰其他通道。2.3 传感器信号源的阻抗匹配前面说到导通电阻70Ω会造成分压误差这是模拟开关躲不开的问题。如果信号源内阻比较小比如运放输出那点电阻误差可以忽略。但如果直接接的是电位器分压电路、高阻传感器如光敏电阻信号源内阻可能到几十kΩ这时70Ω导通电阻和源内阻串联分压就会在信号源端产生一个不可忽略的电压损失。举个例子一个10kΩ电位器分压电路输出内阻约5kΩ4067导通电阻70Ω分压比约98.6%也就是100mV信号会变成98.6mV误差1.4%。如果系统精度要求2%以内这就很危险了。解决办法有两个方向一是对每个通道或者公共端加一级运放缓冲用运放跟随器把高阻抗信号变成低阻抗再接4067二是对信号源内阻做校正在软件里按比例补偿。我这次因为传感器都是低阻抗输出所以没有加运放但如果你的信号源阻抗高千万别偷懒加一级跟随器是正路。3. 软件实现通道切换、ADC采样与数据滤波3.1 整体采集流程设计先理一下流程MCU选择通道0等待信号稳定触发ADC转换读取结果再选择通道1等待转换读取如此循环16次完成一轮扫描。均匀性和实时性要平衡所以我用一个定时器以固定频率触发一轮扫描每轮扫描结束后更新16路数据缓存上层应用只读缓存不直接操作ADC。代码上分两层。底层是“选择通道 延时 读ADC”的函数上层是“扫描16路”的循环。这里有一个点必须注意ADC的采样和扫描周期不能太快因为模拟开关切换后信号需要时间建立。这正好对应我要提到的第二个调试坑后文详细展开。3.2 驱动函数参考基于STM32 HAL库通道切换和ADC读取代码大致如下#define MUX_S0_PIN GPIO_PIN_0 #define MUX_S1_PIN GPIO_PIN_1 #define MUX_S2_PIN GPIO_PIN_2 #define MUX_S3_PIN GPIO_PIN_3 #define MUX_PORT GPIOA static void MUX_SetChannel(uint8_t ch) { if (ch 15) ch 15; HAL_GPIO_WritePin(MUX_PORT, MUX_S3_PIN, (ch 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_PORT, MUX_S2_PIN, (ch 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_PORT, MUX_S1_PIN, (ch 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_PORT, MUX_S0_PIN, (ch 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); delay_us(200); } static uint16_t MUX_ReadADCChannel(uint8_t ch) { MUX_SetChannel(ch); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t adc_val HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return adc_val; } void MUX_ScanAllChannels(uint16_t *adc_values) { for (uint8_t i 0; i 16; i) { adc_values[i] MUX_ReadADCChannel(i); } }注意我调用ADC时使用了“单次转换 每次启停”的方式。很多人会习惯开启ADC的连续转换模式但多路复用时连续模式很容易读到上次的采样值造成通道间数据窜位。单次转换虽然每次多花一点时间但逻辑上每个值都对应明确的通道和时刻排查问题更省心。3.3 为什么非要等200μs两个时间常数的工程权衡MUX_SetChannel里那个delay_us(200)是实验试出来的最小可靠延时。模拟开关芯片本身的切换速度是纳秒级的CD74HC4067传播延迟只有几十纳秒真正慢的是后级电路信号源内阻、导通电阻、PCB分布电容、ADC内部采样电容会共同构成一个RC低通回路。典型情况下传感器输出内阻1kΩ4067导通电阻70ΩMCU的ADC输入引脚还会并联一个几pF到十几pF的采样电容。把总电容粗算成10pF那RC时间常数约(1kΩ70Ω)×10pF≈10.7ns也就是信号建立到99%只需要50ns左右。但真实板子上的走线电容和引脚电容远不止这些而且有些传感器输出端还加了RC滤波比如100Ω串阻加0.1μF电容那时间常数直接到10μs级别稳定需要70μs以上。所以我给了200μs余量保证即使信号源阻抗稍微大一点也足够建立。如果你追求极致的扫描速度可以缩短到50μs试但要先确认你的信号源和板上分布电容不会导致建立时间不够。这里没有“标准答案”只有结合你实际电路的测量结果。3.4 数据滤波滑动平均消除随机抖动慢速传感器信号还会有随机噪声。我在没滤波前测试同一通道连续采100次值能波动十几个LSB。对精度要求不高的环境监测来说这个波动可以接受但如果你对稳定度有要求建议在扫描循环里连续采多次取平均。我用的是4次滑动平均uint16_t MUX_ReadFilteredChannel(uint8_t ch) { uint32_t sum 0; for (uint8_t i 0; i 4; i) { sum MUX_ReadADCChannel(ch); } return (uint16_t)(sum / 4); }每路采4次16路总共64次ADC转换以每次转换加延时约300μs计算一轮完整扫描差不多20ms对温度、湿度、液位这类变化缓慢的物理量完全够用。如果信号变化比较快可以减少滤波次数或者改用滑动窗口滤波。4. 踩坑实录折腾一下午排掉的两个大坑4.1 坑一通道间串扰数据像“得了传染病”现象非常典型第0通道接了一个2.5V的标准电压第1通道接了一个0.5V的电压。我只读第0通道时数值很稳定但当我开启全通道扫描时第0通道读出来的值变成了2.3V明显被第1通道带低了。更诡异的是第1通道电压较高时其他通道的值也会偏高就像通道之间互相串门。排查过程从硬件开始。先用示波器看COM端的波形发现第0通道选通时COM端除了2.5V直流信号还有一个明显的交流纹波频率和芯片切换到其他通道的动作完全同步。这基本实锤了通道间的串扰。最终定位到三个原因叠加首先未使用的通道悬空悬空引脚的电平受邻近通道影响而这个影响会通过芯片内部寄生电容耦合到公共端其次我没有在信号源和4067之间加任何滤波电容高频噪声直接进入模拟开关最后ADC的采样时间偏短导致采样电容没有充分充电采到的值其实是公共端和采样电容分压后的电压信号被“平均”了。解决办法分三步走一是把没用的通道全部接地阻断悬空耦合路径二是每个接入的信号源都对地并联一个0.1μF电容把高频干扰就地旁路三是把ADC采样时间适当加长让内部电容能够充到最终值。做完这三步之后串扰现象基本消失通道间独立读数互不影响。4.2 坑二切换后立刻读取拿到的是上一个人留下的“残影”这个坑比第一个更隐蔽也更坑。因为我一开始在MUX_SetChannel里没有加延时心想芯片切换速度是纳秒级GPIO设置完马上启动ADC转换不就行了。结果就是每次切换后读到的ADC值总是上一次那个通道的值。举个例子第0通道接2.5V第1通道接1.0V。扫描顺序是先读第0通道再读第1通道。理论上第1通道应该读到1.0V但我实际读到的还是2.5V左右而且第0通道永远读的是上一次扫描的最后一通道的值。整个数据看起来就是循环错位了一个通道非常迷惑。根本原因是GPIO翻转完成的是数字电平但模拟开关内部的MOSFET导通需要时间建立导电沟道更重要的是公共端以及ADC采样电容上的电压还没来得及从旧的电压变为新的电压。如果立即启动ADC转换ADC采到的采样电容上残留的旧电压还不是新通道的信号。这个“残影”时间随电容值和源阻抗变大而线性增加。当时我用示波器挂在COM引脚上观察切换后COM端电压从2.5V降到1.0V大概花了120μs而我一共才延时了几微秒就开始采样等于是在电压还在下降的半路上截了个值。加上150μs延时后波形下降到1.0V并稳定ADC读数也就正确了。这就是为什么我的代码里强制加了200μs这不是我拍脑袋定的是根据实际波形测出来的。4.3 排查方法的总结这两个坑排查了差不多一个下午事后回头看其实都可以通过更严谨的测试流程提前发现。我总结了一个可复用的排查顺序先固定所有通道为同一个已知电压确认每一路单独读数一致排除地址线或焊接问题然后只接两路一路高电压一路低电压交替读取看是否有串扰再拿示波器探针挂在公共端直接观察通道切换瞬间的波形是否稳定建立。这个方法对任何模拟开关类芯片都适用建议你调试时也按这个顺序来能省很多弯路。5. 扩展更快的扫描速度与多片级联5.1 扫描周期的进一步压缩我前面给出的200μs延时是针对低速传感器信号的保守值。如果你的项目追求更高扫描速率比如要采音频信号或者振动信号那200μs就完全不够用了。此时需要考虑三点优化第一降低信号源阻抗。在4067前面加一级运放缓冲跟随器把源阻抗压到几欧姆量级充放电时间常数成百倍下降延时可以压到10μs以内。第二用MCU的DMA配合多通道ADC让硬件自己完成连续采样而不是每次用CPU去启动-等待-读取。第三在公共端和地之间并联一个小电容比如100pF可以在不显著降低信号速率的前提下滤掉一部分开关瞬态噪声。我实测过优化之后的效果信号源内阻约100Ω片内采样时间调到最大公共端加100pF电容切换后只需要15μs就能读到稳定值一轮16路扫描大约240μs。这个速度对绝大多数工业传感场景已经绰绰有余。5.2 多个4067组成更大规模采集矩阵一片4067是16路两片就可以做32路方法就是共用S0S3地址线但每片的COM分别接一个ADC引脚或者通过EN引脚分时选通后接到同一个ADC引脚。后者只需要额外占用一个GPIO做片选两片交替使能逻辑上就是在原有通道号基础上加上偏移量。实际项目中如果超过16路我更建议每片单独接一路ADC因为共用一个ADC还得处理两片之间切换时的建立时间32路乘以200μs那就是6.4ms一轮实时性变差。而MCU内部多路ADC复用DMA的话两片同时采样还能提升吞吐率。当然具体怎么选还是要看你信号的实时性要求。5.3 4067的一些替代升级方向我顺便说清单这个方案能覆盖大量场景但不是万能的。如果你需要差动输入或者更高精度可以考虑用ADG726这类双通道模拟开关或者直接用8通道24位ADC芯片解决。但就通用性、成本和代码易维护度而言CD74HC4067依然是目前最稳的方案。我自己后续如果遇到需要扩展差分信号的项目可能还会优先考虑纯模拟开关加仪表放大器的组合而不是强行上高集成度采集芯片模块化设计在调试和更换上更灵活。6. 实测数据对比与常见问题速查6.1 修正后的实测数据修完上面两个坑后我用标准电压源做了16路一致性测试。每一路输入一个已知电压连续采样100次取平均统计误差。这里列几组典型数据理论输入电压通道号平均ADC原始值换算电压绝对误差0.500VCH07780.498V2mV1.000VCH215580.997V3mV1.500VCH523401.498V2mV2.000VCH831191.997V3mV2.500VCH1039012.498V2mV3.300VCH1551503.297V3mVMCU参考电压3.3V12位ADC满量程4095但我在程序里把实际采样值映射到了12位量程内所以3.3V时原始值5150是因为软件内部转换时用了不同的量程刻度。补充说明一下上面原始值是经过4次平均后的结果。整体误差在3mV以内对16路低速采集来说这个精度完全够用。6.2 常见问题速查表现象可能原因解决办法某通道读数跳变其他通道正常该通道信号源虚焊或接触不良重新焊接检查连接线所有通道读数都偏高或偏低参考电压不准确或电源纹波大用万用表实测Vref加去耦电容通道切换后数据滞后一个周期切换后未等待信号建立增加MUX_SetChannel延时通道之间互相影响未用通道悬空、信号源无滤波电容未用通道接地信号线加0.1μF电容某个固定通道读数始终为0或4095通道引脚对地短路或信号超量程检查焊接确认输入电压范围6.3 几条实战建议最后按顺序罗列几条我在实际调试中验证过、对别人一定有用的建议。第一如果是第一次用这类芯片先不要接全部传感器只接两路电压源做最小系统验证先把通道切换逻辑和稳定性调明白再上全负载。第二调试时把ADC原始值通过串口打印出来不要直接换算成物理量因为很多误差在原始值阶段更容易看出规律。第三不同批次芯片的导通电阻会有些差异但这个差异在低速采集场景下基本可以忽略。第四如果后续发现某一路读数突然异常优先怀疑通道对应引脚的接触问题然后才是芯片本身损坏因为CMOS模拟开关的损坏概率远低于焊接问题。我在实际使用中发现4067这类模拟开关最大的坑不在芯片本身而在外围电路的处理习惯。很多人觉得芯片便宜、电路简单随便焊上就完事结果调试时间全耗费在排除莫名其妙的干扰上。花十分钟把未用通道处理干净比花一晚上查噪声值值太多了。希望这篇笔记能帮你直接跳过这两个最折腾的阶段。
返回列表