ARTICLE DETAIL

资讯详情

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

CD74HC4067扩展MCU ADC通道:16路模拟采集方案与调试避坑指南

CD74HC4067扩展MCU ADC通道:16路模拟采集方案与调试避坑指南 在嵌入式项目里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到PB3EN使能脚接到了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对应bit0A1对应bit1A2对应bit2A3对应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.1uF10uF退耦电容所有通道采样值整体偏高或偏低参考电压配置错误读取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或者正犹豫要不要用这个方案希望这篇笔记能帮你少走几步弯路。有更好的思路也欢迎交流毕竟嵌入式这块儿不同的项目能遇到的坑比芯片手册上写的要多得多。
返回列表