资讯详情

资讯详情

CD74HC4067多路ADC扩展踩坑记录:驱动时序与调试方法详解

这段时间一直在调一块采样板MCU自带的ADC通道不够用外扩一片CD74HC4067做16路模拟量采集整体方案不算复杂但实际调下来还是踩了几个坑其中一个差点让我怀疑芯片是坏的。这篇笔记就把完整的实现过程、驱动逻辑、两个典型调试坑和排查方法整理出来给后面要做多通道采集的同行一个参考。先说下项目背景。板子上需要采集16路0到3.3V的模拟信号MCU用的是一颗带2路12位ADC的国产Cortex-M0内核芯片自带ADC通道数不够而且部分通道还被其他功能占用了。外扩方案对比下来选了CD74HC4067这颗16通道模拟多路复用器原因也很直接单芯片方案、成本几毛钱、控制逻辑简单四根地址线加一根使能线就能扩展出16路输入正好匹配现有GPIO资源。1. 整体方案设计与选型思路1.1 为什么选CD74HC4067而不是直接用多路ADC芯片做多路模拟采集方案其实不少。最省事的是直接换一颗ADC通道数更多的MCU但当时板子已经定版换主控等于重新设计硬件时间和成本都扛不住。另一种方案是外挂独立ADC芯片像ADS1115这类带多路输入的I2C ADC精度确实更高但采样率和通道数受限于I2C总线速率16路轮流采一遍速度拉不上去而且每颗芯片价格也不便宜。选CD74HC4067属于中间路线。它本身不做模数转换只是把16路输入信号按需切换到公共输出端外接MCU的一个ADC引脚就能完成所有通道采样。核心优势有三个通道扩展成本极低一片几毛钱到一块多取决于封装和渠道。通道切换靠4位地址线完成普通GPIO就能控制不占用额外外设资源。模拟带宽和导通性能对慢变信号绰绰有余几十千赫兹以下的传感器信号完全够用。说白了这就是一个“模拟旋转开关”一次只能接通一路采样哪路由地址线决定。对于电池电压监测、多路温度采集、电位计读数这类应用扫描频率不需要太快这个方案非常合适。1.2 核心参数与选型时的几个关键点CD74HC4067的数据手册里有几个参数对电路设计影响很大我列个表说明一下参数典型值影响导通电阻Ron约40到120欧姆随电压和温度变化与信号源内阻分压影响采样精度通道切换时间纳秒级到微秒级决定切换后需等待多久再采样工作电压范围2V到6V供电电压影响导通电阻和逻辑电平模拟输入范围VEE到VCC常见接GND到VCC超出范围可能导致通道间漏电开关断开漏电流纳安级高阻抗电路需要注意影响选型时最容易忽略的就是导通电阻。如果信号源内阻很低比如运放输出直接驱动几十欧姆的导通电阻几乎可以忽略但如果信号源是高阻抗传感器比如光电二极管、pH电极这类内阻动辄几十千欧的导通电阻就会和源阻抗分压导致ADC读到的电压偏低。后面调试时这个问题会明显暴露出来我会在第3节详细说明。电源和地去耦也要注意。CD74HC4067是数字控制信号切换模拟信号地址线的高低电平跳变会产生数字噪声耦合到模拟路径上会影响采样稳定性。芯片的VCC引脚我加了0.1微法陶瓷电容靠近放置模拟输入地单独走线回到MCU模拟参考地实测对采样稳定性有帮助。1.3 硬件连接方式与引脚规划我的具体硬件连接是这样的供参考CD74HC4067的VCC接3.3VGND接模拟地。使能引脚EN直接拉低保持芯片一直处于导通状态。地址线S0到S3接MCU的四个空闲GPIOPA0到PA3加10千欧下拉电阻避免上电瞬间地址线悬空导致导通到随机通道。公共输出引脚COM接MCU的ADC输入通道选用ADC的通道0。16路输入引脚X0到X15分别接各路传感器输出每路输入对地并联一个0.1微法电容做滤波和抗干扰。地址线下拉电阻这个细节是后来补上的。之前上电瞬间MCU引脚还没初始化时地址线电平不确定COM端可能莫名其妙接上了某个输入通道导致那一路的信号把整个ADC输入拉偏。加了稳定电平的下拉电阻后上电默认选通X0通道行为就确定了。2. CD74HC4067工作原理与驱动时序分析2.1 芯片内部结构与地址译码逻辑CD74HC4067内部其实就是16个模拟开关每个开关的一端接对应的输入引脚另一端接公共输出。16对开关不可能同时导通而是由4位地址通过译码器选通其中一个。这跟家庭网络里的交换机有点像虽然有很多网口但内部通过MAC地址表决定哪个口和哪个口通信。CD74HC4067的地址译码就像一张查找表地址0000对应X00001对应X1一直到1111对应X15。简单来说地址0000导通X0地址0001导通X1地址0010导通X2...以此类推地址1111导通X15这里有个容易脑子转不过来的地方地址线S0是最低位还是最高位。不同厂家封装的标注习惯有差异网上很多参考图把S0当最高位如果照抄可能会选错通道。我建议连接后先写个简单的通道自检函数控制地址线依次选通16个通道在输入端依次接入不同电压通过ADC读数反推地址映射关系是否正确。这一步看似多此一举实际能省掉后面排查地址错乱的大量时间。2.2 通道切换时序与采样时序的配合CD74HC4067的通道切换延迟极快数据手册里典型值是纳秒级但这不代表切换完成后马上就能做ADC采样。这里有两个层面的延迟需要考虑第一个层面是开关本身的物理建立时间。虽然芯片内部的传输延迟很短但信号经过开关管后需要一点时间达到稳定输出尤其是后面接了滤波电容时RC建立时间会被拉长。第二个层面是MCU内部ADC采样保持电容的充电时间。ADC启动采样后内部采样电容需要从外部信号源吸取电荷如果外部信号源阻抗较高充电时间就会变长采样结果就会偏小。所以正确做法是先切换地址线等待一段时间让模拟路径稳定然后再启动ADC采集。这个等待时间该如何确定呢我举个具体计算例子。如果信号源等效输出阻抗是10千欧CD74HC4067的导通电阻约80欧姆外部并联的滤波电容是0.1微法那么RC时间常数约为τ (10kΩ 80Ω) × 0.1μF ≈ 1.008ms理论上等到5个时间常数后电压基本稳定也就是大约5ms。对于常规ADC采样来说这个时间还可以接受但如果追求更快的扫描频率要么减小滤波电容要么降低信号源阻抗。我实际用的滤波电容也是0.1微法但信号源是运放输出内阻很低只有几十欧这样时间常数就变成了τ (50Ω 80Ω) × 0.1μF 13μs只需要等待约65微秒就稳定了。所以稳定延时的时间不是拍脑袋定的而是根据信号源阻抗、导通电阻和滤波电容三者共同决定。2.3 使能引脚EN的正确用法CD74HC4067的EN引脚低电平有效拉低时芯片正常工作拉高时所有通道断开COM端处于高阻状态。这个引脚有两个典型应用多片级联扩展。比如需要32路甚至更多通道用一片MCU GPIO控制多片CD74HC4067的EN脚先选通其中一片工作其他片全部禁用就能实现通道数的进一步扩展。采样空闲期间关断模拟通路减少漏电和功耗。如果是电池供电的设备这个功能比较有用。不过要注意EN拉高后COM端虽然高阻但ADC输入引脚如果没有内部下拉或者外部偏置会成为悬空状态。如果此时启动ADC采集读取的数值可能是随机的。所以代码里每次在启动采集前必须确保EN处于有效状态并且模拟路径已经稳定。3. 嵌入式驱动实现与核心代码解析3.1 基础驱动函数编写驱动逻辑不复杂核心就是根据通道号设置GPIO输出对应的4位组合值。我用的是HAL库直接操作GPIO寄存器来提升切换速度。基础代码大概长这样#define ADC_MUX_S0_PIN GPIO_PIN_0 #define ADC_MUX_S1_PIN GPIO_PIN_1 #define ADC_MUX_S2_PIN GPIO_PIN_2 #define ADC_MUX_S3_PIN GPIO_PIN_3 #define ADC_MUX_PORT GPIOA void mux_select_channel(uint8_t ch) { if (ch 15) ch 0; HAL_GPIO_WritePin(ADC_MUX_PORT, ADC_MUX_S0_PIN, (ch 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(ADC_MUX_PORT, ADC_MUX_S1_PIN, (ch 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(ADC_MUX_PORT, ADC_MUX_S2_PIN, (ch 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(ADC_MUX_PORT, ADC_MUX_S3_PIN, (ch 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET); delay_us(50); // 等待模拟开关稳定 }上面代码里每个地址位都要单独操作GPIO效率不高但可读性好。如果对切换速度有更高要求可以用一次性写多个引脚方式void mux_select_channel_fast(uint8_t ch) { uint32_t tmp GPIOA-ODR; tmp ~(ADC_MUX_S0_PIN | ADC_MUX_S1_PIN | ADC_MUX_S2_PIN | ADC_MUX_S3_PIN); tmp | (ch 0x01) ? ADC_MUX_S0_PIN : 0; tmp | (ch 0x02) ? ADC_MUX_S1_PIN : 0; tmp | (ch 0x04) ? ADC_MUX_S2_PIN : 0; tmp | (ch 0x08) ? ADC_MUX_S3_PIN : 0; GPIOA-ODR tmp; delay_us(50); }两种写法效果一样后者减少了多次读写GPIO寄存器的开销适合需要快速轮询的场景。3.2 ADC多通道轮询采样与数据滤波驱动写好之后就是采样逻辑。我的做法是对每个通道连续采集多次去掉最大值和最小值后取平均值作为该通道的最终读数。采样函数大致如下#define SAMPLE_NUM 8 uint16_t read_adc_channel(uint8_t ch) { uint32_t sum 0; uint16_t sample_buf[SAMPLE_NUM]; mux_select_channel(ch); for (int i 0; i SAMPLE_NUM; i) { HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); sample_buf[i] HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); delay_us(20); } // 去掉最大最小值后求平均 uint16_t min_val sample_buf[0], max_val sample_buf[0]; for (int i 0; i SAMPLE_NUM; i) { if (sample_buf[i] min_val) min_val sample_buf[i]; if (sample_buf[i] max_val) max_val sample_buf[i]; } uint8_t valid_cnt 0; for (int i 0; i SAMPLE_NUM; i) { if (sample_buf[i] ! min_val sample_buf[i] ! max_val) { sum sample_buf[i]; valid_cnt; } } return (valid_cnt 0) ? (uint16_t)(sum / valid_cnt) : sample_buf[0]; }每轮采样之间加20微秒延时是为了让ADC内部采样保持电容有充分时间恢复和重新充电。去掉极值再平均的做法对付偶发的尖峰脉冲效果很明显尤其是工业现场有电机启停这类干扰源时。如果追求更平滑的输出可以用滑动平均值滤波或者一阶低通滤波。对于变化缓慢的传感器信号一阶低通滤波效果最好代码只需要一条语句filtered_value filtered_value (new_sample - filtered_value) * alpha;alpha取值一般在0.05到0.3之间越小越平滑但响应越慢。具体取多少要看信号变化速度和噪声水平需要现场调参试一下。3.3 通道扫描策略是轮流采样还是按需读取这里有两种策略可选一是主循环里每次查询一个通道查完16个通道后回到第一通道循环二是由事件触发或上位机指令按需读取指定通道。我采用的是第一种方式用一个定时器每隔10毫秒触发一次中断中断里只设置一个标志位主循环检测到标志位后开始扫描16路通道。这样保证了采样周期恒定数据更新率均匀。对于按需读取的场景需要注意一个问题如果相邻两次读取间隔比较长通道切换后外部滤波电容里的电荷可能还没充满。此时即使等待了固定延时首次采样值可能仍然偏低。最好的做法是丢弃切换通道后的第一个采样值用后续的值作为有效数据。我在代码里已经通过连续采集多次来规避如果只采一次这个坑一定要留意。4. 两个调试坑的完整复盘与定位过程4.1 坑之一切换通道后立即采样导致数值错乱这个坑是调试早期遇到的现象非常诡异所有通道读数都比预期低而且每次读到的数值波动很大有些通道甚至读到接近满量程的乱值。刚开始以为是CD74HC4067芯片虚焊或者是地址线接错了用万用表量了所有引脚连接都没问题。然后用示波器挂在COM端观察发现切换地址线后COM端电压波形确实有跳变但需要经过很长一段“回滞”才趋于稳定。稳定时间大约几百微秒远超我代码里设置的几十微秒延时。后来翻数据手册才注意到一个细节CD74HC4067的导通电阻Ron是随输入电压变化的。电压接近电源轨时导通电阻变大同时外部滤波电容的存在使RC常数变大导致建立时间变长。再者MCU内部ADC在启动采样时采样电容会从外部抽取电荷如果外部驱动能力不足采样期间电压会被“拉低”读取结果自然偏小。这个问题本质是模拟开关导通电阻外部滤波电容ADC采样电容三者之间的阻抗匹配问题。解决方案分两步第一步切换通道后的延时从50微秒增加到200微秒。第二步每次读取前多预留一次“预充电”时间即在启动ADC转换前先把对应通道选通并保持足够长的时间让滤波电容充分充电到接近实际电压值。改完代码后测试读数立刻稳定了16路通道的偏差降到了可接受范围。这里我总结一个经验遇到多路模拟开关读数偏小且波动大的情况优先考虑建立时间不够不要急着怀疑芯片坏。4.2 坑之二16路通道之间互相串扰第二个坑出现在功能联调阶段。单独测某一路时数据正常但只要相邻通道接了信号这个通道的数据就被拉偏现象像是“隔壁通道漏电过来了”。最开始怀疑是PCB布线间距不够板子上的模拟信号线确实走得比较近。但在电路上做了割线验证后串扰依然存在说明问题不在物理布线。用示波器单次触发捕捉COM端波形后发现了规律当地址线从X0切到X1时波形不是平滑地从V0跳到V1而是先向V0方向回弹一下再往V1爬升。这个回弹量还随X0和X1的电压差增大而变大。这就是典型的电荷注入效应。CD74HC4067内部的模拟开关是CMOS结构开关管关断瞬间会把沟道里的电荷注入到输出端形成一个短暂的尖峰。尖峰幅度和方向取决于开关管两端的电压差。如果后级是低阻抗负载这个尖峰很快就被衰减掉了但板子上每个输入通道都有0.1微法滤波电容这些电容存储的电荷会在开关切换瞬间通过导通通道互相串。解决方式有几条路可以走减小输入端滤波电容从0.1微法降到10纳法缩短电荷泄放时间。对慢变信号来说滤波效果影响不大。增加通道切换后延时但这种方法只能缓解不能根除。采样前先做一次“假切换”即切换到目标通道后等待几个毫秒确保电荷注入尖峰被彻底衰减掉然后丢弃第一次ADC数据。我最终采用的是第一和第三种方式组合输入滤波电容减小到10纳法切换通道后延时2毫秒再丢弃第一次采样值。改完后串扰现象明显消失不同通道电压差达到2V时串扰误差从原来的几十毫伏降到了几个毫伏以内。4.3 调试过程中用到的排查手段和工具这两个坑之所以能相对快速地定位跟排查方法有一定关系。简单分享几个关键工具和判断逻辑示波器探头要挂在COM输出端而不是输入端。挂在输入端只能看到信号源自身的波形看不到开关切换瞬间的异常现象。用单次触发模式抓切换瞬间的波形。反复触发才能看到稳定的回弹或尖峰自由触发模式下波形混叠看不清异常。用万用表验证直流电平用示波器看交流瞬态两者互补。万用表只能确认最终稳态值瞬态问题还是得靠示波器。怀疑串扰时可以用“跳线法”验证把可疑的两路输入信号断开一路暂时接地看另一路读数是否变化。如果接地后读数恢复说明串扰确实来自通道间能从侧边排查出源头。5. 精度分析与误差来源专项探讨5.1 实测数据对比与误差统计为了量化这套方案的精度我搭了一个简单测试环境用一台可调电源输出精确电压作为16路输入MCU外部参考电压用3.3V高精度LDO供电ADC配置为12位。下面是几组代表性数据通道万用表实测电压(V)ADC原始值转换电压(V)绝对误差(mV)X00.00040.0033X10.5006210.498-2X21.00012420.997-3X31.65020541.649-1X42.50031072.494-6X53.30040943.285-15可以看出电压越高误差越大。最高点3.3V处误差达到了15毫伏左右。分析误差来源主要有以下几个CD74HC4067导通电阻压降。输入电压越高导通电阻越大压降就越明显。MCU参考电压的实际值与标称值存在偏差软件里用的3.3V可能实际是3.28V这个固定偏差可以通过两点校准来消除。ADC本身的差分非线性误差一般厂家会标INL和DNL参数12位ADC的典型INL在几个LSB以内对应电压约1到3毫伏。5.2 提升采样精度的几种可行手段如果对精度有更高要求我建议做两件事一是做硬件两点校准。在贴近应用输入范围的低端和高端各选一个校准点记录实际电压值和ADC读数然后用线性插值修正所有通道的换算系数。这个做法能同时修正参考电压偏差和导通电阻引入的增益误差。二是在信号源和CD74HC4067之间加一级跟随器。运放输入阻抗极高输出阻抗极低可以把传感器的高阻抗隔离掉让CD74HC4067的导通电阻不再与信号源内阻分压。这是最彻底的解决方案代价是每路都需要一片运放成本会翻不少。对于大多数嵌入式采样场景直接用ADC原始计数然后软件做线性校准也就够了没有必要追求实验室级别的精度。6. 常见问题速查与工程落地建议6.1 问题排查表把调试过程中遇到的各类问题整理成一张速查表方便后面直接对照排查。异常现象可能原因解决方法所有通道读数普遍偏低通道切换后建立时间不够增加延时到几百微秒丢弃首个采样值单通道读数比实际值低信号源内阻与导通电阻分压加跟随器或校准增益相邻通道读数互相影响CMOS开关电荷注入串扰减小滤波电容切换后多等待某几个特定通道读数为满量程地址线接反或映射错误用自检程序验证实际通道映射上电瞬间读数异常地址线悬空电平不确定地址线加下拉电阻默认选通通道0采样值随机波动严重供电去耦不良或参考电压不稳VCC和VREF加0.1uF电容高速扫描时采样值严重失真模拟开关带宽不够或RC常数过大降低扫描频率或减小滤波电容6.2 工程化应用中的几个小建议项目调试到现在积累了一点工程经验补充在下面如果通道数超过8路但不需要同时采样CD74HC4067这种方案比直接用双通道ADC加DMA的性价比高得多代码也更直观。地址线要做好初始化。MCU上电后GPIO默认可能是浮空输入状态要在应用初始化前把地址线配置为推挽输出并输出一个确定值。所有模拟输入通道的滤波电容不要选太大。我最初用0.1微法是为了滤除高频干扰但电容越大切换后的稳定时间越长。如果一定要保留大电容必须把切换延时调到毫秒级。多片级联时注意公共输出端COM信号的驱动能力。多个芯片的COM端并联到一个ADC引脚的话只应有一片处于导通状态其余必须禁用否则不仅通道错乱还可能造成信号互相灌电流。调试时抓住“信号链”的思路从信号源到CD74HC4067输入端从输入端到COM端再从COM端到ADC引脚每一段都独立验证。用示波器分段量很快就能缩小故障范围。6.3 这套方案的性能边界与实际选型建议CD74HC4067方案适合的是中低速、中精度的多通道采集场景。如果每路信号带宽只有几百赫兹以下采样周期几十毫秒这个方案完全够用。但如果想用在高频振动信号采集或者需要每通道几千赫兹采样率的场景CD74HC4067的导通带宽和切换速度就不够看了得换成模拟开关带宽更高的芯片或者直接用多通道同步采样ADC。另外如果环境温度变化范围大要注意CD74HC4067的导通电阻温漂会影响精度。数据手册上Ron的温度系数大约是每摄氏度零点几个欧姆对高精度应用会产生额外的误差。这时候要么选低导通电阻型号要么定期做在线校准。用CD74HC4067扩展ADC通道这件事本身不难难的是把建模仿真和实际电路现象对上。调试过程中把示波器波形、数据手册参数和实际代码行为三者结合起来分析解决问题的速度会快很多。这篇笔记里记录的只是我这个项目中遇到的两个典型坑实际项目中可能还会碰到电源噪声、地环路等其他问题工程经验就是一个个坑踩出来的。希望这篇记录能帮后来者少走几步弯路尤其是初期测试时一定别急着怪芯片先把时序和阻抗问题排查干净往往问题出在那些不起眼的细节上。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →