前面我们讨论了AD7705这种ADC器件的驱动开发,在实际中我们使用更多的是AD719x系列的ADC芯片、包括有AD7191、AD7192和AD7193等。接下来我们就来设计并开发AD719x的驱动程序。
1、功能概述
AD7192是一款适合高精密测量应用的低噪声完整模拟前端,内置一个低噪声、 24 位Σ-Δ型模数转换器 (ADC)。片内低噪声增益级意味着可直接输入小信号。
1.1、硬件结构
AD7192可配置为两路差分输入或四路伪差分输入。片内通道序列器可以使能多个通道,AD7192 按顺序在各使能通道上执行转换,这可以简化与器件的通信。 片内 4.92 MHz时钟可以用作 ADC 的时钟源; 或者也可以使用外部时钟或晶振。 该器件的输出数据速率可在 4.7 Hz 至 4.8 kHz 的范围内变化。
AD7192提供两种数字滤波器选项。 滤波器的选择会影响以编程输出数据速率工作时的均方根噪声和无噪声分辨率、建立时间以及 50 Hz/60 Hz 抑制。 针对要求所有转换均需建立的应用, AD7192 具有零延迟特性。
其功能结构图如下:
1.2、内部寄存器
AD7192内部具有多个寄存器,对AD7192的操作就是通过这些片内寄存器进行控制和数据寄存器/数据寄存器加状态信息配置。这些寄存器包括:通信寄存器、状态寄存器、模式寄存器、配置寄存器、ID寄存器、GPOCON寄存器、失调寄存器以及满量程寄存器。其中通信寄存器和状态寄存器共享地址,读操作时针对的是状态寄存器,写操作时针对的是通讯寄存器。对任何寄存器的操作都是从写通讯寄存器开始。
1.2.1、通信寄存器
通信寄存器是一个 8 位只写寄存器。与该器件的所有通信均必须以对通信寄存器的写操作开始。写入通信寄存器的数据决定下一个操作是读操作还是写操作,以及此操作发生在哪一个寄存器。通讯寄存器的格式如下:
其中RS2、RS1、RS0这些位用于指示下一次操作的寄存器是哪一个寄存器,具体如下:
我们使用比较多的就是状态寄存器、模式寄存器、配置寄存器以及数据寄存器。后续会进一步了解这些寄存器。
1.2.2、状态寄存器
状态寄存器是一个8位只读寄存器。要访问ADC状态寄存器,用户必须写入通信寄存器,选择下一个操作为读操作,并将 0 载入位RS2、位RS1 和位RS0。状态寄存器的格式如下:
CHD0、CHD1、CHD2这些位指示哪一通道对应数据寄存器的内容。这些位不是指示目前正在转换哪一通道,而是指示产生数据寄存器所含转换结果时选定了哪一通道。
1.2.3、模式寄存器
模式寄存器是一个24位寄存器,可以从中读取数据,也可以将数据写入其中。此寄存器用来选择工作模式、输出数据速率和时钟源。模式寄存器的格式如下:
AD7192的工作模式有模式寄存器的MD2、MD1、MD0这几位来决定。具体的配置如下:
我们配置什么样的工作模式决定了后续的操作。默认情况下为连续模式,应用比较多的有单次模式、校准模式等。
1.2.4、配置寄存器
配置寄存器是一个 24 位寄存器,可以从中读取数据,也可以将数据写入其中。此寄存器用来配置 ADC 的单极性或双极性模式,使能或禁用缓冲器,使能或禁用激励电流,选择增益,以及选择模拟输入通道。
其中G2、G1、G0等位用于配置通道的增益。可以实现1倍、8倍、16倍、32倍、64倍、128倍等增益,输入的范围也对应调整,具体如下:
位CH7、CH6、CH5、CH4、CH3、CH2、CH1、CH0用于设置使用的通道。每一位对应一个通道,具体配置方式如下:
其中CH0与CH4、CH5不可同时使用,CH1与CH6、CH7不能同时使用。CH0和CH1是差分输入,CH4、CH5、CH6、CH7是单端输入。
余下的ID寄存器、GPOCON寄存器、失调寄存器以及满量程寄存器比较容易理解不再说明。
2、驱动设计与实现
我们已经了解AD7192的基本情况,接下来我们将据此来开发AD7192的驱动程序。
2.1、对象设计
我们的驱动程序依然设计为基于对象的操作。所以首先我们需要先设计对象。而关于对象的设计过程包括两方面内容:对象类型的定义;对象的初始化。
2.1.1、抽象对象类型
对于一个AD7192对象,我们首先考虑到它有8个寄存器,这些寄存器决定了它的操作特性,所以我们记录这些寄存器的值用以标识它的状态。此外增益与极性虽然存在于配置寄存器中,但我们希望在初始化的时候明确的指定它,所以我们也将其作为属性以标识AD7192对象的配置状态。
而对于片选信号的控制、转换就绪信号的读取,总线数据的输入输出这些依赖于硬件电路的操作,我们都将其作为对象的操作来实现。此外,延时操作在不同的硬件平台、有无操作系统的情况下,实现方式大相径庭,因此我们也将其作为对象的操作以便灵活处理。
据此我们可以抽象得AD7192对象类型为:
1 /*定义用于操作的结构体*/ 2 typedef struct Ad7192Object { 3 uint32_t polar; //通道的极性 4 uint32_t gain; //通道增益 5 uint32_t Registers[8]; //存放寄存器值的数组 6 void (*ReadWrite)(uint8_t *wData,uint8_t *rData,uint16_t size); //实现读写操作 7 void (*ChipSelect)(AD7192CSType cs); //实现片选 8 uint16_t (*GetReadyInput)(void); //实现Ready状态监视 9 void (*Delay)(volatile uint32_t nTime); //实现ms延时操作 10 }Ad7192ObjectType;
2.1.2、初始化函数
在使用AD7192前先对其实行初始化,所以我们需要一个对象初始化函数。初始化函数主要包括三个方面的内容:检查对象即各种初始输入是否有效;为对象的属性赋初值以及为操作指定函数指针;对AD7192做前期配置。
关于AD7192的前期配置,首先是软件复位,连续写入40个1就可对AD7192实现复位。复位完成后,对零点和量程进行校准。而后获取各寄存器的当前状态。初始化函数的具体实现代码如下:
1 /*AD7192初始化配置*/ 2 AD7192ErrorType AD7192Initialization(Ad7192ObjectType *adObj, 3 uint32_t Channels, 4 AD7192PolarType polar, 5 AD7192GainType gain, 6 AD7192ReadWriteType readWrite, 7 AD7192ChipSelectType cs, 8 AD7192GetReadyInputType ready, 9 AD7192DelaymsType delayms) 10 { 11 uint32_t polarity[]={UB_UNI,UB_BI}; 12 uint32_t gains[]={GAIN_1,GAIN_8,GAIN_16,GAIN_32,GAIN_64,GAIN_128}; 13 14 if((adObj==NULL)||(readWrite==NULL)||(ready==NULL)||(delayms==NULL)) 15 { 16 return AD7192_InitError; 17 } 18 19 if(cs==NULL) 20 { 21 adObj->ChipSelect=AD719xChipSelect; 22 } 23 else 24 { 25 adObj->ChipSelect=cs; 26 } 27 28 adObj->polar=polarity[polar]; 29 adObj->gain=gains[gain]; 30 31 adObj->Registers[REG_COM_STA]=0x00; 32 adObj->Registers[REG_MODE]=0x00; 33 adObj->Registers[REG_CONF]=0x00; 34 adObj->Registers[REG_DATA]=0x00; 35 adObj->Registers[REG_ID]=0x00; 36 adObj->Registers[REG_GPOCON]=0x00; 37 adObj->Registers[REG_OFFSET]=0x00; 38 adObj->Registers[REG_FS]=0x00; 39 40 adObj->ReadWrite=readWrite; 41 adObj->GetReadyInput=ready; 42 adObj->Delay=delayms; 43 44 AD7192SoftwareReset(adObj); 45 adObj->Delay(1); 46 AD7192InternalZeroScaleCalibration(adObj,Channels); 47 adObj->Delay(1); 48 AD7192InternalFullScaleCalibration(adObj,Channels); 49 50 /*读取并存储全部寄存器的值*/ 51 ReadAD7192Register(adObj,REG_COM_STA, 8, REG_COM_STA); 52 53 return AD7192_OK; 54 }
2.2、对象操作
对象初始化后就可对其进行操作。我们无论对AD7192进行何种操作,其目的都是为了得到我们想要的数据。而从AD7192读取转换的结果有2种方式:单次获取和连续获取。接下来我们就按此来说明AD7192的驱动设计。
2.2.1、实现单次数据转换
单次转换模式下,AD7192 在完成转换后处于关断模式。 将模式寄存器中的MD2、MD1和MD0分别设置为0、0、1,便可启动单次转换,此时AD7192将上电,执行单次转换,然后返回关断模式。时序图如下所示:
单次转换数据获取具体实现代码如下:
1 /*单次转换数据获取*/ 2 uint32_t GetSingleConvertionValue(Ad7192ObjectType *adObj,uint32_t Channels) 3 { 4 uint32_t dataCode=0; 5 AD7192StartSingleConvertion(adObj,Channels); 6 7 adObj->Delay(1); 8 9 dataCode = AD7192ReadConvertingData(adObj); 10 dataCode =dataCode & 0x00FFFFFF; 11 12 ReadAD7192Register(adObj,REG_DATA, 1,REG_DATA); 13 14 return dataCode; 15 }
2.2.2、实现连续数据转换
连续转换模式是上电后的默认转换模式。AD7192连续转换,每次完成转换后,状态寄存器中的RDY位变为低电平。如果CS为低电平,则完成一次转换时,DOUT/RDY 线路也会变为低电平。若要读取转换结果,用户需要写入通信寄存器,指示下一操作为读取数据寄存器。从数据寄存器中读取数据字后,DOUT/RDY变为高电平。时序图如下所示:
连续转换数据获取具体实现代码如下:
1 /*连续转换数据获取,dataCodes为8个元素的数组对应8个通道*/ 2 void GettContinuousConvertionValue(Ad7192ObjectType *adObj,uint32_t Channels,uint32_t *dataCodes,int number) 3 { 4 uint32_t dataCode=0; 5 uint8_t status=255; 6 AD7192StartContinuousConvertion(adObj,Channels); 7 8 for(int i=0;i<number;i++) 9 { 10 dataCode = AD7192ReadConvertingData(adObj); 11 status=((uint8_t)dataCode)&0x07; 12 dataCode =(dataCode>>8) & 0x00FFFFFF; 13 dataCodes[status]=dataCode; 14 } 15 }
2.2.3、零点和量程校准
零点和量程校准包括内部校准和外部校准,我们这里使用内部校准。
1 /*内部零点校准*/ 2 static void AD7192InternalZeroScaleCalibration(Ad7192ObjectType *adObj,uint32_t Channels) 3 { 4 //配置寄存器:斩波禁用,基准电压1,AI1-AI4单通道4路,禁用激励电流,禁用基准电压检测,禁用缓冲器 5 adObj->Registers[REG_CONF] = 0; 6 adObj->Registers[REG_CONF] = CHOP_DIS|REF_IN1|Channels|BURN_DIS|REFDET_DIS|BUF_DIS|adObj->polar|adObj->gain; 7 WriteAD7192Register(adObj,REG_CONF,1); 8 9 //模式寄存器:内部零点校准,禁用状态同传,内部时钟输出,斩波4,使能奇偶校验,不分频,仅用单周期转换使能,禁用60Hz陷波,FS=128 10 adObj->Registers[REG_MODE] = 0; 11 adObj->Registers[REG_MODE] = MODE_INZCL|DAT_STA_DIS|INCLK_MCLK2EN|SINC_4|ENPAR_EN|CLK_DIV_DIS|SINGLECYCLE_DIS|REJ60_DIS|0x080; 12 WriteAD7192Register(adObj,REG_MODE, 1); 13 14 adObj->ChipSelect(AD7192CS_Enable); 15 while(adObj->GetReadyInput()== 1){;} //等待RDY为0; 16 adObj->ChipSelect(AD7192CS_Disable); 17 } 18 19 /*内部量程校准*/ 20 static void AD7192InternalFullScaleCalibration(Ad7192ObjectType *adObj,uint32_t Channels) 21 { 22 23 //配置寄存器:斩波禁用,基准电压1,AI1-AI4单通道4路,禁用激励电流,禁用基准电压检测,禁用缓冲器 24 adObj->Registers[REG_CONF] = 0; 25 adObj->Registers[REG_CONF] = CHOP_DIS|REF_IN1|Channels|BURN_DIS|REFDET_DIS|BUF_DIS|adObj->polar|adObj->gain; 26 WriteAD7192Register(adObj,REG_CONF, 1); 27 28 //模式寄存器:内部量程校准,禁用状态同传,内部时钟输出,斩波4,使能奇偶校验,不分频,禁用单周期转换使能,禁用60Hz陷波,FS=128 29 adObj->Registers[REG_MODE] = 0; 30 adObj->Registers[REG_MODE] = MODE_INFCL|DAT_STA_DIS|INCLK_MCLK2EN|SINC_4|ENPAR_EN|CLK_DIV_2|SINGLECYCLE_DIS|REJ60_DIS|0x080; 31 WriteAD7192Register(adObj,REG_MODE, 1); 32 33 adObj->ChipSelect(AD7192CS_Enable); 34 while(adObj->GetReadyInput()== 1){;} //等待RDY为0; 35 adObj->ChipSelect(AD7192CS_Disable); 36 }
2.2.4、实现内部温度转换
AD7192内置一个温度传感器。利用配置寄存器中的CH2位可以选择温度传感器。如果CH2位设置为1,就会使能温度传感器。使用温度传感器并选择双极性模式时,如果温度为0K,器件应返回0x800000码。为使传感器发挥最佳性能,需要执行单点校准。因此,应记录25°C 时的转换结果并计算灵敏度。 灵敏度约为2815码 /°C。温度传感器的计算公式为 :
温度 (K) = ( 转换结果 – 0x800000)/2815 K
温度 (°C) = 温度 (K) – 273
单点校准之后,内部温度传感器的精度典型值为 ±2°C。具体的实现代码如下:
1 /*读取内部温度数据,返回摄氏度温度*/ 2 float GetTemperatureValue(Ad7192ObjectType *adObj) 3 { 4 uint32_t temperatureCode=0; 5 float temp = 0.0; 6 //模式寄存器:单次转换模式,禁用状态同传,内部时钟输出,斩波4,使能奇偶校验,不分频,禁用单周期转换使能,禁用60Hz陷波,FS=128 7 adObj->Registers[REG_MODE] = 0; 8 adObj->Registers[REG_MODE] = MODE_SING|DAT_STA_DIS|INCLK_MCLK2EN|SINC_4|ENPAR_EN|CLK_DIV_DIS|SINGLECYCLE_DIS|REJ60_DIS|0x080; 9 WriteAD7192Register(adObj,REG_MODE, 1); 10 //配置寄存器:斩波禁用,基准电压1,内部温度,禁用激励电流,禁用基准电压检测,禁用缓冲器 11 adObj->Registers[REG_CONF] = 0; 12 adObj->Registers[REG_CONF] = CHOP_DIS|REF_IN1|TEMP|BURN_DIS|REFDET_DIS|BUF_DIS|UB_BI|GAIN_1; 13 WriteAD7192Register(adObj,REG_CONF, 1); 14 15 temperatureCode = AD7192ReadConvertingData(adObj); 16 temp = (temperatureCode-0x800000)/2815.0-273; 17 return temp; 18 }
3、驱动的使用
我们已经设计并实现了AD7192模数转换器的驱动。现在我们将考虑如何使用这一驱动实现基于AD7192模数转换器的简单应用。
3.1、声明并初始化对象
使用基于对象的操作我们需要先得到这个对象,所以我们先要使用前面定义的AD7192模数转换器对象类型声明一个AD7192模数转换器对象变量,具体操作格式如下:
Ad7192ObjectType ad7192;
声明了这个对象变量并不能立即使用,我们还需要使用驱动中定义的初始化函数对这个变量进行初始化。这个初始化函数所需要的输入参数如下:
Ad7192ObjectType *adObj,所要初始化的对象变量
uint32_t Channels,需要初始化的通道
AD7192PolarType polar,通道初始化的极性
AD7192GainType gain,通道的增益
AD7192ReadWriteType readWrite,读写操作函数
AD7192ChipSelectType cs,片选信号操作函数
AD7192GetReadyInputType ready,就绪信号检测函数
AD7192DelaymsType delayms,毫秒延时函数
对于这些参数,对象变量我们已经定义了。所需要初始化的通道、通道的信号极性、采用的增益倍数根据实际情况选择,通道为宏定义、极性和增益为枚举。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:
1 *定义读写操作函数指针类型*/ 2 typedef void (*AD7192ReadWriteType)(uint8_t *wData,uint8_t *rData,uint16_t size); 3 4 /*实现片选*/ 5 typedef void (*AD7192ChipSelectType)(AD7192CSType cs); 6 7 /*实现Ready状态监视*/ 8 typedef uint16_t (*AD7192GetReadyInputType)(void); 9 10 /*实现ms延时操作*/ 11 typedef void (*AD7192DelaymsType)(volatile uint32_t nTime);
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入NULL即可。具体函数定义如下:
1 /*定义读写操作函数指针类型*/ 2 void AD7192ReadWrite(uint8_t *wData,uint8_t *rData,uint16_t size) 3 { 4 HAL_SPI_TransmitReceive(&ad77192hspi,wData,rData,size,1000); 5 } 6 7 /*实现片选*/ 8 void AD7192ChipSelect(AD7192CSType cs) 9 { 10 if(AD7192CS_Enable==cs) 11 { 12 HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_RESET); 13 } 14 else 15 { 16 HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_SET); 17 } 18 } 19 20 /*实现Ready状态监视*/ 21 uint16_t AD7192GetReadyInput(void) 22 { 23 return HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_0); 24 }
对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:
AD7192Initialization(&ad7192,AIN1_AIN2|AIN3_AIN4,AD7192_Unipolar,AD7192Gain1,AD7192ReadWrite,AD7192ChipSelect,AD7192GetReadyInput,HAL_Delay);
3.2、基于对象进行操作
我们定义了对象变量并使用初始化函数给其作了初始化。接着我们就来考虑操作这一对象获取我们想要的数据。在驱动中我们已经封装了单次或是连续获取某一通道模数转换数据的函数。接下来我们就采用我们封装的AD7192数模转换其的驱动获取通道的数据。
1 /* 单次获取两个差分通道的值 */ 2 void GetDifferentialData(void) 3 { 4 uint16_t dataCode[2]; 5 6 dataCode[0]=GetSingleConvertionValue(&ad7192,AIN1_AIN2); 7 8 dataCode[1]=GetSingleConvertionValue(&ad7192,AIN3_AIN4); 9 }
因为初始化函数初始化为两个差分通道,所以我们的函数就是获取了两个差分通道的转换值。获取了ADC的数据后就可以根据每个通道所对应的物理量量程范围计算得到物理量值。
4、应用总结
在这一篇中我们设计并实现了AD7192数模转换器的驱动程序。并使用这一驱动程序实现了单次获取两个差分通道转换数据的简单应用。得到了我们想要的结果,这说明我们的驱动设计是正确的。
在使用驱动时需要注意。选择通道时,由于可以是2个差分通道和4个单端通道,所以差分通道一不可以与AIN1、AIN2同时使用,同样的差分通道二不能与AIN3、AIN4同时使用。
在使用驱动时需注意,采用SPI接口的器件需要考虑片选操作的问题。如果片选信号是通过硬件电路来实现的,我们在初始化时给其传递NULL值。如果是软件操作片选则传递我们编写的片选操作函数。
完整的源代码可在GitHub下载:https://github.com/foxclever/ExPeriphDriver
欢迎关注: