ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

CD74HC4067扩展MCU ADC:16路模拟采集实战与调试经验

CD74HC4067扩展MCU ADC:16路模拟采集实战与调试经验 手头一个项目要同时采16路模拟量MCU自带的ADC通道不够用换芯片又涉及硬件改动和供货周期正好手边有几片CD74HC4067干脆用它把单路ADC扩成16路一路调下来确实省了不少事。这篇文章把整个调试过程、硬件接法、软件驱动思路以及我在实际调试中踩过的两个坑完整记录下来给正在做多路模拟采集的朋友一个参考。CD74HC4067是一片16通道模拟多路复用器/解复用器四根地址线加一根使能脚就能选通16个模拟通道公共端接MCU的ADC输入即可。它解决的是MCU ADC通道不够用的典型场景适合对采样速率要求不高的多路传感器采集、电池电压巡检、电位器/分压信号读取等应用。只要你对采样频率没那么敏感一片4067只要一两块钱性价比非常直观。下面按我实际调试的顺序来写从选型、硬件、软件到两个坑完整展开。1. 为什么是CD74HC4067三套16路采集方案的实际对比接到需求的时候我最初考虑过三条路换一颗自带更多ADC通道的MCU、外挂多通道ADC芯片、用模拟开关扩展现有ADC。三条路各有各的适用场景但在这个项目里CD74HC4067是综合下来最省事的。先看换MCU这条路。STM32F103这类主流芯片的ADC通道数通常在10到16个之间但实际能用多少还要看封装。我用的LQFP48封装ADC引脚被其他功能占掉一部分真正能拿来采集模拟量的就那么几个通道。换一颗引脚更多、ADC通道更多的MCU硬件要重新画固件底层也要跟着动采购周期也不一定等得起。为了几个模拟通道去动整块板子的核心器件怎么看都不划算。再看外挂多通道ADC芯片比如ADS1115、ADS1262这类。ADS1115是16位ADC精度确实好但I2C接口的吞吐率有限而且在4路ADC不够用、需要2到4颗芯片并联的时候成本和PCB面积都上来了。ADS1262这类高精度ADC性能很强但价格和外围电路复杂度也直接拉满。这个项目的信号本身是电池电压、电位器反馈这类对精度要求没那么极端的模拟量12位ADC配合滤波完全够用没必要上16位甚至24位的方案。最后就是CD74HC4067这条路线。它的思路很简单用模拟开关在单路ADC前面做通道切换让ADC分时复用。一片CD74HC4067有16个模拟输入通道四根地址线S0到S3用二进制编码选通一个公共端SIG接MCU的ADC输入引脚这样MCU只需要占用4个GPIO加1个ADC引脚就能采16路模拟量。三套方案的对比我简单整理了一下方案成本硬件改动采样精度适合场景换更高规格MCU较高大需改板取决于MCU需要更多功能扩展不只缺ADC外挂ADS1115等多通道ADC较高中等I2C接线16位精度高对精度有明确要求通道数不多CD74HC4067扩展低几块钱小4个GPIO12位够用多路低速模拟量巡检当然4067方案也有它的局限性。最直接的一点模拟开关不是理想开关通道之间存在导通电阻信号经过开关会有一定衰减后面我在坑二的排查里就跟这个特性正面撞上了。另外它是单端选通不支持差分输入如果信号是差分性质的这条路就走不通。还有就是16路扫描需要时间每一路切换后都要等信号稳定所以整体采样速率不会太快做音频采样这种高速场景肯定不行但做电池电压巡检、环境监测这类需求完全够用。2. CD74HC4067核心电气特性手册里容易被忽略的几个参数芯片选好了不等于直接就能用动手之前把数据手册里的关键参数吃透后面能少走很多弯路。我把自己觉得最重要的几个特性单独列出来这几个参数恰恰是后面两个坑的根源。2.1 引脚功能与真值表CD74HC4067一共16个通道输入CH0到CH15一个公共端SIG四根地址线S0到S3一个使能端EN。通道选通逻辑非常直接S3S2S1S0选通通道0000CH00001CH10010CH20011CH3...............1111CH15这里需要特别注意的是EN引脚的电平极性。CD74HC4067的EN是低电平有效也就是说EN接地或者被拉低时芯片正常工作EN拉高时所有通道全部关断。我第一次接触这类芯片时就吃过想当然的亏以为是高电平使能结果通道怎么切都是断的。如果你的板子上EN脚悬空芯片会处于不确定状态最好用GPIO控制EN或者直接连到GND。2.2 导通电阻Ron模拟开关最关键的指标模拟开关最核心的参数就是导通电阻Ron。CD74HC4067的Ron不是一个固定值它随着供电电压、信号电压和温度变化。我在数据手册里看到的典型值是这样的供电VCC典型Ron最大Ron2.5V120Ω240Ω4.5V70Ω160Ω6V60Ω130Ω从表格可以明显看出VCC越高Ron越小。以3.3V供电、信号电压在1V到2V之间时Ron大致在80到120欧姆这个区间。这个Ron的绝对值看起来不大但放在分压电路里就会造成实际读数偏差。比如信号源是一个100kΩ电位器分压输出阻抗本身就比较高此时Ron和信号源阻抗串在一起再叠加ADC输入级的采样电容效应问题就来了。这就是坑二的伏笔。2.3 模拟信号电压范围CD74HC4067的模拟信号输入范围是GND到VCC。也就是说如果VCC接3.3V那16个通道上输入的模拟电压绝对不能超过3.3V也不能低于GND。超出这个范围芯片内部的寄生二极管会导通轻则采集值被钳位重则损坏芯片甚至倒灌到MCU的ADC引脚。这一点在实际项目中特别容易踩因为很多传感器输出可能是0到5V或者0到10V的。如果你的信号源输出电压超过了4067的VCC就需要先做电平转换或者分压不能直接把信号怼到芯片输入上。2.4 开关切换时间与信号建立时间CD74HC4067的导通和关断时间在纳秒级别但这里有个容易混淆的概念开关切换速度快不等于信号能瞬间稳定。开关切换完成后信号通路上的寄生电容、后级负载的输入电容、ADC采样电容都需要时间充电到目标电压。这个建立时间取决于信号源内阻和电容的乘积也就是RC常数。这个特性直接决定了坑一的成因。我在第一次调试时天真地以为地址线切过去就可以立刻启动ADC转换结果读出来的数据完全不对后面会详细展开。3. 硬件接线实操16路通道与MCU怎么连最稳妥这部分我把实际接线方式和走线布局经验整理出来。接线不复杂但如果细节处理不好后面排查起来会很头疼。3.1 基础接线清单以STM32F103为例我使用的ADC输入引脚是PA4四个GPIO分别是PB0到PB3。接线如下VCC接3.3VGND接GND两者之间加一个100nF去耦电容尽量靠近芯片电源引脚。CH0到CH15分别接16路待测模拟信号。SIG引脚接MCU的ADC输入引脚PA4。S0接PB0S1接PB1S2接PB2S3接PB3。EN脚接PB4方便软件控制也可以直接接GND让芯片常使能。接线看起来很简单但有三个细节必须注意。第一个细节是SIG引脚到MCU ADC引脚之间的走线尽量短。PA4和4067的SIG如果隔得太远中间走线越长寄生电容越大信号建立时间越长也会更容易受到周围数字信号的干扰。PCB条件允许的情况下4067尽量靠近MCU放置。第二个细节是模拟信号线要和数字控制线S0到S3保持距离。S0到S3上的电平跳变是非常陡峭的数字信号如果长时间和模拟信号线平行走线数字跳变会通过寄生电容耦合到模拟通道上导致ADC采样值抖动。布线紧张时至少保证模拟线和数字线不要平行走太长距离中间用地线隔开更好。第三个细节是未使用的通道一定要处理不能悬空。我的板子上16路只用了12路剩余4路我直接接到了GND。如果让未用通道悬空引脚上的电压会漂移到一个不确定值这会在模拟开关内部产生漏电流从而影响公共端SIG上的电压导致正常通道的采集值也受干扰。3.2 关于信号源阻抗的预先评估这一步很多人会忽略但我建议在画板子之前就过一遍。信号源阻抗直接决定了后面的采样建立时间也决定了用4067到底顺不顺利。如果用电阻分压网络作为信号源分压电阻的值不要选太大。两个10kΩ电阻分压信号源等效内阻是两个电阻的并联约5kΩ这个量级配合4007的Ron还在可以接受的范围内。但如果分压电阻换成100kΩ级别信号源等效内阻就到了50kΩ这时候整个信号链的建立时间就会明显变慢坑二就是从这个地方来的。3.3 电源与参考电压的处理这个项目对精度要求中等ADC参考电压直接用了MCU的VDD。如果你对采集精度有更高要求建议用外部基准电压源连接到MCU的VREF引脚。因为ADC的转换结果实际上是输入电压和参考电压的比例值参考电压不稳采集值就跟着漂。4. 软件驱动GPIO控制通道扫描与ADC读取硬件接好了接下来是软件部分。整体流程不复杂控制S0到S3的电平组合选通目标通道等待信号稳定然后启动ADC转换读取数值。4.1 基础GPIO控制函数首先实现通道选择的基础函数。这个函数接受0到15的通道号通过位运算从通道号中提取每一位分别写到四个GPIO引脚上static void mux_select_channel(uint8_t ch) { HAL_GPIO_WritePin(MUX_S0_GPIO_Port, MUX_S0_Pin, (ch 0) 0x01); HAL_GPIO_WritePin(MUX_S1_GPIO_Port, MUX_S1_Pin, (ch 1) 0x01); HAL_GPIO_WritePin(MUX_S2_GPIO_Port, MUX_S2_Pin, (ch 2) 0x01); HAL_GPIO_WritePin(MUX_S3_GPIO_Port, MUX_S3_Pin, (ch 3) 0x01); // 通道切换后等待信号稳定 delay_us(100); }这里我用宏定义把GPIO端口和引脚号做了映射方便移植。注意最后有一个延迟作用是等模拟开关切换完成、信号建立稳定后再进行ADC采样。4.2 单通道读取与16路扫描单通道读取函数的逻辑是选通通道启动ADC转换等待转换完成读取结果uint16_t mux_read_channel(uint8_t ch) { mux_select_channel(ch); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t val HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return val; }写到这里有个使用习惯要说明一下我用的是HAL库的阻塞式读取每次启动一次转换读一次值读完就停。这套流程对低速巡检场景足够了。如果你对转换效率有要求可以考虑用ADC的扫描模式或者DMA但要注意DMA模式下ADC会连续采样通道切到下一路之前要处理好时序关系否则会读到切换过程中的中间态电压。16路扫描就是循环调用单通道读取函数把所有通道的值存在数组里#define MUX_CHANNEL_COUNT 16 uint16_t adc_values[MUX_CHANNEL_COUNT]; void mux_scan_all_channels(void) { for (uint8_t i 0; i MUX_CHANNEL_COUNT; i) { adc_values[i] mux_read_channel(i); } }4.3 采样值滤波处理单次采样的值直接拿来用在某些场景下会有点抖。我习惯在读取函数外面再加一道软件滤波最简单有效的是多次采样取平均或者用滑动窗口滤波。uint16_t mux_read_channel_filtered(uint8_t ch, uint8_t samples) { uint32_t sum 0; for (uint8_t i 0; i samples; i) { sum mux_read_channel(ch); } return (uint16_t)(sum / samples); }多次采样取平均会成倍增加扫描周期。采样3次取平均16路全部扫一遍就是原来的3倍时间。对低速信号来说问题不大但如果信号变化本来就快就要在实时性和平滑度之间做权衡。4.4 关于扫描周期的计算16路全部扫一遍需要多久这个可以估算。每路通道的耗时等于切换延时100us加ADC采样加转换时间。比如STM32F103的ADC时钟12MHz采样周期1.5周期加转换周期12.5周期一次转换大约1.17us再算上HAL库函数调用的开销单通道大概120us左右。16路全扫一遍就是2ms左右也就是500Hz的扫描频率。这个指标对慢速传感器巡检完全够用也验证了4067方案对这类场景确实匹配。5. 调试坑一通道切换后立即采样读到的居然是上一个通道的值这个坑是第一个让我卡住的问题现在回头看看原因其实并不复杂但当时的排查过程绕了不少弯路。把这个过程完整写出来比直接给结果更有参考价值。5.1 现象描述板子焊好程序写完接上信号源开始测试。我先把CH3接了一个2.0V的稳定电压CH5接了一个1.0V的稳定电压扫描所有通道结果发现CH5读出来的值明显偏高接近CH3的2.0V。单独反复读取CH5第一次读到的值是1.8V左右第二次才接近1.0V真实值。这个现象很典型通道切换后第一次采样拿到的是上一个通道的残留电压等待一段时间或者再采一次才正确。我一开始还以为是4067的通道切换逻辑出问题了通道没切过去结果用万用表量SIG引脚发现电压实际上已经切换到位了但ADC读出来的值却是错的。5.2 排查链路我把当时的排查过程按顺序记录下来方便遇到同类问题的朋友对照第一步用万用表确认S0到S3的GPIO电平是否正确。测量结果发现地址线电平组合完全正确CH5对应的二进制0101确实已经作用到芯片地址引脚上。第二步用示波器观察SIG引脚的电压波形。示波器显示地址切换之后SIG引脚上的电压确实是1.0V而且稳定说明4067本身切换没有问题电压已经正确输出到了公共端。第三步问题收敛到了MCU的ADC采样环节。重新检查ADC初始化代码发现采样周期配置的是1.5周期这是STM32 ADC最短的采样周期配置。到这里基本可以定位了问题出在ADC内部采样电容的建立时间不够。ADC采样时内部采样保持电容要先充电到输入电压才能开始转换如果采样窗口太短电容还没有完全充到目标电压就进入了保持阶段读到的值自然偏低或者偏向上一个通道的电压值。5.3 根因分析模拟开关切换完成后整个信号链路可以等效成一个RC充电回路。信号源内阻加上4067的导通电阻Ron构成了R信号通路的寄生电容和ADC采样电容构成了C。当切换到新通道时C要经过R充电到新电压充电时间是RC的若干倍。如果采样窗口小于这个充电时间采到的就是充电中间态也就是上一个通道的残留电压。这个项目的信号源等效内阻约5kΩ加上4067的Ron约100Ω总共约5.1kΩ。ADC采样电容典型值按8pF算加上走线和芯片寄生电容约15pFRC时间常数大约5.1kΩ乘以15pF约76.5纳秒。看起来非常小但ADC采样时间只有1.5个ADC时钟周期在12MHz ADC时钟下只有125纳秒。这时候问题就来了125纳秒的采样窗口对应大约1.6倍时间常数采样电容大约只能充到目标电压的80%。这个偏差已经是ADC转换精度的量级了。5.4 解决方案解决思路有两个方向我从实际效果来看两个都可以用。第一个方案是加长通道切换后的等待时间。我最终在切换通道后延时100微秒远大于前面的计算值确保信号完全建立后再采。这个方案的好处是简单直接代码看起来也直观适合采样频率要求不高的场景。第二个方案是拉长ADC采样周期。把ADC采样周期从1.5周期改成239.5周期采样窗口显著拉长采样电容也就能充分充电了。这个方案适合ADC在连续采样、不方便插入延时的场景。两个方案选哪个取决于你的整体采样频率要求。如果16路扫描周期在2ms左右还有余量直接用延时的方案就够了。如果对扫描速度有更高要求就试着拉长采样周期找到精度和速度之间的平衡点。6. 调试坑二高内阻信号源接入后ADC读数系统性偏低第一个坑解决之后我以为后面就顺了结果换上一批高阻信号源做测试时又发现新问题。这个坑比第一个更隐蔽也更考验对模拟电路的底层理解。6.1 现象描述测试时用了一个100kΩ电位器作为信号源电位器分压后输出电压用万用表测量是1.20V但通过4067送到ADC读出来的值换算成电压之后是1.02V偏低了不少。一开始我以为还是第一个坑的采样时间问题但把延时加到500微秒、采样周期拉到最大之后读数依然是1.02V左右没有明显改善。这说明问题不在采样时间而是信号源本身带不动4067加ADC这个负载。万用表的内阻是10MΩ级别测量时几乎不从信号源抽取电流所以测出来是真实电压。但4067的Ron和ADC采样电路加起来会从信号源抽取一定电流如果信号源内阻很大这个负载效应就会把电压拉低。6.2 排查链路这次的排查过程比坑一更曲折我按顺序记录第一步检查参考电压和分压电阻的标称值。确认VCC是稳定的3.3V电位器本身阻值也正常没有焊接虚焊的问题。第二步怀疑4067的Ron导致分压。用20kΩ电位器替换100kΩ电位器发现读数偏差明显减小这个实验证实了我的猜测问题确实跟信号源内阻相关信号源内阻越大读数偏差越大。第三步用示波器看SIG引脚在ADC采样期间的实际波形。重点观察采样保持期间SIG电压是否有下降结果确实看到了电压塌陷的现象。第四步确认根因是信号源内阻、4067导通电阻和ADC输入阻抗共同构成了分压关系把输入电压拉低了。6.3 根因分析要充分理解这个问题需要把ADC采样过程再往深里拆一层。ADC在采样阶段内部采样电容会通过采样开关接到输入引脚上这时信号源要向这个电容充电充电电流的路径是信号源 - 信号源内阻 - 4067的Ron - 采样开关 - 采样电容。在这个充电过程中ADC的输入等效阻抗不是一个单纯的电阻而是一个动态变化的量。采样开关闭合的瞬间输入阻抗很低因为采样电容刚开始充电需要很大的电流。如果信号源内阻很高能提供的电流很有限采样电容就充不满最终转换结果就会是个偏低的值。这个问题的本质是信号源驱动能力不足。万用表这类高阻抗测量设备不会被这个效应影响但4067的Ron再加上ADC采样时的瞬态电流需求就让高阻信号源的缺陷暴露出来了。6.4 解决方案解决这个问题有几种思路我从工程实践角度按推荐顺序给你列出来第一个方案是降低信号源内阻。如果用电阻分压网络做信号源分压电阻尽量选低一点的阻值。10kΩ级别的分压电阻在这个场景下基本没有明显问题100kΩ级别就会出现可以观察到的读数偏差。如果你受限于功耗或功耗预算必须用高阻值电阻分压就需要用后面的方案。第二个方案是在4067的SIG引脚后面加一级运放电压跟随器。运放输入阻抗极高基本不向前级索取电流输出阻抗极低可以轻松驱动ADC采样电容。这个方案解决得很彻底是目前工程上最稳妥的做法代价是增加一片运放的成本和一点PCB面积。第三个方案是软件层面补偿校准。这个方案需要对每一路信号做标定把ADC读到的值和实际值建一个映射关系在代码里做补偿。遇到信号源内阻变化、温度变化时补偿关系会漂移所以我个人不推荐作为唯一手段但作为辅助手段是没问题。三个方案我最终选了运放跟随器因为它的效果最直观而且一劳永逸。调试的时候用一块LMV321单运放小板临时接上去验证读数偏差立刻就消失了。7. 还有一些经验电压范围、使能脚、通道悬空等容易中招的细节除了上面两个大坑实际调试过程中还积累了一些零散经验。这些细节单独看都不大但每一个都可能让采集结果变得不对劲我集中写出来供参考。7.1 VCC电压不要和输入信号范围对着干4067的VCC引脚天然决定了模拟通道的电压上限。我一开始想着4067的Ron在VCC较高时更小就把VCC接到了5V结果发现3.3V的MCU GPIO驱动S0到S3时高电平只有3.3V而5V供电下的4067对逻辑高电平的要求接近3.5VGPIO的3.3V高电平无法稳定识别通道切换就变得不可靠了。后来我把VCC接到3.3V跟MCU逻辑电平对齐这个问题直接消失。所以如果你用的是3.3V的MCUVCC就用3.3V不要为了降低Ron去提高VCC逻辑兼容性比那几十欧姆的Ron重要得多。7.2 EN使能脚一定要有确定的电平EN悬空是板上最常见的疏漏之一。4067的EN输入如果悬空芯片内部逻辑状态不确定表现为通道时而导通时而不导通排查起来非常头疼。最靠谱的做法是用一个GPIO单独控制EN软件初始化时拉低使能如果不想占用GPIO直接硬件拉低到GND也是可以的。7.3 未用通道处理要果断未使用的通道不接任何东西芯片内部逻辑虽然不会把悬空通道接入但悬空的引脚会因为外部电磁干扰产生不确定电压通过芯片内部的寄生电容和漏电流影响公共端SIG。我在板子上把未用通道直接接到GND扫描时那些通道也会被选中读到0值对正常通道没有干扰。7.4 定期巡检的项目数据跳变先查电源和参考地如果某个通道的采集值出现无规律的跳变先别急着怀疑4067。我用一个旧开发板测试时出现过类似现象排查了很久最后发现是面包板供电线上压降太大MCU和4067的VCC之间还有几十毫伏的波动ADC参考电压自然也跟着抖。把供电改成独立的稳压输出后问题消失了。多路模拟采集系统对电源质量的要求比纯数字电路要高一个档次这个心理准备要有。7.5 4067可以双向使用但电流限制要注意CD74HC4067本质上是一个双向模拟开关除了当多路选择器把多路信号汇到一路ADC也可以反过来当多路分配器把一路信号分到16个输出中的一个。不过双向可用的前提是信号电流要控制在芯片允许范围内它毕竟不是功率开关不能拿它切大电流负载。我用它做过一个简单的信号路由实验效果正常但始终记得控制电流在毫安级别。CD74HC4067这个芯片本身不难用难的是模拟电路里的那些看不见的电容、阻抗和建立时间。项目做完之后我的体会是模拟开关方案的成败往往不在芯片本身而在对整个信号链路阻抗和时序的理解上。只要把这两点想清楚16路ADC扩展就是一个性价比很高的方案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进