十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

C51单片机驱动AD7746实现高精度微小电容测量与显示

C51单片机驱动AD7746实现高精度微小电容测量与显示 简介本资源是一套基于C51单片机驱动AD7746高精度电容数字转换芯片的完整嵌入式开发工程面向单片机初学者与硬件接口开发实践者解决电容传感信号采集、数值转换与本地可视化显示的核心问题。工程采用Keil uVision环境构建包含13个文件73KB涵盖主程序源码7746.c、编译输出文件.hex/.obj/.lst、项目配置.uvproj/.uvopt、备份文件.bak及预览支持文件.cPreview结构清晰便于理解编译流程与调试逻辑。已有253人学习下载适合作为电容测量系统课程设计、毕业设计或传感器接口开发的参考范例。读者可直接复现AD7746的I²C通信读取、24位数据拼接、十六进制到物理量pF的线性标定转换含CAPDAC补偿、浮点数数码管动态显示等关键环节代码注释明确主循环中已集成延时与显示调度逻辑具备良好可读性与移植基础。 整理硬盘的时候翻出一个老工程基于C51单片机控制AD7746电容检测芯片采集微小电容值之后送到共阴极数码管显示。这块板子当年是我做传感器标定时顺手搭的数字电容表原理不算复杂但真正调通那会儿我在I2C时序和寄存器配置上花掉两个晚上。今天把完整实现思路、关键代码和我踩过的坑都整理出来给同样准备用C51做高精度电容测量的朋友一个可以直接参考的样板。1. 为什么是AD7746C51测高精度电容的选型逻辑1.1 单片机直接测电容为什么不可行很多人看到“测电容”第一反应是RC充放电法用IO口给电容充电再用ADC读电压或者用比较器测量充电时间。这个方法用来粗略判断无极性电容器的好坏没问题想稳定测量pF级别的微小电容就非常吃力了。C51内置ADC的型号本来就不多即使外挂ADC芯片常规ADC的输入电容和PCB寄生电容都在几pF到十几pF传感器本身的电容变化可能只有0.1pF甚至0.01pF。这个量级的信号混在IC引脚电容、走线电容、温漂里根本无法分辨。更要命的是为了测量电容你必须给被测电容提供稳定的交流激励否则电容两端的电荷分布会受到寄生电阻和接触电位的影响。单片机自带的IO口资源很难输出低噪声、频率可控的激励信号。1.2 AD7746的核心优势AD7746是ADI推出的一款24位电容检测芯片它的定位非常明确把小电容变化量转化成数字信号通过I2C接口直接读走。我选它做这个项目理由可以归结为三点第一是分辨率足够硬。AD7746内部有一个24位Σ-Δ ADC配合片内DAC激励源数据手册上给出的分辨率可以达到aF级别。aF是什么概念1pF等于1000fF1fF等于1000aF。实际做下来稳定显示到0.001pF的跳动是能做到的这比单片机方案高出好几个量级。第二是激励源内置。芯片通过EXCA和EXCB引脚输出激励信号连接到传感器上再从CIN1()和CIN1(-)引脚读回差分电容信号。这种“激励-检测”一体化的架构把外围电路需求压到最低只需要一个I2C上拉电阻和必要的去耦电容。第三是接口友好。C51本身没有硬件I2C外设但用普通IO口模拟I2C协议完全够用因为AD7746的输出数据率本来就不高单次转换大约几十毫秒对单片机主频没压力。1.3 这个项目适合谁参考如果你手头有类似的需求比如做电容式液位传感、微距位移测量、湿度传感器标定或者只是想在实验室里搭一个廉价数字电容表这套方案都能直接迁移。项目本身不需要高端单片机一块STC89C52或者AT89S52就能跑成本控制在几十块钱以内。整体难度属于中等偏下真正的坑集中在寄存器配置和模拟I2C时序上这两块后面我会重点拆开讲。2. 硬件搭建连线、电源、数码管驱动一个都不能省2.1 AD7746与C51引脚级连接项目里我用的是STC89C52RC晶振11.0592MHz和AD7746通过I2C通信。连接关系如下AD7746引脚C51引脚说明SDAP1.0I2C数据线需接4.7kΩ上拉到VCCSCLP1.1I2C时钟线需接4.7kΩ上拉到VCCEXCA传感器激励正端输出激励信号到传感器CIN1()传感器信号端差分输入正端CIN1(-)参考电容地端差分输入负端实际布局要靠近传感器返回端VCC3.3VAD7746供电注意要和C51逻辑电平匹配GNDGND共地这里有一个容易忽略的点AD7746的数字接口虽然可以和5V单片机直连但芯片本身供电范围是2.7V到5.25V。为了保证模拟测量部分的稳定性我没把VCC直接接到C51的5V上而是用了AMS1117-3.3稳压然后用AD7746的数据手册推荐电路在VCC和GND之间加一个10μF钽电容和一个0.1μF陶瓷电容并联去耦。C51这边用同一个3.3V供电也可以STC89C52在3.3V下完全能工作这样整体逻辑电平更统一。2.2 电源与PCB布线的几个讲究AD7746是模拟前端芯片对电源噪声和布局非常敏感。我第一次搭板子时图省事直接用面包板飞线结果读回来的数据上下乱跳连稳定到0.1pF都做不到。后来干脆画了一块小板重新布线的过程中总结了几个关键点CIN1()和CIN1(-)的走线要尽量短并且两条线要平行靠近这样才能减少外部电磁场对差分信号的共模干扰。传感器的屏蔽线外层应接到CIN1(-)所在一侧的GND上而不是悬空这个屏蔽层可以有效抑制工频干扰。EXCA激励走线也要短如果布局空间允许在传感器附近加一个0.01μF的滤波电容到GND可以明显改善激励信号的噪声。还有一个容易被忽视的地方AD7746的模拟地和数字地应该用单点连接。具体的做法是C51、数码管等数字电路的地铺在一边AD7746的模拟地单独一根线回到电源地不要在芯片下方让数字地铜皮直接穿过。这样能最大限度减少数字电路开关噪声对电容测量通道的干扰。2.3 共阴极数码管的驱动方式显示部分我用了4位共阴极数码管段选接P0口位选接P2口。P0口内部没有上拉所以必须在P0口外面接10kΩ排阻上拉到VCC。位选这边共阴极数码管每一位的公共端通过NPN三极管S8050接地C51的IO口输出高电平时三极管导通该位被点亮。这样设计的目的是避免单片机IO口直接灌电流。数码管每一段电流按10mA计算4位全部点亮就是160mA直接灌到P0口会损坏单片机。我这边每个段码通路串了一个220Ω限流电阻位选三极管基极串一个1kΩ电阻既可以保证亮度又不会让单片机发热。如果你手里只有74HC573锁存器也可以把段选接锁存器输出这样可以省下一组IO口。我这里为了方便扩展没有用锁存器直接用P0P2因为C51的项目本来就不缺IO口。3. I2C通信与寄存器配置AD7746工作的核心3.1 模拟I2C时序要点C51没有硬件I2C控制器所以核心是先把I2C时序用GPIO模拟扎实。I2C协议本质上就是两根线上的时序配合需要注意以下细节空闲状态下SDA和SCL都必须为高电平。起始信号是SCL保持高电平期间SDA从高拉低停止信号是SCL保持高电平期间SDA从低拉高。数据传输时SDA必须在SCL为低电平期间变化SCL高电平期间SDA保持稳定。AD7746支持400kHz快速模式但C51用IO口模拟时太高的速率容易出问题我实际跑的是100kHz标准模式。数据线电平变化后要加一个小延时给从机一个稳定的采样窗口。这里给出一个可以直接抄的I2C底层函数#include reg52.h #include intrins.h #define I2C_DELAY() {_nop_();_nop_();_nop_();_nop_();} sbit I2C_SDA P1^0; sbit I2C_SCL P1^1; void i2c_start(void) { I2C_SDA 1; I2C_SCL 1; I2C_DELAY(); I2C_SDA 0; I2C_DELAY(); I2C_SCL 0; } void i2c_stop(void) { I2C_SDA 0; I2C_SCL 1; I2C_DELAY(); I2C_SDA 1; I2C_DELAY(); } bit i2c_write_byte(unsigned char dat) { unsigned char i; for(i 0; i 8; i) { I2C_SDA (dat 0x80) ? 1 : 0; dat 1; I2C_DELAY(); I2C_SCL 1; I2C_DELAY(); I2C_SCL 0; } I2C_SDA 1; // 释放SDA等待从机ACK I2C_DELAY(); I2C_SCL 1; I2C_DELAY(); if(I2C_SDA) // 若SDA为高说明从机未应答 return 1; I2C_SCL 0; return 0; } unsigned char i2c_read_byte(bit ack) { unsigned char i; unsigned char dat 0; for(i 0; i 8; i) { I2C_SCL 1; I2C_DELAY(); dat 1; if(I2C_SDA) dat | 0x01; I2C_SCL 0; I2C_DELAY(); } if(ack) { I2C_SDA 0; // 主机回ACK } else { I2C_SDA 1; // 最后一字节不回ACK } I2C_DELAY(); I2C_SCL 1; I2C_DELAY(); I2C_SCL 0; I2C_SDA 1; return dat; }3.2 AD7746寄存器地图速览AD7746的寄存器不多最常用的就是下面这几个0x00状态寄存器。bit7为RDY1表示转换完成、数据可读。0x01电容数据高字节0x02电容数据中字节0x03电容数据低字节0x04电容数据校准字节这个项目用不到跳过0x05、0x06内部温度数据高字节和低字节0x07配置寄存器。控制转换模式连续/单次和通道选择温度/电容/短接0x08CAPDAC设置。bit7使能CAPDACbit6~bit0是偏移量0x09CAPDAC-设置0x0A激励源设置寄存器。选择EXC引脚、激励频率和激励电流每次读取电容数据前我建议先读0x00状态寄存器判断RDY是否为1再连续读0x01、0x02、0x03三个字节。这里有一个诀窍三个字节要在一次I2C读取操作中连续读完不能每读一个字节就回一次起始信号否则数据可能刚好落在转换更新过程中读到新旧数据拼接的脏数据。3.3 配置寄存器和电容设置寄存器怎么填配置寄存器0x07的位定义如下bit[7:5]是转换模式000表示连续转换001表示单次转换011表示单次温度转换。bit[4:3]是通道选择00表示选择内部温度传感器01表示选择外部电容差动输入CIN1()10表示选择外部电容CIN1(-)11表示内部短接。我的项目中传感器接在CIN1()和CIN1(-)之间所以选择外部电容差动输入配置为连续转换模式0x07写入0x08二进制00001000。电容设置寄存器0x08的控制逻辑要单独说明。当传感器本身的静态电容比较大时比如湿敏电阻或者某些电容式探头初始电容可能达到10pF以上。AD7746的差分输入端本身只支持±4.096pF左右的量程超了这个范围内部ADC就会饱和。解决办法是启用CAPDAC把传感器的静态电容先抵消掉一部分让剩下的小变化量落在±4pF的测量窗口内。0x08的bit7是CAPDAC使能位bit6到bit0是DAC值。DAC值与电容的对应关系是CAPDAC值 DAC值 × CAPDAC增益。以默认增益为例每1个LSB对应约0.164pF127个LSB对应约21pF。具体数值要看数据手册里的CAPDAC输出表达式。我建议做法是先关掉CAPDAC读一次原始数据看有没有溢出如果数据偏到量程边缘再逐步增加CAPDAC值直到测量值回到量程中间。激励源设置寄存器0x0A相对简单。bit[7:4]控制激励源选择bit[3:1]控制激励时钟分频bit0控制激励电流。默认配置可以用0x00但某些传感器负载比较大可能需要在EXCA引脚上并一个电阻到GND来增强驱动能力。这个需要根据实际传感器调整不是固定的。3.4 启动转换并正确读取24位数据初始化之后AD7746就会按照配置自动运行。我的主程序里循环执行以下操作unsigned char buf[3]; unsigned char status; long capRaw; float capPF; while(1) { status AD7746_ReadReg(0x00); if(status 0x80) // RDY置位 { buf[0] AD7746_ReadReg(0x01); buf[1] AD7746_ReadReg(0x02); buf[2] AD7746_ReadReg(0x03); capRaw ((long)buf[0] 16) | ((long)buf[1] 8) | buf[2]; if(capRaw 0x800000) // 负值判断 capRaw - 0x1000000; capPF (float)capRaw * 4.096f / 8388608.0f capdacPF; DisplayCap(capPF); } }AD7746_ReadReg函数的完整实现是发送起始信号写0x90AD7746的7位地址0x48左移一位写方向写寄存器地址再发送一次起始信号写0x91读方向读取数据并回ACK或NACK最后发送停止信号。默认I2C从机地址需要确认一下AD7746的地址引脚接GND时地址是0x48产品实际使用时要看一下硬件上的地址配置。capdacPF是CAPDAC设定的偏移电容值如果0x08寄存器写的是0x80也就是CAPDAC使能但DAC值为0那capdacPF就是0。4. 数据从原始码到数码管换算和显示逻辑4.1 原始码到电容值的换算公式推导AD7746输出的24位数据是二进制补码格式0x000000对应0V差分输入0x7FFFFF对应正向满量程0x800000对应负向满量程。如果直接用无符号整数处理负值就会变成很大的正数所以先要判断bit23是否为1是则减去0x1000000得到负数。满量程值是±4.096pF这个数值来自ADC的满量程标定。换算公式实际电容值(pF) (原始补码值 / 8388608) × 4.096 CAPDAC偏移量拿一个具体例子来说如果读到的原始值是0x155555先判断符号位为0说明是正数。转换为十进制是1398101。然后用1398101除以8388608得到0.1667乘以4.096得到0.682pF。这个结果可以理解为传感器电容相对参考端的差值改变了0.682pF。这里有个容易误解的地方AD7746测量的是差分电容变化量不是绝对电容值。如果要测的是一个单端对地电容比如电容式湿度传感器通常把这个传感器接在EXCA激励端和CIN1()之间CIN1(-)接参考电容或直接接地此时读到的仍然是传感器相对基准的变化量。真正决定“绝对电容”的是CAPDAC的偏移值加上这个差分读数。因此在使用前最好用标准电容做一个偏移校准把初始读数记下来后续读数减去初始读数就是电容变化量。4.2 显示范围和小数点策略4位数码管最多显示四个数字而电容值的有效数字往往跨度很大。为了能用有限的数码管清楚表达结果我采用了自适应小数位策略电容值小于10pF时显示格式为X.XXX比如5.123电容值大于等于10pF时显示格式为XX.XX比如12.34。这个逻辑在代码里就是判断整数部分有多少位然后决定把小数点放在第几个数码管后面。共阴极数码管的段码表里小数点是通过段码bit7控制的。比如数字5的段码是0x6D加上小数点后就是0xED。我把这个逻辑直接做在显示函数里unsigned char code segTab[] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; void DisplayCap(float capPF) { int val; int temp; unsigned char dispBuf[4]; char i; if(capPF 10.0f) { val (int)(capPF * 100.0f); // 保留两位小数 temp val / 100; dispBuf[0] segTab[temp]; temp (val % 100) / 10; dispBuf[1] segTab[temp] | 0x80; // 个位数后带小数点 temp val % 10; dispBuf[2] segTab[temp]; dispBuf[3] 0x00; // 最高位熄灭 } else { val (int)(capPF * 1000.0f); // 保留三位小数 temp val / 1000; dispBuf[0] segTab[temp] | 0x80; // 个位数后带小数点 temp (val % 1000) / 100; dispBuf[1] segTab[temp]; temp (val % 100) / 10; dispBuf[2] segTab[temp]; temp val % 10; dispBuf[3] segTab[temp]; } for(i 0; i 4; i) { P0 dispBuf[i]; P2 (1 i); // 位选选择第i位 Delay1ms(2); P0 0x00; // 消隐 } }如果要显示负值可以单独预留一个LED或者用一个0x40段码显示“-”号。实际项目中差分电容变化经常会出现负值比如传感器电容量变小的时候。如果你确认你的测量场景只涉及正电容可以忽略负号否则务必把符号位纳入显示逻辑。4.3 动态扫描的时序处理数码管显示用的是动态扫描每个时刻只有一位数码管导通循环速度足够快后人眼看起来就是四位同时亮。扫描频率不能太低低于50Hz会有明显闪烁我用的2ms扫描切换四位数码管完成一轮需要8ms相当于125Hz刷新率肉眼看着很稳定。动态扫描容易犯的毛病是不加消隐。位选切换的那一瞬间旧位选的电平还没完全落下新段码已经输出就会在数码管上看到轻微的重影。解决办法是在切换位选前先把P0清零留一个短暂延时也就是代码里的P0 0x00那一步。主循环里AD7746的RDY标志位更新一次大约需要几十ms而数码管刷新一次只需要8ms所以显示扫描和电容读取可以和平共处。如果读数的频率很高数码管扫描可能被卡顿可以在轮询RDY的过程中插入显示刷新也就是把显示函数放到中断里主循环只做I2C读取和数据处理。这个项目我考虑到代码简单性直接放在主循环里实测没有卡顿问题。5. 实测中的稳定性和标定让读数更靠谱5.1 上电稳定时间与采样率选择AD7746上电后不能马上开始测量。内部基准源和激励源需要一段时间建立我实测在室温环境下大约需要500ms到1s才能让数据趋于稳定。所以程序里初始化完成后我加了一个1秒的延时再开始轮询RDY。采样率方面连续转换模式下的输出数据率取决于配置寄存器的转换时间设定。AD7746单次转换典型时间是62ms左右连续转换模式下大约每62ms刷新一次数据。如果把转换时间调得更短输出数据率会提高但噪声会增大。做这个项目时我建议直接使用默认转换时间因为对于电容显示来说每秒16次的刷新率已经够用稳定性和低噪声优先级更高。实际开机时我发现一个规律前几次转换的数据经常是异常值尤其是第一次可能是内部电容尚未完全建立。解决方法是启动后先连续读取几次数据并丢弃等到RDY稳定后再把最近一次数据作为起点。5.2 零点标定与满量程校准显示电容值之前零点校准非常重要。具体操作是在传感器不加任何负载的情况下读取AD7746的原始值把这个值记为capZeroRaw。之后每次读取到原始值后先用当前原始值减去capZeroRaw再代入换算公式。这样做的原因在于AD7746的差分输入本身难免残留一些失调PCB走线也会贡献一个固定的分布电容。如果直接按公式换算显示出来的电容值会偏大。在数据手册里也有内部校准寄存器但我觉得工程上不如自己做个软件零点校准更直接。满量程校准我是在传感器并了一个已知容值的标准电容后进行的。比如已知标准电容是10pF把标定前的显示值和实际电容值的比例关系记录下来得到一个增益系数之后把这个系数乘到换算结果里。这个系数通常是接近1的一个值用来修正数据手册标称满量程与实际芯片批次之间的偏差。5.3 温漂和电磁干扰的实测现象在连续跑了两个小时以后我注意到读数会有缓慢漂移大约每小时0.003pF左右这个主要来自温度变化对传感器和电容元件的影响。解决温漂没有特别取巧的办法只能尽量减少热源靠近传感器或者在做液位、位移等长期监测应用时加软件滤波或者周期性重新标定。电磁干扰方面如果在AD7746附近开关继电器、电机或者大电流设备读数会出现尖刺。给传感器输入加上一段均值滤波可以显著改善。我用的是滑动平均取最近8次转换结果做算术平均实测能在不增加明显延迟的前提下把峰峰值噪声压缩到原来的三分之一左右。滤波不是越重越好。如果做快速变化的动态测量取8次平均会引入几十毫秒的滞后。对于本项目这种偏静态的电容显示场景滞后完全看不出影响但如果是做运动检测、液位波动监测就要减小滤波窗口或者改用中值滤波。6. 调试过程中踩过的坑写出来省得你重复6.1 I2C读不到数据可能是地址搞错了最开始写代码时我把AD7746的I2C地址误写成了0x90直接作为7位地址去发送结果从机完全没有应答。AD7746的7位地址是0x48左移一位得到0x90作为写地址、0x91作为读地址。这个细节在数据手册里写得很清楚但一旦忽略了排查起来会非常痛苦。排查I2C问题的通用方法是在每次发送地址后检查ACK位。如果SDA在第九个时钟周期仍然为高说明从机没有应答先查地址是否正确、SDA/SCL有没有接反、上拉电阻是否到位。千万不要一上来就怀疑芯片坏了我后来发现这块芯片极少坏绝大多数是连接和时序问题。6.2 数据跳动特别大查了一圈原来是激励源没配好我最初直接把寄存器0x0A写成0x00觉得默认值就能正常工作结果数据跳变得像随机噪声。后来翻数据手册才发现激励源的时钟分频和激励电流需要和传感器负载匹配。如果传感器电容比较大激励源带不动信号建立不起来测量结果就是乱跳。我当时的解决方法是把激励源设置寄存器改为推荐值并重新检查了EXCA到传感器之间的走线。顺便说一下把传感器信号线屏蔽层接GND之后数据毛刺好了非常多。这件事的教训是AD7746的每个寄存器都不能想当然用默认值尤其是0x08和0x0A这种直接关系到模拟前端的寄存器。6.3 数码管亮度不均匀换段码的瞬间闪了一下如果是动态扫描每一位数码管的点亮时间相同但亮度不同多半是位选三极管的管压降不一致或者限流电阻阻值偏差。我统一换成了精度1%的电阻并对亮度较暗的那位单独降低限流电阻问题解决。如果切换时出现轻微闪烁一般是消隐没做。段码和位选之间的时序处理要严格遵循“先断段码、再切位选”的顺序顺序颠倒就会有鬼影。6.4 负数显示容易把小数点搞丢当测量值是负值时我用一个段码0x40显示负号。但负数显示占了一位小数点位置就不好放了。比如-0.123pF要用四位显示负号、0、小数点、1、2、3总共六个字符根本放不下。我最后的处理是把范围限制在正数显示负值只在负号LED上单独指示数码管显示绝对值。对于大多数传感器场景变化方向比具体数值更重要这个取舍是合理的。6.5 关于Keil工程结构的一点提示标题里写了“KIEL”其实就是Keil。工程里我的代码分了四个文件main.c负责主循环和显示调度i2c.c放底层I2C时序ad7746.c放芯片读写和数据处理函数display.c放数码管驱动。这样拆分的好处是后面如果想把数码管换成LCD或者串口输出只需要替换display.c和main.c里的调用不用碰底层芯片驱动。Keil C51下编译这个工程没什么特殊设置选择AT89C52或STC89C52型号优化等级默认即可注意开启对long类型的支持因为中间结果用到了32位整数。调试时我没有用仿真器直接用STC-ISP把HEX文件下载到芯片里串口打印辅助看数据。如果你手头有逻辑分析仪抓一下SCL和SDA波形对排查I2C问题非常有帮助。本文还有配套的精品资源点击获取
返回列表