在产品设计过程中,很多时候都会用到ADC器件,而在一些特殊场合还需要一些特别的ADC器件。我们在这篇中将讨论常用于医疗器件方面的,DDC114这款电流输入ADC,并为其设计一个驱动程序。
1、功能概述
模数转换器DDC114是一款电流输入型ADC,通过对微小电流信号采用电荷积分的方式进行模数转换。包括4路输入,每路输入有2路积分电容;有2路AD转换器,每2路输入共用1个AD转换器。其内部结构框图如下:
DDC114采用48脚QFN封装,其量程范围、数据格式、转换周期、操作模式等都是通过配置硬件引脚来实现配置的。其封装样式及引脚定义如下:
DDC114通过SPI接口输出数字化的结果,该SPI接口由一组数据时钟(DCLK),一个有效数据引脚(DVALID),一组串行数据输出引脚(DOUT),和一组串行数据输入引脚(DIN)组成。DDC114电流积分型AD芯片各路交叉积分与转换,积分与转换过程以及数据输出基本是独立进行的。DIN只在多个转换器级联时使用,否则应该绑定到DGND。当移位寄存器包含有效数据时,DVALID输出变低。相应的逻辑时序如下:
DDC114存在连续模式和非连续模式两种。当积分时间大于数据转换时间时就切换到了连续模式,当积分时间小于数据转换时间时就转换到了非连续模式。有关连续与非连续模式的所需时钟周期如下:
为了获得最好的噪声抑制特性,一般来说,我们需要将CONV转换信号与CLK时钟的上升沿同步。
2、驱动设计与实现
我们已经了解了DDC114电流输入型ADC的基本特性,接下来我们就依据这些特性和要求实现DDC114电流输入型ADC的驱动程序。
2.1、对象定义
与前面一样,我们依然是基于对象的思想来实现DDC114电流输入型ADC的驱动。所以我们首先来考虑DDC114电流输入型ADC作为对象的基本属性和操作。
首先我们来考虑DDC114对象的属性,我们使用DDC114的目的就是获取各通道的数据,所以我们将当前时刻的数据作为属性记录下来。数据格式虽然是通过硬件管脚来设置的,但在不同的数据格式下解析数据的方式也是不一样的,所以我们将当前配置的数据格式当作属性记录下来。同样的,不同数据格式下量程和零点的设置也是不一样的,所以我你们将其作为属性记录下来,方便完成数据解析。我们知道DDC114连续工作时需要控制转换信号CONV,我们需要记录他每一时刻的状态,以便控制CONV信号的切换,所以我们将器状态也定义为对象的属性。
而DDC114对象所需要进行的基本操作,主要有控制CONV信号、控制RESET信号、控制TEST信号、获取DVALID状态并从SPI获取,这些操作都依赖于具体的平台,所以我们将其定义为对象的操作,这样可以通过函数指针的方式方便操作。还有基于时序控制的需要,我们需要微秒延时函数,而延时函数也依赖于具体的软硬件平台,所以我们将其定义为对象的操作。综上所述,我们可以定义DDC114的对象类型如下:
/*定义DDC114对象类型*/
typedef struct Ddc114Object {
uint32_t dCode[4];
Ddc114PinSetType convStatus;
Ddc114FormatType format; //数据输出格式
uint32_t codeRange; //输出量程编码
uint32_t codeZero; //输出零点编码
void (*GetDatas)(uint8_t *rData,uint16_t rSize);
uint8_t (*GetValid)(void);
void (*SetConv)(Ddc114PinSetType conv);
void (*SetReset)(Ddc114PinSetType reset);
void (*SetTest)(Ddc114PinSetType test);
void (*Delayus)(volatile uint32_t nTime); //实现us延时操作
}Ddc114ObjectType;
我们定义了DDC114的对象类型,这样我们就可以很容易得到我们想要的DDC114对象变量。但是每个对象都是独一无二的,所以我们需要一个初始化对象变量的过程,所以我们考虑实现一个DDC114对象变量的初始化函数。而需要初始化的就是对象的属性和操作,据此我们定义DDC114对象变量的初始化函数如下:
/*DDC114对象初始化*/
void Ddc114Initialization(Ddc114ObjectType *ddc, //DDC114对象变量
Ddc114FormatType format, //数据输出格式
DDC114GetDatas getDatas, //获取测量数据函数指针
DDC114GetValid getValid, //数据有效性状态获取函数指针
DDC114SetConv conv, //转换设置函数指针
DDC114SetReset reset, //复位操作函数指针
DDC114SetTest test, //测试模式操作函数指针
DDC114Delayus delayus //微秒延时函数指针
)
{
if((ddc==NULL)||(getDatas==NULL)||(getValid==NULL)||(conv==NULL)||(reset==NULL)||(test==NULL)||(delayus==NULL))
{
return ;
}
ddc->GetDatas=getDatas;
ddc->GetValid=getValid;
ddc->SetConv=conv;
ddc->SetReset=reset;
ddc->SetTest=test;
ddc->Delayus=delayus;
ddc->format=format;
if(format==DDC114_OUT20)
{
ddc->codeRange=1048575;
ddc->codeZero=4096;
}
else
{
ddc->codeRange=65535;
ddc->codeZero=256;
}
for(int i=0;i<4;i++)
{
ddc->dCode[i]=ddc->codeZero;
}
ddc->SetTest(DDC114_Pin_Reset);
ddc->SetReset(DDC114_Pin_Reset);
ddc->Delayus(100);
ddc->SetReset(DDC114_Pin_Set);
ddc->convStatus=DDC114_Pin_Reset;
ddc->SetConv(ddc->convStatus);
}
2.2、对象操作
我们对DDC114要进行哪些操作呢?复位、测试、获取状态等都是我们可以进行的操作,但最重要的还是从DDC114获取转换数据。当DDC114的数据准备好之后,就会将DVALID信号拉低,检测到DVALID信号就可以通过SPI端口获取数据。DDC114通过SPI端口一次性传送4个通道的数据,根据数据格式的不同分别传送80位或者64位。其操作时序如下图所示:
根据上述的描述及时序图我们可以编写获取DDC114转换数据的函数如下:
/*DDC114获取各通道的转换数据*/
void Ddc114GetDataCode(Ddc114ObjectType *ddc)
{
uint8_t rData[10];
uint16_t timeOut=0;
if(ddc->convStatus==DDC114_Pin_Reset)
{
ddc->convStatus=DDC114_Pin_Set;
}
else
{
ddc->convStatus=DDC114_Pin_Reset;
}
ddc->SetConv(ddc->convStatus);
while((ddc->GetValid())&&(timeOut<500))
{
timeOut++;
}
if(ddc->format == DDC114_OUT20)
{
ddc->GetDatas(rData,10);
}
else
{
ddc->GetDatas(rData,8);
}
Ddc114ParseDatas(ddc,rData);
}
我们读到了DDC114的输出数据后,是一个80位或者64位的数据流。我们感兴趣的是各个通道的转换数据,所以我们还需要对数据进行解析。而各通道转换数据的格式如下:
我们根据设定的数据格式以及读取的数据位,可以解析得到各个通道的转换值。
/*解析从DDC114读取的数据*/
static void Ddc114ParseDatas(Ddc114ObjectType *ddc,uint8_t *rData)
{
uint32_t tCode[10]={0};
if(ddc->format==DDC114_OUT20)
{
for(int i=0;i<10;i++)
{
tCode[i]=(uint32_t)(rData[i]);
}
ddc->dCode[0]=((tCode[7]&0x0F)<<16)+(tCode[8]<<8)+tCode[9];
ddc->dCode[1]=(tCode[5]<<12)+(tCode[6]<<4)+((tCode[7]&0xF0)>>4);
ddc->dCode[2]=((tCode[2]&0x0F)<<16)+(tCode[3]<<8)+tCode[4];
ddc->dCode[3]=(tCode[0]<<12)+(tCode[1]<<4)+((tCode[2]&0xF0)>>4);
}
else
{
for(int i=0;i<8;i++)
{
tCode[i]=(uint32_t)rData[i];
}
ddc->dCode[0]=(tCode[6]<<8)+tCode[7];
ddc->dCode[1]=(tCode[4]<<8)+tCode[5];
ddc->dCode[2]=(tCode[2]<<8)+tCode[3];
ddc->dCode[3]=(tCode[0]<<8)+tCode[1];
}
}
3、驱动的使用
我们已经设计并实现了DDC114电流输入型ADC的驱动程序。接下来我们将简单的说明如何使用这一驱动,并设计一个简单的示例验证这一驱动程序的正确性。
3.1、声明并初始化对象
我们是基于对象设计的DDC114电流输入型ADC的驱动程序,所以在使用驱动时,我们需要先声明一个对象变量,然后基于该对象变量来实现具体的对象操作。我们先声明对象如下:
Ddc114ObjectType ddc;
声明了这个对象变量之后,我们还需要使用初始化函数对其进行初始化方可使用。这一初始化函数拥有8个参数:
Ddc114ObjectType *ddc, //DDC114对象变量
Ddc114FormatType format, //数据输出格式
DDC114GetDatas getDatas, //获取测量数据函数指针
DDC114GetValid getValid, //数据有效性状态获取函数指针
DDC114SetConv conv, //转换设置函数指针
DDC114SetReset reset, //复位操作函数指针
DDC114SetTest test, //测试模式操作函数指针
DDC114Delayus delayus //微秒延时函数指针
第一个参数为所要初始化的对象变量。第二个参数是数据格式,根据具体的应用设置,我们这里是将它设置为20数据的格式。主要的实现的是后面6个函数指针,它们都是用于实现DDC114在具体的应用平台的操作函数。原型定义如下:
typedef void (*DDC114GetDatas)(uint8_t *rData,uint16_t rSize);
typedef uint8_t (*DDC114GetValid)(void);
typedef void (*DDC114SetConv)(Ddc114PinSetType conv);
typedef void (*DDC114SetReset)(Ddc114PinSetType reset);
typedef void (*DDC114SetTest)(Ddc114PinSetType test);
typedef void (*DDC114Delayus)(volatile uint32_t nTime); //实现us延时操作
在使用前我们先来实现这6个面向平台的操作接口函数,具体实现如下:
/* 读取数据 */
static void GetDatasFromDDC(uint8_t *rData,uint16_t rSize)
{
HAL_SPI_Receive(&hspi1, rData, rSize, 1);
}
/*读取有效引脚*/
static uint8_t ReadValidPin(void)
{
return HAL_GPIO_ReadPin(DDC_DVALID_GPIO_Port,DDC_DVALID_Pin);
}
/*设置转换引脚*/
static void SetConvPin(Ddc114PinSetType conv)
{
if(conv==DDC114_Pin_Reset)
{
HAL_GPIO_WritePin(DDC_CONV_CTL_GPIO_Port, DDC_CONV_CTL_Pin, GPIO_PIN_RESET);
return;
}
HAL_GPIO_WritePin(DDC_CONV_CTL_GPIO_Port, DDC_CONV_CTL_Pin, GPIO_PIN_SET);
return;
}
/*设置复位引脚*/
static void SetResetPin(Ddc114PinSetType reset)
{
if(reset==DDC114_Pin_Reset)
{
HAL_GPIO_WritePin(DDC_RESET_GPIO_Port, DDC_RESET_Pin, GPIO_PIN_RESET);
return;
}
HAL_GPIO_WritePin(DDC_RESET_GPIO_Port, DDC_RESET_Pin, GPIO_PIN_SET);
return;
}
/*设置测试引脚*/
static void SetTestPin(Ddc114PinSetType test)
{
if(test==DDC114_Pin_Reset)
{
HAL_GPIO_WritePin(DDC_TEST_GPIO_Port, DDC_TEST_Pin, GPIO_PIN_RESET);
return;
}
HAL_GPIO_WritePin(DDC_TEST_GPIO_Port, DDC_TEST_Pin, GPIO_PIN_SET);
return;
}
而延时函数,则用我们在STM32平台统一使用的微秒延时函数。定义了这些函数后,我们就可以初始化对象变量如下:
/*DDC114对象初始化*/
Ddc114Initialization(&ddc, //DDC114对象变量
DDC114_OUT20, //数据输出格式
GetDatasFromDDC, //获取测量数据函数指针
ReadValidPin, //数据有效性状态获取函数指针
SetConvPin, //转换设置函数指针
SetResetPin, //复位操作函数指针
SetTestPin, //测试模式操作函数指针
Delayus //微秒延时函数指针
);
3.2、基于对象进行操作
完成了对象的初始化后,我们就可以基于对象来实现相应的操作了。在我们这个应用实例中,我们使用它来采集光度数据值。
/*光度检测处理*/
void DePhotometricMeasurement(void)
{
if(aPara.phyPara.waveband>9)
{
luxCode[0].dCode=0;
luxCode[1].dCode=0;
luxCode[2].dCode=0;
luxCode[3].dCode=0;
ddcPeriod=0;
}
else
{
ddcPeriod++;
if(ddcPeriod==1)
{
/*DDC114获取各通道的转换数据*/
Ddc114GetDataCode(&ddc);
if(dataOrder[aPara.phyPara.waveband]>15)
{
dataOrder[aPara.phyPara.waveband]=0;
}
for(int i=0;i<4;i++)
{
luxCode[i].fData.waveBand=aPara.phyPara.waveband+1;
luxCode[i].fData.serialnumber=dataOrder[aPara.phyPara.waveband];
luxCode[i].fData.dataCode=ddc.dCode[i]&0xFFFFF;
}
dataOrder[aPara.phyPara.waveband]++;
ddcPeriod=0;
}
}
aPara.phyPara.dataCode1=luxCode[0].dCode;
aPara.phyPara.dataCode2=luxCode[1].dCode;
aPara.phyPara.dataCode3=luxCode[2].dCode;
aPara.phyPara.dataCode4=luxCode[3].dCode;
}
4、应用总结
我们设计并实现了DDC114电流输入型ADC的驱动程序。在我们的一个应用实例中,我们使用这一驱动程序采集了光度数据。测试结果如下图:
其中的红色数据就是我们通过调试工具查看到的DDC114的数据,对应4个通道。上面的为原始数据,通过上位软件查看的结果如下:
经过上述测试,说明我们设计的DDC114的驱动程序是正确的。在使用时需要注意几点,一是,最好将MCU的SPI接口配置为主机模式而不仅仅是只读模式;二是,SPI的速度最好不要太快,虽然DDC114可以达到10M的频率,但高速时波形会变差;三是,积分时间一定要规划好,否则很容易出现超量程的现象。