News:

Let's find out together what makes a PIC Tick!

Main Menu

Positron8 - AD9833 Waveform Generator Library

Started by top204, Feb 18, 2024, 01:48 PM

Previous topic - Next topic

top204

The AD9833 waveform generator device has been around for quite some years now, and it is a device I have always meant to take a look at, but never got around to doing so. So when I saw a recent post on the forum about the AD9833, it triggered my interest again. Also with my interest in designing and producing a Medium Wave AM transmitter to play MP3 files of old radio shows when repairing the old valve radios, I decided to take a look at one and ended up writing a library so an AD9833 device can be used to generate multiple waveforms and frequencies.

Below is the listing for the AD9833 library:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' AD9833 Waveform Generator interface library.
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'
' Default SPI Pins that connect to the AD9833 device
'
$ifndef AD9833_FSync_Pin
    $define AD9833_FSync_Pin PORTC.1                            ' Connects to the AD9833 FSync pin
    $SendWarning "AD9833_FSync_Pin missing from the main program, so using the default pin of PORTC.1"
$endif
$ifndef AD9833_SClk_Pin
    $define AD9833_SClk_Pin  PORTC.3                            ' Connects to the AD9833 SCLK pin
    $SendWarning "AD9833_SClk_Pin missing from the main program, so using the default pin of PORTC.3"
$endif
$ifndef AD9833_SData_Pin
    $define AD9833_SData_Pin PORTC.5                            ' Connects to the AD9833 SData pin
    $SendWarning "AD9833_SData_Pin missing from the main program, so using the default pin of PORTC.5"
$endif
'
' Control Register bit values
'
$define cAD9833_B28_Bit     13
$define cAD9833_HLB_Bit     12
$define cAD9833_FSelect_Bit 11
$define cAD9833_PSelect_Bit 10
$define cAD9833_Reset_Bit   8
$define cAD9833_Sleep1_Bit  7
$define cAD9833_Sleep12_Bit 6
$define cAD9833_OpBitEn_Bit 5
$define cAD9833_Div2_Bit    3
$define cAD9833_Mode_Bit    1
'
' Control Register bit masks
'
$define cAD9833_B28_Mask      8192                              ' %0010000000000000
$define cAD9833_HLB_Mask      4096                              ' %0001000000000000
$define cAD9833_FSelect_Mask  2048                              ' %0000100000000000
$define cAD9833_PSelect_Mask  1024                              ' %0000010000000000
$define cAD9833_Reset_Mask    256                               ' %0000000100000000
$define cAD9833_Sleep1_Mask   128                               ' %0000000010000000
$define cAD9833_Sleep12_Mask  64                                ' %0000000001000000
$define cAD9833_OpBitEn_Mask  32                                ' %0000000000100000
$define cAD9833_Div2_Mask     8                                 ' %0000000000000100
$define cAD9833_Mode_Mask     2                                 ' %0000000000000010

$define cAD9833_MCLK_Freq 25000000.0                            ' The frequency (in Hz) of the clock driving the AD9833's MCLK pin
$define cAD9833_Max_Freq  12500000                              ' The maximum frequency is 12.5MHz
'
' Registers
'
$define cAD9833_Freq0_Reg        $4000
$define cAD9833_Freq1_Reg        $8000
$define cAD9833_Phase_Write_Cmd  $C000                          ' Setup for Phase write
$define cAD9833_Phase1_Write_Reg $2000                          ' Which phase register
$define cAD9833_Phase1_Out_Reg   $0400                          ' Output is based on Reg0/Reg1
$define cAD9833_Freq1_Out_Reg    $0800
'
' Modes
'
$define cAD9833_Off      0                                      ' No waveform output
$define cAD9833_Sine     1                                      ' Sine waveform output
$define cAD9833_Triangle 2                                      ' Triangle waveform output
$define cAD9833_Square1  3                                      ' Square wave waveform output
$define cAD9833_Square2  4                                      ' Square wave (frequency divided by 2) waveform output
'
' Channels
'
$define cAD9833_Channel0 0
$define cAD9833_Chan0    0
$define cAD9833_Channel1 1
$define cAD9833_Chan1    1

$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif
'
' Create some global variables used in the library
'
    Dim AD9833_wControl  As Word                                ' Holds the contents of the Control Register
    Dim AD9833_dFreq[2]  As Dword                               ' Frequency in Hz for both channels
    Dim AD9833_wPhase[2] As Word                                ' Phase angle 0..360 for both channels

'--------------------------------------------------------------------------------------------------
' Initialise the AD9833 device interface
' Input     : None
' Output    : None
' Notes     : None
'
Proc AD9833_Init()
    PinHigh AD9833_FSync_Pin
    PinLow AD9833_SData_Pin
    PinHigh AD9833_SClk_Pin
    AD9833_Reset()
EndProc

'--------------------------------------------------------------------------------------------------
' Reset the AD9833 device
' Input     : None
' Output    : None
' Notes     : None
'
Proc AD9833_Reset()
    AD9833_wControl = cAD9833_Reset_Mask
    AD9833_WriteCtrlReg(AD9833_wControl)
    AD9833_dFreq[0]  = 0
    AD9833_dFreq[1]  = 0
    AD9833_wPhase[0] = 0
    AD9833_wPhase[1] = 0
EndProc

'--------------------------------------------------------------------------------------------------
' Set the waveform to produce from the AD9833 device
' Input     : pWaveform holds the value for the type of waveform to producce:
'           :   0 = Off
'           :   1 = Sine
'           :   2 = Triangle
'           :   3 = Square1
'           :   4 = Square2
' Output    : None
' Notes     : The bits required to alter for the different waveforms are:
'             OpBitEn_Bit | Mode_Bit | Div2_Bit | Waveform
'                 0       |    0     |   X      | Sine
'                 0       |    1     |   X      | Triangle
'                 1       |    0     |   0      | Square1
'                 1       |    0     |   1      | Square2
'
Proc AD9833_SetWave(pWaveform As Byte)
    Symbol cSleepBits = ~(cAD9833_Sleep1_Mask | cAD9833_Sleep12_Mask)

    If pWaveform > 4 Then
        ExitProc
    EndIf
    AD9833_wControl = AD9833_wControl & cSleepBits                      ' Clear some bits in Control Register
'
' Manipulate bits in the Control Register for the required waveform
'
    Select pWaveform
        Case cAD9833_Off                                                ' Is the waveform to be off?
            AD9833_wControl.cAD9833_Sleep1_Bit = 1                      ' \ Yes. So manipulate the appropriate bits in AD9833_wControl
            AD9833_wControl.cAD9833_Sleep12_Bit = 1                     ' /

        Case cAD9833_Sine                                               ' Is the waveform to be Sine?
            AD9833_wControl.cAD9833_OpBitEn_Bit = 0                     ' \ Yes. So manipulate the appropriate bits in AD9833_wControl
            AD9833_wControl.cAD9833_Mode_Bit = 0                        ' /

        Case cAD9833_Triangle                                           ' Is the waveform to be Triangle?
            AD9833_wControl.cAD9833_OpBitEn_Bit = 0                     ' \
            AD9833_wControl.cAD9833_Mode_Bit = 1                        ' | Yes. So manipulate the appropriate bits in AD9833_wControl
            AD9833_wControl.cAD9833_Div2_Bit = 0                        ' /

        Case cAD9833_Square1                                            ' Is the waveform to be Square1?
            AD9833_wControl.cAD9833_OpBitEn_Bit = 1                     ' \
            AD9833_wControl.cAD9833_Mode_Bit = 0                        ' | Yes. So manipulate the appropriate bits in AD9833_wControl
            AD9833_wControl.cAD9833_Div2_Bit = 1                        ' /

        Case cAD9833_Square2                                            ' Is the waveform to be Square2?
            AD9833_wControl.cAD9833_OpBitEn_Bit = 1                     ' \
            AD9833_wControl.cAD9833_Mode_Bit = 0                        ' | Yes. So manipulate the appropriate bits in AD9833_wControl
            AD9833_wControl.cAD9833_Div2_Bit = 0                        ' /
    EndSelect
    AD9833_WriteCtrlReg(AD9833_wControl)                                ' Write AD9833_wControl to the AD9833 device's Control Register
EndProc

'--------------------------------------------------------------------------------------------------
' Set the frequency of the AD9833 device (in MHz)
' Input     : pChan holds the channel to alter the frequency for (0 or 1)
'           : pFreq holds the frequency required (in MHz)
' Output    : None
' Notes     : None
'
$define AD9833_SetFreqMHz(pChan, pFreq) AD9833_SetFreq((pFreq * 1000000), pChan)

'--------------------------------------------------------------------------------------------------
' Set the frequency of the AD9833 device (in Hz)
' Input     : pChan holds the channel to alter the frequency for (0 or 1)
'           : pFreq holds the frequency required (in Hz)
' Output    : None
' Notes     : None
'
Proc AD9833_SetFreq(pChan As AD9833_bChannel, pFreq As Dword)
Global Dim AD9833_bChannel As Byte Shared                               ' Holds the channel for procedure parameters
    Dim wLSB As Word
    Dim wMSB As Word
    Symbol cPow2_28 = (268435456.0 / cAD9833_MCLK_Freq)                 ' Calculate the frequency constant of: pow(2, 28) / cAD9833_MCLK_Freq

    If pFreq > cAD9833_Max_Freq Then
        pFreq = cAD9833_Max_Freq
    EndIf
    If pFreq.SDword < 0 Then
        pFreq = 0
    EndIf

    Select pChan
        Case 0
            wLSB = cAD9833_Freq0_Reg                                    ' Bit-15 and bit-14 hold 01
            AD9833_dFreq[0] = pFreq
        Case 1
            wLSB = cAD9833_Freq1_Reg                                    ' Bit-15 and bit-14 hold 10
            AD9833_dFreq[1] = pFreq
        Case Else
            ExitProc
    EndSelect
    wMSB = wLSB                                                         ' Copy wLSB into wMSB
    AD9833_wControl.cAD9833_B28_Bit = 1                                 ' Make sure bit-B28 is set
    AD9833_WriteCtrlReg(AD9833_wControl)
'
' Convert the frequency to a value for the AD9833 device:
'   pFreq = (pFreq * pow(2, 28) / 25MHz)) Where... (25MHz = Frequency driving the MCLK pin)
'
    pFreq = pFreq * cPow2_28                                            ' cPow2_28 holds the calculation (pow(2, 28) / crystal frequency)
'
' Write 28 bits in two sets of 16-bits
'
    wLSB = wLSB | (pFreq & %0011111111111111)
    wMSB = wMSB | ((pFreq >> 14) & %0011111111111111)
    AD9833_WriteData32(wLSB, wMSB)
EndProc

'--------------------------------------------------------------------------------------------------
' Set the channel to output the waveform from
' Input     : pChan holds the channel to output the waveform from (0 or 1)
' Output    : None
' Notes     : None
'
Proc AD9833_SetFreqChan(pChan As AD9833_bChannel)
Global Dim AD9833_bChannel As Byte Shared                               ' Holds the channel for procedure parameters

    Select pChan
        Case 0
            AD9833_wControl.cAD9833_FSelect_Bit = 0
        Case 1
            AD9833_wControl.cAD9833_FSelect_Bit = 1
        Case Else
            ExitProc
    EndSelect
    AD9833_WriteCtrlReg(AD9833_wControl)
EndProc

'--------------------------------------------------------------------------------------------------
' Set the phase for a channel
' Input     : pChan holds the channel to change (0 or 1)
'           : pPhase holds the phase value (0 to 360)
' Output    : None
' Notes     : None
'
Proc AD9833_SetPhase(pChan As AD9833_bChannel, pPhase As Word)
Global Dim AD9833_bChannel As Byte Shared                               ' Holds the channel for procedure parameters
    Dim wPh   As Word
    Dim wData As Word
    Symbol cMaxPhase = 360                                              ' The maximum phase is 360.0
    Symbol cPhase = (4095.0 / 360.0)

    Select pChan
        Case 0
            wData = %1100000000000000                                   ' Bit-15, bit-14 and bit-13 hold 110
            AD9833_wPhase[0] = pPhase
        Case 1
            wData = %1110000000000000                                   ' Bit-15, bit-14 and bit-13 hold 111
            AD9833_wPhase[1] = pPhase
        Case Else
            ExitProc
    EndSelect
    If pPhase > cMaxPhase Then
        pPhase =cMaxPhase
    EndIf
    If pPhase.SWord < 0 Then
        pPhase = 0
    EndIf
    wPh = pPhase * cPhase
    wData = wData | (wPh & %0000111111111111)
    AD9833_WriteData16(wData)
EndProc

'--------------------------------------------------------------------------------------------------
' Choose the Phase channel to use
' Input     : pChan holds the channel to use (0 or 1)
' Output    : None
' Notes     : None
'
Proc AD9833_SetPhaseChan(pChan As AD9833_bChannel)
Global Dim AD9833_bChannel As Byte Shared                               ' Holds the channel for procedure parameters

    Select pChan
        Case 0
            AD9833_wControl.cAD9833_PSelect_Bit = 0
        Case 1
            AD9833_wControl.cAD9833_PSelect_Bit = 1
        Case Else
            ExitProc
    EndSelect
    AD9833_WriteCtrlReg(AD9833_wControl)
EndProc

'--------------------------------------------------------------------------------------------------
' Write to the AD9833 device's Control Register
' Input     : pValue holds the value to write to the Control register
' Output    : None
' Notes     : None
'
Proc AD9833_WriteCtrlReg(pValue As AD9833_wParam1)
Global Dim AD9833_wParam1 As Word Shared

    AD9833_WriteData16(pValue & %0011111111111111)                      '  Bit-15 and bit-14 = 00
EndProc

'--------------------------------------------------------------------------------------------------
' Write a 16-bit value to the AD9833 device
' Input     : pData holds the 16-bit value to write
' Output    : None
' Notes     : None
'
Proc AD9833_WriteData16(pData As AD9833_wParam1)
Global Dim AD9833_wParam1 As Word Shared

    PinClear AD9833_FSync_Pin                                           ' Start the SPI interface
    SHOut AD9833_SData_Pin, AD9833_SClk_Pin, MsbFirst_H, [pData\16]
    PinSet AD9833_FSync_Pin                                             ' End the SPI interface
EndProc

'--------------------------------------------------------------------------------------------------
' Write two 16-bit values to the AD9833 device
' Input     : pLSB holds the Least Significant 16-bit value to write
'           : pMSB holds the Most Significant 16-bit value to write
' Output    : None
' Notes     : None
'
Proc AD9833_WriteData32(pLSB As AD9833_wParam1, pMSB As AD9833_wParam2)
Global Dim AD9833_wParam1 As Word Shared
Global Dim AD9833_wParam2 As Word Shared

    PinClear AD9833_FSync_Pin                                           ' Start the SPI interface
    SHOut AD9833_SData_Pin, AD9833_SClk_Pin, MsbFirst_H, [pLSB\16, pMSB\16]
    PinSet AD9833_FSync_Pin                                             ' End the SPI interface
EndProc

And below is a simple demo listing that cycles a square wave from 1KHz to 1MHz:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A demonstration of the AD9833 waveform generator chip sweeping a square wave frequency from low to high
' A square wave is produced that will sweep, smoothly, from 1KHz ro 1MHz.
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'
' Note. If simulating in Proteus, the AD9833 device's component is extremely slow when operating with sine or triangle waveforms
' to the point where they are no good because they slow down the whole program simulation an extreme amount.
'
    Device       = 18F26K40                                 ' Tell the compiler what device to compile for
    Declare Xtal = 64                                       ' Tell the compiler what frequency the device is operating at (in MHz)
    Declare Create_Coff = True                              ' Create a COF file for debugging purposes
'
' Setup USART1
'
    Declare Hserial1_Baud = 9600                            ' Set the Baud rate for USART1
    Declare HRSOut1_Pin = PORTC.6                           ' Tell the compiler what pin to use for USART1
'
' Set the pins to use for the SPI interface to the AD9833 device
'
$define AD9833_SClk_Pin  PORTC.0                            ' Connects to the AD9833 device's SCLK pin
$define AD9833_SData_Pin PORTC.1                            ' Connects to the AD9833 device's SData pin
$define AD9833_FSync_Pin PORTC.2                            ' Connects to the AD9833 device's FSync pin

    Include "AD9833.inc"                                    ' Load the AD9833 waveform generator library into the code
'
' Create any global variables for the demo here
'
    Dim dFrequency As Dword                                 ' Holds the frequency value (in Hz)

'--------------------------------------------------------------------------------------------------
' The main program starts here
' Sweep the frequency from 1KHz to 1MHz
' And also transmit the ASCII frequency value to a serial terminal
'
Main:
    Setup()                                                 ' Setup the program and peripherals
    AD9833_SetWave(cAD9833_Square1)                         ' Choose the square wave output waveform
    AD9833_SetFreqChan(cAD9833_Chan0)                       ' Use channel 0 for the output waveform

    Do                                                      ' Create a loop
        For dFrequency = 1000 To 1000000                    ' Create a loop for the frequency sweep (in Hz)
            AD9833_SetFreq(cAD9833_Chan0, dFrequency)       ' Set the frequency of channel 0 (in Hz)
            HRsoutLn Dec dFrequency, " Hz"                  ' Transmit the frequency value to a serial terminal
        Next
    Loop                                                    ' Do it forever

'--------------------------------------------------------------------------------------------------
' Setup the program and peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Oscillator_64MHz()                                      ' Setup the device to operate at 64MHz with its internal oscillator
    AD9833_Init()                                           ' Initialise the AD9833 device
EndProc

'------------------------------------------------------------------------------------------------
' Set a PIC18F26K40 device to internal 64MHz operation with an HFINTOSC_1MHZ fuse
' Input     : None
' Output    : None
' Notes     : None
'
Proc Oscillator_64MHz()
    OSCCON1 = %01100000
    OSCCON3 = %00000000
    OSCEN   = %00000000
    OSCFRQ  = %00001000                                     ' 64MHz
    OSCTUNE = %00000000
    DelayMS 100
EndProc

'------------------------------------------------------------------------------------------------
' Setup the fuses to use the internal oscillator on a PIC18F26K40 device with the OSC pins as standard I/O pins
'
Config_Start
    RSTOSC   = HFINTOSC_1MHZ            ' With HFFRQ = 4MHz and CDIV = 4:1
    FEXTOSC  = Off                      ' External Oscillator not enabled
    CLKOUTEN = Off                      ' CLKOUT function is disabled
    CSWEN    = On                       ' Writing to NOSC and NDIV is allowed
    FCMEN    = Off                      ' Fail-Safe Clock Monitor disabled
    MCLRE    = EXTMCLR                  ' VPP pin is MCLR
    PWRTE    = On                       ' Power up timer enabled
    LPBOREN  = off                      ' LPBOREN disabled
    BOREN    = On                ' Brown-out Reset enabled and controlled by software (BORCONbits_SBOREN is enabled)
    BORV     = VBOR_285                 ' Brown-out Reset Voltage set to 2.85V
    ZCD      = Off                      ' ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY  = Off                      ' PPSLOCK bit can be set and cleared repeatedly (subject to the unlock sequence)
    STVREN   = Off                      ' Stack full/underflow will not cause Reset
    Debug    = Off                      ' Background debugger disabled
    XINST    = Off                      ' Extended Instruction Set and Indexed Addressing Mode disabled
    SCANE    = Off                      ' Scanner module is Not available for use. SCANMD bit is ignored
    LVP      = Off                      ' Low Voltage programming disabled
    WDTE     = Off                      ' WDT disabled
    WDTCPS   = WDTCPS_13                ' Watchdog Divider ratio 1:x (8 seconds)
    WDTCWS   = WDTCWS_7                 ' Window always open (100%). Software control. Keyed access not required
    WDTCCS   = LFINTOSC                 ' WDT reference clock is the 31.2kHz HFINTOSC output
    WRT0     = Off                      ' Block 0 (000800-001FFFh) not write-protected
    WRT1     = Off                      ' Block 1 (002000-003FFFh) not write-protected
    WRTC     = Off                      ' Configuration registers (300000-30000Bh) not write-protected
    WRTB     = Off                      ' Boot Block (000000-0007FFh) write-protected
    WRTD     = Off                      ' Data EEPROM not write-protected
    Cp       = Off                      ' User NVM code protection disabled
    CPD      = Off                      ' Data NVM code protection disabled
    EBTR0    = Off                      ' Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
    EBTR1    = Off                      ' Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
    EBTRB    = Off                      ' Boot Block (000000-0007FFh) not protected from table reads executed in other blocks
Config_End

A proteus simulation screenshot is shown below, but be aware that the AD9833 device's component in Proteus is extremely slow to simulate. So if a sine or triangle waveform is chosen, it will slow down the simulation an extreme amount, where it is not actually possible to use it. Below is also the complete AD9833 library source code for the Positron8 compiler and some demonstrations and proteus projects. It is named: "AD9833_Library.zip"

AD9833_Waveform_Generator.jpg