很多时候我们需要输出某种函数信号,如方波、三角波、正弦波等,但想要获得这样的函数信号,不论是硬件电路还是软件实现,却并不是一件简单的事情。不过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发送数据 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 else20 {21 dev->freqconstant=10.73741824; //默认是25m22 }23 24 dev->writedata=write;25 dev->delayms=delayms;26 27 if(cs!=null)28 {29 dev->chipselcet=cs;30 }31 else32 {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 else15 {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
全新款FLIR GF77隆重上市,多种工业气体都逃不过它的“火眼金睛”!
股市逢低 关注LED龙头企业
RTC 场景下的屏幕共享优化实践1
网络技术的特点
一个成功的技术领导者什么才最重要?
AD9833函数发生器的驱动设计与实现
区块链保护数据的能力有多强
微软OneDrive 5月更新:引入AI扫描、整理文档功能
2024技术展望: 人工智能领域将迎来哪些变化?
国产蓝牙耳机哪个好,口碑最好的国产蓝牙耳机
iPod Shuffle介绍
DHT11的使用注意事项
有哪些热门的蓝牙耳机?2022热门蓝牙耳机推荐
这四个问题或将严重削弱物联网市场的潜力和未来
VisCPM:迈向多语言多模态大模型时代
企业对于物联网安全意识是否有所加强?又该如何应对呢?
使用车载安全气囊充气机爆管的优势
哪一个stream的内置方法不属于中间操作
代码重构的经验总结
离线语音识别与在线语音识别有什么不一样?