在做项目的时候需要同时采集十几路模拟量,MCU片上ADC通道不够用,于是我把CD74HC4067请了出来。这东西本质就是一个16选1的模拟多路复用开关,用4个IO就能把1路ADC扩展成16路,性价比很高。这篇文章就是我对整个过程的记录,包括硬件接线、软件控制、以及两个调试时真正踩过的坑——如果你也在用4067扩展ADC,建议认真看看,这几处细节能帮你省下不少排查时间。
1. 为什么需要扩展ADC通道:CD74HC4067能解决什么问题
1.1 多通道采集场景下的真实需求
很多单片机(比如STM32F103系列)片上ADC虽然有多达十几甚至二十几个输入引脚,但如果你要采集多个传感器、多个电压点,同时还需要保留一些引脚做通信、控制、下载调试,那通道数就变得捉襟见肘。比如我做的一个环境监测板,要采集8路NTC温度、4路模拟量输入(0~3.3V信号)、还有2路电池电压监测,加起来14路模拟信号,MCU实在分不出这么多独立ADC引脚,就算分得出,布线也会被逼疯。
还有一种情况是电路板上传感器分布在比较远的区域,如果把每个传感器的模拟信号直接拉回MCU引脚,那你需要布十几根模拟线到主控附近,这些线很容易引入干扰。这时把4067放在信号源附近,通过4根控制线和1根模拟总线把信号汇总回MCU,走线距离大幅缩短,抗干扰能力反而更好。
所以4067这类模拟多路复用器(MUX)的价值就在这:用少量GPIO控制选通,就能把单个ADC输入扩展成多个通道,代价是采样时序上要做一点额外处理。
1.2 CD74HC4067的工作原理
CD74HC4067是高速CMOS模拟多路复用器/多路解复用器,核心结构就是一组由地址线控制的模拟开关。它有16个输入/输出通道(编号X0~X15,或者叫C0~C15),1个公共端(通常叫COM或者SIG),以及4根地址选择线(S0~S3,或者叫A/B/C/D),还有1个使能端(EN,低电平有效)。
当EN为高时,所有通道开关全部断开,公共端呈高阻状态;EN为低时,根据S0~S3这4根线的二进制组合,选通对应的那一路通道连接到公共端。比如S3 S2 S1 S0 = 0000选通通道0,为0001选通通道1,以此类推,1010就选通通道10。
这个设计本质上是一棵由CMOS传输门组成的开关树,信号既可以从公共端流向某个通道,也可以反向传输,所以它也可以用作1转16的解复用器。数据手册上给出的导通电阻典型值在几十欧姆量级(受供电电压影响),带宽可以达到几十甚至上百MHz,对于低频模拟信号采集绰绰有余。
1.3 为什么选CD74HC4067,而不是其他方案
选型时我对比过几个常见替代方案:CD4051(单8选1)、74HC4052(双4选1)、ADG706(低导通电阻16选1)等。CD4051只有8通道,扩展性差一些;74HC4052是两个独立的4通道,适合差分信号,但单端扩展能力还是一般;ADG706性能更好,导通电阻只有几欧,但是价格贵了不少,货源也不如4067好买。对一般单片机采集场景来说,4067已经足够用了,价格便宜、封装种类多(DIP、SOP、TSSOP都有)、货源充足,电气性能满足需求,所以选了它。
另外还有一点,CD74HC4067的导通电阻随供电电压变化,如果你给它的VCC是3.3V,导通电阻会比5V供电时大一些。数据手册上3.3V供电下典型值大约70欧左右,5V下大约40欧。这个电阻虽然不会阻断信号,但会在信号源内阻和ADC输入阻抗之间引入一个额外阻值,影响最终的采样精度,后面第4章会重点展开。
2. 硬件设计与接线实操
2.1 引脚功能与供电细节
拿到CD74HC4067,先看引脚定义。常见的SOP-16封装,引脚大致是:S0、S1、S2、S3(地址线),EN(使能),VCC和GND,公共端SIG(也叫COM),以及X0~X15这16个通道引脚。特别注意,不同的厂家手册对公共端的命名不一样,有的叫COM,有的叫SIG,还有叫Z,接的时候一定认准丝印和实际功能。
供电方面,CD74HC4067的VCC一般建议与单片机供电一致。我的板子是3.3V系统,所以VCC接3.3V,GND与模拟地共地。需要特别注意的是VCC不要接反,CMOS器件对电压很敏感,接反基本必烧。另外在VCC和GND之间就近加一个0.1uF去耦电容,这个建议一定要做,我之前有块板子为了省一个电容没加,结果采样值总有低频波动,后来补上电容后明显稳定很多。
地址线和使能线直接接MCU的GPIO即可,配置为推挽输出。由于4067是CMOS输入,高电平阈值大概在0.5×VCC左右,所以3.3V系统直接IO驱动没问题。如果走线很长,可以串一个33Ω或100Ω的小电阻,改善信号边沿、减少振铃。
2.2 模拟信号通道连接方式
我被问到最多的一个问题是:传感器信号应该接到4067的哪个方向?其实从信号方向上来讲,接哪个方向都行。一种是把16路传感器输出接到X0~X15,公共端SIG接到MCU的ADC输入引脚,这是最常见的用法;另一种是把ADC输入接到其中一个通道,公共端接传感器,不推荐正常使用,容易把自己绕晕。
接线时要注意:不要接反信号极性。模拟信号都是相对参考地的电压,4067只负责切换通道,本身不改变信号性质。所以所有传感器、分压电阻的GND要和4067的GND、MCU的GND在同一个参考点上,尽量采用单点接地,避免地环路引入共模干扰。
另外,凡是空闲不用的通道引脚,或者实际没接信号的通道,最好给它们一个明确电平,比如直接对GND接一个10K下拉电阻,或者软件上在初始化时把所有地址线设置为0000(选通X0),避免引脚悬空导致内部CMOS传输门状态不确定、泄漏电流增大。如果通道悬空且不处理,单片机端可能读到随机的漂移值,这个在做通道自检时非常容易误判。
2.3 输入阻抗与信号调理的取舍
这是我后来才深刻体会的一点:4067不是理想开关,它的导通电阻会叠加在信号源和ADC之间。以我的NTC测温电路为例,NTC和固定电阻分压后,信号源阻抗一般在10K欧级别。如果后级ADC输入阻抗也很高(比如STM32内置ADC等效采样输入阻抗在几K到几十K之间),那么导通电阻几十欧看起来似乎不太要紧,但实际并非如此。
问题出在ADC的采样保持电容上。STM32内置ADC在采样阶段,采样开关闭合后会从外部信号源给内部采样电容充电,充电时间取决于外部源阻抗和采样电容的乘积。外部源阻抗越大,建立时间越长,如果采样时间配置不够,采到的电压会比实际值低。4067的导通电阻其实是额外增加的源阻抗,会让建立时间变长。换句话说,本来信号源阻抗5K,加上4067的70欧看似影响不大,但再加上PCB走线、外围电阻,实际有效阻抗上升,导致压降和采样误差。
如果信号源本身输出阻抗很高(比如直接接一个高阻传感器,没有经过运放缓冲),那就有必要在4067公共端和MCU的ADC引脚之间加一级运放跟随器,起到阻抗变换作用。用普通LM358、LMV321这类单电源轨到轨运放即可,不过要保证运放供电范围覆盖信号幅度。我做的板子后来在SIG端到ADC引脚之间加了一颗电压跟随器,精度提升非常明显,这个后面章节实测会展示数据。
3. 软件驱动设计:从GPIO控制选通到ADC采样
3.1 软件控制思路与选通时序
软件端需要考虑的核心问题是:选通通道后,什么时刻开始采样才算准确。因为4067开关切换不是瞬时的,CMOS传输门存在导通延迟,而且公共端线上原本可能残留上一个通道的电压,选通新通道后,信号源要重新给公共端的寄生电容充电,电压需要一段时间才能稳定到目标值。这个稳定时间跟信号源阻抗和寄生电容有关,一般几十微秒量级就足够。
所以,软件流程应该是:设置S0~S3输出对应通道的二进制地址,然后等待一小段延时(比如50us~1ms,具体看信号源阻抗),再启动ADC转换。如果连续扫描多个通道,千万不能选通后立刻读ADC,否则大概率读到的是上一个通道残留电压与本通道信号的混叠值,表现为数据跳变、通道间串扰。
另外,使能信号EN可以一直拉低保持使能,也可以每次切换前拉高断开所有通道,设置好地址后拉低重新使能。我用的是后者,主要是想着这样能避免切换瞬间不同通道之间可能出现的瞬间导通短路风险。其实4067的CMOS开关是“先断后合”类型的,内部逻辑保证不会出现两个通道同时导通,所以直接保持EN低也没问题。不过个人习惯还是每次切换时先拉高EN、改地址、再拉低EN,虽然多几行代码,但心里更踏实。
3.2 完整示例代码:STM32 HAL库实现16通道扫描
下面我用STM32F103 + HAL库来写一个完整示例。为了方便阅读,我把代码精简为纯轮询方式,不使用中断和DMA。实际项目如果你有充足的中断资源,可以做定时触发ADC,在ADC转换完成中断里切换下一个通道然后启动下一次转换,效率更高。但轮询方式最容易理解,也最适合排障。
// 定义通道选择控制引脚,根据你的实际接线修改 #define MUX_EN_PIN GPIO_PIN_6 #define MUX_S0_PIN GPIO_PIN_7 #define MUX_S1_PIN GPIO_PIN_8 #define MUX_S2_PIN GPIO_PIN_9 #define MUX_S3_PIN GPIO_PIN_10 #define MUX_GPIO_PORT GPIOB // ADC句柄外部声明,假设你已经用CubeMX配置好了ADC1_IN0单通道 extern ADC_HandleTypeDef hadc1; // 通道索引 -> 地址线电平映射 static const uint8_t mux_channel_table[16][4] = { {0,0,0,0}, // channel 0: S3 S2 S1 S0 {0,0,0,1}, // channel 1 {0,0,1,0}, // channel 2 {0,0,1,1}, // channel 3 {0,1,0,0}, // channel 4 {0,1,0,1}, // channel 5 {0,1,1,0}, // channel 6 {0,1,1,1}, // channel 7 {1,0,0,0}, // channel 8 {1,0,0,1}, // channel 9 {1,0,1,0}, // channel 10 {1,0,1,1}, // channel 11 {1,1,0,0}, // channel 12 {1,1,0,1}, // channel 13 {1,1,1,0}, // channel 14 {1,1,1,1} // channel 15 }; static void mux_select_channel(uint8_t ch) { if (ch > 15) return; // 第一步:强制断开所有通道 HAL_GPIO_WritePin(MUX_GPIO_PORT, MUX_EN_PIN, GPIO_PIN_SET); // 第二步:按通道表设置地址线 HAL_GPIO_WritePin(MUX_GPIO_PORT, MUX_S0_PIN, mux_channel_table[ch][3] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_GPIO_PORT, MUX_S1_PIN, mux_channel_table[ch][2] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_GPIO_PORT, MUX_S2_PIN, mux_channel_table[ch][1] ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(MUX_GPIO_PORT, MUX_S3_PIN, mux_channel_table[ch][0] ? GPIO_PIN_SET : GPIO_PIN_RESET); // 第三步:使能MUX HAL_GPIO_WritePin(MUX_GPIO_PORT, MUX_EN_PIN, GPIO_PIN_RESET); // 第四步:等待信号稳定,这个延时长短很重要 delay_us(100); } static uint16_t read_mux_channel_adc(uint8_t ch) { mux_select_channel(ch); HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); // 超时100ms uint16_t adc_val = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); return adc_val; } void adc_scan_all_channels(uint16_t *buf, uint8_t len) { if (len > 16) len = 16; for (uint8_t i = 0; i < len; i++) { buf[i] = read_mux_channel_adc(i); } }如果你用CubeMX生成工程,记得把GPIO输出速度设置为High,然后ADC采样时间尽可能配置为最长。STM32F103的ADC采样时间可选1.5/7.5/13.5/28.5/41.5/71.5/239.5周期,我用的是71.5周期,配合100us延时,实测稳定性不错。如果你信号源阻抗很大,可以再拉长采样时间,或者用239.5周期,但整个扫描周期会变长,看需求权衡。
3.3 为什么必须在选通后加延时,以及延时的设定依据
很多新手写4067驱动的时候会漏掉选通后延时的处理,写入的代码风格是:设置地址后立刻启动ADC,读出来的值乱跳,然后就开始怀疑4067坏了。实际上不是4067的问题,而是你根本没有给它喘息的时间。
从传输门关闭到完全导通,CD74HC4067的开关时间典型在几十纳秒级别,开关速度本身很快。但快不代表稳定,因为通道切换后,公共端SIG以及连接到ADC输入引脚的走线电容需要被信号源驱动到目标电压。假设信号源阻抗10K,公共端和走线寄生电容加ADC采样电容总共约50pF(实际上STM32内部采样电容约十几pF,走线再多几十pF),那么时间常数τ = 10K × 50pF = 0.5us。要建立到0.1%精度大约需要7τ,也就是3.5us。看起来100us延时已经留足了余量。
但为什么还有人反应延时加到1ms才稳定?因为实际电路里信号源阻抗可能不止10K,比如NTC在低温时阻值可能达到100K以上,再加上和4067导通电阻串联,等效阻抗可能到120K,这时τ = 120K × 50pF = 6us,建立到0.1%需要42us,100us还能勉强覆盖。如果你是直接高阻输出且没有跟随器,等效阻抗推到1M欧,那τ直接变成50us,建立时间需要350us,这时100us就明显不够了。所以延时的设定依据就是:最坏情况下的信号源等效阻抗 × 等效电容,再留出几倍余量。如果你的信号源是运放输出的低阻信号,几十us延时足够;如果是高阻传感器,加到1ms都不为过。
因此,我更推荐在硬件上加电压跟随器,把信号源阻抗降下来,这样软件延时就可以固定在一个相对小的值,整个扫描速率还能提上去。
4. 两个调试坑实录:现象、排查与解决
4.1 坑一:通道串扰与电压跌落,根源在“导通电阻 + 采样保持电容”
现象描述
调试时我先把通道0接一个稳定的2.5V基准电压,通道1接一个1.0V电压,用万用表量输入的电压都很准。但程序扫描出来的结果,通道0的ADC转换值比理论偏低,大约只有2.42V左右;通道1倒是基本正常。更奇怪的是,当我切换通道0后立刻读通道1,通道1会瞬间跳高,要等一会儿才回到1.0V附近。
排查过程
最开始我怀疑是代码里选通地址写错了,于是单独固定选通通道0读10次,发现每次都偏低,排除地址错误。我又怀疑ADC参考电压有问题,用万用表量MCU的VREF引脚,3.29V没问题。接着我把4067的SIG端直接飞线断开,MCU的ADC引脚直接接到通道0的2.5V信号上,发现读数准了。这样一来,问题就锁定了:偏差是4067引入的。
于是我去查CD74HC4067在3.3V供电下的导通电阻,手册写典型值70欧,最大可能到120欧。单独一个70欧串联在2.5V信号源和MCU高输入阻抗ADC之间,按理说几乎不会产生压降,因为ADC输入引脚几乎没有直流电流。但问题出在ADC采样保持电容上:ADC采样时,采样开关闭合,采样电容从外部源充电,充电电流经过信号源内阻和4067导通电阻,只要充电时间不够,采样电容上的电压就达不到外部电压值,最终转换结果就偏低。
简单算一下:STM32F103的采样电容典型为14pF,假设外部信号源阻抗非常低(基准电压源输出阻抗约0.1欧),但串联了4067的70欧,再加上芯片内部采样开关的电阻(大概几K欧),等效源阻抗仍然有约几千欧。如果在采样时间内电容没有完全充满,采集值自然偏低。而且通道0的信号源接的是基准电压源模块,输出阻抗虽然低,但实际走线又串联了转接板插针的接触电阻,整体下来就出现了明显跌落。
解决方案
解决方案有两个方向。硬件上,在4067公共端SIG之后串接一个电压跟随器,把高阻抗的“4067+采样电容”问题隔离掉。电压跟随器输入阻抗高达几十兆欧,输出阻抗极低,ADC采样电容可以迅速通过运放输出充电,完美解决采样建立时间不足的问题。软件上,把ADC采样时间设置为最大(239.5周期),这样采样保持电容有更多时间充电,也能缓解一部分,但不能根治。
我把两套方案都试了。纯软件加大采样时间后,通道0从2.42V恢复到了2.48V,还是差了0.02V;加上运放跟随器后,通道0读回2.499V,基本准确了。所以最终硬件方案是:4067 SIG -> 电压跟随器 -> MCU ADC引脚。如果你想省掉运放,至少也要把ADC采样时间调大,并且在PCB布局时减少SIG走线长度和寄生电容。
4.2 坑二:切换通道瞬间引起的毛刺导致读数异常,必须先等再采
现象描述
一开始我的代码里选通通道后只延时了1us就开始采样,结果17路通道扫描出来几乎每路都有随机偏差,连续采几轮数据,同一通道跳动范围能达到±50mV。但单独把某一通道地址固定,延时1ms再去读,数据又非常稳。这两种表现组合在一起,基本指向同一个问题:选通后还没有建立稳定,就启动了ADC。
排查过程
我先在选通后延时500us,再采样,数据跳动明显减小,但偶尔还是会有某个通道突然跳一个大值。后来我用示波器钩子夹在SIG端观察,发现每次切换地址线的瞬间,SIG端会有一个几百毫伏的尖峰毛刺,持续几微秒到几十微秒。这个毛刺的成因包括:地址线翻转时,数字信号通过CMOS开关的寄生电容耦合到模拟通道上;还有公共端线上的寄生电容上残留的电荷与切换瞬间新通道的电压不一致,导致电压跳变,需要一定时间重新平衡。
如果在毛刺还没消失的时候就启动ADC采样,采样保持电容可能会采到毛刺附近的高电压,造成读数偏高。这就解释了为什么同一通道多次采样会偏高,而不是全部偏低——毛刺是瞬态的,不一定每次都刚好撞上。
解决方案
解决思路很简单:给毛刺一个“消失窗口”。实际代码中,我把选通后的延时加大到了100us(先加运放,所以延时不需要太长),并且加了软件去抖:每个通道连续采样5次,去掉最大值和最小值,取中间3次平均。这样即使偶发毛刺被采到,也能被滤波算法剔除。
另外还有一个细节:地址线如果同时翻转多根,毛刺会更明显。比如从通道0(0000)切到通道15(1111),4根线同时变化,瞬态电流大。如果改成按顺序从0扫到15,每次只有部分位变化,毛刺会小一些。你也可以用格雷码顺序扫描,但那样通道顺序会乱,实际工程中我倾向于保持自然顺序,用延时+滤波来兜底。
经过这两个调整后,16通道扫描的数据非常稳定:连续读取100轮,每通道最大偏差不超过±2mV(3.3V系统对应满量程约±1.6个LSB),这个精度对于绝大多数传感器采集足够用了。
4.3 其他容易踩的坑:悬空通道、接地、参考电压
除了上面两个主要坑,还有几个值得提一下,都是我实际验证过的。
第一,未使用的通道引脚尽量不要悬空。如果程序扫描了16路,但实际只有几路接了传感器,那么未接的通道引脚会因为CMOS输入悬空而呈现不确定电平,反映在ADC上就是抖动的随机值。我做自检时会把它当成故障误报,一开始很头疼。后来把所有未用通道通过10K电阻下拉到GND,未接通道读到的值就稳定在0附近。如果你希望未接通道读出某个固定的偏置值(比如中点电压),也可以下拉到电阻分压中点。
第二,4067的GND和模拟信号GND必须低阻抗连接。如果模拟信号参考地和4067的GND是分开的,两段GND之间的压差会直接叠加到信号上。在高精度小信号场景下,这一点可能成为最主要的误差来源。我处理的办法是在4067周围敷铜,把模拟地铺成一片,再用一颗0欧电阻单点连接到MCU的数字地。
第三,ADC的参考电压稳定非常重要。如果MCU的VREF+不是独立引脚,而是直接接VCC,那么VCC自身的波动会直接反映到ADC结果上。我的板子使用3.3V LDO供电,LDO输出有几十mV波纹,导致ADC读数有一个固定频率的波动。后来我在VREF引脚(如果MCU支持)或者3.3V处加上LC滤波(磁珠+10uF+0.1uF),波动才压下去。
5. 实测数据与优化效果心得
5.1 加运放前后的实测对比
为了让大家直观看到优化效果,我整理了一组实测数据。测试条件:MCU为STM32F103C8T6,3.3V供电,基准电压源输出2.500V,通道0接基准、通道1接1.000V稳压源,其他通道接分压电阻模拟不同电压。先用裸4067+默认配置(采样时间71.5周期,延时10us)扫描,然后是软件加大采样时间,最后是外接电压跟随器。每个通道读100次取平均,换算成电压值。
| 方案 | 通道0理论2.500V实测值 | 通道0误差 | 通道1理论1.000V实测值 | 通道1误差 | 数据稳定性(±偏差) |
|---|---|---|---|---|---|
| 裸4067,采样时间71.5周期,延时10us | 2.416V | -84mV | 0.982V | -18mV | ±30mV |
| 裸4067,采样时间239.5周期,延时100us | 2.486V | -14mV | 0.994V | -6mV | ±8mV |
| 加电压跟随器,采样时间71.5周期,延时100us | 2.499V | -1mV | 0.999V | -1mV | ±2mV |
| 加电压跟随器,采样时间239.5周期,延时500us,5次取中值平均 | 2.500V | ~0mV | 1.000V | ~0mV | ±1mV |
从表中能清楚看到,电压跟随器带来的提升是质的飞跃。不同信号源的输出阻抗不同,通道0因为接了低阻基准源,裸4067时误差大主要是采样保持电容充电不足;通道1误差小一些是因为信号源本身做了分压,输出阻抗在几百欧左右,充电稍快。如果你在实际项目中遇到某些通道准、某些通道不准,大概率就是信号源阻抗差异导致的,加跟随器前建议先逐个通道测一遍输出阻抗。
5.2 进一步降低噪声的软件滤波策略
硬件优化后,软件滤波仍然有必要。我最终采用的方式是“5次采样去极值平均”,也就是每个通道连续采5次,排序后去掉最大值和最小值,剩下的3个取算术平均。这比单纯的多次平均更抗尖峰脉冲干扰,效率也比中值滤波高一些,因为只做一次排序。如果你追求更平滑的数据,可以用滑动平均或者一阶低通滤波。比如一阶低通公式:filtered = (1-alpha)*filtered + alpha*new_sample,alpha根据你的采样频率和期望截止频率来定。
但我提醒一下:软件滤波会引入滞后,如果信号本身变化很快(比如音频、振动信号),滤波会限制带宽,这时候反而要减少滤波力度。对于NTC温度、电池电压这类慢变信号,5次去极值平均完全足够。
另外一个细节是:ADC转换结果可能受时钟配置影响。STM32的ADC最大时钟是14MHz,超过这个频率转换精度会下降。如果你用CubeMX自动配置,有的配置会把ADC时钟分频设置得过高,导致转换结果噪声增大。建议检查一下RCC的ADC时钟分频系数,保证ADC输入时钟在12MHz以下,精度表现会更好。
5.3 从采样周期到系统稳定性的经验总结
这次做完,我的体会是:模拟多路切换不是简单地把IO拉高拉低,再调ADC,而是一整套小系统设计。硬件上要考虑导通电阻、信号源阻抗、采样保持电容、电源去耦、地回路;软件上要考虑选通延时、采样时间、滤波算法。任何一个环节偷懒,最终都会表现为ADC读数不准确、不稳定,排查起来异常痛苦。
如果再来一次,我会在设计阶段就把电压跟随器加进去,而不是等测试数据不好看再改板。因为等改板、焊接、再测试,一个来回就是好几天。另外,PCB布局时尽量让所有模拟信号走线短而粗,避开数字信号线。4067的地址线、使能线属于数字信号,尤其在切换瞬间电流变化很大,要避免它们与SIG线平行走线,不然毛刺会更明显。
还有一点,16通道扫描并不是毫秒级的事情。按我现在的代码,每通道选通延时100us + ADC采样时间71.5周期(约20us)+ 5次采样,总时间大约每通道600us,16通道一轮大约10ms。如果你的应用需要更快,可以改成“选通后延时100us,然后连续采样5次”并行处理,我为了代码简单才串行做。大家可以根据实际速率需求优化,但注意不能为了速度丢掉稳定性。
架构上,如果通道数量超过16路,还可以用两片4067组成32路或者更大规模的切换网络,控制线就变多了。更进一步的方案是使用SPI接口的模拟多路开关芯片,比如ADG706之外,还有MAX14661这类I2C控制的,但成本会上涨。对多数嵌入式项目,CD74HC4067仍然是扩展ADC通道最经济、最简单、最稳定的选择。
6. 写在最后的一些实用建议
文章差不多了,最后再分享几个我在这次调试中沉淀下来的小建议,不一定写进文档,但实际开发时真的很管用。
- 调试时先用固定电平测试:接传感器之前,先用两路稳定的基准电压源或者电阻分压接到4067的任意两个通道,这样可以快速验证硬件和软件基本链路是否正常。我习惯用2.5V和1.0V这两个电压,因为差值明显,一眼就能看出通道顺序和读数是否正确。
- 程序里预留一个自检模式:初始化时扫描所有16个通道,如果发现某个通道读数为满量程或接近0,先看看是不是该通道被留空了。不然传感器还没买回来,你把PCB焊好了上电,看到一堆乱跳值很容易误判。
- 串口打印时加上通道序号:16路数据一起往串口发的时候,如果格式不带通道号,你根本分不清哪一路是哪一路。我用的格式是"C%02d:%04d\r\n",这样一眼就能对照接线顺序。
- 拔掉仿真器后再测精度:有时候你把JLINK、ST-LINK连接在板上,调试口的数字信号翻转可能会影响模拟采集,尤其是ADC参考地离调试口很近时。测精度时尽量断电后拔掉调试器,用独立的USB转串口供电输出数据,数据会干净不少。
我在实际使用中最大的体会是:嵌入式调试里很多问题表面上看起来是“玄学”,比如数据乱跳、某一路突然偏高,但只要你肯花时间把信号链路拆开,一步一步排除,最终都能落到一个具体的电气参数上。4067这种芯片用好了,能给系统省下非常多的IO和PCB空间,值得好好掌握。后续如果有机会,我还会写一篇关于多片4067级联扩展更多通道的内容,到时候再和大家继续分享。