外设驱动库开发笔记3:AD527x系列数字电位器驱动
在一些时候我们需要使用精度更高的数字电位器来实现我们的应用。我们经常使用AD527x系列数字电位器来实现这类应用。在通常情况下,AD527x系列数字电位器完全能够满足要求。为了减少重复工作,在这里我们将分系并实现AD527x系列数字电位器的驱动。
1、功能概述
我们在这里讨论的AD527x系列数字电位器包括:AD5270、AD5271、AD5272和AD5724,他们的功能是相同的,主要在数字位或通讯接口上有写差别。
AD527x系列数字电位器集业界领先的可变电阻性能与非易失性存储器(NVM)于一体,这些器件的端到端电阻容差误差小于1%,并提供50次可编程(50-TP)存储器。将电阻值编程写入50-TP存储器之前,可进行无限次调整。这些器件不需要任何外部电压源来帮助熔断熔丝,并提供50次永久编程的机会。在50-TP激活期间,一个永久熔断熔丝指令会将游标位置固定
对于AD527x系列数字电位器,皆有一个16位宽的移位寄存器,一切对AD527x系列数字电位器的操作都是同过这个以为寄存器完成的。移位寄存器的格式如下所示。
该16位移位寄存器由两个应设为0的未用位、四个控制位和10个RDAC数据位组成,并且数据以MSB优先方式加载。对于AD5271和AD5274只有8位数据,则最后两位会被忽略。四个控制位决定软件命令的功能,具体的功能码如下所示:
我们对AD527x系列数字电位器的操作就是以这10个命令为基础的,事实上NOP命令是可以忽略的,因为它不会有任何操作发生。其中有命令5和命令7需要说一下。
命令7则用于设置控制寄存器。控制寄存器仅后4为有效。C0用于设置50-TP的编程使能。C1用于设置RDAC的写保护。C2用于电阻容差校准。C3则是指示50-TP的编程状态。具体结构如下:
而命令5用于设置读出的50-TP的内容。就是说这条命令用于设置我下次读取50-TP时究竟是那一条的类容,因为总共有50条。具体的取值如下:
共50条需要50个编码,使用了D0到DF5位,编号1开始一一对应50个编程位置。
2、驱动设计与实现
我们已经了解了AD527x系列数字电位器的基本情况,接下来我们就设计并实现AD527x系列数字电位器的驱动。
2.1、对象定义
同样的我们将基于对象操作的思想来设计AD527x系列数字电位器的驱动。既如此,我们首先必须要定义AD527x系列数字电位器对象。
2.1.1、抽象对象类型
在抽象出AD527x系列数字电位器对象类型之前,我们先来分析一下AD527x系列数字电位器。一个对象最起码包含属性和操作两个特性,我们来分析一下AD527x系列数字电位器对象包含有那些属性和操作。
对于AD527x系列数字电位器包含有多种器件,不同的器件在通讯接口和档位等方面会有差别,所以我们将设备的类型作为其属性以分辩究竟是哪种器件,进而分辨接口和档位差异。游标的当前位置以及控制寄存器的值我们也将其设置为属性以确定设备当前的状态。当设备时I2C接口时,需要有一个设备地址,所以我们将设备地址设置为属性,这个属性只在I2C接口模式时才起作用。而在使用SPI接口的器件时,需要一个片选信号,所以我们将操作片选信号作为AD527x系列数字电位器的一个操作,这个操作只在使用SPI接口的器件时才起作用。此外,AD527x系列数字电位器对象还需要实现数据的发送与接收以及操作过程中必要的延时函数,我们均将其作为对象的操作。据上述分析我们可以抽象出AD527x系列数字电位器对象类型如下:
1 /*定义用于SPI接口的对象类型*/ 2 typedef struct AD527xObject { 3 AD527xType type; //设备类型 4 uint8_t devAddress; //设备地址,用于I2C接口 5 uint8_t conreg; //控制寄存器 6 uint16_t rdac; //游标寄存器现值 7 void (*ChipSelcet)(AD527xCSType en); //片选信号,用于SPI接口 8 void (*Receive)(struct AD527xObject *rx,uint8_t *rData,uint16_t rSize); 9 void (*Transmit)(struct AD527xObject *rx,uint8_t *wData,uint16_t wSize); 10 void (*Delayms)(volatile uint32_t nTime); //ms延时操作指针 11 }AD527xObjectType;
2.1.2、对象的初始化
一个对象我们需要对其初始化才能使用,初始化函数至少包含有2方面内容:一是为对象变量赋必要的初值;二是检查这些初值是否是有效的。特别是一些操作指针错误的话可能产生严重的后果。基于这一原则,我们设计AD527x系列数字电位器的对象初始化函数如下:
1 /* 初始化AD527x对象,I2C接口必须初始化devAddress,SPI接口必需初始化void (*ChipSelcet)(bool) */ 2 void AD527xInitialization(AD527xObjectType *rx, 3 uint8_t address, 4 AD527xType type, 5 AD527xReceive recieve, 6 AD527xTransmit transmit, 7 AD527xChipSelcet cs, 8 AD527xDelayms delayms) 9 { 10 if((rx==NULL)||(recieve==NULL)||(transmit==NULL)||(delayms==NULL)) 11 { 12 return; 13 } 14 15 if((type==AD5270)||(type==AD5271)) //使用SPI接口 16 { 17 if(cs==NULL) //硬件电路实现片选 18 { 19 rx->ChipSelcet=DefaultChipSelcet; 20 } 21 else 22 { 23 rx->ChipSelcet=cs; 24 } 25 rx->devAddress=0x00; 26 } 27 else //使用I2C接口 28 { 29 if((address==0x58)||(address==0x5C)||(address==0x5E)) 30 { 31 rx->devAddress=address; 32 } 33 else if((address==0x2C)||(address==0x2E)||(address==0x2F)) 34 { 35 rx->devAddress=(address<<1); 36 } 37 else 38 { 39 rx->devAddress=0x00; 40 } 41 42 rx->ChipSelcet=NULL; 43 } 44 45 rx->type=type; 46 47 rx->conreg=0x00; 48 rx->rdac=0x0000; 49 50 rx->Receive=recieve; 51 rx->Transmit=transmit; 52 rx->Delayms=delayms; 53 54 ReadControlRegister(rx); 55 56 SetSoftShutMode(rx,SOFT_NORMAL_MODE); 57 }
2.2、对象操作
前面我们已经描述过,对AD527x系列数字电位器的操作命令有9个。这9个命令皆是对寄存器进行读写操作的,所以我们这里将这些操作分为读寄存器操作和写寄存器操作,并以此设计驱动程序。
2.2.1、写寄存器操作
首先我们需要说明写寄存器操作是针对对象的操作函数,而不是对象变量包含的操作,因为我们只在对象变量中放入依赖于外界平台的基本操作。写寄存器操作会以回调的方式调用对象变量包含的基本操作。
因为AD527x系列数字电位器对象包括不同接口和不同档位的器件,所以我们设计写寄存器操作时需要考虑AD527x系列数字电位器对象的类型。而这个类型已在初始化时赋予了对象变量。据此我们设计写寄存器操作函数如下:
1 /* 写寄存器操作 */ 2 static void AD527xWriteRegister(AD527xObjectType *rx,uint16_t cmd) 3 { 4 uint8_t tData[2]; 5 6 tData[0]=(uint8_t)(cmd>>8); 7 8 tData[1]=(uint8_t)cmd; 9 10 if((rx->type==AD5270)||(rx->type==AD5271)) //SPI接口 11 { 12 rx->ChipSelcet(AD527xCS_ENABLE); 13 rx->Delayms(1); 14 } 15 16 rx->Transmit(rx,tData,2); 17 18 if((rx->type==AD5270)||(rx->type==AD5271)) //SPI接口 19 { 20 rx->Delayms(1); 21 rx->ChipSelcet(AD527xCS_DISABLE); 22 } 23 }
2.2.2、读寄存器操作
与写寄存器操作一样,读寄存器操作一样要考虑到AD527x系列数字电位器对象的类型。在使用SPI接口的对象类型种需要考虑片选信号的处理。我们设计读寄存器操作如下:
1 /* 读寄存器操作 */ 2 static void AD527xReadRegister(AD527xObjectType *rx,uint16_t cmd,uint8_t *rData) 3 { 4 uint8_t tData[2]; 5 6 if((rx->type==AD5270)||(rx->type==AD5271)) //SPI接口 7 { 8 rx->ChipSelcet(AD527xCS_ENABLE); 9 rx->Delayms(1); 10 } 11 12 rx->Transmit(rx,tData,2); 13 14 rx->Receive(rx,rData,2); 15 16 if((rx->type==AD5270)||(rx->type==AD5271)) //SPI接口 17 { 18 rx->Delayms(1); 19 rx->ChipSelcet(AD527xCS_DISABLE); 20 } 21 }
2.2.3、面向命令的操作
我们已经实现了对继存存其的读操作和写操作,但我们并不想通过调用这两个函数并传递命令来实现我们的应用。所以我们将不同的操作命令所要完成的功能封装成函数,在这些函数中调用读写寄存器操作函数来完成。这样使用驱动就变得更为简便。例如我们设计读写RDAC的函数如下:
1 /* 设置AD527x游标位置 */ 2 void SetRDACForAd527x(AD527xObjectType *rx,uint16_t data) 3 { 4 uint16_t temp=0; 5 6 if((rx->type==AD5271)||(rx->type==AD5274)) //256档 7 { 8 temp=data>255?255:data; 9 } 10 else if((rx->type==AD5270)||(rx->type==AD5272)) //1024档 11 { 12 temp=data>1023?1023:data; 13 } 14 15 temp=COMMAND_W_RDAC|temp; 16 17 if(((rx->conreg)&0x02)!=0x02) 18 { 19 SetControlRegister(rx,PROGRAM_RDAC_ENABLE|rx->conreg); 20 } 21 22 AD527xWriteRegister(rx,temp); 23 } 24 25 /* 读取RDAC游标寄存器的内容 */ 26 uint16_t ReadRDACFromAd527x(AD527xObjectType *rx) 27 { 28 uint8_t rData[2]; 29 30 uint16_t cmd=COMMAND_R_RDAC; 31 32 AD527xReadRegister(rx,cmd,rData); 33 34 rx->rdac=(rData[0]<<8)+rData[1]; 35 36 return rx->rdac; 37 }
3、驱动的使用
我们已经实现了AD527x系列数字电位器的驱动。接下来我们来考虑如何使用这一驱动实现我们的应用。
3.1、声明并初始化对象
我们已经定义了AD527x系列数字电位器对象类型。所以我们先要使用对象类型声明一个AD527x系列数字电位器对象变量。形式如下:
AD527xObjectType ad527x;
当然,这里定义的这个对象变量还不能直接使用。我们需要使用初始化函数对这个对象变量进行初始化。初始化函数前面已经说过,传递的参数皆是与对象变量相关的。初始化函数的参数如下:
AD527xObjectType *rx,待初始化的对象变量。
uint8_t address,采用I2C接口通讯是的设备地址。
AD527xType type,对象的设备类型。
AD527xReceive recieve,数据接收函数指针。
AD527xTransmit transmit,数据发送函数指针。
AD527xChipSelcet cs,使用SPI接口通讯时,片选操作函数指针。
AD527xDelayms delayms,毫秒延时操作函数指针。
对于这些参数,对象变量我们已经定义了。对象类型根据实际器件输入即可。而设备地址在使用I2C接口时按要求输入即可,如果是SPI接口则任意uint8_t类型的值均可。最主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型图下:
1 /*定义片选信号函数指针类型*/ 2 typedef void (*AD527xChipSelcet)(AD527xCSType en); 3 4 /*定义接收数据函数指针类型*/ 5 typedef void (*AD527xReceive)(struct AD527xObject *rx,uint8_t *rData,uint16_t rSize); 6 7 /*定义发送数据函数指针类型*/ 8 typedef void (*AD527xTransmit)(struct AD527xObject *rx,uint8_t *wData,uint16_t wSize); 9 10 /*定义ms延时操作指针*/ 11 typedef void (*AD527xDelayms)(volatile uint32_t nTime);
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数只在使用SPI接口是需要定义,否则可以传入NULL即可。具体函数定义如下:
1 /*定义片选信号函数*/ 2 void AD527xCS(AD527xCSType en) 3 { 4 if(AD527xCS_ENABLE==en) 5 { 6 HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_RESET); 7 } 8 else 9 { 10 HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_SET); 11 } 12 } 13 14 /*定义接收数据函数*/ 15 void AD527xReceiveData(struct AD527xObject *rx,uint8_t *rData,uint16_t rSize) 16 { 17 HAL_SPI_Receive (&hspi, rData, rSize, 1000); 18 } 19 20 /*定义发送数据函数*/ 21 void AD527xTransmitData(struct AD527xObject *rx,uint8_t *wData,uint16_t wSize) 22 { 23 HAL_SPI_Transmit (&hspi, wData, wSize, 1000); 24 }
对于延时函数我们可以采用各种方法实现。我们曹勇的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:
AD527xInitialization(&ad527x,0x00,AD5270,AD527xReceiveData,AD527xTransmitData,AD527xCS,HAL_Delay);
这是使用SPI接口器件的初始化操作,使用I2C接口的初始化操作类似次操作即可。
3.2、基于对象进行操作
我们已经定义了对象变量并对其进行了初始化。接下来我们就要看看如何操作对象得到我们想要的结果。
我们在前面已经根据操作命令做了封装,所以我们需要什么养的功能只需要调用相应的函数就可以了。如我们想要设置RDAC为最大值则:
SetValueToAd5270(&ad527x,1023);
其中第1个参数为要操作的对象指针,第2个参数为要设置的游标位置值。
4、应用总结
我们已经实现AD527x系列数字电位器的驱动及基于此驱动的应用,得到了与我们预期一致的结果,说明驱动的设计时符合需求的。
在使用驱动时需注意,采用I2C接口的器件需要考虑设备地址的问题。设备地址由ADDR引脚的状态决定。由三种取值如下:
在使用驱动时需注意,采用SPI接口的器件需要考虑片选操作的问题。如果片选信号是通过硬件电路来实现的,我们在初始化时给其传递NULL值。如果是软件操作片选则传递我们编写的片选操作函数。
源码地址GitHub:https://github.com/foxclever/ExPeriphDriver
欢迎关注:
如果阅读这篇文章让您略有所得,还请点击下方的【好文要顶】按钮。
当然,如果您想及时了解我的博客更新,不妨点击下方的【关注我】按钮。
如果您希望更方便且及时的阅读相关文章,也可以扫描上方二维码关注我的微信公众号【木南创智】