A sine wave generator using AD9833 and AD9850 using STM32F103RB
This is a sine wave generator using DDS IC' AD9833 and AD9850. The STM32F1 microcontroller produces the SPI commands for the two DDS.
Learn more about STM32F1 in my blog: https://www.teachmemicro.com
Diff: AD9833.cpp
- Revision:
- 0:6069c0f2a245
- Child:
- 2:602f7589c53e
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AD9833.cpp Tue Nov 21 11:24:25 2017 +0000 @@ -0,0 +1,369 @@ +/* + * AD9833.cpp + * + * Copyright 2016 Bill Williams <wlwilliams1952@gmail.com, github/BillWilliams1952> + * + * Thanks to john@vwlowen.co.uk for his work on the AD9833. His web page + * is: http://www.vwlowen.co.uk/arduino/AD9833-waveform-generator/AD9833-waveform-generator.htm + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ +#include "mbed.h" +#include "AD9833.h" + +#define SPI_MOSI PA_7 +#define SPI_MISO PA_6 +#define SPI_SCK PA_5 +#define FNCpin PA_4 + +DigitalOut fnc_pin(FNCpin); +SPI SPI_dev(SPI_MOSI, SPI_MISO, SPI_SCK); + +/* + * Create an AD9833 object + */ +AD9833 :: AD9833 (uint32_t referenceFrequency ) { + // Pin used to enable SPI communication (active LOW) + + FNCWrite(1); + + /* TODO: The minimum resolution and max frequency are determined by + * by referenceFrequency. We should calculate these values and use + * them during setFrequency. The problem is if the user programs a + * square wave at refFrequency/2, then changes the waveform to sine. + * The sine wave will not have enough points? + */ + refFrequency = referenceFrequency; + + // Setup some defaults + DacDisabled = false; + IntClkDisabled = false; + outputEnabled = false; + waveForm0 = waveForm1 = SINE_WAVE; + frequency0 = frequency1 = 1000; // 1 KHz sine wave to start + phase0 = phase1 = 0.0; // 0 phase + activeFreq = REG0; activePhase = REG0; +} + +/* + * This MUST be the first command after declaring the AD9833 object + * Start SPI and place the AD9833 in the RESET state + */ +void AD9833 :: Begin ( void ) { + wait(0.1); + Reset(); // Hold in RESET until first WriteRegister command +} + +/* + * Setup and apply a signal. phaseInDeg defaults to 0.0 if not supplied. + * phaseReg defaults to value of freqReg if not supplied. + * Note that any previous calls to EnableOut, + * SleepMode, DisableDAC, or DisableInternalClock remain in effect. + */ +void AD9833 :: ApplySignal ( WaveformType waveType, + Registers freqReg, float frequencyInHz, + Registers phaseReg, float phaseInDeg ) { + SetGENFrequency ( freqReg, frequencyInHz ); + SetPhase ( phaseReg, phaseInDeg ); + SetWaveform ( freqReg, waveType ); + SetOutputSource ( freqReg, phaseReg ); +} + +/*********************************************************************** + Control Register +------------------------------------------------------------------------ +D15,D14(MSB) 10 = FREQ1 write, 01 = FREQ0 write, + 11 = PHASE write, 00 = control write +D13 If D15,D14 = 00, 0 = individual LSB and MSB FREQ write, + 1 = both LSB and MSB FREQ writes consecutively + If D15,D14 = 11, 0 = PHASE0 write, 1 = PHASE1 write +D12 0 = writing LSB independently + 1 = writing MSB independently +D11 0 = output FREQ0, + 1 = output FREQ1 +D10 0 = output PHASE0 + 1 = output PHASE1 +D9 Reserved. Must be 0. +D8 0 = RESET disabled + 1 = RESET enabled +D7 0 = internal clock is enabled + 1 = internal clock is disabled +D6 0 = onboard DAC is active for sine and triangle wave output, + 1 = put DAC to sleep for square wave output +D5 0 = output depends on D1 + 1 = output is a square wave +D4 Reserved. Must be 0. +D3 0 = square wave of half frequency output + 1 = square wave output +D2 Reserved. Must be 0. +D1 If D5 = 1, D1 = 0. + Otherwise 0 = sine output, 1 = triangle output +D0 Reserved. Must be 0. +***********************************************************************/ + +/* + * Hold the AD9833 in RESET state. + * Resets internal registers to 0, which corresponds to an output of + * midscale - digital output at 0. + * + * The difference between Reset() and EnableOutput(false) is that + * EnableOutput(false) keeps the AD9833 in the RESET state until you + * specifically remove the RESET state using EnableOutput(true). + * With a call to Reset(), ANY subsequent call to ANY function (other + * than Reset itself and Set/IncrementPhase) will also remove the RESET + * state. + */ +void AD9833 :: Reset ( void ) { + WriteRegister(RESET_CMD); + wait(0.015); +} + +/* + * Set the specified frequency register with the frequency (in Hz) + */ +void AD9833 :: SetGENFrequency ( Registers freqReg, float frequency ) { + // TODO: calculate max frequency based on refFrequency. + // Use the calculations for sanity checks on numbers. + // Sanity check on frequency: Square - refFrequency / 2 + // Sine/Triangle - refFrequency / 4 + if ( frequency > 12.5e6 ) // TODO: Fix this based on refFreq + frequency = 12.5e6; + if ( frequency < 0.0 ) frequency = 0.0; + + // Save frequency for use by IncrementFrequency function + if ( freqReg == REG0 ) frequency0 = frequency; + else frequency1 = frequency; + + int32_t freqWord = (frequency * pow2_28) / (float)refFrequency; + int16_t upper14 = (int16_t)((freqWord & 0xFFFC000) >> 14), + lower14 = (int16_t)(freqWord & 0x3FFF); + + // Which frequency register are we updating? + uint16_t reg = freqReg == REG0 ? FREQ0_WRITE_REG : FREQ1_WRITE_REG; + lower14 |= reg; + upper14 |= reg; + + // I do not reset the registers during write. It seems to remove + // 'glitching' on the outputs. + WriteControlRegister(); + // Control register has already been setup to accept two frequency + // writes, one for each 14 bit part of the 28 bit frequency word + WriteRegister(lower14); // Write lower 14 bits to AD9833 + WriteRegister(upper14); // Write upper 14 bits to AD9833 +} + +/* + * Increment the specified frequency register with the frequency (in Hz) + */ +void AD9833 :: IncrementFrequency ( Registers freqReg, float freqIncHz ) { + // Add/subtract a value from the current frequency programmed in + // freqReg by the amount given + float frequency = (freqReg == REG0) ? frequency0 : frequency1; + SetGENFrequency(freqReg,frequency+freqIncHz); +} + +/* + * Set the specified phase register with the phase (in degrees) + * The output signal will be phase shifted by 2π/4096 x PHASEREG + */ +void AD9833 :: SetPhase ( Registers phaseReg, float phaseInDeg ) { + // Sanity checks on input + phaseInDeg = fmod(phaseInDeg,360); + if ( phaseInDeg < 0 ) phaseInDeg += 360; + + // Phase is in float degrees ( 0.0 - 360.0 ) + // Convert to a number 0 to 4096 where 4096 = 0 by masking + uint16_t phaseVal = (uint16_t)(BITS_PER_DEG * phaseInDeg) & 0x0FFF; + phaseVal |= PHASE_WRITE_CMD; + + // Save phase for use by IncrementPhase function + if ( phaseReg == REG0 ) { + phase0 = phaseInDeg; + } + else { + phase1 = phaseInDeg; + phaseVal |= PHASE1_WRITE_REG; + } + WriteRegister(phaseVal); +} + +/* + * Increment the specified phase register by the phase (in degrees) + */ +void AD9833 :: IncrementPhase ( Registers phaseReg, float phaseIncDeg ) { + // Add/subtract a value from the current phase programmed in + // phaseReg by the amount given + float phase = (phaseReg == REG0) ? phase0 : phase1; + SetPhase(phaseReg,phase + phaseIncDeg); +} + +/* + * Set the type of waveform that is output for a frequency register + * SINE_WAVE, TRIANGLE_WAVE, SQUARE_WAVE, HALF_SQUARE_WAVE + */ +void AD9833 :: SetWaveform ( Registers waveFormReg, WaveformType waveType ) { + // TODO: Add more error checking? + if ( waveFormReg == REG0 ) + waveForm0 = waveType; + else + waveForm1 = waveType; + WriteControlRegister(); +} + +/* + * EnableOutput(false) keeps the AD9833 is RESET state until a call to + * EnableOutput(true). See the Reset function description. + */ +void AD9833 :: EnableOutput ( bool enable ) { + outputEnabled = enable; + WriteControlRegister(); +} + +/* + * Set which frequency and phase register is being used to output the + * waveform. If phaseReg is not supplied, it defaults to the same + * register as freqReg. + */ +void AD9833 :: SetOutputSource ( Registers freqReg, Registers phaseReg ) { + // TODO: Add more error checking? + activeFreq = freqReg; + if ( phaseReg == SAME_AS_REG0 ) activePhase = activeFreq; + else activePhase = phaseReg; + WriteControlRegister(); +} + +//---------- LOWER LEVEL FUNCTIONS NOT NORMALLY NEEDED ------------- + +/* + * Disable/enable both the internal clock and the DAC. Note that square + * wave outputs are avaiable if using an external Reference. + * TODO: ?? IS THIS TRUE ?? + */ +void AD9833 :: SleepMode ( bool enable ) { + DacDisabled = enable; + IntClkDisabled = enable; + WriteControlRegister(); +} + +/* + * Enables / disables the DAC. It will override any previous DAC + * setting by Waveform type, or via the SleepMode function + */ +void AD9833 :: DisableDAC ( bool enable ) { + DacDisabled = enable; + WriteControlRegister(); +} + +/* + * Enables / disables the internal clock. It will override any + * previous clock setting by the SleepMode function + */ +void AD9833 :: DisableInternalClock ( bool enable ) { + IntClkDisabled = enable; + WriteControlRegister(); +} + +// ------------ STATUS / INFORMATION FUNCTIONS ------------------- +/* + * Return actual frequency programmed + */ +float AD9833 :: GetActualProgrammedFrequency ( Registers reg ) { + float frequency = reg == REG0 ? frequency0 : frequency1; + int32_t freqWord = (uint32_t)((frequency * pow2_28) / (float)refFrequency) & 0x0FFFFFFFUL; + return (float)freqWord * (float)refFrequency / (float)pow2_28; +} + +/* + * Return actual phase programmed + */ +float AD9833 :: GetActualProgrammedPhase ( Registers reg ) { + float phase = reg == REG0 ? phase0 : phase1; + uint16_t phaseVal = (uint16_t)(BITS_PER_DEG * phase) & 0x0FFF; + return (float)phaseVal / BITS_PER_DEG; +} + +/* + * Return frequency resolution + */ +float AD9833 :: GetResolution ( void ) { + return (float)refFrequency / (float)pow2_28; +} + +// --------------------- PRIVATE FUNCTIONS -------------------------- + +/* + * Write control register. Setup register based on defined states + */ +void AD9833 :: WriteControlRegister ( void ) { + uint16_t waveForm; + // TODO: can speed things up by keeping a writeReg0 and writeReg1 + // that presets all bits during the various setup function calls + // rather than setting flags. Then we could just call WriteRegister + // directly. + if ( activeFreq == REG0 ) { + waveForm = waveForm0; + waveForm &= ~FREQ1_OUTPUT_REG; + } + else { + waveForm = waveForm1; + waveForm |= FREQ1_OUTPUT_REG; + } + if ( activePhase == REG0 ) + waveForm &= ~PHASE1_OUTPUT_REG; + else + waveForm |= PHASE1_OUTPUT_REG; + if ( outputEnabled ) + waveForm &= ~RESET_CMD; + else + waveForm |= RESET_CMD; + if ( DacDisabled ) + waveForm |= DISABLE_DAC; + else + waveForm &= ~DISABLE_DAC; + if ( IntClkDisabled ) + waveForm |= DISABLE_INT_CLK; + else + waveForm &= ~DISABLE_INT_CLK; + + WriteRegister ( waveForm ); +} + +void AD9833 :: FNCWrite(int val){ + fnc_pin = val; +} + +void AD9833 :: WriteRegister ( int16_t dat ) { + /* + * We set the mode here, because other hardware may be doing SPI also + */ + SPI_dev.format(8, 2); + + /* Improve overall switching speed + * Note, the times are for this function call, not the write. + * digitalWrite(FNCpin) ~ 17.6 usec + * digitalWriteFast2(FNC_PIN) ~ 8.8 usec + */ + FNCWrite(0); // FNCpin low to write to AD9833 + + //delayMicroseconds(2); // Some delay may be needed + + // TODO: Are we running at the highest clock rate? + SPI_dev.write(dat & 0xF0); // Transmit 16 bits 8 bits at a time + SPI_dev.write(dat & 0x0F); + + FNCWrite(1); // Write done +} \ No newline at end of file