ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CD74HC4067扩展16路ADC采集:从入门到踩坑全解析

CD74HC4067扩展16路ADC采集:从入门到踩坑全解析 从入门到踩坑这篇笔记把 CD74HC4067 扩展 16 路 ADC 采集讲透做嵌入式项目ADC 通道不够用这事我估计不少人碰到过。前阵子我接手一个环境监测的小项目主控是 STM32F103C8T6板子上能用的模拟输入引脚满打满算就那么几个可是现场要采集 16 路传感器信号有温度、湿度、光照、气压还有几路 4~20mA 变送器出来的模拟量。一开始我想直接换一颗引脚更多的 MCU评估完发现不但要改板子还要重写不少驱动成本和时间都不划算。后来选了一片 CD74HC4067用 4 根 GPIO 加一根 ADC 输入就把单路 ADC 扩成了 16 路效果还不错。这期调试笔记就把接线、软件实现和调试中遇到的两个坑一起说清楚尤其是那两个坑真让我花了不少冤枉时间。1. 为什么用 CD74HC4067 而不是换 MCU1.1 项目里的真实困境STM32F103C8T6 这颗芯片其实带 3 个 ADC但真正能用于模拟输入的引脚并不富裕尤其当外围功能把引脚占掉之后能挪出来给 ADC 的更少。我那个项目里要采 16 路模拟量如果靠换 MCU 解决首先得考虑 LQFP100 甚至更封装的型号电路板要重画电源、晶振、调试接口这些都得重新布局产品外壳开模的尺寸可能也得跟着变。对于一个已经接近定型的项目来说这个代价太大了。用模拟多路开关扩展 ADC 通道思路就完全不同MCU 不用换ADC 外设不用动只要在 ADC 输入引脚前面接一颗 16 选 1 的模拟开关把 16 路传感器信号轮流切到同一个 ADC 引脚上一次只采集一路分时完成所有通道的信息获取。对采样率要求不太高的场景这种做法非常实用。1.2 几个方案的对比我当时列了几个备选方案简单对比如下方案优点缺点适合场景换更高引脚数 MCU通道数原生增加采集质量高改板、改驱动、周期长项目还处于原型阶段外挂多通道 ADC 芯片精度高、采样快价格高通信和驱动复杂对精度和同步性要求高CD74HC4067 模拟开关扩展成本低、电路简单、改动小通道间分时轮询无法同时采集串扰需注意传感器信号变化慢、节点多多颗低通道模拟开关级联灵活但线路复杂控制引脚多软件复杂特殊布局需求我的项目里传感器信号基本都是慢变信号温度、湿度这类信号本身还有惯性轮询扫描完全够用。CD74HC4067 当前价格很低一片就解决 16 路板子也只要在原有 ADC 输入前端加一点电路省时省力。1.3 CD74HC4067 的工作原理CD74HC4067 本质上是 16 选 1 模拟多路复用器/多路解复用器内部有 16 个模拟开关公共端 COMMON也叫 COM 或 I/O 引脚通过 4 位地址线 S3、S2、S1、S0 选通对应的通道 C0 到 C15。注意它是双向的既可以把 COMMON 当作输入、C0~C15 当作输出也可以反过来用。它还有一个低电平有效的使能引脚 EN。EN 为高时所有开关都断开COMMON 处于高阻状态EN 为低时地址线才会真正生效选中其中一个通道让 COMMON 和该通道之间形成通路。这个引脚很关键我后面要讲的第一个调试坑就和它有关。2. 硬件接线与参数选型2.1 最小接线方案我用的电路非常典型把 CD74HC4067 放在传感器信号和 STM32 的 ADC 引脚之间。接线关系如下表CD74HC4067 引脚接到哪S0、S1、S2、S3STM32 的 PA0、PA1、PA2、PA3推挽输出ENSTM32 的 PA4推挽输出软件拉低COMMONCOMSTM32 的 PA5ADC1_IN5C0~C1516 路传感器模拟信号输出VCC3.3VGND系统地注意 VCC 和 GND 之间要加 0.1uF 去耦电容位置尽量靠近芯片电源脚。COM 引脚和 ADC 输入之间的连线要短避免长走线捡到噪声。我板子上 COM 到 PA5 的走线只有 1 厘米左右实测噪声控制在比较好的水平。2.2 需要吃透的关键参数CD74HC4067 有几个参数直接影响系统设计建议选型和使用时重点看工作电压CD74HC4067 是 HC 系列电源范围大约在 2V 到 6V3.3V 系统可以直接用。如果手头是 CD40674000 系列电源范围更宽但速度慢一些。用的时候一定要看清型号后缀。导通电阻 RON这是重点。在 VCC5V 条件下RON 典型值在几十欧到一百欧左右而且会随输入电压、温度变化。对低阻抗源影响不大但对高阻抗传感器必须警惕我后面会举例说明。泄漏电流当某一路开关断开时该通道仍会有极小的泄漏电流通常只有 nA 级。对大部分传感器信号来说可以忽略但如果信号源阻抗极高比如光电二极管直接输出这点泄漏也可能在源电阻上产生误差电压。切换时间地址建立到开关真正导通大约在几十纳秒量级看起来很快但切换到 ADC 读取之间还要考虑后级电容充电问题这就是第二个坑的关键源头。2.3 高阻源下的分压误差很多人只关注 CD74HC4067 能选通多少路忽略了导通电阻带来的影响。比如一个 NTC 热敏电阻分压电路上拉电阻是 10kΩNTC 阻值也是 10kΩ 量级那么节点等效输出阻抗大约是 5kΩ。这个阻抗和 CD74HC4067 的导通电阻 RON 串联后再进 ADC如果 RON 是 100Ω误差大约是 100/(5000100)约 2%对温度测量来说已经不小了而且 RON 随温度变化还会引入额外的漂移。我当时的处理办法是对阻抗特别高的传感器先经过一级运放缓冲让输出阻抗降到几十欧以内再进多路复用器。如果信号源本身就是低阻抗比如 4~20mA 采样电阻上的电压直接接问题不大。这块在做硬件设计时就必须想清楚等软件出问题再回头补运放就难受了。3. 软件实现分时扫描 16 路3.1 GPIO 控制与通道切换我用的 STM32F103配合 HAL 库。首先要配置 5 个 GPIOPA0~PA3 作为通道地址输出PA4 作为 EN 控制输出全部配置成推挽输出初始电平拉低。通道切换函数很简单就是给 PA0~PA3 赋值然后拉低 ENvoid MUX_SetChannel(uint8_t ch) { // ch 范围 0~15对应 C0~C15 GPIOA-ODR ~(0x0F 0); // 先把 S0~S3 全部清零 GPIOA-ODR | ((ch 0x0F) 0); MUX_Enable(); } void MUX_Enable(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); } void MUX_Disable(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }这里直接操作 ODR 寄存器比反复调用 HAL_GPIO_WritePin 要快多个引脚同时赋值也能保证通道切换的同步性。3.2 基于 HAL 库的 ADC 单通道采集ADC 我配置成单通道模式PA5 作为 ADC1_IN5。初始化部分不多说了关键是采集函数。因为我只需要一个 ADC 引脚所以不用 DMA 也没问题阻塞式读取足够uint16_t ReadADC_Channel(uint8_t ch) { uint16_t value; MUX_SetChannel(ch); delay_us(100); // 等待多路开关和运放电路稳定 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); value HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return value; }然后把 16 路轮流扫一遍存到一个数组里uint16_t adc_values[16]; void ScanAllADC(void) { for (uint8_t i 0; i 16; i) { adc_values[i] ReadADC_Channel(i); } }3.3 建立时间和轮询周期设计代码里的 delay_us(100) 不是我随手写的是有讲究的。模拟开关虽然切换时间只有几十纳秒但开关导通的瞬间后面接的 ADC 采样保持电容、PCB 上的寄生电容、如果有滤波电容的话还要给这些电容充电这个建立过程需要时间。如果后级加了 RC 低通滤波比如一个 1kΩ 电阻加 100nF 电容时间常数是 100us要让信号稳定到 0.1% 以内大约需要 5 个时间常数也就是 500us。我在电路里没有额外加重型 RC 滤波只有 ADC 引脚附近一个 1nF 的电容时间常数很小100us 足够。但如果你在 COM 到 ADC 之间加了较大的滤波电容这个延时必须重新核算。这是第二个坑的根源后面细说。轮询周期方面16 路全部扫一遍每路 100us加上 ADC 转换时间单轮不到 3ms。对温度、湿度这种慢信号来说每 10ms 扫一轮完全够用即使做 100 次平均再输出一个值实时性也完全没问题。4. 两个调试坑实测与排查4.1 坑一EN 引脚悬空导致所有通道选不中我第一版电路打样回来后程序也写好了烧进去一跑发现所有通道的 ADC 读数都不对有的通道是 0有的通道是满量程随机跳。当时第一反应是传感器接线有问题拿万用表一个一个量传感器输出发现传感器本身信号是好的于是怀疑 ADC 配置不对又把 ADC 初始化翻来覆去改依然没有效果。后来用示波器去量 COM 引脚波形发现 COM 引脚在切换通道时根本没有变化始终像悬空的高阻状态。再查 CD74HC4067 的原理图发现 EN 引脚在原理图上竟然没接地而是空着的。这颗芯片的 EN 是低电平有效悬空状态下引脚电平不确定内部逻辑可能在禁止状态所有开关常开COM 当然不变化。我后来在软件里把 PA4 接 EN 并拉低问题瞬间消失。这个坑说到底是硬件粗心原理图评审也没注意到焊接时发现了但想当然觉得“使能脚不接也没事”。其实数据手册上写得清清楚楚EN 不能悬空不用的时候必须接到确定的电平。对于低电平有效的使能端要么直接接地要么用 GPIO 控制就是不能让它“自由飞翔”。排查这样的问题时示波器是最好的工具量一下 EN 引脚电平是否稳定量一下 COM 是否随地址切换而变化很快就能缩小范围。只靠万用表量直流电压有时会骗人因为悬空脚在万用表内阻下会显示一个虚电平容易误判。4.2 坑二切换通道后第一拍数据一直是乱的EN 的问题解决后16 路都能读出来了但我很快发现一个新的麻烦每次切换通道后第一次采到的值明显偏大或偏小第二次、第三次读到的数据才稳定。我把延时从 10us 加到 100us 效果有所改善但某些通道依然存在“首拍跳变”。这个现象有几个来源多个通道的传感器输出阻抗不同驱动能力不同切换后给 ADC 采样电容充电的时间不一样。模拟开关断开时被选中的通道引脚上还残留之前的状态电荷没有完全泄放。COM 引脚到 ADC 输入之间的寄生电容和采样保持电容在开关刚导通的瞬间要从 0 或某个残留电压充到目标电压充多少时间取决于源阻抗和电容乘积。我最终的解决办法是每次切换通道后先做一次“无效读取”也就是切换后延时一小段时间然后启动一次 ADC 转换但丢弃这次结果紧接着再采一次作为有效值。相当于让第一拍去“预热”采样链路第二拍才正式记录。uint16_t ReadADC_Channel(uint8_t ch) { uint16_t value; MUX_SetChannel(ch); delay_us(50); // 第一拍丢弃让开关和电容稳定 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); // 第二拍真正读取 HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 100); value HAL_ADC_GetValue(hadc1); HAL_ADC_Stop(hadc1); return value; }用这种方法之后16 路数据都干净了。后来又发现一个细节如果某一路传感器是被 CD74HC4067 切走的也就是当前不选它但它自身和 GND 之间有个大电容切到该通道后首拍还是会有充放电现象。这时候“无效读取”配合足够的延时是最简单可靠的方案。4.3 排查工具与手筋调试这两个坑时我的工具箱里最有用的东西排序是这样的示波器看 COM 引脚波形、看 ADC 输入引脚采样前的稳定度定位建立时间问题。万用表检查 EN 电平、传感器输出、供电电压排除最简单的接线错误。串口打印把每路 ADC 原始值直接打出来观察是固定错误还是随机错误帮助判断是硬件问题还是时序问题。另外我习惯在软件里保留一个“调试通道”从 16 路里抽一路接已知电压比如 1.5V 基准每次扫描时这一路必须是稳定值。如果这一路都开始跳说明问题出在公共链路而不是具体某路传感器。5. 稳定性和精度滤波、校准与优化5.1 软件滤波滑动平均与中值实测中即使排除了上面两个坑ADC 读数偶尔还是会有点毛刺尤其是在工业现场电机启停、继电器吸合都可能通过电源或空间耦合进噪声。软件滤波是最廉价的抗干扰手段。我用得最多的是滑动平均对慢变信号效果很好#define FILTER_N 8 typedef struct { uint16_t buffer[FILTER_N]; uint8_t index; uint32_t sum; uint8_t count; } sliding_filter_t; uint16_t sliding_filter_add(sliding_filter_t *f, uint16_t value) { if (f-count FILTER_N) { f-count; } else { f-sum - f-buffer[f-index]; } f-buffer[f-index] value; f-index (f-index 1) % FILTER_N; f-sum value; return f-sum / f-count; }如果担心单个脉冲毛刺中值滤波更合适把最近 N 次采样排序后取中间值能直接干掉离群点。对温度这种信号我一般先用滑动平均窗口取 8 或 16 即可。5.2 多路复用场景下的特殊校准用 CD74HC4067 扩展 ADC本质上每个通道的信号路径都经过同一个开关导通电阻的差异和温度漂移会给测量带来系统误差。如果项目对精度要求高可以做简单的软件校准。我常用的做法是“两点校准”已知两个标准电压 V1 和 V2分别从某一通道输入读出 ADC 值 C1 和 C2然后算出该通道的增益和偏置gain (V2 - V1) / (C2 - C1) offset V1 - gain * C1实际电压 gain * code offset如果线性度还不够可以做三点校准把测量范围分成两段每段单独算增益和偏置工程上足够应对大多数场景。因为 CD74HC4067 导通电阻随电压非线性三点校准能对这种非线性进行分段补偿这一点在 4~20mA 变送器采样时尤其有用。5.3 后续优化方向这个方案后续还有不少可玩的空间用 DMA 配合定时器触发可以把 16 路采集做成后台自动循环CPU 开销几乎为零。每路切换后做一次无效采样再开启有效采样利用 DMA 连续搬运代码结构会更高效。如果每路传感器都需要独立量程或单位换算可以把通道号作为索引做成一个查表配置扩展性更好。如果担心抗干扰能力在 COM 引脚对地加一个小电容做高频滤除或者在后级加一级 RC 低通。但加电容后必须重新核算建立时间或者干脆每通道做“两次读取丢弃首拍”的补偿。我个人经验是CD74HC4067 这种方案最适合“通道多、信号慢、成本敏感”的项目。它解决的是 ADC 引脚不足的问题而不是提高采样率或精度的问题用的时候摆正预期就好。如果哪天项目里出现了需要高速同步采集的信号就应该考虑外置多通道同步采样 ADC 了不过那是另一个话题了。
返回列表