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.

AD9850SPI.cpp

Committer:
Taisuke Yamada
Date:
2020-01-13
Branch:
switch-to-cmsis-bitops
Revision:
6:8c15ffb18f88
Parent:
4:c60c1caa2593

File content as of revision 6:8c15ffb18f88:

/**
 * AD9850 driver using hardware SPI.
 * 
 * - http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf
 * - http://developer.mbed.org/users/liamg/code/AD9850-function-generator-SPI-driver/
 */

#include "AD9850SPI.h"

#ifndef AD9850_CLOCK_REF
#define AD9850_CLOCK_REF 125000000U
#endif

#ifndef UINT32_MAX
#define UINT32_MAX ((uint32_t)-1)
#endif

#define UINT5_MAX 31
#define AD9850_PHASE_MAX 360

/**
 * Create AD9850SPI instance.
 *
 * SPI instance should be externally created and passed in,
 * with DATA as MOSI and W_CLK as SCLK. Note that NC can be
 * given for MISO, as AD9850 does not have any output.
 *
 * @param spi SPI interface to use
 * @param fu_ud Chip select pin
 * @param reset Reset pin
 */
AD9850SPI::AD9850SPI(SPI &spi, PinName fq_ud, PinName reset_pin)
    : _spi(spi), _fq_ud(fq_ud), _reset(reset_pin) {
    reset();
}

AD9850SPI::~AD9850SPI() {}

/**
 * Reset SPI parameter (only) to match with AD9850 requirement.
 *
 * This is useful when sharing SPI bus line with multiple chips
 * with different SPI parameter.
 */
void
AD9850SPI::reset_spi(void) {
    //
    // Configure SPI mode. AD9850 isn't actually an SPI, but can be
    // handled as SPI in following manner:
    //
    // - SCLK is low on inactive (CPOL=0)
    // - DATA is sampled on asserting edge (CPHA=0)
    // - SSEL needs to be kept low during whole 40-bit transfer
    //   - Hardware controlled SSEL tends to only support up to 16-bit transfer
    // 
    // So AD9850 can be handled as SPI with CPOL=0, CPHA=0, with some
    // nonstandard pins that needs to be controlled separately.
    // 
    // For SSEL, FQ_UD pin can be specified as GPIO-based SSEL in SPI module.
    // However, it is separately controlled in this code as semantic of FQ_UD
    // pin is quite different from usual SSEL. FQ_UD is expected to be usually
    // low, and expected to go high-low to finalize loaded value.
    //

    _spi.format(8 /* bits */, 0 /* mode */);
}

/**
 * Reset device and enable serial mode.
 * Also reconfigures SPI bus parameter for transfer.
 */
void
AD9850SPI::reset(void) {
    // initialize bus parameter first
    reset_spi();

    //
    // DS Figure 7. Master Reset Timing Sequence
    //
    // - Minimum reset width is 5 CLKIN cycles
    // - Minimum reset latency is 13 CLKIN cycles
    // - Minimum reset delay from clkin edge is 3.5ns (= ~142MHz)
    //
    // Slowest reference clock supported by AD9850 is 1MHz (1us cycle time).
    // Even in that case, ~20us would be enough for resetting.
    //

    _reset = 0; wait_us(20); // just in case
    _reset = 1; wait_us(6);
    _reset = 0; wait_us(14);

    //
    // DS Figure 10. Serial Load Enable Sequence
    //
    // - Set hardware pins to D0=1, D1=1, D2=0
    // - Send a single pulse in W_CLK, then to FU_UD
    //   - This is actually just a parallel load of D[012] values
    //
    // A dummy write to SPI followed by FU_UD update should do the trick.
    //

    _fq_ud = 0; wait_us(1);
    _spi.write(0);
    _fq_ud = 1; wait_us(1);
    _fq_ud = 0; wait_us(1);
}

/**
 * Set frequency.
 * Note frequency should be <33% of reference clock when used as a clock generator.
 *
 * @param freq      Frequency in Hz (maps to W0-W31)
 * @param powerdown Power-down bit (maps to W34)
 * @param phase     Phase in degrees (maps to W35-W39)
 */
void
AD9850SPI::setFrequency(int freq, int powerdown, int phase) {
    //
    // DS Table IV. 40-Bit Serial Load Word Function Assignment
    //
    // - 40bit data in total
    // - W0-W31: Frequency normalized to 32-bit value (LSB-first)
    // - W32-W33: MUST be 0 (DS Table II. Factory Reserved Internal Test Codes)
    // - W34: Power-down bit
    // - W35-W39: Phase normalized to 5-bit scaled value (LSB-first)
    //
    // AD9850 requires LSBit-first transfer while SPI is MSBit-first.
    // So bit order needs to be reversed before passing the data to SPI.
    //

    unsigned int scaled_freq  = __RBIT(freq * (UINT32_MAX / AD9850_CLOCK_REF));
    unsigned int scaled_phase = __RBIT(phase * (UINT5_MAX / AD9850_PHASE_MAX));

    char buffer[5];
    *(unsigned int *)buffer = __REV(scaled_freq);
    buffer[4] = 0b00111111 & (
        ((!!powerdown) << 5) | (scaled_phase >> 27)
    );

    _spi.write(buffer, sizeof(buffer), NULL, 0);
    _fq_ud = 1; wait_us(1);
    _fq_ud = 0; wait_us(1);
}