我们的经常需要采集一些精度要求较高的模拟信号,使用MCU集成的ADC难以达到要求、所以我们需要独立的ADC芯片。这一节我们就来设计并实现AD7705芯片的驱动、并探讨驱动的使用方法。
1、功能概述
AD7705/AD7706是用于低频测量的完整模拟前端。可以直接从传感器接收低电平输入信号,并产生串行数字输出。
1.1、硬件结构
AD7705和AD7706均为完整16位、低成本、Σ-Δ型ADC,适合直流和低频交流测量应用。其具有低功耗(3 V时最大值为1 mW)特性,因而可用于环路供电、电池供电或本地供电的应用中。片内可编程增益放大器提供从1至128的增益设置,无需使用外部信号调理硬件便可接受低电平和高电平模拟输入。
AD7705拥有两个差分通道,而AD7706则拥有一个差分通道和两个伪差分通道。在定制比率应用器件时,差分基准电压输入还能提供极大的灵活性。采用16引脚封装,具体的定义及结构如下:
AD7705/AD7706设备的工作电压从2.7 V到3.3 V或4.75 V到5.25 V不等。在VDD为5v和参考电压为2.5 V的情况下,输入信号范围从0 mV到20 mV,从0 V到2.5 V,都可以在这两种设备上使用。在VDD为3v和参考电压为1.25 V的情况下,可以处理0 mV到10 mV到0 V到1.225 V的单极输入信号范围。
1.2、片上寄存器
AD7705/AD7706每个包含8个片上寄存器,可以通过串口访问。第一个是通信寄存器,第二个是配置寄存器,第三个是时钟寄存器,第四个是数据寄存器,余下的是校准寄存器。具体如下:
需要说明的是测试寄存器,不要改变此寄存器中的任何位的状态,这个寄存器用于设备测试。
1.2.1、通信寄存器
通信寄存器用于控制通道选择,并决定下一个操作是读操作还是写操作,并决定下一个读操作或写操作访问哪个寄存器。通讯寄存器各位含义如下:
所有到AD7705/AD7706的通信都必须从通信寄存器的写操作开始。写入该寄存器的数据决定下一个操作是读操作还是写操作,以及该操作发生在哪个寄存器。其中RS2–RS0三位决定下一操作是针对哪一个寄存器进行的。
而CH1, CH0两位决定是针对哪一通道的操作。
1.2.2、配置寄存器
配置寄存器是一个8位寄存器,可以从中读取或写入数据,用于确定校准模式、增益设置、双极/单极操作和缓冲模式。
1.2.3、时钟寄存器
时钟寄存器是一个8位寄存器,可以从中读取或写入数据,,并包含筛选器选择位和时钟控制位。
1.2.4、数据寄存器
数据寄存器是一个16位的只读寄存器,它包含来自AD7705/AD7706的最新转换结果。如果通信寄存器为该寄存器的写操作设置了部件,则必须执行写操作才能将部件返回到其默认状态。然而,写入该部分的16位数据将被AD7705/AD7706忽略。
1.2.5、校准寄存器
校准寄存器是一系列的寄存器对,用于存储通道校准数据。每对包括一个零标度校准寄存器和一个满标度校准寄存器,并对应一个通道。当开启系统零点或量程校准时,将根据对应通道上的数据来校准。当然如果有必要,也可以通过数字接口来读写这些寄存器。
2、驱动设计与实现
我们已经了解了AD7705模数转换器的结构及内部寄存器配置,接下来我们将根据我们的了解设计并实现AD7705模数转换器的驱动。
2.1、对象定义
我们对AD7705模数转换器的操作依然是基于对象的,所以我们要先得到对象。首先的工作当然是抽象得到对象的特性进而得到我们需要的对象。
2.1.1、抽象对象类型
一个对象最起码包含属性和操作两方面内容,我们先来分析一下AD7705模数转换器对象需要包含哪些属性和操作。
对于AD7705模数转换器来说,总共有8个寄存器,这些寄存器是实现操作的基础,我们要配置并了解这些寄存器的状态,所以我们将这些寄存器抽象为对象的属性,以便随时掌握操作的目标。
进而我们考虑AD7705模数转换器对象的操作。首先我们要操作AD7705模数转换器就是向其发送命令和读取数据,所以我们将向AD7705模数转换器发送命令和读取数据作为对象的一个操作。AD7705模数转换器采用SPI通讯接口,有时需要在软件中对片选信号进行操作,所以我们将片选型号的操作作为对象的另一个操作。在一些情况下,有些针对对象的活动需要延时进行,而在不同的平台中采取的延时方式不尽相同,为了操作方便我们将延时操作作为对象的一个操作。此外AD7705模数转换器有一个转换数据就绪检测的功能,我们将读取就绪信号作为它的另一个操作。
据以上的分析我们可以抽象AD7705模数转换器的对象类型如下:
/* 定义AD7705对象类型 */
typedef struct AD7705Object {
uint8_t registers[3]; //用于存储通讯、配置和时钟寄存器
uint8_t (*ReadWriteByte)(uint8_t data); //读写操作
uint8_t (*CheckDataIsReady)(void); //就绪信号检测
void (*ChipSelect)(AD7705CSType cs); //实现片选
void (*Delayms)(volatile uint32_t nTime); //实现ms延时操作
void (*Delayus)(volatile uint32_t nTime); //实现us延时操作
}AD7705ObjectType;
2.1.2、对象初始化
我们虽然得到了是对象,但对象不能直接使用,我们需要对其进行初始化方能使用。所以接下来我们考虑AD7705模数转换器对象的初始化函数。
初始化函数至少包含有2方面内容:一是为对象变量赋必要的初值;二是检查这些初值是否是有效的。特别是一些操作指针错误的话可能产生严重的后果。基于这一原则,我们设计AD7705模数转换器的对象初始化函数如下:
/* AD7705对象初始化函数 */
void AD7705Initialization(AD7705ObjectType *ad,
AD7705GainType gain,
AD7705MclkType mclk,
AD7705OutRateType rate,
AD7705ReadWriteByteType spiReadWrite,
AD7705CheckDataIsReadyType checkReady,
AD7705ChipSelect cs,
AD7705Delay msDelay,
AD7705Delay usDelay)
{
if((ad==NULL)||(spiReadWrite==NULL)||(checkReady==NULL)||(msDelay==NULL)||(usDelay==NULL))
{
return;
}
ad->CheckDataIsReady=checkReady;
ad->ReadWriteByte=spiReadWrite;
ad->Delayms=msDelay;
ad->Delayus=usDelay;
if(cs==NULL) //硬件电路实现片选
{
ad->ChipSelect=DefaultChipSelect;
}
else
{
ad->ChipSelect=cs;
}
//设置成单极性、无缓冲、增益为1、滤波器工作、自校准
ad->registers[REG_SETUP]=SelfCalibration|Unipolar|BufferDisable|FSYNCEnable|gains[gain];
ad->registers[REG_CLOCK]=CLKEnable; //默认主时钟输出
if((mclk==Mclk4915200)||(mclk==Mclk2000000))
{
ad->registers[REG_CLOCK]|=CLKDIVEnable;
}
else
{
ad->registers[REG_CLOCK]|=CLKDIVDisable;
}
if(((mclk<=Mclk4915200)&&(rate<=Rate200Hz))||((mclk>=Mclk1000000)&&(rate>=Rate50Hz)))
{
ad->registers[REG_CLOCK]|=updateRate[rate];
}
else
{
ad->registers[REG_CLOCK]=0x00;
return;
}
}
2.2、对象操作
我们获取对象的目的就是希望通过对象来得到我们想要的数据。对于AD7705模数转换器来说,我们想要得到的就是各个通道的输入信号。所以我们对AD7705模数转换器对象的操作就是得到通道的模数转换值。据此我们设计读取AD7705模数转换器单个通道的值的函数如下:
//读取AD7705单个通道的值
uint16_t GetAD7705ChannelValue(AD7705ObjectType *ad,AD7705ChannelType channel)
{
ad->ChipSelect(AD7705CS_Enable);
//初始化通道
AD7705ChannelConfig(ad,channel);
ad->Delayms(20);
ad->registers[REG_COMM]=DataRegister|ReadOperation|OperatingMode|channels[channel];
ad->ReadWriteByte(ad->registers[REG_COMM]);
//等待数据准备好
while(ad->CheckDataIsReady()==1)
{
}
uint16_t dataLowByte;
uint16_t dataHighByte;
dataHighByte = ad->ReadWriteByte(0xFF); //读数据寄存器
ad->Delayus(200);
dataLowByte = ad->ReadWriteByte(0xFF); //读数据寄存器
ad->Delayus(200);
dataHighByte = dataHighByte << 8;
uint16_t value;
value = dataHighByte | dataLowByte;
ad->ChipSelect(AD7705CS_Disable);
return value;
}
其中设置寄存器和时钟寄存器的值,在初始化函数中已经记录下来,在配置时,我们只需要下发数据就好了。
3、驱动的使用
我们已经设计并实现了AD7705模数转换器的驱动,接下来我们考虑如何使用这一驱动程序实现AD7705模数转换器的应用。
3.1、声明并初始化对象
应用的设计一如既往,我们需要使用AD7705模数转换器对象类型声明一个对象变量。形式如下:
AD7705ObjectType ad7705;
声明了这个对象变量并不能用于操作AD7705模数转换器,我们还需要使用初始化函数对对象变量进行初始化。初始换函数所需参数如下:
AD7705ObjectType *ad,要初始化的AD7705对象
AD7705GainType gain,增益系数
AD7705MclkType mclk,主时钟频率
AD7705OutRateType rate,输出更新速率
AD7705ReadWriteByteType spiReadWrite,SPI口读写操作函数
AD7705CheckDataIsReadyType checkReady,就绪检测函数
AD7705ChipSelect cs,片选操作函数
AD7705Delay msDelay,毫秒延时函数
AD7705Delay usDelay,微秒延时函数
对于这些参数,对象变量我们已经定义了。采用的增益倍数根据实际情况选择,为枚举。AD7705采用的数字时钟则根据我们的实际使用情况输入,为枚举。而输出更新速率根据需要选择,为枚举。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:
/*定义读写AD7705函数指针类型*/
typedef uint8_t (*AD7705ReadWriteByteType)(uint8_t data);
/*定义就绪检测函数指针类型*/
typedef uint8_t (*AD7705CheckDataIsReadyType)(void);
/*定义片选信号函数指针类型*/
typedef void (*AD7705ChipSelect)(AD7705CSType cs);
/*定义延时操作函数指针类型*/
typedef void (*AD7705Delay)(volatile uint32_t nTime);
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入NULL即可。具体函数定义如下:
/*定义片选信号函数*/
void AD7705CS(AD7705CSType en)
{
if(AD7705CS_Enable==en)
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_RESET);
}
else
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_SET);
}
}
/* 定义就绪信号读取函数 */
uint8_t AD7705CheckReady(void)
{
return HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_0);
}
/*定义发送数据函数*/
uint8_t AD7705WriteReadData(uint8_t wData)
{
uint8_t rxData=0;
HAL_SPI_TransmitReceive(&ad7705hspi,&wData,&rxData,1,1000);
return rxData;
}
对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:
AD7705Initialization(&ad7705,Gain_1,Mclk2457600,Rate200Hz,AD7705WriteReadData,AD7705CheckReady,AD7705CS,HAL_Delay,Delayus);
3.2、基于对象进行操作
我们定义了对象变量并使用初始化函数给其作了初始化。接着我们就来考虑操作这一对象获取我们想要的数据。在驱动中我们已经封装了获取某一通道模数转换数据的函数,这里我们将调用这一函数获取AD7705两个通道的模数转换值。
/*获取通道数据*/
void GetChannelValue(void)
{
uint16_t dataCode[2];
dataCode[0]=GetAD7705ChannelValue(&ad7705,Channel1);
dataCode[1]=GetAD7705ChannelValue(&ad7705,Channel2);
}
获取了ADC的数据后就可以根据每个通道所对应的物理量量程范围计算得到物理量值。
4、应用总结
我们实现了AD7705模数转换器的驱动并使用驱动实现了简单的应用,得到了AD7705两个通道的模数转换数据,结果与预期一致。
在使用驱动时需注意,采用SPI接口的器件需要考虑片选操作的问题。如果片选信号是通过硬件电路来实现的,我们在初始化时给其传递NULL值。如果是软件操作片选则传递我们编写的片选操作函数。
完整的源代码可在GitHub下载:https://github.com/foxclever/ExPeriphDriver