外设驱动库开发笔记4:AD9833函数发生器驱动

很多时候我们需要输出某种函数信号,如方波、三角波、正弦波等,但想要获得这样的函数信号,不论是硬件电路还是软件实现,却并不是一件简单的事情。不过AD9833这类函数生成芯片可以简化这方面的操作,这一节我们就来设计并实现AD9833的驱动。

1、功能概述

各种类型的检测、信号激励和时域反射(TDR)应用都需要波形发生器。而AD9833就是一款低功耗、可编程波形发生器,能够产生正弦波、三角波和方波输出。

1.1、硬件配置及功能描述

AD9833无需额外的外部元件就能够产生正弦波、三角波和方波输出。输出频率和相位可通过软件进行编程,调整简单。AD9833通过一个三线式串行接口写入数据。该串行接口能够以最高40 MHz的时钟速率工作,并且与DSP和微控制器标准兼容。该器件采用2.3 V5.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波形发生器的对象类型如下:

/* 定义AD9833对象类型 */
typedef struct Ad9833Object{uint16_t ctlRegister;                //控制寄存器float freqConstant;                          //频率计算常数void (*WriteData)(uint8_t *tData,uint16_t tSize);        //向DAC发送数据void (*ChipSelcet)(AD9833CSType en);     //片选信号void (*Delayms)(volatile uint32_t nTime);       //ms延时操作指针
}Ad9833ObjectType;

2.1.2、对象初始化

我们虽然得到了AD9833的对象,但对象不能直接使用,我们需要对其进行初始化方能使用。所以接下来我们考虑AD9833波形发生器对象的初始化函数。

初始化函数至少包含有2方面内容:一是为对象变量赋必要的初值;二是检查这些初值是否是有效的。特别是一些操作指针错误的话可能产生严重的后果。基于这一原则,我们设计AD9833波形发生器的对象初始化函数如下:

/* 初始化AD9833对象 */
void AD9833Initialization(Ad9833ObjectType *dev,float mclk,AD9833WriteData write,AD9833ChipSelcet cs,AD9833Delayms delayms)
{if((dev==NULL)||(write==NULL)||(delayms==NULL)){return;}dev->ctlRegister=0x0000;if(mclk>0){dev->freqConstant=268.435456/mclk;}else{dev->freqConstant=10.73741824; //默认是25M}dev->WriteData=write;dev->Delayms=delayms;if(cs!=NULL){dev->ChipSelcet=cs;}else{dev->ChipSelcet=DefaultChipSelcet;}
}

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后的8MCLK周期内,DAC输出端会出现信号。复位功能可使相应的内部寄存器复位至0,以提供中间电平的模拟输出。复位操作不会使相位、频率或控制寄存器复位。

/* 复位AD9833对象 */
void ResetAD9833Object(Ad9833ObjectType *dev)
{uint16_t regValue=dev->ctlRegister;regValue|=AD9833_CTRLRESET;SendToAD9833(dev,regValue);dev->Delayms(1);regValue&=(~AD9833_CTRLRESET);SendToAD9833(dev,regValue);dev->ctlRegister=regValue;
}

SLEEP功能可关断AD9833中不使用的部分,以将功耗降至最低。可关断的芯片部分是内部时钟和DAC。休眠功能需要操作DB7(SLEEP1)DB6(SLEEP12)位。具体配置如下:

/* 设置AD9833休眠状态 */
void SetAD9833SleepMode(Ad9833ObjectType *dev,Ad9833SleepMode mode)
{uint16_t regValue=dev->ctlRegister;regValue&=(~(AD9833_CTRLSLEEP1|AD9833_CTRLSLEEP12));switch(mode){case DACTurnOff:{regValue|=AD9833_CTRLSLEEP12;break;}case MCLKTurnOff:{regValue|=AD9833_CTRLSLEEP1;break;}case DACMCLKTurnOff:{regValue|=(AD9833_CTRLSLEEP1|AD9833_CTRLSLEEP12);break;}default:{break;}}SendToAD9833(dev,regValue);dev->ctlRegister=regValue;
}

AD9833可从芯片提供各种输出,所有这些输出均通过VOUT引脚提供。输出选项包括DAC数据的MSB、正弦波 输出或三角波输出。控制寄存器的DB5(OPBITEN)DB3(DIV2)DB1(MODE)决定 AD9833将提供的输出。具体如下:

/* 设置AD9833的输出模式 */
void SetAD9833OutputMode(Ad9833ObjectType *dev,Ad9833OutMode mode)
{uint16_t regValue=dev->ctlRegister;regValue&=(~(AD9833_CTRLOPBITEN|AD9833_CTRLDIV2|AD9833_CTRLMODE));switch(mode){case triangular:{regValue|=AD9833_CTRLMODE;break;}case square_msb_2:{regValue|=AD9833_CTRLOPBITEN;break;}case square_msb:{regValue|=(AD9833_CTRLOPBITEN|AD9833_CTRLDIV2);break;}default:{break;}}SendToAD9833(dev,regValue);dev->ctlRegister=regValue;
}

2.2.2、操作频率寄存器

写频率寄存器时,Bit D15Bit D14设置为0110。控制寄存DB13(B28)DB12(HLB)位决定操作的频率寄存器。如果希望更改某个频率寄存器的全部内容,则必须向 同一地址执行两次连续写入,因为频率寄存器是28位宽。 第一次写入包含14LSB,第二次写入则包含14MSB 对于此工作模式,B28(D13)控制位应置1。在某些应用中,用户无需更新频率寄存器的全部28个位。 在粗调情况下,只需更新14MSB,而在精调情况下,则只需更新14LSB。通过将B28 (D13)控制位清0时,28位频率寄存器用作两个14位寄存器,其中一个包含14MSB,另一个则包含14LSB。这意味着,可单独更新频率字的 14MSB而不影响14LSB,反之亦然。控制寄存器中的 Bit HLB (D12)确定要更新的具体14个位。数据结构如下:

/* 设置频率寄存器的值 */
void SetAD9833FreqRegister(Ad9833ObjectType *dev,WriteAd9833FreqReg reg,uint32_t freqValue)
{uint16_t msbFreq,lsbFreq;uint32_t freqReg;freqReg =(uint32_t)(dev->freqConstant*freqValue);lsbFreq = (freqReg & 0x0003FFF);msbFreq = ((freqReg & 0xFFFC000) >> 14);ConfigFreqRegisterStyle(dev,reg);switch(reg){case FREQ0_B28:{lsbFreq |=FREQ0_Address;SendToAD9833(dev,lsbFreq);msbFreq |=FREQ0_Address;SendToAD9833(dev,msbFreq);break;}case FREQ0_B14_LSB:{lsbFreq |=FREQ0_Address;SendToAD9833(dev,lsbFreq);break;}case FREQ0_B14_MSB:{msbFreq |=FREQ0_Address;SendToAD9833(dev,msbFreq);break;}case FREQ1_B28:{lsbFreq |=FREQ1_Address;SendToAD9833(dev,lsbFreq);msbFreq |=FREQ1_Address;SendToAD9833(dev,msbFreq);break;}case FREQ1_B14_LSB:{lsbFreq |=FREQ1_Address;SendToAD9833(dev,lsbFreq);break;}case FREQ1_B14_MSB:{msbFreq |=FREQ1_Address;SendToAD9833(dev,msbFreq);break;}default:{break;}}
}

2.2.3、操作相位寄存器

写入相位寄存器时,Bit D15Bit D14设置为11Bit D13确定将载入的相位寄存器。具体结构如下:

/* 设置相位寄存器的值 */
void SetAD9833PhaseRegister(Ad9833ObjectType *dev,Ad9833PhaseReg reg,float phaseValue)
{uint16_t phaseReg=0;float phaseConstant=651.8986469;phaseReg=(uint16_t)(phaseValue*phaseConstant);phaseReg&=0x0FFF;if(reg==PHASE0){phaseReg|=PHASE0_Address;}else{phaseReg|=PHASE1_Address;}SendToAD9833(dev,phaseReg);
}

3、驱动的使用

我们已经设计并实现了AD9833波形发生器的驱动,接下来我们考虑如何使用这一驱动程序实现AD9833波形发生器的应用。

3.1、声明并初始化对象

驱动是基于对象的操作设计的,所以我们先要使用Ad9833ObjectType声明对象变量。形如:

Ad9833ObjectType ad9833;

声明了这个对象变量并不能用于操作AD9833波形发生器,我们还需要使用初始化函数对对象变量进行初始化。初始换函数所需参数如下:

Ad9833ObjectType *dev,所要初始化的AD9833对象设备

float mclkAD9833采用的数字时钟,默认为25M

AD9833WriteData write,写AD9833对象函数

AD9833ChipSelcet csAD9833片选信号操作函数

AD9833Delayms delayms,操作ms延时函数

对于这些参数,对象变量我们已经定义了。AD9833采用的数字时钟则根据我们的实际使用情况输入。主要的是我们需要定义几个函数,并将函数指针作为参数。这几个函数的类型如下:

/* 定义AD9833写数据指针类型 */
typedef void (*AD9833WriteData)(uint8_t *tData,uint16_t tSize);/* 定义AD9833片选操作指针类型 */
typedef void (*AD9833ChipSelcet)(AD9833CSType en);/* 定义AD9833 ms延时操作指针类型 */
typedef void (*AD9833Delayms)(volatile uint32_t nTime);

对于这几个函数我们根据样式定义就可以了,具体的操作可能与使用的硬件平台有关系。片选操作函数用于多设备需要软件操作时,如采用硬件片选可以传入NULL即可。具体函数定义如下:

/*定义片选信号函数*/
void AD9833CS(AD9833CSType en)
{if(AD9833CS_ENABLE==en){HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_RESET);}else{HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_SET);}
}/*定义发送数据函数*/
void AD9833TransmitData(uint8_t *wData,uint16_t wSize)
{HAL_SPI_Transmit (&ad9833hspi, wData, wSize, 1000);
}

对于延时函数我们可以采用各种方法实现。我们采用的STM32平台和HAL库则可以直接使用HAL_Delay()函数。于是我们可以调用初始化函数如下:

AD9833Initialization(&ad983325.0AD9833TransmitDataAD9833CSHAL_Delay);

3.2、基于对象进行操作

接下来我们将操作对象生成我们想要的波形。如我们想要生成频率为10MHz,相位为0的正弦波,编码如下:

/* 生成波形 */
void SignalGenerator(void)
{SetAD9833FreqRegister(&ad9833,FREQ0_B28,10000000);SetAD9833PhaseRegister(&ad9833,PHASE0,0.0);SelectAD9833FregRegister(&ad9833,FREQ0);SelectAD9833PhaseRegister(&ad9833,PHASE0);SetAD9833OutputMode(&ad9833,sinusoid);
}

在这段程序中我们使用的是频率寄存器0和相位寄存器0,并且频率寄存器采用的是修改28位的形式。对于其他的操作方式我们我们可以作相应的更改。

4、应用总结

我们已经实现AD9833波形发生器的驱动及基于此驱动的应用。我们输出正弦波,三角波及方波均得到了与我们预期一致的结果,说明驱动的设计是符合需求的。

控制寄存器的DB11(FSELECT)DB10(PSELECT)位决定所使用的频率寄存器和相位寄存器,默认是FREQ0寄存器和PHASE0寄存器。若需要修改则可以调用SelectAD9833FregRegisterSelectAD9833PhaseRegister函数进行配置。

在使用驱动时需注意,采用SPI接口的器件需要考虑片选操作的问题。如果片选信号是通过硬件电路来实现的,我们在初始化时给其传递NULL值。如果是软件操作片选则传递我们编写的片选操作函数。

完整的源代码可在GitHub下载:https://github.com/foxclever/ExPeriphDriver

欢迎关注:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499376.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PID控制器改进笔记之二:改进PID控制器之手自动切换

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

外设驱动库开发笔记5:AD7705系列ADC驱动

我们的经常需要采集一些精度要求较高的模拟信号,使用MCU集成的ADC难以达到要求、所以我们需要独立的ADC芯片。这一节我们就来设计并实现AD7705芯片的驱动、并探讨驱动的使用方法。 1、功能概述 AD7705/AD7706是用于低频测量的完整模拟前端。可以直接从传感器接收低…

PID控制器改进笔记之三:改进PID控制器之正反作用

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

PID控制器改进笔记之四:改进PID控制器之设定值响应

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

PID控制器改进笔记之五:改进PID控制器之串级设定

前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。所以在这篇中我们来讨论改进…

滤波器开发之一:基于算数平均的平滑滤波器

信号采集是非常常见的需求,我们也总是希望采集到的数据是纯净而真实的,但这只是我们的希望。环境中存在太多的干扰信号,为了让我们得到的数据尽可能地接近实际值,我们需要降低这些干扰信号的影响,于是就有了滤波器的用…

外设驱动库开发笔记6:AD719x系列ADC驱动

前面我们讨论了AD7705这种ADC器件的驱动开发,在实际中我们使用更多的是AD719x系列的ADC芯片、包括有AD7191、AD7192和AD7193等。接下来我们就来设计并开发AD719x的驱动程序。 1、功能概述 AD7192是一款适合高精密测量应用的低噪声完整模拟前端,内置一个…

滤波器开发之二:基于算数平均的带阻平滑滤波器

信号采集是非常常见的需求,我们也总是希望采集到的数据是纯净而真实的,但这只是我们的希望。环境中存在太多的干扰信号,为了让我们得到的数据尽可能地接近实际值,我们需要降低这些干扰信号的影响,于是就有了滤波器的用…

滤波器开发之三:基于算数平均的阶进平滑滤波器

信号采集是非常常见的需求,我们也总是希望采集到的数据是纯净而真实的,但这只是我们的希望。环境中存在太多的干扰信号,为了让我们得到的数据尽可能地接近实际值,我们需要降低这些干扰信号的影响,于是就有了滤波器的用…

外设驱动库开发笔记7:LTC2400系列ADC驱动

有些时候我们需要对高精度的ADC来处理一些要求较高的模拟量采集。在处理温控器的过程中我们就使用到了LTC2400这款ADC。接下来我们就来设计并实现LTC2400的驱动。 1、功能概述 LTC2400是一个供电电压2.7V到5.5V的微功率24位转换器,集成了振荡器、4ppm INL和0.3ppm…

外设驱动库开发笔记8:GPIO模拟I2C驱动

I2C总线简单方便,是我们经常使用的一种总线。但有时候我们的MCU没有足够多的I2C控制器来实现我们的应用,所幸我可以使用普通的GPIO引脚来模拟低速的I2C总线通信。这一节我们就来实现使用软件通过普通GPIO操作I2C设备的驱动。 1、功能概述 I2C总线使用两…

嵌入式IAP开发笔记之一:面向STM32的BootLoader程序

对于很多人来说,BootLoader并不是一个陌生的词,甚至会经常用到它。因为在很多情况下我们都需要BootLoader程序,比如我们需要对系统在线升级时就需要它,还有当我们需要在外部存储器中运行程序时也需要用到它。在这里我们就来设计一…

外设驱动库开发笔记9:SHT1x系列温湿度传感器驱动

在我们的产品中,经常需要检测温湿度数据。有很多检测温湿度的方法和模块,其中SHT1x系列温湿度传感器就是一种成本较低使用方便的温湿度检测模块。下面我们就来说一说如何实现SHT1x系列温湿度传感器的驱动。 1、功能概述 SHT1x包括 SHT10, S…

Modbus协议栈应用实例之一:Modbus RTU主站应用

自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例。所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,在这一篇中我们先来使用协议栈实现Modbus RTU主站的示例。 1、何为RTU主站 Modbus协议是…

uCOS-III应用开发笔记之一:uCOS-III在STM32的移植

uCOS-III实时操作系统在MCU平台被广泛使用,在这里我们将简单的记录如何将uCOS-III实时操作系统移植到目标平台上并运行。 1、必要的准备 在开始uCOS-III实时操作系统的移植前,我们还需要做一些必要的准备,如确定目标板、准备目标工程及uCOS…

外设驱动库开发笔记10:SHT2x系列温湿度传感器驱动

温湿度检测是嵌入式编程中经常应用到的一项功能。在我们的产品中亦经常使用。SHT2x系列温湿度传感器作为一种高精度低成本的集成模块,一直应用于我们的产品中。在这里我们讨论如何封装SHT2x系列温湿度传感器的驱动。 1、功能概述 SHT20配有一个全新设计的CMOSens芯…

Modbus协议栈应用实例之二:Modbus RTU从站应用

自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例。所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,这一篇中我们将使用协议栈实现一个Modbus RTU从站应用。 1、何为RTU从站 Modbus协议是一…

外设驱动库开发笔记11:SHT3x系列温湿度传感器驱动

在我们的产品中经常会遇到温湿度检测的需求。可以用于检测温湿度的传感器元件也有很多。我们经常使用的SHT各系列数字温湿度传感器来实现应用需求。在这里我们将设计并实现SHT3x系列温湿度传感器的驱动。 1、功能概述 SHT3x系列温湿度传感器是适用于各种应用的高品质湿度传感…

LwIP应用开发笔记之十:LwIP带操作系统基本移植

现在,TCP/IP协议的应用无处不在。随着物联网的火爆,嵌入式领域使用TCP/IP协议进行通讯也越来越广泛。在我们的相关产品中,也都有应用,所以我们结合应用实际对相关应用作相应的总结。 1、技术准备 我们采用的开发平台是STM32F407…

ThreadX应用开发笔记之一:移植ThreadX到STM32平台

现在一些小型系统中也往往有多任务处理的需求,这就为实时操作系统提供了用武之地。事实上国内外各种各样的RTOS有很多,而且基本都在走开源的路线,ThreadX也不例外,在这一篇中我们就来学习ThreadX初步应用并将其移植到STM32平台中。…