ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CD74HC4067模拟开关扩展ADC通道:16路采集的硬件设计、驱动代码与两个关键调试坑

CD74HC4067模拟开关扩展ADC通道:16路采集的硬件设计、驱动代码与两个关键调试坑 MCU自带的ADC通道不够用这个槛我猜不少搞嵌入式的朋友都撞过。手里的单片机选型已经定死在板子上但模拟输入却有十六路还都是毫秒级直流信号换主控不现实外接独立ADC又觉得成本高。最后我用一颗几块钱的CD74HC4067多路模拟开关把单片机的ADC输入扩展成了16路。第一版代码跑起来倒是很快但后面两天时间几乎全耗在两个调试坑上——一个让采样值永远差半口气另一个让没接线的通道凭空变出电压来。这篇笔记就把这套方案的选型逻辑、硬件接线、驱动代码和两个坑的完整排查过程都记下来给准备用多路开关扩展ADC的朋友做个参考。1. 为什么单片机自带的ADC不够用16路采集的选型决策1.1 项目背景与必须加通道的现实当时做的是一块多通道参数监测板传感器输出0到3.3V的直流模拟量一共16路对刷新率要求不高每路100Hz足够。主控是一颗比较常见的STM32满打满算能作为ADC输入的引脚大概十来个但布局上还要留一半引脚给按键、串口和状态灯真正能自由分配给模拟采集的ADC通道只剩了5个。这种情况下摆在面前的选择其实不多。换MCU是最直接的思路但板子结构和BOM已经冻结换主控意味着改板、改驱动、重新做EMC测试周期完全扛不住。另一种思路是外扩一片独立ADC芯片用SPI或I2C去读比如ADS1115或者AD7606精度高、速度快但价格和PCB面积对当时的项目来说都偏奢侈。最后剩下的方案就是在ADC前端加模拟多路开关把16路信号分时切换进同一个ADC引脚。这个思路在工业采集板上太常见了几乎没有悬念。1.2 多路开关扩展和独立ADC扩展怎么权衡很多人在这个岔路口会犹豫我直接把两个方案摊开对比过一遍。维度多路模拟开关 内置ADC独立ADC芯片成本低一颗几块钱中高十几到几十块起步开发量小GPIO切通道轮询采样中要调SPI/I2C驱动和寄存器速度受开关切换、建立时间限制适合慢信号可以做到很高甚至多通道同步采样PCB面积小一个8脚或24脚小芯片就够大特别是多通道同步采样芯片精度受导通电阻、泄漏电流、串扰影响高参考电压和转换精度都更有保障这个表格一眼就能看出来如果只是慢速巡检直流电压多路模拟开关是性价比最高的路。我当时16路信号全是经过调理板输出的0到3.3V直流电压输出阻抗不高12bit分辨率也够用。如果换成高速振动信号或者需要16bit以上的精密采样那就别省这个钱直接上独立ADC。1.3 为什么最后落在CD74HC4067上模拟多路开关其实有好几种CD74HC4051是8选1CD74HC4052是双4选1CD74HC4053是三组2选1CD74HC4067是16选1。最早我考虑过两片CD74HC4051级联每片用8个通道通过片选再扩展成16路但这样多占一个IO还要多处理一组片选逻辑。CD74HC4067只要4根地址线加1根公共线就能搞定16路接线和程序都最省。CD74HC4067是TI HC系列里的经典型号兼容SN74HC4067国内叫74HC4067的也很多DIP和SOP封装都好买。供电范围2V到6V直接3.3V供电控制逻辑和3.3V单片机匹配完全不用加电平转换这一点在后来的调试中省了不少事。通道数刚好16路和需求严丝合缝所以基本没有犹豫就定它了。2. 动工前先读懂CD74HC4067通道表、导通电阻和唯一需要注意的EN2.1 这颗芯片怎么把16路接成1路CD74HC4067内部的核心是一组CMOS传输门。16个输入输出引脚CH0到CH151个公共引脚COM4根地址选择线S0到S3外加一个使能引脚EN。扩展ADC时的接法就是把16路传感器信号分别接在CH0到CH15上COM脚引出到单片机ADC引脚。S0到S3接4个普通GPIO。这个芯片本质上是一个单刀十六掷模拟开关每次只让一个通道的开关闭合其他通道全部断开。哪一路闭合由S0到S3的二进制组合决定真值表如下。ENS3S2S1S0导通的通道00000CH000001CH100010CH200011CH300100CH4..................01111CH151XXXX全部断开注意COM不是单纯的输出它和CHx之间的传输门是双向的。我们做ADC扩展时把COM当输出用信号从CHx进从COM出。如果反过来把信号接在COM上把它当四路分配器用也可以。2.2 导通电阻、泄漏电流和开关速度三个关键指标这芯片有三个指标在选型和调试时必须心里有数。第一个是导通电阻Ron数据手册上在VCC4.5V时典型值大概70ΩVCC降到3.3V之后实测会升到100Ω以上。Ron会给信号源接入ADC的路径上增加一点阻抗但单片ADC的输入阻抗极高所以Ron不会造成明显的直流分压这是新手容易担心的问题。真正的麻烦在于它会参与ADC采样电容的充电时间常数信号源内阻越大通道切换后电压建立越慢这就是我后面要说的坑之一。第二个是关断状态下的泄漏电流也就是off-state leakage current常温下大概几百纳安量级。纳安看起来很小但当被测信号源内阻很大、或者未选通的通道引脚悬空时这微小的电流会在回路里产生可观测的电压偏移。第二个调试坑就和它直接相关。第三个是开关速度tON和tOFF一般在几十纳秒级别。对于直流慢信号来说完全够用但如果想用它在几十kHz以上的速度反复切换就要谨慎评估了。2.3 供电电压与逻辑电平匹配CD74HC4067的供电范围是2V到6V但不同供电电压下Ron和开关特性都会变。我的建议是VCC直接和MCU用同一个电压不要图省事把芯片接5V然后用3.3V GPIO去控制。芯片自己的VIH大概是0.5VCC如果VCC5V3.3V的高电平虽然高于2.5V阈值但噪声裕量很小板子上有点干扰就可能误触发。3.3V对3.3V最干净没有电平隐患。EN引脚是低电平有效EN1时所有通道会被强制关断COM变成高阻。这个引脚在正常使用时要直接接地不能悬空。我当时样板第一次打回来就因为嫌布线麻烦没把EN拉下来结果通道怎么切读数都是乱的这是后话。3. 硬件连接与轮询采集第一版能跑的代码3.1 硬件接线从传感器到ADC引脚的完整链路硬件接线图我脑子里的最终版本是这样16路传感器输出按顺序接到CD74HC4067的CH0到CH15COM脚接单片机ADC输入引脚比如PA0S0到S3接PA1、PA2、PA3、PA4四个GPIOEN接地VCC接3.3V对地放一个0.1uF退耦电容芯片地线和模拟地单点汇合。有一个细节第一版我没处理好为了滤波我在COM引脚和地之间放了一个100nF的陶瓷电容。当时想着能把公共端电压稳一稳结果这个电容直接导致了第一个调试坑的放大。后面会详细说滤波电容不能乱加尤其是放在COM这种所有通道共享的节点上加得越大通道切换后电压建立越慢。每路传感器输出端要不要加RC滤波我的建议是如果传感器本身噪声不大先别加。等把基础的采集调稳了再在传感器端加小电容滤波比如1nF到10nF级别用软件滤波替代大电容更稳妥。如果传感器输出阻抗很高超过10kΩ最好在进4067之前加一级电压跟随器。一时图省事不加采样建立时间就会变得很长后面所有等待时间的坑都会加倍。3.2 写一个最基础的多路切换ADC读取函数我用的是STM32的HAL库核心逻辑就两件事设置地址线电平然后启动一次ADC转换。下面这段是当时第一版能跑通的代码。// 假设 S0PA1, S1PA2, S2PA3, S3PA4 void Mux_SetChannel(uint8_t ch) { HAL_GPIO_WritePin(S0_GPIO_Port, S0_Pin, (ch 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(S1_GPIO_Port, S1_Pin, (ch 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(S2_GPIO_Port, S2_Pin, (ch 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(S3_GPIO_Port, S3_Pin, (ch 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); } uint16_t ADC_ReadChannel(void) { HAL_ADC_Start(hadc); HAL_ADC_PollForConversion(hadc, 10); uint16_t value HAL_ADC_GetValue(hadc); HAL_ADC_Stop(hadc); return value; } uint16_t Mux_ReadADC(uint8_t ch) { Mux_SetChannel(ch); // 注意这里第一版没有加任何延时 return ADC_ReadChannel(); }主循环里就对着0到15循环调用Mux_ReadADC把16个通道的值存到数组里。这套代码的逻辑本身没问题GPIO切换地址、ADC启动转换、读取结果都是常规操作。真正的问题出在Mux_SetChannel之后没有任何稳定时间就立刻启动ADC等于地址线刚跳完模拟开关还没稳定公共线电压还没建立我就把采样结果读回来了。3.3 如果想用DMA连续采集有人在论坛上问过要不要用DMA连续采集提高速度。我的回答是对于16路直流慢信号巡检普通轮询完全够用没必要为DMA自找麻烦。算一笔账每路100Hz16路就是每秒1600次采集单片机主频72MHz一次查询式ADC转换加切换时间按100us算也就占用16%的CPU时间完全能接受。如果确实要把单通道速度推到几千Hz用定时器触发ADCDMA是可行的但地址切换必须和触发信号严格配合。我以前试过在DMA中断里改GPIO和启动下一次转换时序稍微没算好采出来的数据就对不上通道了。建议先把轮询方案跑通再考虑用定时器做精确切换。慢信号场景下过早优化反而容易引入新bug。4. 调试坑之一切换通道后采样值总是慢半拍4.1 现象同一路电压切过去第一枪永远偏低这个坑是最早暴露的。我给CH0接了稳定的2.5VCH1接了稳定的1.2V。先从CH0切到CH1连续采样10次第一次读到1.8V左右后面几次才逐渐掉到1.2V。反过来从CH1切到CH0第一次读到1.6V后面慢慢爬到2.5V。稳定复现百试不爽。一开始我还以为是CD74HC4067坏了或者地址线接反了。但仔细观察发现读数的变化是一个平滑的指数过程不是跳变而且方向永远是从上一路的值朝当前路的值靠拢。从2.5V切到1.2V时第一个值偏高从1.2V切到2.5V时第一个值偏低。这个规律非常关键说明公共端上还残留着上一通道的电压需要一段时间才能过渡到新通道的电压。4.2 排查从怀疑芯片坏到示波器抓到真相排查链路其实不复杂但很典型。第一步用手指头捏着万用表去量CH1输入的电压稳定1.2V说明前端信号没问题。第二步量CD74HC4067的COM引脚电压发现切换后COM脚电压确实不是立刻跳到1.2V而是有个明显的时间过程。第三步把示波器探头夹在COM脚上看切换瞬间的波形抓到的是一条指数上升曲线。这时候问题范围已经缩得很小了COM节点上的电容在作怪。怪谁就怪我在COM和地之间放的那颗100nF电容。当时的等效回路是传感器输出内阻约1kΩ串联CD74HC4067的导通电阻约100Ω再加上被充放电的100nF电容时间常数τ大约等于110us。一个时间常数只能充到63%的电要充到99%才敢说稳定那就要5个τ也就是550us。我代码里切换完地址立刻启动ADC采样时间也没敢拉长那不等于是趁电容还没充满的时候就取了个半截电压吗。与这个坑伴生的还有第二个因素MCU内部ADC的采样电容。即使外部电容拆掉ADC自身的采样保持电容也需要在采样窗口内充电。如果信号源内阻大而ADC的采样时间配置又太短内部小电容没充满电转换结果照样不准。STM32的ADC采样时间是可以配置的从1.5个周期到239.5个周期都有大阻抗源必须选长采样时间。4.3 根因分析公共线上的电容要时间充放电把根因讲透一点。CD74HC4067切换通道的本质是把COM引脚从连接到上一路信号瞬间切到连接下一路信号。COM引脚上不可避免地有寄生电容如果外部还挂了滤波电容那这个节点的电压就不可能瞬间跳到新值。新信号源要经过输出内阻和模拟开关的Ron给这些电容充电或者放电充电速度由RC时间常数决定。计算公式是 t_settle ≈ 5 × (Rs Ron) × C_load。其中Rs是信号源输出阻抗Ron是模拟开关导通电阻C_load是COM节点上对手的总电容。这个公式不需要多高深的数学估算时直接用就够。按照这个公式算了一下我用100nF电容和1kΩ源阻抗需要等550us才能采到准确值。难怪当时几百us以内的延时全都不管用因为根本不等够就会采到错误值。4.4 修复延时、丢弃第一次转换、降低源阻抗定位到根因后修复方案就非常清晰了。我按优先级做了三件事。第一软件上在Mux_SetChannel之后加固定延时直接把延时设成1ms比计算出来的550us留了一倍余量。同时丢弃通道切换后的第一次转换结果只保存第二次到之后的读数。处理完这两点数据马上就正常了。第二把COM端的100nF电容改成了1nF把RC时间常数砍掉两个数量级。原来的硬件滤波器换成了软件滤波后面会用中值平均来处理采样值的抖动。第三把ADC采样时间从小采样周期改成了最长的那档。HAL库里通过ADC_ChannelConfTypeDef的SamplingTime字段配置改成ADC_SAMPLETIME_239CYCLES_5。这一步对内部采样电容充电不充分的问题改善很大。如果源阻抗实在高最彻底的方案是在传感器输出端加一级运放跟随器把输出阻抗从几十kΩ降到个位数Ω这样后续怎么切都不会有建压问题。我后来的正式板子上给所有的传感器信号都加了跟随器成本很低但稳定性和噪声表现提升非常明显。5. 调试坑之二悬空的通道会产生幽灵电压5.1 现象没接线的那一路居然读到0.5V第一个坑填平之后数据看着靠谱多了但很快又出现了一个新状况。我只在CH0上接了3.3V分压的1.65V信号其他15个通道什么都没有接按理说这些通道应该读到0。结果程序跑起来CH1、CH2、CH3这些通道读数不是0大概在0.4V到0.8V之间乱跳还带一点电源工频的规律。用手碰一下悬空的引脚读数马上跟着变松手又回到乱跳状态。这个现象太容易让人误判了。我第一反应是GPIO地址控制错了可能地址线顺序反了或者相位不对导致选中的通道和我以为的不是同一个。后来写了个测试逻辑把地址线逐根、逐组合切换再用万用表量COM脚和各个CH脚的连通关系确认地址对应完全没问题。通道选择是对的但悬空通道上就是有电压。5.2 排查从软件配置怀疑到接地实验确认排查到这一步思路反而开阔了。我怀疑是未选通道的状态在捣乱于是做了一个很朴素的实验把这15个未使用的通道用杜邦线全部接到GND再重新跑采样程序。结果所有通道的读数都变成了接近0幽灵电压消失得干干净净。再把接地线拆掉幽灵电压又回来了。这个实验直接证明了问题不在软件而在悬空引脚的静电和耦合效应。第三个验证是用示波器高阻探头去测悬空的引脚可以看到明显的干扰波形频率成分很杂其中50Hz工频分量尤其醒目。一个金属引脚什么都不接它就是一根天线周围任何电场变化都会在它上面感应出电压。5.3 根因分析CMOS开关断开不等于真空隔离很多人对模拟开关有个误解以为芯片内部通道没导通就是完全断开。实际上CMOS模拟开关在关断状态下并不是理想的空气间隙而是存在两条偷跑的路径。一条是关断漏电流数据手册里的off-state leakage current给出了几百纳安到微安不等的数值。悬空引脚的电位不确定时这个漏电流会流过公共端和导通信道的等效电阻叠加出直流偏置。另一条是寄生电容。芯片内部的传输门在断开时源漏之间、衬底之间都存在寄生电容高频信号可以从悬空引脚直接耦合到公共线上表现为交流干扰。如果拿手去摸悬空引脚人体耦合进来的工频干扰就会通过寄生电容串到ADC采样点上。还有一点容易被忽略CMOS模拟开关在导通和关断瞬间会有电荷注入效应。虽然CD74HC4067的电荷注入不算严重但悬空通道会把这个效应放大让公共线上的电压出现瞬态毛刺。毛刺如果落在采样窗口里就会变成读数误差。5.4 修复未用通道全部接地、降低输入阻抗、布局隔离修复的核心原则只有一条绝对不要让模拟输入引脚悬空。所有没有实际信号接入的通道全部通过一个低阻电阻接到GND比如100Ω到1kΩ。我实际用了100Ω这样即使后续把某个通道从空置状态改成接信号只需要换个排针插线就行不用改硬件。第二种手段是降低信号源阻抗。悬空引脚之所以影响大是因为它和COM之间的耦合路径上阻抗很高稍微一点漏电流和电场就能制造出明显的电压。如果传感器输出都经过运放跟随器输出阻抗压到很低外部干扰被源阻抗分压掉串扰就会小很多。PCB布局上也要注意。模拟输入线尽量短不要和数字总线平行走线过长的距离。CD74HC4067底下最好不要走高速信号线或者用地线把模拟输入区域包起来。我原来的样板为了省事S0到S3的地址线从芯片旁边绕了一大圈正好和几个悬空的输入引脚贴近耦合干扰被进一步放大。正式板子改成了地线隔离走线效果比只靠软件处理更明显。5.5 顺带踩到的小雷EN引脚不能悬空调试第二个坑的过程中其实还摸到过一个更隐蔽的雷CD74HC4067的EN引脚如果悬空整个芯片的工作状态可能完全乱掉。当时有一版样板的EN脚没有接地现象是无论S0到S3怎么组合COM口的输出都像是在随机选通道。后来补了一根线把EN直接拉到GND问题立刻消失。EN低电平有效这是CD74HC4067和不少模拟开关不太一样的地方。如果你习惯性地把它当成普通高电平使能引脚去接上拉电阻那就更尴尬了。我的建议是原理图上就把EN接地封死这个隐患不要让生产或调试环节去猜。6. 两个坑填平之后实测数据、滤波处理和适用边界6.1 修改前后的实测对比数据修复完两个坑之后我专门用同一批输入信号做了对比测试记录如下。通道理论电压修复前读数切换后立即采样修复后读数延时丢弃第一次CH02.520V2.512V2.521VCH11.234V第一次1.185V逐渐爬升1.235VCH50.000V悬空改接GND0.423V乱跳0.001VCH83.300V3.289V3.301VCH150.825V第一次0.742V0.826V可以看到修复前误差主要出现在两个场景通道切换后的第一个点以及悬空通道被选中时。修复后稳定性提升明显16路轮询一次整个周期大概几十毫秒连续运行10个小时读数波动基本都在±2mV以内。这个精度对于12bit ADC和直流监测场景已经完全满足要求。6.2 常用ADC值滤波处理基础采集稳定后软件滤波还是要做的。我用的比较多的有三种。第一种是滑动平均适合信号本身比较干净、只是有小幅白噪声的情况。维护一个长度为N的环形缓冲每来一个新值就求一次平均N取8到16比较合适。第二种是中值滤波适合偶尔有尖峰毛刺的场景。比如现场有继电器动作瞬时干扰串进采样线取中间值能很干净地剔除离群点。第三种是一阶低通滤波算法极其轻量适合对CPU和内存都抠得很紧的场景。核心就一行代码filtered filtered alpha * (raw - filtered);alpha取0到1之间越小滤波越重响应越慢。我16路信号里对于传感器本身噪声大的几路用的就是这种一阶低通加滑动平均的组合效果不错。注意滤波会引入滞后如果后面要做闭环控制需要评估这个延迟能不能接受。6.3 什么时候这套方案不可用CD74HC4067扩展ADC这套方案不是万能的它有一个非常清晰的适用边界超过了就别硬用。采样速度超过几十kbps时不要用这方案。模拟开关虽然切换时间只有几十ns但加上外部RC建立时间和ADC采样时间整个链路很难跑到高速。我在实验室试过用DMA连续循环切换16通道、每通道目标10kHz采样率最终发现就算地址切换及时输出信号的前几个采样点也会因为建立问题失真改来改去不如用独立ADC。精度要求超过16bit时也别用。模拟开关的Ron会随温度漂移关断漏电流和电荷注入都会给信号叠加误差。真要精密测量用独立ADC芯片加差分输入才是正道。高阻信号源、又不愿意加跟随器的情况下同样不适用。我算过一组数据信号源内阻100kΩCOM端电容哪怕只有1nF时间常数也要100us再加上漏电流产生的偏移读出来的值误差可能超过10mV。这种情况下老老实实加运放跟随器或者考虑仪器放大器。6.4 如果让我重来一次的几点提醒这片子跑稳定之后回头总结整个调试过程最想提醒的其实是几句话。第一原理图上每个模拟输入引脚都要明确去向不用的通道直接规划接地不要留悬空当时我在原理图上省了4个接地电阻样板调试时花掉的时间远超那4颗电阻的钱。第二公共端的滤波电容不要随手放先算RC再动手。COM脚的电容值对建立时间影响太大了宁可软件滤波也不要硬塞一个大电容上去。第三地址切换和ADC采样之间的等待不要依赖拍脑袋的延时值。用示波器实测COM脚的建立波形再反推延时这样出来的参数最靠谱。我最后在代码里写死的是500us这个值就是先从公式估算再实测校出来的两个方向互相印证过。如果你也踩到这两个坑按上面思路去排查多半能少走半天弯路。我现在反而觉得这个方案最大的价值不只是省了一颗ADC芯片而是把模拟开关那些藏在数据手册小字里的电气特性彻底摸透了。
返回列表