Fast SPI-based serial interface to AD9850 clock generator. Has same interface as (bit-banging based) AD9850 library, but more than x100 faster in frequency update speed. Can sweep 1KHz to 30MHz in 1KHz step in a few seconds.

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers AD9850SPI.cpp Source File

AD9850SPI.cpp

00001 /**
00002  * AD9850 driver using hardware SPI.
00003  * 
00004  * - http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf
00005  * - http://developer.mbed.org/users/liamg/code/AD9850-function-generator-SPI-driver/
00006  */
00007 
00008 #include "AD9850SPI.h"
00009 
00010 #ifndef AD9850_CLOCK_REF
00011 #define AD9850_CLOCK_REF 125000000U
00012 #endif
00013 
00014 #ifndef UINT32_MAX
00015 #define UINT32_MAX ((uint32_t)-1)
00016 #endif
00017 
00018 #define UINT5_MAX 31
00019 #define AD9850_PHASE_MAX 360
00020 
00021 /**
00022  * Create AD9850SPI instance.
00023  *
00024  * SPI instance should be externally created and passed in,
00025  * with DATA as MOSI and W_CLK as SCLK. Note that NC can be
00026  * given for MISO, as AD9850 does not have any output.
00027  *
00028  * @param spi SPI interface to use
00029  * @param fu_ud Chip select pin
00030  * @param reset Reset pin
00031  */
00032 AD9850SPI::AD9850SPI(SPI &spi, PinName fq_ud, PinName reset_pin)
00033     : _spi(spi), _fq_ud(fq_ud), _reset(reset_pin) {
00034     reset();
00035 }
00036 
00037 AD9850SPI::~AD9850SPI() {}
00038 
00039 /**
00040  * Reset SPI parameter (only) to match with AD9850 requirement.
00041  *
00042  * This is useful when sharing SPI bus line with multiple chips
00043  * with different SPI parameter.
00044  */
00045 void
00046 AD9850SPI::reset_spi(void) {
00047     //
00048     // Configure SPI mode. AD9850 isn't actually an SPI, but can be
00049     // handled as SPI in following manner:
00050     //
00051     // - SCLK is low on inactive (CPOL=0)
00052     // - DATA is sampled on asserting edge (CPHA=0)
00053     // - SSEL needs to be kept low during whole 40-bit transfer
00054     //   - Hardware controlled SSEL tends to only support up to 16-bit transfer
00055     // 
00056     // So AD9850 can be handled as SPI with CPOL=0, CPHA=0, with some
00057     // nonstandard pins that needs to be controlled separately.
00058     // 
00059     // For SSEL, FQ_UD pin can be specified as GPIO-based SSEL in SPI module.
00060     // However, it is separately controlled in this code as semantic of FQ_UD
00061     // pin is quite different from usual SSEL. FQ_UD is expected to be usually
00062     // low, and expected to go high-low to finalize loaded value.
00063     //
00064 
00065     _spi.format(8 /* bits */, 0 /* mode */);
00066 }
00067 
00068 /**
00069  * Reset device and enable serial mode.
00070  * Also reconfigures SPI bus parameter for transfer.
00071  */
00072 void
00073 AD9850SPI::reset(void) {
00074     // initialize bus parameter first
00075     reset_spi();
00076 
00077     //
00078     // DS Figure 7. Master Reset Timing Sequence
00079     //
00080     // - Minimum reset width is 5 CLKIN cycles
00081     // - Minimum reset latency is 13 CLKIN cycles
00082     // - Minimum reset delay from clkin edge is 3.5ns (= ~142MHz)
00083     //
00084     // Slowest reference clock supported by AD9850 is 1MHz (1us cycle time).
00085     // Even in that case, ~20us would be enough for resetting.
00086     //
00087 
00088     _reset = 0; wait_us(20); // just in case
00089     _reset = 1; wait_us(6);
00090     _reset = 0; wait_us(14);
00091 
00092     //
00093     // DS Figure 10. Serial Load Enable Sequence
00094     //
00095     // - Set hardware pins to D0=1, D1=1, D2=0
00096     // - Send a single pulse in W_CLK, then to FU_UD
00097     //   - This is actually just a parallel load of D[012] values
00098     //
00099     // A dummy write to SPI followed by FU_UD update should do the trick.
00100     //
00101 
00102     _fq_ud = 0; wait_us(1);
00103     _spi.write(0);
00104     _fq_ud = 1; wait_us(1);
00105     _fq_ud = 0; wait_us(1);
00106 }
00107 
00108 /**
00109  * Set frequency.
00110  * Note frequency should be <33% of reference clock when used as a clock generator.
00111  *
00112  * @param freq      Frequency in Hz (maps to W0-W31)
00113  * @param powerdown Power-down bit (maps to W34)
00114  * @param phase     Phase in degrees (maps to W35-W39)
00115  */
00116 void
00117 AD9850SPI::setFrequency(int freq, int powerdown, int phase) {
00118     //
00119     // DS Table IV. 40-Bit Serial Load Word Function Assignment
00120     //
00121     // - 40bit data in total
00122     // - W0-W31: Frequency normalized to 32-bit value (LSB-first)
00123     // - W32-W33: MUST be 0 (DS Table II. Factory Reserved Internal Test Codes)
00124     // - W34: Power-down bit
00125     // - W35-W39: Phase normalized to 5-bit scaled value (LSB-first)
00126     //
00127     // AD9850 requires LSBit-first transfer while SPI is MSBit-first.
00128     // So bit order needs to be reversed before passing the data to SPI.
00129     //
00130 
00131     unsigned int scaled_freq  = __RBIT(freq * (UINT32_MAX / AD9850_CLOCK_REF));
00132     unsigned int scaled_phase = __RBIT(phase * (UINT5_MAX / AD9850_PHASE_MAX));
00133 
00134     char buffer[5];
00135     *(unsigned int *)buffer = __REV(scaled_freq);
00136     buffer[4] = 0b00111111 & (
00137         ((!!powerdown) << 5) | (scaled_phase >> 27)
00138     );
00139 
00140     _spi.write(buffer, sizeof(buffer), NULL, 0);
00141     _fq_ud = 1; wait_us(1);
00142     _fq_ud = 0; wait_us(1);
00143 }