资讯动态

CD74HC4067多路复用器实战:用4个GPIO扩展16路ADC采样

发布时间:2026/9/5 8:59:51 来源:尧图企业网站定制
1. 为什么用CD74HC4067MCU的ADC引脚永远不够用做嵌入式开发尤其是做传感器数据采集类项目时有一个问题几乎绕不开MCU的ADC通道不够用。我手头这块主控芯片自带ADC引脚也就十几个听起来不少可一旦接上多路传感器、电位器、模拟量变送器或者要做电池电压监测加温度采集再加几个旋钮输入的组合引脚瞬间就被占满了。换一颗ADC通道更多的 MCU当然是个办法但代价是重新画板子、改封装、调代码项目周期和成本都受不了。另一种方案是用外部ADC芯片比如ADS1115、ADS1256这类优点是精度高、带可编程增益放大器但价格不便宜而且走I2C或SPI接口也要占用额外的外设资源。CD74HC4067这颗芯片解决的正是这个问题。它本质上是一个16通道的模拟多路复用器/解复用器通过4根选择引脚S0到S3的高低电平组合可以从16路输入中挑一路接到公共引脚COM。换句话说MCU只需要付出4个GPIO加1个ADC引脚的代价就能采集16路模拟信号。更关键的是这玩意儿的导通电阻典型值只有几十欧姆切换速度也足够快到微秒级别用在常规的传感器采集场景里完全够用。除了通道扩展CD74HC4067还有一个非常实用的特性信号是双向的。既可以拿来把16路模拟输入分时送到单片机的ADC引脚也可以反向操作用一个输出引脚分时驱动16路数字负载。我实际用过它做LED指示灯分组扫描虽然IO扩展芯片如74HC595也能干这事但4067不需要串行时钟逻辑更直接。这颗芯片常见的替代型号还有74HC4051单通道8选1、74HC4052双通道4选1和74HC4053三通道2选1看需求选择即可。回到本文的场景我的应用是采集16路电位器电压信号用来做一套多旋钮的模拟量输入面板。每一路都是一个10K电位器分压从0V到3.3V连续可调ADC转换精度设定为12位。系统需要实时刷新所有16路的当前电压值刷新周期要求不超过50ms——这个性能要求CD74HC4067应付起来绰绰有余。2. 硬件接线与选型看似简单但容易踩坑的地方2.1 引脚分布与最小接线方案CD74HC4067的引脚功能分布很清晰拿到手第一件事就是对着数据手册把关键引脚认全。常用的引脚如下16路输入通道X0到X15在芯片右侧公共输出端COM在左下角靠近GND的位置选择引脚S0到S3以及使能引脚EN在芯片底部电源VCC和GND在两侧。这里要重点说明一下使能引脚EN。它低电平有效也就是说把它接到GND时芯片正常工作接到高电平时所有通道全部断开COM输出呈高阻态。很多初学者第一次用这颗芯片时会忽略EN引脚悬空不接——这是个大坑因为EN内部没有上下拉电阻悬空状态下电平不确定会导致通道切换完全混乱。正确做法是直接硬接地或者用一个10K电阻下拉到GND然后在需要批量关断所有通道时再通过GPIO拉高控制。接线方案抄作业版本如下VCC接3.3V也可以接5V但要注意选通电平阈值会跟着变GND接系统共地EN接GND低电平使能S0、S1、S2、S3分别接MCU的4个GPIOX0到X15分别接16路模拟信号输入COM接MCU的ADC引脚我从实践中得到一个还算有用的经验如果主板和采集板分离建议在COM输出到MCU ADC引脚之间串一个1K电阻加100nF电容到地组成RC低通滤波。这样做有两个好处第一端口接反或者热插拔时1K电阻能起到限流保护作用第二RC滤波可以滤掉通道切换时产生的高频毛刺让ADC采到的值稳定不少。代价是信号响应会慢一点点但100nF配1K的RC时间常数只有0.1ms远小于采样周期影响可以忽略。2.2 电源电压与逻辑电平匹配问题CD74HC4067可以在2V到6V的宽电压范围内工作这给设计带来了不少便利但也埋了一个隐患——前级输入信号的电压范围必须控制在芯片供电电压和GND之间。比如说我系统里ADC参考电压是3.3V芯片也接3.3V供电那X0到X15的输入电压就不能超过3.3V否则内部模拟开关会进入非线性区导通电阻剧增甚至可能通过ESD保护二极管产生漏电流。如果你要采集的信号源供电是5V而MCU是3.3V系统那就要小心了。最简单稳妥的办法是把4067的VCC也接5V但这样一来S0到S3的选择信号如果用3.3V GPIO驱动逻辑高电平电压2.0V左右已经超过5V供电下芯片的VIH阈值实际测试是能正常工作的。不过为了保险起见我还是建议这种情况用两个电阻分压或者加电平转换芯片把控制信号抬到5V防止在高温或者电源波动时出现误判。2.3 公用ADC引脚输入阻抗与源阻抗的影响这可能是玩4067时问题最多、却最少被人注意到的地方——源阻抗和输入阻抗的匹配问题。CD74HC4067的导通电阻RON虽然标称只有几十欧姆但它不是固定的会随着输入电压、电源电压、温度这几个变量浮动。具体数值可以从数据手册的RON-VIN曲线图上查到大致规律是输入电压接近VCC一半时导通电阻最大输入电压接近电源轨时导通电阻最小。更麻烦的是MCU的ADC采样本身要求输入源阻抗不能太高。以STM32的12位ADC为例数据手册里给出的采样时间对应的建议源阻抗典型值是10K左右超过这个值之后采样精度就会下降。如果前级接的是高阻抗信号源比如几十K的分压电阻网络那信号经过4067的几十欧姆RON之后进ADC总源阻抗很容易超过推荐范围导致采样值偏小或波动。我的处理方法是在每一路信号进入4067之前都先用运放跟随器缓冲一下。如果信号源本身输出阻抗就很低比如是运放输出或者稳压后的电压那可以省略缓冲直接接。16路传感器全都接运放确实有点奢侈成本高而且占地方所以我在实际项目中采用了一个折中方案——对电位器这类高阻抗源的信号把电位器阻值从10K降到1K这样源阻抗降到2.5K以下再经过4067串入的几十欧姆导通电阻总阻抗也就在2.6K左右直接喂给ADC完全没问题。3. 软件控制逻辑与采样时序设计3.1 通道选通表与GPIO控制实现CD74HC4067的通道选通逻辑非常简单S0、S1、S2、S3四个引脚组成一个4位二进制数对应16个通道。S0是最低位S3是最高位。通道0对应二进制0000通道15对应二进制1111。我用STM32的HAL库写了一段控制函数代码看起来像这样#define MUX_EN_PORT GPIOA #define MUX_EN_PIN GPIO_PIN_4 #define MUX_S0_PORT GPIOA #define MUX_S0_PIN GPIO_PIN_5 #define MUX_S1_PORT GPIOA #define MUX_S1_PIN GPIO_PIN_6 #define MUX_S2_PORT GPIOA #define MUX_S2_PIN GPIO_PIN_7 #define MUX_S3_PORT GPIOB #define MUX_S3_PIN GPIO_PIN_0 void MUX_SelectChannel(uint8_t ch) { if(ch 15) ch 15; HAL_GPIO_WritePin(MUX_S0_PORT, MUX_S0_PIN, (ch 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_S1_PORT, MUX_S1_PIN, (ch 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_S2_PORT, MUX_S2_PIN, (ch 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_S3_PORT, MUX_S3_PIN, (ch 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); /* 等待模拟开关稳定 */ delay_us(10); }这段代码的核心思路是先把待切换通道号拆成四个二进制位分别写到S0到S3引脚上然后延时10微秒等待模拟开关内部导通稳定。GPIO的初始化用STM32CubeMX自动生成就没问题记得把S0到S3和EN都配置成推挽输出初始电平全部拉低。有一点值得注意切换通道后不能马上就启动ADC采样必须给模拟开关一点稳定时间。4067的导通时间典型值在几十纳秒量级但考虑到PCB走线寄生电容、RC滤波网络的建立时间我习惯预留10到20微秒再触发采样实测发现效果比不延时直接采样好得多。3.2 多路轮询采样与数据存储策略最自然的思路是在一个定时器中断里按顺序切换16个通道每个通道启动一次ADC转换转换完成后再切下一个。但这样有个问题ADC转换完成标志需要等待占用了CPU时间。16路采样下来如果每路都同步等待CPU基本就被卡死了中断里做不了其他事。更好的方案是采用DMAPing-Pong缓冲的轮询策略。我先用定时器触发一个固定的采样节拍在节拍中断里切换通道然后立刻启动ADC的DMA传输DMA把转换结果自动搬运到缓冲区传输完成中断里再切换下一路通道以此循环。简化版的实现框架如下#define ADC_CHANNEL_COUNT 16 volatile uint16_t adc_raw[ADC_CHANNEL_COUNT]; volatile uint8_t current_channel 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc-Instance ADC1) { adc_raw[current_channel] g_adc_val; current_channel; if(current_channel ADC_CHANNEL_COUNT) { current_channel 0; g_adc_scan_complete 1; } MUX_SelectChannel(current_channel); HAL_ADC_Start_DMA(hadc1, (uint32_t*)g_adc_val, 1); } }这里有一个很关键的小技巧DMA传输长度设置为1一次只搬运一个通道的转换结果。每次DMA完成中断里读取结果、切换下一个通道、再启动下一次传输。这样循环往复16路通道的采样值源源不断地进入adc_raw数组。如此设计的优点在于整个采样过程完全由DMA和中断驱动主循环不需要等待ADC转换。而且因为每次采样之间都间隔了完整的通道切换延时和DMA中断处理时间等效于每个通道都留足了稳定时间采样质量明显好于裸奔轮询。3.3 转换结果处理与电压值计算ADC原始值采集回来之后要换算成实际电压值。这里的计算方式要看MCU的ADC参考电压和分辨率。我用的STM32默认参考电压是3.3VADC是12位分辨率满量程是4096。所以电压计算公式是float voltage (float)adc_raw[i] * 3.3f / 4095.0f;注意4095和4096的区别。STM32的12位ADC满量程读数是4095也就是2的12次方减1所以严格来说应该除以4095而不是4096。虽然大部分时候这点误差小于1mV但如果你追求精度还是应该写对。实际项目里如果MCU的VREF引脚没有接到精密参考源直接用3.3V计算会引入不小误差。因为VREF的实际值不一定是精确的3.3V如果板子上用的是普通LDO稳压器供电可能实际只有3.27V。这种情况下你可以在板上放一个精密稳压源或者用万用表实测VREF引脚对GND的电压然后把实测值写进代码当参考电压用。我习惯在系统初始化时加一个自检流程把ADC其中一个通道接到板上已经校准好的2.5V Reference芯片根据采样值反推实际的VREF电压这样算出来的电压值精度会好很多。4. 两个调试坑的完整回顾从现象到根因4.1 坑一悬空未用通道引发的串扰现象描述16路电位器全部接上之后采集到的数据表现得很诡异。单独采样某一通道时数值是正常的但只要程序切换到其他通道之前已采样过的通道数值就开始漂移。用手触摸未接线的那几路通道的输入端已经采样完的通道数值会跟着一起变化。排查过程最初怀疑是4067损坏换了一颗新芯片问题依旧。然后怀疑是MCU ADC的参考电压不稳给参考引脚加了大电容滤波还是不行。最后拿示波器直接看MCU ADC引脚的波形发现当某一通道悬空时该通道输入引脚上的电压在随机跳变频率还不低。根因分析CD74HC4067悬空的输入引脚处于高阻态没有任何路径把引脚电位拉到一个确定电平。因此引脚上的电压完全受外界电场、手指靠近时的耦合电容影响随机浮动。当通道切换到悬空引脚时COM引脚直接连到这个高阻态的引脚上而COM引脚连接着ADC采样电容悬空引脚的噪声电压就通过开关管耦合到了ADC输入端。即使ADC没有采样这一个通道但在轮询循环中它会短暂地打开悬空开关这个操作会在COM上注入额外电荷形成逐个通道的串扰。解决方案把所有未使用的输入引脚统一接到GND或者接一个上拉电阻到GND。我选的是在PCB设计时把所有未用到通道的X引脚在背面的焊盘上直接短接到GND。对于已有PCB板子不好改的情况可以飞一根线把所有悬空引脚接到一起再接地。这个坑给人的教训是多路复用器输入端最忌讳开路所有输入都要有一个确定的直流电平要么接信号要么接地绝对不要留白。4.2 坑二GPIO切换毛刺引发的ADC采集错乱现象描述修复坑一之后通道间串扰的问题消失了但新问题随之而来每次上电后的首次采集第0路数据总是不对有时候是最大值有时候是最小值有时候直接是乱码。但软件复位或者多次采集之后又恢复正常看起来很有规律——只有第一次不对。排查过程用示波器观察MCU ADC引脚的波形切换通道时可以发现一个很窄的负向毛刺大概只有几百纳秒宽但幅度很可观能把信号拉低到接近GND。再放大看毛刺出现的时间恰好就在S0到S3引脚电平变化的那一瞬间。根因分析这个毛刺的来源有两个因素叠加。一是GPIO在做电平翻转时由于内部驱动能力不同4个引脚的翻转时间不完全一致某个瞬间会短暂地选中一个不是目标的“过渡通道”二是4067内部模拟开关导通阻抗和PCB寄生电容构成了一个微分电路把通道切换时的电压阶跃转换成了尖峰毛刺。如果ADC在通道切换后立刻启动采样恰好撞上这个毛刺采出来的值自然就是错的。而后续轮询时毛刺往往已经消失了所以只有第一次采集会中招。解决方案代码层面做两个优化。一是通道切换之后必须插入延时给毛刺留出衰减时间我的做法是延时50微秒二是在初始化完成之后先对整个通道表做一次虚拟扫描即把所有16个通道完整切换一遍让系统先经历一次瞬态之后才开始正式采样。这样首轮的异常值会落在丢弃区不会污染正式数据。C语言实现的伪代码如下void MUX_Init_Sweep(void) { for(uint8_t i 0; i 16; i) { MUX_SelectChannel(i); HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); HAL_ADC_Stop(hadc1); } }4.3 这些坑给整体设计带来的反思回过头看这两个坑都属于典型的模拟电路与数字逻辑交汇处容易出错的问题。坑一的本质是模拟输入缺少直流偏置路径坑二的本质是数字控制信号的瞬态行为影响了模拟采样窗口。反思的结论有三条可以沉淀下来作为以后设计多路复用采样电路时的检查清单所有模拟输入必须保证有明确的直流电平无论是接信号源还是接地不能让引脚悬空。通道切换与ADC采样之间必须插入足够的延时延时时间至少覆盖模拟开关稳定时间加上采样电容充电时间。系统上电后正式采样前先做一次通道扫描预充电让PCB上的寄生电容都充到目标电位再开始正式数据采集。5. 采样稳定性的进一步优化手段5.1 多次采样取平均法与中值滤波法对比CD74HC4067应用中最常见的精度问题是切换通道后ADC读数跳动。即使信号本身很稳定连续读几次也总是在一个范围内波动幅度可能有十几个LSB。这种情况下最有效的手段就是软件滤波。我测试过两种方案。第一种是多次采样取平均思路很简单每个通道连续读取N次求和后除以N。这个方法对白噪声类型的干扰效果最好N取8或者16就能让波动幅度降到1到2个LSB以内。缺点是如果信号本身是突变型的平均会把边沿变缓响应变慢。第二种是冒泡排序后取中值每次读取N个样本按大小排序取中间那个值。这个方法对抗偶发性的尖峰干扰特别有效比如电机启停引起的电源毛刺一次突变性的错误采样不会影响最终结果。缺点是计算量大一些而且如果干扰不是偶发性的而是持续性的效果不明显。我的建议是两种方法结合使用先做一次中值滤波剔除异常值再做一次平均滤波平滑随机噪声。这两个滤波函数加起来也就几十行C语言代码运行时间在微秒级别对于这种低速采集场景无所谓开销。5.2 采样周期与通道稳定时间的定量关系精确计算每个通道需要多少稳定时间可以用RC充电模型估算。简化起见把4067的导通电阻R_ON看作约100欧姆PCB走线和ADC输入端的寄生电容C_PCB看成约100pF那么RC时间常数是100乘以100pF等于10ns。理论上等待5倍时间常数就能让电容充到最终值的99%以上也就是50ns左右。但实际上还有前置的信号源阻抗和RC滤波电容如果我在ADC引脚前加了一个1K电阻和100nF电容组成滤波那时间常数就变成了1K乘以100nF等于100微秒这在采集速率为百Hz级别的应用里影响可以忽略。实操中我不会仔细算这些参数因为芯片手册的导通电阻离散性很大环境温度也会影响它。我就是留出足够裕量切换通道后延时50到100微秒再启动ADC这样不管是100欧姆还是300欧姆的导通电阻都能保证稳定。如果采样速率有严格要求那就把这个延时压缩到10微秒配合更大的滤波电容也能保证精度。5.3 为什么我最终留下了1K电阻加100nF滤波在调试过程中我试过不加任何滤波直接采样也试过加不同参数的滤波最终选择在COM到ADC引脚之间串1K电阻、并联100nF电容到地。这个组合的截止频率大约为1.6KHz对于静态电位器采集和慢变传感器信号完全够用同时还能很好地抑制通道切换毛刺。有一个细节这个滤波电容会存住上一次采样通道的电压切到新通道后需要一个充电时间。如果前后两个通道的电压差很大比如通道0是0V通道1是3V那电容要从0V充到3V需要一段时间。我的延时设计已经覆盖了这个过程所以实际测试中相邻通道的电压差最大时也没有出现明显的首点偏差。如果你对采样速率有更高要求可以把100nF换成10nFRC时间常数缩小10倍截止频率提高到16KHz精度和速率之间做一个折衷。6. 常见问题速查与经验延伸6.1 CD74HC4067应用问题排查一览表这里整理了一份我在调试过程中遇到的问题和对应的排查方向方便读者直接对照参考。现象可能原因排查方向所有通道读数都是0EN引脚电平不对通道没被使能检查EN是否接地某一路固定在最大值该路输入悬空或对VCC短路用万用表量通道输入电压相邻通道数据互相影响悬空通道导致COM节点串扰未用通道全部接地切换通道瞬间读值乱跳GPI翻转毛刺碰上ADC采样切换后加延时避开毛刺采样值整体偏低输入源阻抗太高前级加运放跟随器缓冲采样值波动超过20个LSB电源噪声或参考电压不稳加大滤波电容检査VREF这张表基本涵盖了我在这类采集系统上遇到过的多数问题。如果你碰到表中没有的情况优先用示波器量信号波形比起拿万用表猜要高效得多。6.2 从16路ADC扩展到更多路数的思路16路ADC有时候还是不够用我曾经经历过一次需要用48路模拟输入的方案设计。这时候用CD74HC4067做扩展还有几种级联玩法。最简单的思路是用两片4067组合成32路输入。两片芯片的X通道分别接不同信号源两片芯片的COM端各自接一颗ADC引脚这样用两个ADC通道加4个选择引脚就实现32路采集。如果MCU的ADC引脚只有1个空闲可以用一片4067作为“前级选择器”把两片后级4067的COM端信号合并到一片前级4067的两个输入通道上相当于两级树状选择结构逻辑上就是将两级选通地址拼接成一个更大的地址。级联时要注意一个坑一级选通后整条链路的导通电阻会相加两级就是两倍RON。如果信号源阻抗又高总阻抗可能突破ADC输入要求的极限。所以级联路数多的时候每级后面加一个运放跟随器是很有必要的成本多一点但稳得很。6.3 其他多路ADC扩展方案对比CD74HC4067不是唯一的选择我把几种常见的扩展方案放在一起做了个对比帮助大家根据实际需求选择。方案通道数接口方式优点缺点74HC40518GPIO并行便宜、简单通道数少CD74HC406716GPIO并行便宜、通道多高速采样精度一般ADS11154/8I2C高精度、可配置增益速度慢、价格稍高ADS12568SPI24位精度、高速价格贵、配置复杂多路运放后接多个ADC引脚不限并行采集快、精度高占用ADC引脚多从性价比和易用性角度来说CD74HC4067依然是多路低速模拟采集场景下的王者。如果你的项目对精度要求很高比如需要24位ADC做称重传感器采集那就别用4067了直接上HX711或者ADS1256。多路复用器本身不增加量化精度它只是帮你把模拟通道“切换”到ADC前最终精度上限取决于ADC本身和前端调理电路。7. 实操心得与扩展建议做这个16路ADC扩展项目前后断断续续调了两三个晚上真正遇到的问题其实不太多核心就是那两个坑。但是复盘下来有一些心得体会非常值得记录。第一点用CD74HC4067这类模拟多路复用器时一定要把“模拟输入不能悬空”当一个硬性设计约束来对待。不管是在原理图设计阶段还是PCB布局阶段都要留出处理未使用通道的手段。我见过很多参考设计图纸上把多余的X引脚直接空着这对数字芯片没问题但对模拟复用器来说就是埋雷项目后期必炸。第二点软件上的延时看似简单其实决定了采样数据的质量。通道切换后给模拟开关留出稳定时间这个技巧不仅适用于CD74HC4067任何模拟多路复用器、模拟开关芯片都适用。我之前接74HC4051时也遇到类似的毛刺问题加延时后立竿见影。第三点关于滤波电容的选择我的原则是能用软件解决的问题不要动硬件能用便宜电容解决的问题不要换贵的芯片。100nF电容贴上就能解决大部分采样抖动问题不至于为了ADC稳定性去换一颗高精度ADC或加昂贵的精密参考源。后续如果继续扩展这个项目我有两个方向可以走一是加一个仪表放大器在前端做信号调理这样就能采集毫伏级别的传感器信号比如热电偶、应变片电桥的输出整个系统的适应面会宽很多二是引入RTOS或者定时器驱动的事件循环架构把ADC采样任务和其他控制任务分离让系统实时性更好而不是像现在这样靠中断和大循环驱动。最后分享一个小技巧调试这种多路采集系统时准备一套带标记的杜邦线或者排线把每个通道对应的信号源按顺序标好可以省下大量排查接线错误的时间。我刚开始调试时没做标记结果有一路电位器和另一路电位器的线接反了查了很久才发现是接线问题而不是芯片问题。从此之后所有涉及多路信号的接线一律先贴标签再接这个习惯帮我少踩了很多不必要的坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价