恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

用CD74HC4067扩展ADC采集的完整实战:电路、代码与调试坑

  • 首页
  • 资讯中心
  • /
  • 用CD74HC4067扩展ADC采集的完整实战:电路、代码与调试坑

相关资讯

Generative AI 应用安全指南:基于 Microsoft generative-ai-for-beginners 开源课程的安全编码实践 2026/9/8 23:42:54
AI大模型就业班学习路线:从Python基础到RAG与智能体实战 2026/9/8 23:37:54
AI模型部署实战:四种主流方式与选型指南 2026/9/8 23:37:54

最新资讯

基于STM32的智能流量计与水泵报警系统设计实战解析
基于Zapier SDK与AI编码代理的日历事件自动迁移实践
React学习笔记:组件拆分、状态管理与Vite+TS工程化实践
AI伦理测试框架实战:从偏见检测到CI/CD落地
Python + React 构建企业级能源管理系统:MyEMS 技术选型与工程实践
SpringBoot+Vue+AI图书管理系统:从数据库到部署全流程实战

今日推荐

基于MongoDB的图书管理系统:数据建模与Spring Boot+Vue实战
Claude Code安装配置全攻略:从零开始用上终端AI编程助手
tmux 会话管理与终端复用:AI 编程工作流的调度中枢实战

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

用CD74HC4067扩展ADC采集的完整实战:电路、代码与调试坑

发布时间:2026/9/8 23:42:54
用CD74HC4067扩展ADC采集的完整实战:电路、代码与调试坑 上礼拜调试一块多路采集板MCU内置ADC只有8个通道传感器却有14路逼得我把吃灰的CD74HC4067翻出来扩通道。4067这种16选1模拟开关在嵌入式里算是最朴素的ADC扩展方案几毛钱一颗、控制逻辑简单但真把它调通调准坑也不少。这篇笔记就记录这次用CD74HC4067扩展16路ADC采集的完整过程从电路连接、软件驱动写到两个折腾到半夜的调试坑希望对正在扩通道的你有参考价值。1. 为什么非要用CD74HC406716路ADC的方案对比1.1 需求背景这次的项目是一块环境监测板需要采集16路模拟量包括温湿度传感器输出、光敏电阻分压、电位器位置反馈等。MCU选的是STM32F103系列片上ADC有16个外部通道但资源被分配了实际可用的只剩8路。而且这16路传感器分布在不同的接口板上走线长度差别很大信号类型也不统一有的输出阻抗高有的带一点容性负载。当时摆在面前的路有三条换MCU、外挂独立ADC芯片、用模拟多路开关扩展。换MCU意味着重新画板、重新评估成本周期太长外挂ADS1115这类16位ADC芯片精度确实高但一路芯片只能处理4路16路得4颗成本直接上去了。最后选了CD74HC4067一颗芯片解决16路采集配合MCU内置ADC整体成本几乎没有增加。1.2 方案选型的逻辑模拟多路开关的本质是一个单刀多掷开关通过地址线选择16路输入中的某一路把它连接到公共输出端再由MCU内置ADC完成采样。CD74HC4067在这个场景下的优势很明显对比项CD74HC4067扩展外挂ADC芯片换多通道MCU成本极低一颗几毛钱中高按路数翻倍高需换型号电路复杂度简单只需地址线和公共线中需配置I2C/SPI中需重新评估采样精度受导通电阻影响高取决于型号通道数扩展可级联扩展性强受限受限软件复杂度低切换地址读ADC即可中需驱动协议低当然4067也有短板导通电阻和切换时间决定了它不适合高频采样场景但用在几十赫兹到几百赫兹的低速多路采集上完全够用。如果你做的是高速采集或者对精度要求极高的工业测量还是得老老实实上独立ADC。提示选型时一定要明确采样速率需求。4067适合信号变化不快、对采样率要求不高的场景ADC每个通道的采样率往往只有几十到几百Hz这正是模拟开关的舒适区。2. 4067的工作原理与关键参数2.1 内部结构与地址选择逻辑CD74HC4067内部是16路模拟开关每一路开关由一对互补的CMOS传输门组成地址线S0到S3组合出16种状态EN引脚是整个芯片的使能端拉低时正常工作拉高时所有开关断开。真值表非常简单S3S2S1S0从0000到1111分别选中C0到C15通道公共端COM就是被选中的那一路。比如S30,S20,S11,S00也就是地址0010选中C2通道。实际接线时把传感器信号接到C0~C15公共端COM接MCU的ADC引脚S0~S3接四个GPIOEN接一个GPIO拉低。有一点要特别提醒4067的COM端是双向的。你可以把多路信号汇入COM然后接ADC也可以反着用把一路信号通过COM分发给16路输出做多路DAC输出或LED驱动。这次我们只用前者但理解双向特性对排查问题有帮助。2.2 导通电阻与切换时间的实测感受数据手册上CD74HC4067的导通电阻典型值是70Ω左右在5V供电下但这是理想值实际会随电源电压和信号电平变化。3.3V供电时导通电阻能到120Ω到200Ω如果输入信号接近电源轨电阻还会更大。这个参数在纯数字开关选型时容易被忽略但在模拟信号链里影响非常大。举个例子传感器源阻抗如果是100kΩ经过4067后ADC看到的是100kΩ串联200Ω再对采样电容充电。200Ω本身不大但如果源阻抗本身低这200Ω就变成了分压的一部分直接造成读数偏低。切换时间这边数据手册标的是几十纳秒但那是开关本身的机械动作时间不代表采样系统能立刻稳定。实际上开关切换后信号源内阻、PCB走线寄生电容、ADC内部采样电容三者构成的RC回路需要时间充电这才是决定采样稳定性的关键。这个坑后面详细说。3. 硬件电路设计与PCB注意事项3.1 最小系统接线这次用的最小系统接线是这样的VCC接3.3VGND接电源地电源引脚旁边放一颗100nF去耦电容位置尽量靠近芯片。S0~S3接MCU的四个GPIO例如PA0到PA3。EN接一个GPIO比如PA4软件里拉低使能。C0~C15接16路传感器信号。COM引脚接MCU的ADC输入引脚比如PA5ADC1_IN5中间不要串电阻。注意COM到ADC引脚之间的线越短越好而且不要在这条线上加π型滤波电容。因为模拟开关的输出有导通电阻后面再并电容会跟这个电阻组成低通滤波器导致信号建立时间变长高速切换时会出现严重的通道串扰。3.2 去耦与地线处理4067虽然是个低速器件不代表它不需要好电源。开关动作瞬间内部所有传输门都会经历短暂的导通过渡会产生一个微小的电流尖峰如果电源去耦不好这个尖峰会耦合到模拟开关的输出端表现为切换瞬间的毛刺。地线处理上模拟地和数字地建议单点连接传感器信号走线的下方要保持完整的地平面。这次做的是双层板我把4067附近的顶层走线尽量加粗底层保持完整覆铜接地实测噪声比第一版有明显改善。还有一个容易踩的坑是悬空引脚。CN74HC4067的C0~C15如果有通道没用到建议直接接地。原因后面在第二个坑里会详细讲这里先记住结论。3.3 信号源阻抗的匹配这部分的经验值很重要。经过这次调试我总结出一个原则进入4067的信号源阻抗最好控制在5kΩ以下如果超过10kΩ必须采取措施。对高阻抗信号最简单的处理是每个通道加一级电压跟随器。比如光敏电阻分压这种源阻抗几十kΩ直接用电压跟随器缓冲后再进4067。成本会高一点但换来的是稳定性和精度值得。如果不想加运放还有一个折中办法在每个通道对地并联一个100nF电容把源阻抗转换成对电容充电的问题这样开关导通后虽然是缓慢建立但稳定后读数不受分压影响。4. 软件实现驱动代码与完整采集流程4.1 GPIO与ADC初始化软件部分用的STM32标准库开发环境是Keil。首先初始化控制GPIO和ADCvoid MUX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // S0~S3 EN推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 默认使能4067 GPIO_ResetBits(GPIOA, GPIO_Pin_4); } void ADC_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // PA5作为ADC输入模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); // 采样时间调长这个参数很关键 ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 1, ADC_SampleTime_239Cycles5); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); }4.2 核心通道切换与采样函数通道切换和采样的核心逻辑void MUX_SelectChannel(uint8_t ch) { if (ch 15) return; GPIO_WriteBit(GPIOA, GPIO_Pin_0, (ch 0x01) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_1, (ch 0x02) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_2, (ch 0x04) ? Bit_SET : Bit_RESET); GPIO_WriteBit(GPIOA, GPIO_Pin_3, (ch 0x08) ? Bit_SET : Bit_RESET); // 切换后延时等待信号建立 Delay_us(200); } uint16_t MUX_ReadChannel(uint8_t ch) { MUX_SelectChannel(ch); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); }这个简单的函数里藏着这次调试最大的两个坑下面详细展开。4.3 轮询采集完整逻辑完整采集16路数据时我希望每路采集4次取平均同时做一次滑动滤波。这里我把切换和采样的逻辑合在一起#define CHANNEL_COUNT 16 #define SAMPLE_TIMES 4 uint16_t raw_values[CHANNEL_COUNT] {0}; uint16_t filtered_values[CHANNEL_COUNT] {0}; void PollAllChannels(void) { uint16_t sum, val; uint8_t i, j; for (i 0; i CHANNEL_COUNT; i) { MUX_SelectChannel(i); // 采集4次去掉最大最小再平均 sum 0; uint16_t min_val 0xFFFF, max_val 0; for (j 0; j SAMPLE_TIMES; j) { val MUX_ReadChannel(i); if (val min_val) min_val val; if (val max_val) max_val val; sum val; } sum - min_val max_val; raw_values[i] sum / (SAMPLE_TIMES - 2); // 简单滑动平均 filtered_values[i] (filtered_values[i] raw_values[i]) / 2; } }每次切换后延时200us是为了让信号充分建立但实际使用中发现这个延时并不是固定的需要根据信号源阻抗动态调整。这个等坑一里细说。5. 调试坑一通道切换后采样值飘移延迟不够不是小问题5.1 现象描述第一版代码里MUX_SelectChannel函数只写了切换GPIO没有加延时切换后立刻读ADC。结果就是读出来的数值在初始几次不稳定尤其是从高通道切到低通道时数值会先偏大然后慢慢回落稳定下来需要几十到几百微秒。如果用串口打印看波形能看到明显的阶梯状爬升。更麻烦的是这个不稳定时间和信号源阻抗强相关。源阻抗低的通道勉强能读源阻抗高的通道怎么读都是错的而且错的方向还不一样有的是偏低有的是偏高完全看信号路径上的寄生参数。5.2 排查思路与示波器确认一开始我怀疑是GPIO切换速度不够用示波器看了S0~S3的波形切换速度是纳秒级的没问题。接着看COM引脚也就是ADC输入端的波形问题就出来了通道切换后COM端电压不是一下子跳变到目标值而是按指数曲线缓慢爬升爬升时间最长的通道要400us才能稳定到最终值的99%。这个指数曲线就是典型的RC充电曲线。信号源等效电阻Rs加上4067的导通电阻Ron共同对负载电容CL充电时间常数τ (Rs Ron) × CL。CL包括4067输出端的寄生电容、PCB走线电容、还有STM32内部ADC采样电容加起来大概在20pF到50pF。如果Rs是100kΩτ就是100kΩ × 50pF 5us理论上5个τ也就25us但实测要400us说明还有别的因素。后来发现第一版PCB我把COM引脚的走线拉得很长中间还走过一个过孔这部分寄生电容远大于预期。另外测试时用了示波器探头10×档输入电容约15pF1×档能达到100pF以上探头本身就在加重负载测量出来的建立时间被放大了。5.3 为什么延时不能一刀切现在很多人写4067的驱动喜欢固定Delay几个微秒这种做法在源阻抗稳定的场景下没问题但一旦信号源阻抗变化范围大就必须动态考虑。从工程角度最简单的做法是把MUX_SelectChannel的延时参数化。我给这个函数加了一个全局变量mux_settle_us根据实测每路阻抗单独设置。高阻通道设500us低阻通道设50us采集一轮16路也就几毫秒对低速应用完全能接受。5.4 最终的解决方案与计算依据解决这个问题我做了三件事把ADC的采样时间从1.5周期改成239.5周期。STM32F103的ADC采样时间寄存器可以配置从1.5到239.5个ADC时钟周期这个值决定ADC内部采样开关保持的时间直接影响采样电容能否充满。对高阻信号源采样时间必须拉长。MUX_SelectChannel里根据通道号设置不同的建立延时默认给200us。经验公式是如果信号源等效阻抗为Rs负载电容估到100pF至少要等5倍以上的时间常数t_settle ≥ 5 × (Rs Ron) × CL。Rs10kΩ时t_settle ≈ 5 × 10k × 100p 5us但保守起见我会加10倍余量。把COM引脚的PCB走线缩短去掉过孔让4067的COM输出离STM32的ADC引脚尽量近。这一条其实是治本硬件改完后同样的软件配置建立时间缩短了将近一半。注意排查这类问题时不要上来就怀疑代码。先量一下ADC输入引脚的波形看信号是从什么时候开始建立的、什么时间到稳态再对症下药。示波器探头用10×档1×档会严重影响高阻节点的测量结果。6. 调试坑二没选中的通道不是高阻信号在内部悄悄分压6.1 这是个让人意外的坑第二个坑是我印象最深的也是花时间最多的。现象是系统看起来一切正常16路都能读到值但某些通道的读数系统性偏低。比如用可调电源从0V到3.3V扫一个通道读数是线性递增的但斜率只有0.9左右3.3V输入只读到2.97V怎么校准都修不回来。我当时以为是ADC的增益误差做了单点校准修正后又发现不同电压段误差不一样明显不是一个固定偏移能解决的。直到我把输入源改成高精度可调源再拿万用表量4067公共端COM的电压才发现问题开关选通时COM端电压跟输入不一样被旁边的通道“拉”低了一截。6.2 内部原理分析去翻CD74HC4067的数据手册慢慢理解了这个芯片的一个关键特性当选中的通道被导通时未选中的通道并不是完全断开的高阻态而是会呈现一个到VSS的低阻路径。换句话说未选中的通道相当于在每个输入引脚上都接了一个到地的“伪开关”。这是什么概念想象一下你的信号源是一个3.3V经过100kΩ电阻分压出来的电压等效内阻约50kΩ。进入4067接口后通道C0被选中理论上COM端应该是这个分压值。但旁边C1通道有一个到VSS的等效低阻路径可能是几千欧也可能是几十千欧具体取决于芯片内部结构。这样信号源的50kΩ内阻和C1通道的接地路径之间就形成了一个分压最终COM端的电压比真实值低。更可怕的是这个分压比例跟相邻通道的连接状态有关。你把某个相邻通道悬空和把它接地结果完全不同。我在测试时发现把C1接地C0读数直接掉了一大截而C1悬空C0还能保持相对正常。这就是为什么有时候代码怎么调都调不对因为干扰源完全在硬件链路里。6.3 工程设计上的应对措施这个问题从根本上说是CMOS传输门结构的固有特性解决办法有几个方向一是尽量降低信号源阻抗。4067的导通电阻在百欧级别未选通通道的接地等效电阻也在这个量级附近。如果信号源内阻能控制在1kΩ以下这个分压效应可以忽略不计。对高阻信号前面加一级运放跟随器是目前最彻底的方案。二是给高阻通道并联电容。在传感器输出端对地并一个100nF电容相当于给信号源提供了一个低阻抗的交流通路。这样开关切换后电容上保持的电压就是信号源的开路电压而不是分压后的值。代价是切换后建立时间变长所以需要配合坑一里的延时策略。三是在软件上理解并规避。对稳定信号你可以在一个通道上持续采样多次取稳定值而不是切换一次读一次。因为在长采样时间下信号最终会稳定到一个固定值只是这个值因为分压效应偏低。但这个方法不推荐因为源头的问题没解决读出来的值是错的。6.4 关于“备用通道接地”的讨论网上很多资料建议未使用的通道接地我实测下来这个建议要分情况看。如果备用通道直接接地确实避免了悬空引脚带来的噪声耦合但也会让同源高阻通道的分压更明显因为等效到地的路径更“实”了。我的建议是备用通道悬空但在PCB上预留到地的焊盘。如果量产时发现某一路噪声大再割线接地调试。对于当前全部16路都用满的场景这个问题不存在但万一以后需要扩展预留这个灵活性很重要。提示这颗芯片在纯数字信号场景下未选通通道的特性基本无感但一旦进了模拟信号链内部漏电路径的影响就会被放大。设计时留足阻抗预算比事后靠校准补救要可靠得多。7. 校准与滤波扩展ADC的精读补救方案7.1 软件滤波不能替代硬件优化遇到数据抖动大家先想到的是软件滤波。滑动平均、中值滤波、一阶低通这些确实能平滑波形但要注意滤波只能处理噪声不能纠正系统误差。比如分压效应读出来的值始终偏低你滤波再狠它也是稳定的偏低治标不治本。我这次的滤波策略是每通道连续采样4次去掉最大最小取平均再加上一阶低通。实际效果是在无外部干扰的情况下最终输出稳定在±1个LSB以内16路全部扫一遍大约耗时20ms对于温湿度、光照这类慢变量系统响应速度完全够。7.2 三点校准的思路理想情况下MCU内部ADC的转换结果是线性的实际用起来会有增益误差和偏移误差。4067的导通电阻还会引入一个附加的增益误差因为Ron会跟信号源内阻分压。我做了一个三点校准表每个通道分别输入0V、1.65V、3.3V三个精密电压记录ADC原始值然后线性拟合出每个通道的斜率k和偏移b公式是实际电压 k × 原始ADC值 b。把16个通道的k和b存在Flash里运行时查表修正。实测校准效果非常好。校准前某些通道偏差5%以上校准后全量程误差控制在0.5%以内。但前面也说了这套校准只对硬件链路固定的情况有效一旦信号源阻抗变了校准表就失效。7.3 实测数据一览这是我在某几个典型通道上做的原始值和校准对比通道输入电压(V)原始ADC读数(LSB)校准前计算(V)校准后计算(V)C00.00020.0010.000C01.65020381.6441.650C03.30040333.2543.298C70.00050.0040.001C71.65020111.6221.649C73.30039873.2163.301偏差从原来最大的3%降到0.3%以内肉眼可见的改善。8. 调试工具与排查技巧这次的调试过程用到的工具也值得记一笔。串口调试助手是主力我在MCU里加了一条调试命令可以实时把16路ADC原始值和校准后的电压值通过串口打出来配合上位机软件画成波形数据变化的趋势一目了然。另一个得力工具是示波器。排查坑一的时候示波器一探下去就清楚了。排查坑二的时候万用表反而比示波器好用因为分压效应是个直流特性万用表测得的稳定值最能说明问题。GDB调试在嵌入式里对这类硬件问题帮助有限但如果你用的是带MMU的MPU比如RK3568这类直接Linux下写个ADC测试驱动再用GDB看变量效率会高很多。这次STM32的开发还是Keil MDK加硬件调试器问题定位主要靠串口打印和示波器。9. 收尾记录几点个人心得这次用CD74HC4067扩展16路ADC采集前前后后花了三天最大的收获不是驱动写得多漂亮而是对这个芯片的内部特性有了切身理解。模拟开关不是理想的机械开关它有自己的导通电阻、漏电通道和寄生电容在数字世界里可以忽略的参数放到模拟信号链里都会被放大。如果你也准备用这颗芯片我建议按这个顺序做先用精密信号源和万用表验证单个通道在低阻抗信号下的精度再用高阻抗信号源测试通道间相互影响最后才写完整驱动。顺序反了一旦数据不对排查起来成本很高。另外如果你有比较充足的预算可以考虑ADS1220这类多通道ADC专用芯片它不需要前端搞模拟开关内部自带输入多路器和可编程增益放大器省心很多。当然前提是项目对成本和开发周期的要求没那么敏感。我这块板子最终还是要用4067毕竟成本摆在那里。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号