• 外设驱动库开发笔记44:DDC114 ADC驱动


      在产品设计过程中,很多时候都会用到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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

      我们定义了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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

      我们读到了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];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    3、驱动的使用

      我们已经设计并实现了DDC114电流输入型ADC的驱动程序。接下来我们将简单的说明如何使用这一驱动,并设计一个简单的示例验证这一驱动程序的正确性。

    3.1、声明并初始化对象

      我们是基于对象设计的DDC114电流输入型ADC的驱动程序,所以在使用驱动时,我们需要先声明一个对象变量,然后基于该对象变量来实现具体的对象操作。我们先声明对象如下:

    Ddc114ObjectType ddc;
    
    • 1

      声明了这个对象变量之后,我们还需要使用初始化函数对其进行初始化方可使用。这一初始化函数拥有8个参数:

    Ddc114ObjectType *ddc,    //DDC114对象变量
    Ddc114FormatType format,  //数据输出格式
    DDC114GetDatas getDatas,  //获取测量数据函数指针
    DDC114GetValid getValid,  //数据有效性状态获取函数指针
    DDC114SetConv conv,       //转换设置函数指针
    DDC114SetReset reset,     //复位操作函数指针
    DDC114SetTest test,       //测试模式操作函数指针
    DDC114Delayus delayus     //微秒延时函数指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      第一个参数为所要初始化的对象变量。第二个参数是数据格式,根据具体的应用设置,我们这里是将它设置为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延时操作
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      在使用前我们先来实现这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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

      而延时函数,则用我们在STM32平台统一使用的微秒延时函数。定义了这些函数后,我们就可以初始化对象变量如下:

    /*DDC114对象初始化*/
        Ddc114Initialization(&ddc,              //DDC114对象变量
                          DDC114_OUT20,      //数据输出格式
                          GetDatasFromDDC,   //获取测量数据函数指针
                          ReadValidPin,      //数据有效性状态获取函数指针
                          SetConvPin,        //转换设置函数指针
                          SetResetPin,       //复位操作函数指针
                          SetTestPin,        //测试模式操作函数指针
                          Delayus            //微秒延时函数指针
                          );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    4、应用总结

      我们设计并实现了DDC114电流输入型ADC的驱动程序。在我们的一个应用实例中,我们使用这一驱动程序采集了光度数据。测试结果如下图:

      其中的红色数据就是我们通过调试工具查看到的DDC114的数据,对应4个通道。上面的为原始数据,通过上位软件查看的结果如下:

      经过上述测试,说明我们设计的DDC114的驱动程序是正确的。在使用时需要注意几点,一是,最好将MCU的SPI接口配置为主机模式而不仅仅是只读模式;二是,SPI的速度最好不要太快,虽然DDC114可以达到10M的频率,但高速时波形会变差;三是,积分时间一定要规划好,否则很容易出现超量程的现象。

    欢迎关注:

  • 相关阅读:
    numpy.around
    【git】本地仓库与远程仓库如何建立连接
    关于JWT
    精彩东博会丨我委会员单位联通沃音乐打卡第五届中国—东盟信息港论坛:穿越元宇宙 沉浸新技术
    CentOS 挂载新磁盘以及磁盘扩容操作教程
    可变电阻元件封装
    leetcode 40.组合总和Ⅱ 回溯法求解 (c++版本)
    RK3568平台开发系列讲解(音视频篇)如何把音视频流进行网络传输?
    docker-compose 搭建 单机版ELK
    Cy3.5-PEG-maleimide/马来酰亚胺,Cy3.5-聚乙二醇-马来酰亚胺,MAL-PEG-Cy3.5
  • 原文地址:https://blog.csdn.net/foxclever/article/details/125837362