CD74HC4067扩展MCU ADC通道:16路模拟采集方案与调试避坑指南
2026/9/4 12:27:26 网站建设 项目流程

在嵌入式项目里,ADC通道不够用是特别常见的需求痛点。我之前做过一个环境监测板,单颗MCU要采集8路模拟量,还不包括板内的电流电压监测,满打满算ADC引脚刚刚够用。后来产品升级,需要把传感器数量翻倍到16路,MCU又不能换,这时候就得想办法做通道扩展。这次用的方案就是标题里说的CD74HC4067,一颗典型的16通道模拟多路复用器。

先说一下为什么选它而不是别的方案。市面上能扩展ADC的方案其实不少,比如用多个ADC芯片通过I2C或者SPI总线挂,比如用运放做求和电路轮询选通,还有直接用带更多ADC通道的大封装MCU。但在我这个场景下,CD74HC4067是最合适的选择,原因有三个:一是它不需要额外的总线协议,纯逻辑电平控制,代码逻辑极其简单;二是它成本低,几块钱一颗,外围电路只要几个退耦电容,基本零外围;三是它的模拟开关特性对直流信号和中低速信号几乎没有衰减。如果你的项目也是MCU原生ADC通道不够用,而且传感器输出的是电压信号,这个方案值得参考。

这篇笔记我打算把硬件连接、驱动代码、采样时序这些基础内容讲透,但重点还是放在标题里说的“两个调试坑”上——这两个坑一个差点让整个方案推翻,另一个花了我一整个下午才定位到,都是网上资料里很少提到的地方,写出来帮大家避避雷。

1. 方案整体设计与CD74HC4067工作原理

1.1 一颗芯片如何把1路ADC变成16路

CD74HC4067本质上是一个由4根地址线控制的单刀16掷模拟开关。芯片上有4根地址线A0、A1、A2、A3,组合起来刚好是2的4次方等于16种状态,对应16个通道的选通。还有一根使能引脚EN,低电平有效,当EN拉高时所有通道全部断开,这个引脚用来控制整颗芯片的开关状态。

信号流向是这样的:16个输入通道接外部的传感器信号,公共端COM脚接MCU的ADC输入引脚。当你通过地址线选中某个通道时,该通道和COM之间就会导通,外部信号就能进到MCU的ADC引脚里。因为所有通道共用同一个COM端,所以同一时刻只能有一个通道被选通,这就是一个典型的时分复用采样方案。

芯片的供电电压范围是2V到12V,逻辑电平兼容TTL,这意味着你完全可以用3.3V的MCU直接驱动它的地址线和使能脚,不需要额外的电平转换电路,对嵌入式开发来说省了不少事。需要注意的是,模拟信号输入范围不能超过电源电压,也不能低于地电位,超出范围会导致内部ESD二极管导通,轻则采样不准,重则烧芯片。

1.2 硬件设计:引脚分配与信号完整性考量

硬件连接是我在打样前反复核对过的部分。我用的MCU原生ADC有3个可用通道,这次只用了其中1个——PC0口作为ADC输入。地址线A0、A1、A2、A3分别接到MCU的四个普通GPIO口PB0到PB3,EN使能脚接到了PB4,这样做的目的是让控制引脚集中在一组端口,方便后续代码里用位操作一次性切换通道。

芯片端的信号输入通道我按CH0到CH15顺序排布,前8路CH0到CH7接的是一路传感器阵列的电压输出,后8路CH8到CH15接的是另一组需要监测的信号源。所有传感器输出都是0到3.3V的模拟电压,这正好在芯片和MCU的采样范围内。COM端到MCU ADC引脚之间我串了一个100欧姆的电阻,这个电阻的作用是在长走线和开关切换瞬间限制电流,保护ADC引脚,实测对采样精度的影响很小。

电源退耦方面,我在CD74HC4067的VCC引脚和地之间并了一个0.1uF的陶瓷电容,位置尽可能靠近芯片引脚。这个电容的作用是滤除电源线上的高频噪声,避免模拟开关内部的逻辑电路翻转时产生的干扰窜进模拟通路。高速数字信号和模拟信号在PCB上要尽量分开走,如果板子空间紧张,至少保证地平面完整,模拟信号线不要跨过数字信号线的回流路径。我这次是两层板,做的时候特意把模拟信号线和控制线隔开了一段距离,实测串扰在可接受范围内。

1.3 为什么用多路复用而不是直接换更多ADC通道的MCU

可能有人要问,直接换一颗ADC通道更多的MCU不就行了?这个问题的答案取决于项目约束。我当时的项目MCU已经选型冻结,ROM空间和外设资源都刚好够用,如果要换芯片,整个BSP、外设驱动、甚至PCB封装都要重做,周期和成本都不可控。CD74HC4067的方案只需要改几根飞线和几十行代码,就能让ADC通道数翻一倍以上,性价比非常明显。

另外还有一点,当信号源数量多且采样速率要求不高时,多路复用的方式本身就是划算的。比如我的场景是环境监测,传感器输出变化很慢,1秒采一遍都嫌快,16路通道轮询一遍耗时不到1毫秒,性能完全冗余。但如果你的信号是高频动态信号,比如音频或者振动波形,那多路复用就不太适合了,因为每路信号实际有效的采样率只剩原始的十六分之一,这时候应该考虑真正的多ADC并行采样或者增加外部ADC芯片。

2. 驱动代码与采样时序设计

2.1 基础驱动:一个函数搞定通道切换

驱动CD74HC4067的代码非常直观,核心就是一个通道切换函数加一个采样函数。地址线映射的规则是:A0对应bit0,A1对应bit1,A2对应bit2,A3对应bit3,选中的通道号直接写入GPIO端口即可。我用的MCU是Cortex-M0内核,GPIO操作通过寄存器直接赋值,效率很高。

#define MUX_A0_PORT GPIOB #define MUX_A0_PIN GPIO_PIN_0 #define MUX_A1_PORT GPIOB #define MUX_A1_PIN GPIO_PIN_1 #define MUX_A2_PORT GPIOB #define MUX_A2_PIN GPIO_PIN_2 #define MUX_A3_PORT GPIOB #define MUX_A3_PIN GPIO_PIN_3 #define MUX_EN_PORT GPIOB #define MUX_EN_PIN GPIO_PIN_4 void mux_set_channel(uint8_t ch) { if (ch > 15) { ch = 15; } HAL_GPIO_WritePin(MUX_A0_PORT, MUX_A0_PIN, (ch & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_A1_PORT, MUX_A1_PIN, (ch & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_A2_PORT, MUX_A2_PIN, (ch & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_A3_PORT, MUX_A3_PIN, (ch & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); } void mux_enable(void) { HAL_GPIO_WritePin(MUX_EN_PORT, MUX_EN_PIN, GPIO_PIN_RESET); } void mux_disable(void) { HAL_GPIO_WritePin(MUX_EN_PORT, MUX_EN_PIN, GPIO_PIN_SET); }

上面这段代码用的是HAL库的GPIO操作函数,如果用的是标准外设库或者裸机寄存器操作,原理一模一样,只是写寄存器的方式不同。整个驱动就这三四个函数,谈不上什么复杂度。这里有个小技巧,如果控制引脚恰好处于同一个GPIO端口的连续位,可以直接用一个16位端口的并行写操作一次性输出4位地址,速度更快,代码也更简洁,我之所以分开写是因为引脚分布在PB0到PB4,中间有交叉,没法用连续的位段操作。

2.2 采样流程:切换、稳定、采样三步走

实际采样时不能切完通道立刻读ADC,因为模拟开关从选通到信号稳定需要一定时间,这个时间取决于开关的导通电阻和外部电路的对地电容。CD74HC4067的导通电阻典型值在几十欧姆到上百欧姆之间,和电源电压、温度都有关系,输出端再并上电容的话,会形成一个RC低通滤波器,时间常数就是导通电阻乘以电容值。如果不等待稳定就直接采样,读到的电压值会明显偏低或者偏高,和真实值差很多。

所以我的采样流程固定三步:先切换通道,然后延时等待,最后读ADC原始值,转换并保存。延时时长的选择要留足裕量,但也不能太长,否则轮询速度会变慢。对于和MCU体积差不多的贴片电容,几十纳法的电容配几十欧姆的导通电阻,时间常数在微秒级,延时个50到100微秒已经足够。

uint16_t mux_read_channel(uint8_t ch, uint8_t sample_cnt) { uint32_t sum = 0; uint16_t adc_val = 0; uint8_t i; mux_set_channel(ch); delay_us(100); for (i = 0; i < sample_cnt; i++) { adc_val = adc_read_single(); sum += adc_val; delay_us(10); } return (uint16_t)(sum / sample_cnt); }

我用的ADC是12位分辨率,单次转换模式,没有开DMA和连续扫描,主要是因为这里对速度要求不高,而且多路复用的时序逻辑用单次转换更好控制。每次读16路,每路采样8次取平均,一轮下来大概也就十几毫秒,对慢速信号来说足够了。如果你的应用采样速度要求更高,可以考虑把ADC配置成DMA模式,先触发一轮转换,然后把数据从DMA缓冲区里搬运出来,这能省掉CPU等待转换的时间。

2.3 关于参考电压和采样精度的几个参数选择

ADC的参考电压直接决定采样精度。我用的MCU内部参考电压是3.3V,也就是ADC满量程对应3.3V,那么12位分辨率下,1个LSB对应的电压是3.3V除以4096,约等于0.8mV。如果传感器输出范围本来就是0到3.3V,这个配置没问题;如果传感器输出范围很窄,比如只有0到1V,那直接用3.3V参考电压就会浪费大量分辨率,这时候应该考虑使用外部参考电压芯片,比如REF3030之类的,把参考电压降到接近信号满量程的数值,精度能提升好几倍。

CD74HC4067本身的导通电阻会带来串联分压误差。当后级ADC输入阻抗不够高时,信号源内阻和开关导通电阻构成分压器,导致ADC读到的电压低于实际值。好在绝大多数MCU的ADC输入级都有采样保持电容,输入等效阻抗很高,几十欧姆的导通电阻造成的误差微乎其微。我实测用高精度万用表对比过,误差在1%以内,完全可以接受。

如果需要更高精度,可以在硬件上改进:一是降低信号源内阻,加一级电压跟随器缓冲;二是在ADC输入引脚加一个到地的电容,形成低通滤波器,抑制开关切换时的毛刺。但增加电容也有副作用,就是信号建立时间变长,切换后等待时间要相应加大。

3. 实操过程记录:从硬件焊接上电到16路采集跑通

3.1 面包板搭建与飞线验证

我习惯先做最小系统验证再画正式板子。先拿面包板和杜邦线搭了一套测试环境:MCU最小系统板一块,CD74HC4067芯片一颗,面包板一块,外接两路可调稳压电源模拟传感器信号。芯片的16个通道引脚在面包板上排开,COM脚用一个杜邦线直接连到MCU的ADC引脚上。地址线和使能脚从MCU的GPIO口飞线过去,注意不要接反,A0对应最低位。

第一次上电的时候,我把所有传感器输出都调成了1.5V左右,这样如果采到的数据都在同一个水平线上,说明基本连接没问题。然后用示波器量了一下通道切换时COM脚上的波形,确认切换动作是正常的——每个通道选通时,COM脚电压应该等于该通道输入电压。这一步非常关键,它能快速区分是硬件连接问题还是软件逻辑问题。

验证通过的标志是:串口打印出来的16路数据分别对应各通道的输入电压,误差在1%以内。我当时还试着把一个通道接到GND,另一个通道接到3.3V,其余通道接1.5V,打印出来的数据能清晰分辨这三个电平,说明寻址和选通都是正确的。

3.2 接入真实传感器后的标定流程

面包板验证通过后,我把方案移植到正式PCB上,焊接好CD74HC4067和外围电容,接入真实的传感器阵列。接上真实传感器后发现了一个新问题:有些传感器的输出阻抗很高,信号在PCB走线上衰减很明显,尤其是走线较长的通道,采到的值和万用表实测值相差比较多。

排查后发现原因有两方面。一是传感器输出端没有加缓冲,输出阻抗和CD74HC4067的导通阻抗以及PCB走线阻抗形成分压,把信号拉低了。二是我在PCB上预留的滤波电容参数取值偏大,导致信号建立时间过长,采样前等待时间不够。解决办法是在每个传感器的输出端加了一级运放跟随器做阻抗变换,再把滤波电容改小,同时把通道切换后的延迟从50微秒增加到200微秒。做完这三件事之后,16路数据的误差全部控制在1%以内。

3.3 软件滤波的必要性

即使硬件上做了处理,ADC采到的数据仍然会有波动。我的信号源是直流电压信号,波动主要来自两方面:一是环境电磁干扰耦合到长走线上,二是MCU内部ADC本身的量化噪声。为了降低波动,我采用了两种方法:程序中做了多次采样取平均值,每路采8到16次;硬件上在ADC输入引脚对地并联了一个100nF滤高频电容。

实际使用中,多次采样取平均对随机噪声的抑制效果非常明显,波动从原来的约10个LSB降到了2到3个LSB,对应电压波动约2.4mV。如果信号更干净,波动会更小。对于明显的尖峰干扰,简单的平均值滤波压不住,我会再加一阶低通滤波或者中值滤波,看具体波形决定。这套组合拳下来,数据稳定性完全满足项目要求。

4. 两个调试坑的完整复盘

4.1 坑一:通道切换后数据串扰严重,原因竟是使能脚浮空

这个问题是在面包板验证阶段发现的。当时调程序,发现选通某个通道时,采样值偶尔会跳到另一个通道的电压上去。比如我选通CH3,输入1.5V,但采回来的数据一会儿是1.5V,一会儿又变成了2.8V,而这个2.8V是CH5的输入电压,导致整个数据流看起来就是在随机跳变。

最初怀疑是地址线时序问题,加了延时没改善;又怀疑是芯片本身损坏,换了一颗芯片依然复现。后来用示波器逐个引脚去量,发现EN使能脚的电平居然是浮空的,高不成低不就的样子。这才想起来,我之前把EN脚接到了一个IO口上,但IO口忘了初始化为输出模式,默认是浮空输入。芯片手册明确写了EN脚低电平有效,信号浮空意味着有可能在某个半高电平附近抖动,相当于芯片随机地被使能或者禁止。

解决方法是把EN引脚固定接低电平,或者像我在正式板上做的那样,用GPIO控制并确保初始化成推挽输出模式。如果想更稳妥,硬件上可以直接把EN脚通过一个10K电阻下拉到地,这样即使固件没有初始化这个引脚,芯片也默认处于使能状态,不会因为浮空导致通道随机导通。

这个坑给我最大的教训:所有控制引脚的GPIO模式一定要在初始化代码里显式设置,不能被默认状态碰运气。尤其是带使能脚的芯片,任何一个控制引脚状态不确定,整个系统的行为就无法预期。

4.2 坑二:单次采样偶发偏差大,排查后指向电源耦合噪声

第一个坑解决之后,数据的稳定性好了很多,但仍然偶发单个采样点偏差特别大的情况,而且没有任何规律。用示波器监测ADC引脚的波形,发现正常情况下的波形很干净,但偶尔会出现窄脉冲毛刺,这个毛刺的幅度能到几百毫伏,宽度只有几十纳秒,ADC采样恰好落在毛刺上就会采到一个很大的偏差值。

排查过程比较曲折。一开始怀疑是传感器信号本身的问题,换了不同的信号源,毛刺依然存在。又怀疑是芯片开关切换时的电荷注入效应,但加长延时之后毛刺还在。后来我偶然用示波器去看了CD74HC4067的VCC引脚,发现电源引脚上的噪声明显偏高,有几十毫伏的周期性波动和尖峰。这其实是很典型的电源问题:CD74HC4067内部是一个复杂的模拟开关矩阵,通道切换时内部晶体管高速通断,会在电源线上产生电流尖峰,如果没有足够的退耦电容吸收,这些尖峰就会通过芯片内部电路耦合到COM脚上。

解决办法很直接:在CD74HC4067的VCC和GND之间靠近引脚处并联一个0.1uF陶瓷电容,如果空间允许,最好再加一个10uF电解电容做低频退耦。我原来只在电源输入端放了一个大电容,芯片附近没有放,这就是失策之处。加了两个退耦电容之后,电源噪声明显下降,偶发偏差基本消失。

这个坑说明一个道理:模拟电路的设计永远要把电源完整性放在第一位。无论你的模拟开关芯片本身性能多好,如果电源不干净,一切都是白搭。板子上的每个模拟芯片,都默认应该有一个就近的退耦电容,这不是可选项,而是必选项。

4.3 两个坑之外的其他小问题速查

除了上面两个大坑,调试过程中还遇到了一些较小的问题,虽然不至于卡住进度,但也值得记录一下。

一个是关于ADC采样值偏高的问题。排查了一圈,最后发现是ADC参考电压配置不对——我把参考电压配置成了内部参考,但实际硬件接了外部参考源,导致读数偏移。这种问题在代码移植时很常见,从别的项目复用驱动代码时,一定要仔细检查参考电压相关的配置寄存器。

另一个是接触不良问题。面包板上的杜邦线插接处氧化或者松动,会导致通道阻抗变大,采到的电压偏低且不稳定。这个在开发验证阶段遇到过几次,排查办法很简单——万用表量一下信号源端和芯片输入端的电压,如果不一致,多半是接触电阻的问题。

还有一个是关于采样顺序的安排。16路采样的时候,如果第一路和最后一路的间隔时间太长,对于变化缓慢的信号无所谓,但对于变化较快的信号,各通道之间的时间差会被当成幅值差,造成误判。建议在轮询循环中固定顺序,并记录每轮采样的时间戳,方便后续处理时间相关性。

现象可能原因排查方法解决办法
采样值在多个通道间跳变使能脚浮空示波器量EN引脚电平EN脚固定下拉或初始化GPIO推挽输出
偶发单个采样点偏差大电源去耦不足引发毛刺示波器检查VCC引脚噪声芯片附近加0.1uF+10uF退耦电容
所有通道采样值整体偏高或偏低参考电压配置错误读取ADC参考电压寄存器按实际硬件配置参考源
某个通道数据时好时坏接触不良或焊点虚焊万用表对比信号源和芯片输入端重新插拔或补焊
信号建立慢导致数值偏低后端滤波电容过大计算RC时间常数减小电容或增大延时

5. 方案扩展与性能边界探讨

5.1 还能不能继续扩展更多通道

当你的需求超过16路ADC时,一个直观的想法是多级级联。比如用4颗CD74HC4067,每颗提供16路,再用更高位的多路复用器选通4颗芯片的COM端,这样最多可以扩展到64路输入。这种多级方案的代价是信号路径变长,导通电阻叠加,精度会更难保证,而且代码也要做两层通道映射。

如果不想增加复杂度,还有一个替代方向:在CD74HC4067之后再加一颗ADC芯片,比如ADS1115或者ADS1220,用I2C或者SPI总线和MCU通信。本质上就是把多路复用和ADC转换解耦,每一级都用最合适的芯片,精度和可扩展性都更好。缺点是成本更高,固件也要引入外置ADC驱动。哪种方案更合理,取决于你对精度、成本、开发周期的权衡。

5.2 CD74HC4067与新型I2C多路复用器的对比

市面上还有一类I2C接口的多路复用器,比如TCA9548A,可以扩展I2C总线的通道数。这类芯片的用途和CD74HC4067不同,TCA9548A是给I2C设备做总线切换用的,不能直接用来扩展模拟量采样通道。有些人会混淆这两类芯片的用途,在选型时需要注意区分。

如果传感器本身就是数字接口,比如I2C或SPI的温湿度传感器,那扩展通道应该用TCA9548A这类总线多路复用器。如果传感器输出的是模拟电压或电流,那就需要CD74HC4067这种模拟开关。选错类型,方案大概率要返工。

5.3 采样率的边界与性能优化方向

CD74HC4067的开关时间参数大约在几十纳秒级别,理论切换频率可以达到几MHz,但实际系统的采样率远远达不到这个数值,真正的瓶颈在ADC转换时间和信号建立时间。我目前实现的16路轮询周期大约是十几毫秒,折算下来每路等效采样率不到100Hz,对慢速信号足够。

如果要把某一路的采样率提升到几千赫兹甚至更高,可以考虑动态通道分配的策略:16路信号里只重点采几路,其他通道降低采样频率。比如在代码里判断当前要采样哪一路,优先保证关键通道的采样率,把不重要的通道放到轮询间隙去采。这种思路在老式的数据采集系统里叫优先扫描和普通扫描结合,实现起来并不复杂,但能显著提升系统的实用性。

6. 写在最后的一点心得

这套CD74HC4067扩展16路ADC的方案,整体调试下来最大的感受是:模拟开关本身并不复杂,真正的复杂度全藏在那些看起来不起眼的细节里。比如GPIO初始化模式忘了设、芯片退耦电容漏加,这两个问题任何一个都能让整个系统表现得很奇怪,但排查起来又特别费时。反而是芯片的通道切换、地址译码这些核心机制,几乎没出过问题。

以后再遇到类似的通道扩展需求,我会更早地把电源完整性和GPIO初始化状态检查放进测试清单里,而不是等到数据异常了再去排查。调试的过程就是一个不断排除变量的过程,把变量的范围缩小得足够小,问题自然就会浮现出来。

我这里用的方案和代码都是最简单直接的那一类,没有加任何跑偏的东西。如果你刚好也在调试CD74HC4067,或者正犹豫要不要用这个方案,希望这篇笔记能帮你少走几步弯路。有更好的思路也欢迎交流,毕竟嵌入式这块儿,不同的项目能遇到的坑,比芯片手册上写的要多得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询