关于ADC我们已经讨论过不少了,但在不同的应用需求下,我们会选择不同的原件。在这里我们将讨论ADS111x系列ADC驱动的设计与实现。
ADS1113、 ADS1114 和 ADS1115 器件 (ADS111x)是兼容 I2C 的 16 位高精度低功耗模数转换器。ADS111x 器件采用了低漂移电压基准和振荡器。ADS1114 和 ADS1115 还采用可编程增益放大器(PGA) 和数字比较器。凭借这些特性加之较宽的工作电源电压范围,使得ADS111x非常适合功率受限和空间受限的传感器测量应用。其引脚定义及封装如下:
ADS111x 可在数据速率高达每秒 860 个样本 (SPS)的情况下执行转换。 PGA 可提供从 ±256mV 到±6.144V 的输入范围,从而实现精准的大小信号测量。ADS1115具有一个输入多路复用器 (MUX),可实现两路差分输入测量或四路单端输入测量。
ADS111x通过I2C接口进行通信。ADS111x有一个地址引脚ADDR,用来配置设备的I2C地址。这个引脚可以连接到GND、VDD、SDA或SCL,允许用一个引脚选择四个不同的地址,具体如下:
器件会对地址引脚的状态进行连续采样。优先使用GND,VDD和SCL来设置期间地址。如果使用SDA设置设备地址,需要在SCL线路低电平后,至少保持SDA线路低电平100 ns,以确保I2C通信时设备正确解码该地址。
ADS111x有四个寄存器,可以通过使用地址指针寄存器的I2C接口访问。转换寄存器包含最后一次转换的结果。配置寄存器用于改变ADS111x的工作模式和查询设备状态。另外两个寄存器Lo_thresh和Hi_thresh设置了用于比较器函数的阈值,在ADS1113中不可用。
对ADS111x寄存器的操作都是通过寄存器地址指针寄存器来实现的,其数据结构如下:
对于做后两位的定义为:00,转换寄存器;01,配置寄存器;10,低门限值寄存器;11,高门限值寄存器。
16位转换寄存器以二进制补码格式存放最后一次转换的结果。其格式如下:
在上电之后,转换寄存器被清除为0,并保持0直到第一次转换完成。
16位配置寄存器用于控制操作模式,输入选择,数据速率,满量程和比较器模式。其格式如下:
多路选择器用于配置当前需要采集的通道,只有ADS1115具有该功能。PGA用于配置采集的增益大小,DR用于配置苏剧的输出速率。
比较器使用的上下限阈值以二进制补码格式存储在两个16位寄存器中。比较器实现为数字比较器;因此,当PGA设置发生更改时,这些寄存器中的值必须更新。具体的数据格式如下:
通过设置高阈值寄存器MSB为1和低阈值寄存器MSB为0,可以启用ALERT/RDY引脚的转换准备函数。要使用ALERT/RDY引脚的比较器函数,高阈值寄存器的值必须总是大于低阈值寄存器的值。当设置为RDY模式时,ALERT/RDY引脚在单发模式下输出OS位,在连续转换模式下提供连续转换就绪脉冲。
在前述中我们已经梳理了ADS111x系列模数转换器的相关技术特性。接下来我们需要依据我们了解的这些技术数据设计并设计ADS111x系列模数转换器的驱动程序。
与以前的驱动设计一样,我们依然是基于对象来设计ADS111x系列模数转换器的驱动程序。所以我们要先抽象并定义ADS111x系列模数转换器对象类型。一般来讲对象包括属性与操作两方面,我们将据此逐一分析ADS111x系列模数转换器对象的属性与操作。
先考虑ADS111x系列模数转换器对象的属性。ADS111x系列模数转换器采用I2C接口总线,而每台I2C的从站都有一个设备地址,该地址表示了每台设备的身份,所以我们将器作为ADS111x系列模数转换器对象的一个属性。此外为了查看当前ADS111x系列模数转换器对象的配置情况,我们希望记录配置寄存器的当前值,所以我们设定一个属性来记录它。每次读取回来的个通道的数据实际表示了各通道的当前状态所以我们也将其作为ADS111x系列模数转换器对象的属性来记录之。
再来考虑ADS111x系列模数转换器对象的操作。我们对ADS111x系列模数转换器所要进行的操作主要有下发命令、读取数据等,这些动作都需要依据具体的软硬件平台来实现,我们以对象操作的方式来处理它。为了控制时序,我们需要延时操作函数,而延时操作也需要基于具体的平台来实现,所以我们将延时函数也作为对象的一个操作。
根据上述对ADS111x系列模数转换器对象属性和操作的分析,我们可以抽象出ADS111x系列模数转换器的对象类型如下:
/*定义ADS111x对象类型*/
typedef struct Ads111xObject {
uint8_t devAddress; //设备地址
uint16_t dataCode[8]; //读取的数据值
uint16_t config; //配置寄存器值
void (*Transmit)(struct Ads111xObject *ads,uint8_t *tData,uint16_t tSize);
void (*Receive)(struct Ads111xObject *ads,uint8_t *rData,uint16_t rSize);
void (*Delayus)(volatile uint32_t nTime); //实现us延时操作
}Ads111xObjectType;
抽象了对象类型后就可声明对象变量,可是这个对象变量必须作必要的初始化才能使用。所以我们需要一个初始化函数来对其进行初始化。在此函数中,我们将检测变量的有效性和初始状态赋值,并对设备进行必要的配置。根据这些要求我们设计ADS111x系列模数转换器的对象初始化函数如下:
/*ADS111x初始化配置*/
void Ads111xInitialization(Ads111xObjectType *ads, //ADS111x对象变量
uint8_t devAddress, //设备地址
Ads111xGainType gain, //增益
Ads111xDataRateType dr, //输出速率
Ads111xTransmit transmit,//发送函数指针
Ads111xReceive receive, //接收函数指针
Ads111xDelayus delayus //us延时函数指针
)
{
uint16_t channels[]={0x0000,0x1000,0x2000,0x3000,0x4000,0x5000,0x6000,0x7000};
uint16_t gains[]={0x0000,0x0200,0x0400,0x0600,0x0800,0x0A00};
uint16_t dataRates[]={0x0000,0x0020,0x0040,0x0060,0x0080,0x00A0,0x00C0,0x00E0};
uint16_t config=0x8103;
if((ads==NULL)||(transmit==NULL)||(receive==NULL)||(delayus==NULL))
{
return ;
}
ads->Transmit=transmit;
ads->Receive=receive;
ads->Delayus=delayus;
Ads111xReset(ads);
if((devAddress==0x48)||(devAddress==0x49)||(devAddress==0x4A)||(devAddress==0x4B))
{
ads->devAddress=(devAddress<<1);
}
else if((devAddress==0x90)||(devAddress==0x92)||(devAddress==0x94)||(devAddress==0x96))
{
ads->devAddress=devAddress;
}
else
{
ads->devAddress=0x00;
}
config=config|channels[ADS111X_AIN0_AIN1]|gains[gain]|dataRates[dr];
Ads111xWriteRegister(ads,ConfigRegister,config);
ads->Delayus(200);
ads->config=Ads111xReadRegister(ads,ConfigRegister);
}
为了从ADS111x访问特定的寄存器,必须先向地址指针寄存器中写一个适当的值来指示要访问的寄存器地址。也就是说不论读写那个寄存器都需要首先写地址指针寄存器才能实现。
当从ADS111x读取数据时,先前写入到地址指针寄存器的值决定了被读取的是哪一个寄存器。要想改变所要度的寄存器需要先修改地址指针寄存器的值。也就是说都一个寄存器的值分为两步完成,首先写地址指针寄存器,然后再读所指向的寄存器的值。具体的操作时序图如下:
根据前面的分析以及上述的时序图我们可以实现读ADS111x系列模数转换器寄存器的操作函数如下:
/*读ADS111x寄存器*/
static uint16_t Ads111xReadRegister(Ads111xObjectType *ads,Ads111xRegisterType reg)
{
uint8_t wData;
uint8_t rData[2];
uint16_t result=0;
wData=(uint8_t)reg;
ads->Transmit(ads,&wData,1);
ads->Delayus(200);
ads->Receive(ads,rData,2);
result=rData[0];
result=(result<<8)+rData[1];
return result;
}
需要说明一下的是,如果是连续多次读取同一个寄存器则不需要先修改地址指针寄存器的值。
对于ADS111x系列模数转换器来说,写一个寄存器也是从写地址指针寄存器开始的。但是与读寄存器不同的是并不是两步完成的,而是一次全部完成,即先写地址指针寄存器接着就写所要写的寄存器。具体的操作时序如下:
根据我们前述的分析以及上面的时序图,我们可以实现写ADS111x系列模数转换器的函数如下:
/*写ADS111x寄存器*/
static void Ads111xWriteRegister(Ads111xObjectType *ads,Ads111xRegisterType reg,uint16_t regValue)
{
uint8_t wData[3];
wData[0]=(uint8_t)reg;
wData[1]=(uint8_t)(regValue>>8);
wData[2]=(uint8_t)regValue;
ads->Transmit(ads,wData,3);
}
我们已经设计并实现了ADS111x系列模数转换器的驱动程序,为了验证驱动程序的正确性,这一节我们将来讨论基于驱动程序设计一个简单的应用验证。
我们是基于对象实现的ADS111x系列模数转换器驱动程序,所以我们先来声明一个ADS111x系列模数转换器的对象变量。
Ads111xObjectType ads1115;
声明了这个对象变量后,我们还需要使用前面实现的初始化函数Ads111xInitialization对这个对象变量进行初始化。这个初始化函数所需要的输入参数如下:
Ads111xObjectType *ads, //ADS111x对象变量
uint8_t devAddress, //设备地址
Ads111xGainType gain, //增益
Ads111xDataRateType dr, //输出速率
Ads111xTransmit transmit,//发送函数指针
Ads111xReceive receive, //接收函数指针
Ads111xDelayus delayus //us延时函数指针
在这些参数中,第一个为我们想要初始化的对象变量。devAddress就是ADS111x系列模数转换器的设备地址,根据实际输入即可。增益和数据输出均为枚举量,根据实际需要选择输入即可。还有三个函数指针是我们需要实现的参数,这几个函数的原型定义如下:
typedef void (*Ads111xTransmit)(struct Ads111xObject *ads,uint8_t *tData,uint16_t tSize);
typedef void (*Ads111xReceive)(struct Ads111xObject *ads,uint8_t *rData,uint16_t rSize);
typedef void (*Ads111xDelayus)(volatile uint32_t nTime); //实现us延时操作
这些函数的实现依赖于具体的软硬件平台,这里我们基于STM32F103硬件平台和HAL库来实现的相关操作函数。
/*通过I2C2发送数据到ADS115*/
static void BmcbAds111xTransmit(struct Ads111xObject *ads,uint8_t *tData,uint16_t tSize)
{
HAL_I2C_Master_Transmit(&hi2c2,ads->devAddress,tData,tSize,1000);
}
/*通过I2C2端口从ADS1115接收数据*/
static void BmcbAds111xReceive(struct Ads111xObject *ads,uint8_t *rData,uint16_t rSize)
{
HAL_I2C_Master_Receive(&hi2c2,ads->devAddress,rData,rSize,1000);
}
而延时函数采用我们在STM32系统下实现的通用函数即可。于是我们可以使用初始化函数实现对象变量初始化如下:
Ads111xInitialization(&ads1115, //ADS111x对象变量
0x90, //设备地址
ADS111X_GAIN_8, //增益
ADS111X_DR_128, //输出速率
BmcbAds111xTransmit, //发送函数指针
BmcbAds111xReceive, //接收函数指针
Delayus //us延时函数指针
);
我们初始化了对象变量就可一使用它来实现相应的操作,所以我们含需要实现相关的应用函数。事实上,这款驱动程序我们已经应用到实际的工程应用中,所以我们节选其中关于读取差分通道的操作。
Ads111xChannelType Channel[sChCount]={ADS111X_AIN0_AIN1,ADS111X_AIN2_AIN3};
if(sCh>=sChCount)
{
sCh=0;
}
Ads111xGetDataCode(&ads1115,Channel[sCh]);
sCh++;
aPara.phyPara.pressure=Ads111xCalcPhysicalValue(&ads1115,ADS111X_AIN0_AIN1,PRES_RANGE,PRES_ZERO);
aPara.phyPara.vacuum=Ads111xCalcPhysicalValue(&ads1115,ADS111X_AIN2_AIN3,VACU_RANGE,VACU_ZERO);
我们就已经完成了ADS111x系列模数转换器的驱动设计与验证。在我们的实际使用过程中运行稳定,效果也很好。
在使用驱动程序时需要注意,此驱动只适用于ADS1113、 ADS1114 和 ADS1115 器件。