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-10
- Branch:
- switch-to-cmsis-bitops
- Revision:
- 4:c60c1caa2593
- Parent:
- 1:567d552aa1a4
- Child:
- 6:8c15ffb18f88
File content as of revision 4:c60c1caa2593:
/** * 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 -1U #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); }