外设驱动库开发笔记4:AD9833函数发生器驱动
很多时候我们需要输出某种函数信号,如方波、三角波、正弦波等,但想要获得这样的函数信号,不论是硬件电路还是软件实现,却并不是一件简单的事情。不过AD9833这类函数生成芯片可以简化这方面的操作,这一节我们就来设计并实现AD9833的驱动。
1、功能概述
各种类型的检测、信号激励和时域反射(TDR)应用都需要波形发生器。而AD9833就是一款低功耗、可编程波形发生器,能够产生正弦波、三角波和方波输出。
1.1、硬件配置及功能描述
AD9833无需额外的外部元件就能够产生正弦波、三角波和方波输出。输出频率和相位可通过软件进行编程,调整简单。AD9833通过一个三线式串行接口写入数据。该串行接口能够以最高40 MHz的时钟速率工作,并且与DSP和微控制器标准兼容。该器件采用2.3 V至5.5 V电源供电。
1.2、内部寄存器
AD9833包含一个16位控制寄存器,让用户可以配置AD9833的操作。mode位之外的所有控制位均在MCLK的内部下降沿采样。
控制寄存器各位的含义如下:
AD9833包含两个频率寄存器和两个相位寄存器,频率寄存器为28位:时钟速率为25 MHz时,可以实现0.1 Hz的分辨率;而时钟速率为1 MHz时,则可以实现0.004 Hz的分辨率。
每次写数据时,都是从写控制寄存器器开始,每次写的16为数据的高两位用以决定所写的寄存器。
如上图所示,写不同寄存器时高两位需根据寄存器的不同设定不同的值。
2、驱动设计与实现
我们已经了解了AD9833的基本情况。接下来我们就据此实现AD9833波形发生器驱动的设计及实现。
2.1、对象定义
AD9833波形发生器的驱动依然采用基于对象的操作,所以我们需要先得到AD9833波形发生器的对象。
2.1.1、抽象对象类型
一个对象最起码包含属性和操作两方面内容,我们先来分析一下AD9833波形发生器对象需要包含哪些属性和操作。
对于AD9833波形发生器来说,控制寄存器的状态决定了下一步的操作,所以我们将控制寄存器的状态抽象为对象的属性,以便随时掌握操作的目标。此外,作为函数发生器,输出的信号具有周期性,在输出频率固定的情况下,计算有一个常数,我们将其作为属性已确认输出型号的频率。
进而我们考虑AD9833波形发生器对象的操作。首先我们要操作AD9833波形发生器则需要向其传送数据,所以我们将向AD9833波形发生器写数据作为对象的一个操作。AD9833波形发生器采用SPI通讯接口,有时需要在软件中对片选信号进行操作,所以我们将片选型号的操作作为对象的另一个操作。在一些情况下,有些针对对象的活动需要延时进行,而在不同的平台中采取的延时方式不尽相同,为了操作方便我们将延时操作作为对象的一个操作。
据以上的分析我们可以抽象AD9833波形发生器的对象类型如下:
1 /* 定义AD9833对象类型 */ 2 typedef struct Ad9833Object{ 3 uint16_t ctlRegister; //控制寄存器 4 float freqConstant; //频率计算常数 5 void (*WriteData)(uint8_t *tData,uint16_t tSize); //向DAC发送数据 6 void (*ChipSelcet)(AD9833CSType en); //片选信号 7 void (*Delayms)(volatile uint32_t nTime); //ms延时操作指针 8 }Ad9833ObjectType;
2.1.2、对象初始化
我们虽然得到了AD9833的对象,但对象不能直接使用,我们需要对其进行初始化方能使用。所以接下来我们考虑AD9833波形发生器对象的初始化函数。
初始化函数至少包含有2方面内容:一是为对象变量赋必要的初值;二是检查这些初值是否是有效的。特别是一些操作指针错误的话可能产生严重的后果。基于这一原则,我们设计AD9833波形发生器的对象初始化函数如下:
1 /* 初始化AD9833对象 */ 2 void AD9833Initialization(Ad9833ObjectType *dev, 3 float mclk, 4 AD9833WriteData write, 5 AD9833ChipSelcet cs, 6 AD9833Delayms delayms) 7 { 8 if((dev==NULL)||(write==NULL)||(delayms==NULL)) 9 { 10 return; 11 } 12 13 dev->ctlRegister=0x0000; 14 15 if(mclk>0) 16 { 17 dev->freqConstant=268.435456/mclk; 18 } 19 else 20 { 21 dev->freqConstant=10.73741824; //默认是25M 22 } 23 24 dev->WriteData=write; 25 dev->Delayms=delayms; 26 27 if(cs!=NULL) 28 { 29 dev->ChipSelcet=cs; 30 } 31 else 32 { 33 dev->ChipSelcet=DefaultChipSelcet; 34 } 35 }
2.2、对象操作
我们已知AD9833波形发生器包含3类寄存器:控制寄存器、频率寄存器和相位寄存器。接下来我们就实现对这三个寄存器的操作。
2.2.1、操作控制寄存器
AD9833波形发生器有一个16位的控制寄存用于配置各种操作。其中DB13(B28)、DB12(HLB)、DB11(FSELECT)、DB10(PSELECT)、DB8(RESET)、DB7(SLEEP1)、DB6(SLEEP12)、DB5(OPBITEN)、DB3(DIV2)、DB1(MODE)等位是可以操作的。与频率寄存器和相位寄存器相关的配置我们在后续说明,这里先看看复位、休眠及输出模式的配置。
AD9833上电时,器件应复位。要使AD9833复位, 应将DB8(RESET)位置1。要使器件退出复位,应将该位清0。在reset 置0后的8个MCLK周期内,DAC输出端会出现信号。复位功能可使相应的内部寄存器复位至0,以提供中间电平的模拟输出。复位操作不会使相位、频率或控制寄存器复位。
1 /* 复位AD9833对象 */ 2 void ResetAD9833Object(Ad9833ObjectType *dev) 3 { 4 uint16_t regValue=dev->ctlRegister; 5 6 regValue|=AD9833_CTRLRESET; 7 SendToAD9833(dev,regValue); 8 9 dev->Delayms(1); 10 11 regValue&=(~AD9833_CTRLRESET); 12 SendToAD9833(dev,regValue); 13 14 dev->ctlRegister=regValue; 15 }
SLEEP功能可关断AD9833中不使用的部分,以将功耗降至最低。可关断的芯片部分是内部时钟和DAC。休眠功能需要操作DB7(SLEEP1)和DB6(SLEEP12)位。具体配置如下:
1 /* 设置AD9833休眠状态 */ 2 void SetAD9833SleepMode(Ad9833ObjectType *dev,Ad9833SleepMode mode) 3 { 4 uint16_t regValue=dev->ctlRegister; 5 6 regValue&=(~(AD9833_CTRLSLEEP1|AD9833_CTRLSLEEP12)); 7 8 switch(mode) 9 { 10 case DACTurnOff: 11 { 12 regValue|=AD9833_CTRLSLEEP12; 13 break; 14 } 15 case MCLKTurnOff: 16 { 17 regValue|=AD9833_CTRLSLEEP1; 18 break; 19 } 20 case DACMCLKTurnOff: 21 { 22 regValue|=(AD9833_CTRLSLEEP1|AD9833_CTRLSLEEP12); 23 break; 24 } 25 default: 26 { 27 break; 28 } 29 } 30 SendToAD9833(dev,regValue); 31 32 dev->ctlRegister=regValue; 33 }
AD9833可从芯片提供各种输出,所有这些输出均通过VOUT引脚提供。输出选项包括DAC数据的MSB、正弦波 输出或三角波输出。控制寄存器的DB5(OPBITEN)、DB3(DIV2)和DB1(MODE)决定 AD9833将提供的输出。具体如下:
1 /* 设置AD9833的输出模式 */ 2 void SetAD9833OutputMode(Ad9833ObjectType *dev,Ad9833OutMode mode) 3 { 4 uint16_t regValue=dev->ctlRegister; 5 6 regValue&=(~(AD9833_CTRLOPBITEN|AD9833_CTRLDIV2|AD9833_CTRLMODE)); 7 8 switch(mode) 9 { 10 case triangular: 11 { 12 regValue|=AD9833_CTRLMODE; 13 break; 14 } 15 case square_msb_2: 16 { 17 regValue|=AD9833_CTRLOPBITEN; 18 break; 19 } 20 case square_msb: 21 { 22 regValue|=(AD9833_CTRLOPBITEN|AD9833_CTRLDIV2); 23 break; 24 } 25 default: 26 { 27 break; 28 } 29 } 30 31 SendToAD9833(dev,regValue); 32 33 dev->ctlRegister=regValue; 34 }
2.2.2、操作频率寄存器
写频率寄存器时,Bit D15和Bit D14设置为01或10。控制寄存DB13(B28)和DB12(HLB)位决定操作的频率寄存器。如果希望更改某个频率寄存器的全部内容,则必须向 同一地址执行两次连续写入,因为频率寄存器是28位宽。 第一次写入包含14个LSB,第二次写入则包含14个MSB。 对于此工作模式,B28(D13)控制位应置1。在某些应用中,用户无需更新频率寄存器的全部28个位。 在粗调情况下,只需更新14个MSB,而在精调情况下,则只需更新14个LSB。通过将B28 (D13)控制位清0时,28位频率寄存器用作两个14位寄存器,其中一个包含14个MSB,另一个则包含14个LSB。这意味着,可单独更新频率字的 14个MSB而不影响14个LSB,反之亦然。控制寄存器中的 Bit HLB (D12)确定要更新的具体14个位。数据结构如下:
1 /* 设置频率寄存器的值 */ 2 void SetAD9833FreqRegister(Ad9833ObjectType *dev,WriteAd9833FreqReg reg,uint32_t freqValue) 3 { 4 uint16_t msbFreq,lsbFreq; 5 uint32_t freqReg; 6 7 freqReg =(uint32_t)(dev->freqConstant*freqValue); 8 lsbFreq = (freqReg & 0x0003FFF); 9 msbFreq = ((freqReg & 0xFFFC000) >> 14); 10 11 ConfigFreqRegisterStyle(dev,reg); 12 13 switch(reg) 14 { 15 case FREQ0_B28: 16 { 17 lsbFreq |=FREQ0_Address; 18 SendToAD9833(dev,lsbFreq); 19 msbFreq |=FREQ0_Address; 20 SendToAD9833(dev,msbFreq); 21 break; 22 } 23 case FREQ0_B14_LSB: 24 { 25 lsbFreq |=FREQ0_Address; 26 SendToAD9833(dev,lsbFreq); 27 break; 28 } 29 case FREQ0_B14_MSB: 30 { 31 msbFreq |=FREQ0_Address; 32 SendToAD9833(dev,msbFreq); 33 break; 34 } 35 case FREQ1_B28: 36 { 37 lsbFreq |=FREQ1_Address; 38 SendToAD9833(dev,lsbFreq); 39 msbFreq |=FREQ1_Address; 40 SendToAD9833(dev,msbFreq); 41 break; 42 } 43 case FREQ1_B14_LSB: 44 { 45 lsbFreq |=FREQ1_Address; 46 SendToAD9833(dev,lsbFreq); 47 break; 48 } 49 case FREQ1_B14_MSB: 50 { 51 msbFreq |=FREQ1_Address; 52 SendToAD9833(dev,msbFreq); 53 break; 54 } 55 default: 56 { 57 break; 58 } 59 } 60 }
2.2.3、操作相位寄存器
写入相位寄存器时,Bit D15和Bit D14设置为11。Bit D13确定将载入的相位寄存器。具体结构如下:
1 /* 设置相位寄存器的值 */ 2 void SetAD9833PhaseRegister(Ad9833ObjectType *dev,Ad9833PhaseReg reg,float phaseValue) 3 { 4 uint16_t phaseReg=0; 5 float phaseConstant=651.8986469; 6 7 phaseReg=(uint16_t)(phaseValue*phaseConstant); 8 phaseReg&=0x0FFF; 9 10 if(reg==PHASE0) 11 { 12 phaseReg|=PHASE0_Address; 13 } 14 else 15 { 16 phaseReg|=PHASE1_Address; 17 } 18 19 SendToAD9833(dev,phaseReg); 20 }
3、驱动的使用
我们已经设计并实现了AD9833波形发生器的驱动,接下来我们考虑如何使用这一驱动程序实现AD9833波形发生器的应用。
3.1、声明并初始化对象
驱动是基于对象的操作设计的,所以我们先要使用Ad9833ObjectType声明对象变量。形如:
Ad9833ObjectType ad9833;
声明了这个对象变量并不能用于操作AD9833波形发生器,我们还需要使用初始化函数对对象变量进行初始化。初始换函数所需参数如下:
Ad9833ObjectType *dev,所要初始化的AD9833对象设备
float mclk,AD9833采用的数字时钟,默认为25M
AD9833WriteData write,写AD9833对象函数
AD9833ChipSelcet cs,AD9833片选信号操作函数
AD9833Delayms delayms,操作ms延时函数
对于这些参数,对象变量我们已经定义了。AD9833采用的数字时钟则根据我们的实际使用情况输入。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:
1 /* 定义AD9833写数据指针类型 */ 2 typedef void (*AD9833WriteData)(uint8_t *tData,uint16_t tSize); 3 4 /* 定义AD9833片选操作指针类型 */ 5 typedef void (*AD9833ChipSelcet)(AD9833CSType en); 6 7 /* 定义AD9833 ms延时操作指针类型 */ 8 typedef void (*AD9833Delayms)(volatile uint32_t nTime);
对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入NULL即可。具体函数定义如下:
1 /*定义片选信号函数*/ 2 void AD9833CS(AD9833CSType en) 3 { 4 if(AD9833CS_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 AD9833TransmitData(uint8_t *wData,uint16_t wSize) 16 { 17 HAL_SPI_Transmit (&ad9833hspi, wData, wSize, 1000); 18 }
对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:
AD9833Initialization(&ad9833,25.0,AD9833TransmitData,AD9833CS,HAL_Delay);
3.2、基于对象进行操作
接下来我们将操作对象生成我们想要的波形。如我们想要生成频率为10MHz,相位为0的正弦波,编码如下:
1 /* 生成波形 */ 2 void SignalGenerator(void) 3 { 4 SetAD9833FreqRegister(&ad9833,FREQ0_B28,10000000); 5 SetAD9833PhaseRegister(&ad9833,PHASE0,0.0); 6 7 SelectAD9833FregRegister(&ad9833,FREQ0); 8 SelectAD9833PhaseRegister(&ad9833,PHASE0); 9 10 SetAD9833OutputMode(&ad9833,sinusoid); 11 }
在这段程序中我们使用的是频率寄存器0和相位寄存器0,并且频率寄存器采用的是修改28位的形式。对于其他的操作方式我们我们可以作相应的更改。
4、应用总结
我们已经实现AD9833波形发生器的驱动及基于此驱动的应用。我们输出正弦波,三角波及方波均得到了与我们预期一致的结果,说明驱动的设计是符合需求的。
控制寄存器的DB11(FSELECT)和DB10(PSELECT)位决定所使用的频率寄存器和相位寄存器,默认是FREQ0寄存器和PHASE0寄存器。若需要修改则可以调用SelectAD9833FregRegister和SelectAD9833PhaseRegister函数进行配置。
在使用驱动时需注意,采用SPI接口的器件需要考虑片选操作的问题。如果片选信号是通过硬件电路来实现的,我们在初始化时给其传递NULL值。如果是软件操作片选则传递我们编写的片选操作函数。
完整的源代码可在GitHub下载:https://github.com/foxclever/ExPeriphDriver
欢迎关注:
如果阅读这篇文章让您略有所得,还请点击下方的【好文要顶】按钮。
当然,如果您想及时了解我的博客更新,不妨点击下方的【关注我】按钮。
如果您希望更方便且及时的阅读相关文章,也可以扫描上方二维码关注我的微信公众号【木南创智】