拓冰建站拓冰建站
首页 / 资讯中心 / 正文

C51模拟I2C驱动AD7745电容传感器完整教程

简介面向单片机与嵌入式开发者的AD7745/AD7746电容检测芯片驱动源码包基于C51单片机通过I2C总线实现与芯片的通信涵盖初始化、数据读取及错误处理等核心流程可直接用于触摸按键、液位检测等电容测量场景。压缩包共13个文件以C源码为主并包含Keil工程文件ewp/eww/ewd、编译依赖与调试配置文件整体仅31KB结构简洁便于阅读与移植。资源已有995人学习源码中通过主程序与驱动文件分离的方式展示工程组织思路对熟悉STM32、ARM等平台的开发者同样具有参考价值可快速理解电容检测芯片的驱动原理与I2C时序控制方法。1. 用C51给AD7745写I2C驱动比你想的更简单做电容检测的工程师很多会选AD7745因为它在小尺寸封装里做到了±4 fF分辨率配合CIN引脚可以直接接传感器电容。而常见项目里用的还是STC89C52这类增强型8051它们大多没有硬件I2C控制器所以驱动AD7745的工作就是把这套I2C通信协议用GPIO一根线一根线地“抠”出来。本文从I2C通信协议讲起给出C51模拟I2C的完整思路再落到AD7745的寄存器配置和24位数据读取最后聊几个调试时特别容易翻车的细节。如果你正在用51单片机做液位、湿度或接近检测这篇可以直接照着写。2. AD7745的硬件连接与I2C通信协议要点2.1 AD7745/AD7746的引脚定义与I2C地址选择先把芯片引脚看清楚。AD7745是单通道电容输入AD7746是双通道但两者的I2C寄存器映射几乎一致驱动代码可以直接复用。关键引脚如下表引脚名功能说明连接建议SCLI2C时钟线接C51的任意IO需上拉4.7kΩ到VDDSDAI2C数据线同上开漏输出需要上拉ADDR地址选择接地或接VDDEXCA/EXCB激励输出接传感器激励电极CIN1/CIN2电容测量输入接传感器感应电极VDD/GND电源和地2.7~5.25VC51常用5V或3.3VI2C总线上的设备地址由ADDR引脚决定。当ADDR接地时7位地址是0x48写地址为0x90读地址为0x91当ADDR接VDD时7位地址为0x49写地址0x92读地址0x93。在C51驱动里我们一般把地址宏定义成7位然后在读写函数里左移一位补上R/W位。常见的一个坑是AD7745的I2C地址不能像EEPROM那样用多个地址引脚设多个值它只有两档。如果板子上挂多个AD7745只能靠ADDR引脚区分最多两片。所以设计时如果要多通道采集要么用AD7746双通道要么换带硬件I2C的芯片或增加I2C切换器。2.2 I2C时序图里的起始、停止与数据位I2C通信协议里最要紧的是时序图。起始条件SCL保持高电平时SDA产生一个下降沿。停止条件SCL高电平时SDA产生一个上升沿。数据位在SCL高电平期间SDA电平必须保持稳定只能在SCL低电平时改变。这个规则在模拟I2C时尤其重要因为任何一条引脚的毛刺都会被当成数据。C51没有硬件I2C外设所以用两根普通IO模拟。驱动核心是四个函数I2CStart、I2CStop、I2CSendByte、I2CReceiveByte。这些函数里除了置位和清位最关键的是延时。延时太短芯片采样不到稳定电平延时太长通信速率低但可靠性高。我一般会用空循环做微秒级延时具体循环次数要根据晶振频率调整。2.3 C51引脚定义与初始化假设用P1.0作SCLP1.1作SDAC51代码里的定义如下#include reg52.h #include intrins.h sbit I2C_SCL P1^0; sbit I2C_SDA P1^1; #define I2C_DELAY() { _nop_(); _nop_(); _nop_(); _nop_(); }这里的I2C_DELAY是一个内联的延时宏在12MHz晶振下四个_nop_()约4微秒I2C时钟大概在100kHz左右。如果晶振更高或更低需要调整_nop_()的个数。注意SDA引脚要设置为准双向口C51默认就是SCL同样。如果使用STC系列可以配置为开漏但为了兼容最常见的情况直接准双向即可。这个初始化其实不需要额外代码因为上电后P1口默认就是输入状态我们只需要定义sbit。但提醒一点如果SDA被外部设备拉住上电时要先释放总线可以在主程序开始调一个I2CStop函数。具体实现见下一章。3. C51软件模拟I2C驱动核心函数3.1 起始、停止、发送与接收字节的C51实现下面这套函数是所有驱动的基础我已经在实际项目里用过多次直接放到C51工程就能跑。//--------------------------------------------------------------------------------- // 函 数 名I2C_Start // 功 能产生I2C起始信号 //--------------------------------------------------------------------------------- void I2C_Start(void) { I2C_SDA 1; I2C_SCL 1; I2C_DELAY(); I2C_SDA 0; // SCL高电平期间SDA下降沿 - 起始条件 I2C_DELAY(); I2C_SCL 0; // 拉低时钟准备传输数据 } //--------------------------------------------------------------------------------- // 函 数 名I2C_Stop // 功 能产生I2C停止信号 //--------------------------------------------------------------------------------- void I2C_Stop(void) { I2C_SDA 0; I2C_SCL 1; I2C_DELAY(); I2C_SDA 1; // SCL高电平期间SDA上升沿 - 停止条件 I2C_DELAY(); } //--------------------------------------------------------------------------------- // 函 数 名I2C_SendByte // 功 能向总线发送一个字节(MSB先行) // 输入参数dat 待发送字节 // 返 回 值0有ACK, 1无ACK //--------------------------------------------------------------------------------- bit I2C_SendByte(uchar dat) { uchar i; bit ack; for (i 0; i 8; i) { I2C_SCL 0; if (dat 0x80) I2C_SDA 1; // 先打最高位 else I2C_SDA 0; dat 1; I2C_DELAY(); I2C_SCL 1; // 在SCL高电平期间数据必须稳定 I2C_DELAY(); } // 释放SDA读取应答 I2C_SCL 0; I2C_SDA 1; I2C_DELAY(); I2C_SCL 1; I2C_DELAY(); ack I2C_SDA; // 正常从机应答时SDA应为0 I2C_SCL 0; I2C_DELAY(); return ack; } //--------------------------------------------------------------------------------- // 函 数 名I2C_ReceiveByte // 功 能从总线接收一个字节 // 输入参数ack_bit 0继续读下一个字节时发送应答, 1最后字节发送非应答 // 返 回 值接收到的字节 //--------------------------------------------------------------------------------- uchar I2C_ReceiveByte(bit ack_bit) { uchar i; uchar dat 0; I2C_SCL 0; I2C_SDA 1; // 释放总线准备接收 for (i 0; i 8; i) { I2C_SCL 1; I2C_DELAY(); dat 1; if (I2C_SDA) dat | 0x01; I2C_SCL 0; I2C_DELAY(); } // 发送应答/非应答 I2C_SDA ack_bit; I2C_DELAY(); I2C_SCL 1; I2C_DELAY(); I2C_SCL 0; I2C_DELAY(); I2C_SDA 1; // 释放SDA return dat; }逻辑说明起始信号要求在SCL为高时把SDA拉低所以代码先置高SCL再在SDA写0。停止信号相反。发送字节时每次先在SCL低电平期间准备好SDA再拉高SCL让从机采样高低电平之间各插一次延时保证时序满足100kHz标准模式。接收字节时主机在每个SCL高电平读取SDA并先移位再写位保证顺序正确。最后要发送应答位对读多字节而言最后一个字节应发NACK告诉从机不再读。参数说明I2C_SendByte返回值为0表示收到ACK1表示NACK。调用时可以用if (I2C_SendByte(addr 1))来判断设备是否存在。I2C_ReceiveByte的ack_bit参数要在调用前想清楚连续读取多字节时前几个传0最后一个传1。3.2 ACK检测与总线异常的恢复ACK是I2C通信协议中最容易出问题的一环。正常情况下从机在第9个时钟周期把SDA拉低作为应答。如果设备不存在、地址错误、或从机忙SDA保持高就是NACK。在调试初期我建议在C51的串口上打印I2C_SendByte的返回值能直接判断设备是否在线。还有一个很隐蔽的坑如果主机发送完起始信号后发现从机没应答或执行过程中出错总线可能停留在SDA为低的状态。这种时候必须连续发送9个时钟脉冲把总线“解锁”。常见做法是调用一个复位函数把SCL翻转9次然后发停止信号。但在C51模拟I2C里更简单的做法是直接拉高SDA和SCL多来几个停止信号。我在代码里会这样实现//--------------------------------------------------------------------------------- // 函 数 名I2C_ResetBus // 功 能异常时复位I2C总线释放SDA //--------------------------------------------------------------------------------- void I2C_ResetBus(void) { uchar i; I2C_SDA 1; for (i 0; i 9; i) { I2C_SCL 1; I2C_DELAY(); I2C_SCL 0; I2C_DELAY(); } I2C_Stop(); }这个函数的作用是即使从机占住SDA主机的9个时钟也会迫使它释放。注意在循环里不要动SDA保持输入状态。执行完以后总线空闲可以重新发起通信。另外如果C51晶振偏低比如6MHz而AD7745要求SCL最低只能到10kHz左右此时需要拉大I2C_DELAY中的空操作数量把SCL频率降到10k~20k通信会更稳定。4. AD7745寄存器配置与电容读取4.1 AD7745寄存器映射AD7745内部有一组寄存器读写都要通过I2C。先看关键寄存器寄存器地址名称功能描述0x07CAPDAC电容DAC设置用于抵消传感器固有电容0x08CAPSETUP电容通道设置如使能、选择CIN1/CIN20x09EXCSETUP激励设置配置激励电压和频率0x0ACONFIG配置转换模式、采样速率等0x0BCAPDATA电容转换结果24位3个字节0x0FSTATUS状态寄存器判断转换是否完成注意AD7745的寄存器地址是8位读和写操作时先发送设备地址再发送寄存器地址然后读或写数据。与普通EEPROM不同AD7745支持单次写一个字节也支持连续读。但24位数据必须分3次读我们一般用连续读模式减少I2C通信协议开销。4.2 配置测量模式的步骤与代码配置AD7745的常见步骤是设置CAPDAC寄存器清零或在需要时抵消大电容。设置CAPSETUP选择电容输入通道并使能。设置CONFIG选择连续转换模式并启动转换。等待转换完成读STATUS或延时。读取CAPDATA高、中、低三个字节拼成24位有符号数。下面是一段配置和读取代码//--------------------------------------------------------------------------------- // 函 数 名AD7745_Init // 功 能初始化AD7745配置为连续转换模式 // 注意0x0A的0xA2是连续转换内部时钟实际需按数据手册确认 //--------------------------------------------------------------------------------- void AD7745_Init(void) { I2C_Start(); I2C_SendByte(0x90); // AD7745写地址 (ADDR接地) I2C_SendByte(0x07); // CAPDAC寄存器 I2C_SendByte(0x00); // 初始不抵消电容 I2C_Stop(); I2C_Start(); I2C_SendByte(0x90); I2C_SendByte(0x08); // CAPSETUP寄存器 I2C_SendByte(0x01); // 使能CIN1CAPDAC使能内部温度传感器关闭 I2C_Stop(); I2C_Start(); I2C_SendByte(0x90); I2C_SendByte(0x0A); // CONFIG寄存器 I2C_SendByte(0xA2); // 连续转换采样速率约16.6Hz内部转换时钟 I2C_Stop(); } //--------------------------------------------------------------------------------- // 函 数 名AD7745_ReadCapData // 功 能读取24位电容转换结果二进制补码 // 返 回 值24位有符号数低24位有效 //--------------------------------------------------------------------------------- long AD7745_ReadCapData(void) { long value 0; uchar i; I2C_Start(); I2C_SendByte(0x90); I2C_SendByte(0x0B); // CAPDATA起始地址 I2C_Start(); // 重复起始切换为读模式 I2C_SendByte(0x91); // 读地址 for (i 0; i 2; i) { value (value 8) | I2C_ReceiveByte(0); // 前两字节发ACK } value (value 8) | I2C_ReceiveByte(1); // 最后一字节发NACK I2C_Stop(); return value; }逻辑说明AD7745_Init分三次写操作分别配置CAPDAC、CAPSETUP和CONFIG。0x07寄存器的0x00表示不开启电容DAC如果你的传感器静态电容有几十pF后面可以增大这个值来抵消让输出能回到零点附近。0x08的0x01只使能了CIN1通道如果使用AD7746的CIN2需要改成0x02。0x0A的0xA2是连续转换模式但不同版本的AD7745对CONFIG寄存器位的定义可能略有差异务必以你手里的数据手册为准。读取函数中连续读三个字节注意重复起始信号的使用。I2C_ReceiveByte(0)是发送ACKI2C_ReceiveByte(1)是发送NACK。这个细节一定要和I2C通信协议对应起来。还有一个容易错的地方long在C51里是4字节但这里只用了低24位所以高位第4字节默认是0或符号扩展需要根据你的计算逻辑决定是否做符号扩展。如果原始数据是补码且最高位为1表示负电容此时需要把long的高8位全部填1才能得到正确的有符号值。4.3 将原始码值换算为电容值AD7745的电容量程由内部设置决定常见为±4 pF或±8 pF。24位数据的最大值对应正满量程最小值对应负满量程。换算公式为实际电容 原始码值 / 8388608 * 满量程 CAPDAC补偿值这里8388608是2^23。CAPDAC补偿值由你写入0x07寄存器的数值决定具体映射关系要看数据手册的CAPDAC表格。我一般会把换算过程单独写成一个函数比如//--------------------------------------------------------------------------------- // 函 数 名AD7745_GetCapacitance // 功 能将原始码值换算为pF示例满量程取±8.192pF //--------------------------------------------------------------------------------- float AD7745_GetCapacitance(void) { long raw AD7745_ReadCapData(); // 符号扩展如果bit23为1则补齐高字节 if (raw 0x800000) raw | 0xFF000000; return (float)raw / 8388608.0f * 8.192f; }注意这里的满量程8.192pF并不是所有模式都适用只是我项目里用到的值。你最好在初始化时先读取状态寄存器确认转换已完成再用这个函数取值。如果读数一直是一个固定的大数多半是CAPDAC没有设置或者传感器电容已经超出量程需要先减少CAPDAC的补偿值。5. 调试技巧与注意事项5.1 排查I2C通信失败的第一步先测波形。用示波器探头分别抓SCL和SDA看起始、停止、字节时序是否正常。如果SCL没有时钟检查延时函数是否被优化掉——C51的Keil编译器在优化级别高时空循环可能会被删掉导致完全没有延时。解决方法是把延时变量声明为volatile或者用_nop_()。另一个常见问题是SCL和SDA接反特别是用杜邦线连接时先查线路再查代码。5.2 上拉电阻与电平匹配AD7745的SDA/SCL是开漏输出必须接上拉电阻否则SDA永远拉不低。3.3V供电的AD7745接5V的C51需要确认引脚兼容。多数C51是5V IO可以直接连接但上拉到5V会给3.3V芯片的引脚施加5V电压长期工作有风险。稳妥做法是让两者都上拉到3.3V或者用电平转换芯片。我自己的板子上曾因为偷懒没加上拉电阻I2C通信时好时坏最后用示波器才发现SDA根本没有低电平。5.3 电容读数的稳定处理读数跳动时不要急着改硬件。先在驱动里做多次采样平均比如连续读8次去掉最大最小值再平均。另外AD7745的转换是连续的但每次读到的数据可能来自不同时刻建议先读STATUS寄存器确认新数据就绪后再读CAPDATA。读STATUS的代码如下//--------------------------------------------------------------------------------- // 函 数 名AD7745_ReadStatus // 功 能读取状态寄存器bit7为1表示转换完成 //--------------------------------------------------------------------------------- uchar AD7745_ReadStatus(void) { uchar status; I2C_Start(); I2C_SendByte(0x90); I2C_SendByte(0x0F); I2C_Start(); I2C_SendByte(0x91); status I2C_ReceiveByte(1); I2C_Stop(); return status; }如果转换未完成就读取数据可能还是上一次的旧值导致曲线出现毛刺。用状态位做软件握手后读数会平滑很多。如果你把这段代码烧进去后I2C通信还是不稳定优先检查电源纹波和复位电路我踩过的坑里一半以上是因为电源纹波太大导致AD7745内部逻辑误动作。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门