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@6:8c15ffb18f88, 2020-01-13 (annotated)
- Committer:
- Taisuke Yamada
- Date:
- Mon Jan 13 23:20:58 2020 +0900
- Branch:
- switch-to-cmsis-bitops
- Revision:
- 6:8c15ffb18f88
- Parent:
- 4:c60c1caa2593
Minor macro update
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
Taisuke Yamada |
4:c60c1caa2593 | 1 | /** |
Taisuke Yamada |
4:c60c1caa2593 | 2 | * AD9850 driver using hardware SPI. |
Taisuke Yamada |
4:c60c1caa2593 | 3 | * |
Taisuke Yamada |
4:c60c1caa2593 | 4 | * - http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf |
Taisuke Yamada |
4:c60c1caa2593 | 5 | * - http://developer.mbed.org/users/liamg/code/AD9850-function-generator-SPI-driver/ |
Taisuke Yamada |
4:c60c1caa2593 | 6 | */ |
Taisuke Yamada |
4:c60c1caa2593 | 7 | |
Taisuke Yamada |
4:c60c1caa2593 | 8 | #include "AD9850SPI.h" |
Taisuke Yamada |
4:c60c1caa2593 | 9 | |
Taisuke Yamada |
4:c60c1caa2593 | 10 | #ifndef AD9850_CLOCK_REF |
Taisuke Yamada |
4:c60c1caa2593 | 11 | #define AD9850_CLOCK_REF 125000000U |
Taisuke Yamada |
4:c60c1caa2593 | 12 | #endif |
Taisuke Yamada |
4:c60c1caa2593 | 13 | |
Taisuke Yamada |
4:c60c1caa2593 | 14 | #ifndef UINT32_MAX |
Taisuke Yamada |
6:8c15ffb18f88 | 15 | #define UINT32_MAX ((uint32_t)-1) |
Taisuke Yamada |
4:c60c1caa2593 | 16 | #endif |
Taisuke Yamada |
4:c60c1caa2593 | 17 | |
Taisuke Yamada |
4:c60c1caa2593 | 18 | #define UINT5_MAX 31 |
Taisuke Yamada |
4:c60c1caa2593 | 19 | #define AD9850_PHASE_MAX 360 |
Taisuke Yamada |
4:c60c1caa2593 | 20 | |
Taisuke Yamada |
4:c60c1caa2593 | 21 | /** |
Taisuke Yamada |
4:c60c1caa2593 | 22 | * Create AD9850SPI instance. |
Taisuke Yamada |
4:c60c1caa2593 | 23 | * |
Taisuke Yamada |
4:c60c1caa2593 | 24 | * SPI instance should be externally created and passed in, |
Taisuke Yamada |
4:c60c1caa2593 | 25 | * with DATA as MOSI and W_CLK as SCLK. Note that NC can be |
Taisuke Yamada |
4:c60c1caa2593 | 26 | * given for MISO, as AD9850 does not have any output. |
Taisuke Yamada |
4:c60c1caa2593 | 27 | * |
Taisuke Yamada |
4:c60c1caa2593 | 28 | * @param spi SPI interface to use |
Taisuke Yamada |
4:c60c1caa2593 | 29 | * @param fu_ud Chip select pin |
Taisuke Yamada |
4:c60c1caa2593 | 30 | * @param reset Reset pin |
Taisuke Yamada |
4:c60c1caa2593 | 31 | */ |
Taisuke Yamada |
4:c60c1caa2593 | 32 | AD9850SPI::AD9850SPI(SPI &spi, PinName fq_ud, PinName reset_pin) |
Taisuke Yamada |
4:c60c1caa2593 | 33 | : _spi(spi), _fq_ud(fq_ud), _reset(reset_pin) { |
Taisuke Yamada |
4:c60c1caa2593 | 34 | reset(); |
Taisuke Yamada |
4:c60c1caa2593 | 35 | } |
Taisuke Yamada |
4:c60c1caa2593 | 36 | |
Taisuke Yamada |
4:c60c1caa2593 | 37 | AD9850SPI::~AD9850SPI() {} |
Taisuke Yamada |
4:c60c1caa2593 | 38 | |
Taisuke Yamada |
4:c60c1caa2593 | 39 | /** |
Taisuke Yamada |
4:c60c1caa2593 | 40 | * Reset SPI parameter (only) to match with AD9850 requirement. |
Taisuke Yamada |
4:c60c1caa2593 | 41 | * |
Taisuke Yamada |
4:c60c1caa2593 | 42 | * This is useful when sharing SPI bus line with multiple chips |
Taisuke Yamada |
4:c60c1caa2593 | 43 | * with different SPI parameter. |
Taisuke Yamada |
4:c60c1caa2593 | 44 | */ |
Taisuke Yamada |
4:c60c1caa2593 | 45 | void |
Taisuke Yamada |
4:c60c1caa2593 | 46 | AD9850SPI::reset_spi(void) { |
Taisuke Yamada |
4:c60c1caa2593 | 47 | // |
Taisuke Yamada |
4:c60c1caa2593 | 48 | // Configure SPI mode. AD9850 isn't actually an SPI, but can be |
Taisuke Yamada |
4:c60c1caa2593 | 49 | // handled as SPI in following manner: |
Taisuke Yamada |
4:c60c1caa2593 | 50 | // |
Taisuke Yamada |
4:c60c1caa2593 | 51 | // - SCLK is low on inactive (CPOL=0) |
Taisuke Yamada |
4:c60c1caa2593 | 52 | // - DATA is sampled on asserting edge (CPHA=0) |
Taisuke Yamada |
4:c60c1caa2593 | 53 | // - SSEL needs to be kept low during whole 40-bit transfer |
Taisuke Yamada |
4:c60c1caa2593 | 54 | // - Hardware controlled SSEL tends to only support up to 16-bit transfer |
Taisuke Yamada |
4:c60c1caa2593 | 55 | // |
Taisuke Yamada |
4:c60c1caa2593 | 56 | // So AD9850 can be handled as SPI with CPOL=0, CPHA=0, with some |
Taisuke Yamada |
4:c60c1caa2593 | 57 | // nonstandard pins that needs to be controlled separately. |
Taisuke Yamada |
4:c60c1caa2593 | 58 | // |
Taisuke Yamada |
4:c60c1caa2593 | 59 | // For SSEL, FQ_UD pin can be specified as GPIO-based SSEL in SPI module. |
Taisuke Yamada |
4:c60c1caa2593 | 60 | // However, it is separately controlled in this code as semantic of FQ_UD |
Taisuke Yamada |
4:c60c1caa2593 | 61 | // pin is quite different from usual SSEL. FQ_UD is expected to be usually |
Taisuke Yamada |
4:c60c1caa2593 | 62 | // low, and expected to go high-low to finalize loaded value. |
Taisuke Yamada |
4:c60c1caa2593 | 63 | // |
Taisuke Yamada |
4:c60c1caa2593 | 64 | |
Taisuke Yamada |
4:c60c1caa2593 | 65 | _spi.format(8 /* bits */, 0 /* mode */); |
Taisuke Yamada |
4:c60c1caa2593 | 66 | } |
Taisuke Yamada |
4:c60c1caa2593 | 67 | |
Taisuke Yamada |
4:c60c1caa2593 | 68 | /** |
Taisuke Yamada |
4:c60c1caa2593 | 69 | * Reset device and enable serial mode. |
Taisuke Yamada |
4:c60c1caa2593 | 70 | * Also reconfigures SPI bus parameter for transfer. |
Taisuke Yamada |
4:c60c1caa2593 | 71 | */ |
Taisuke Yamada |
4:c60c1caa2593 | 72 | void |
Taisuke Yamada |
4:c60c1caa2593 | 73 | AD9850SPI::reset(void) { |
Taisuke Yamada |
4:c60c1caa2593 | 74 | // initialize bus parameter first |
Taisuke Yamada |
4:c60c1caa2593 | 75 | reset_spi(); |
Taisuke Yamada |
4:c60c1caa2593 | 76 | |
Taisuke Yamada |
4:c60c1caa2593 | 77 | // |
Taisuke Yamada |
4:c60c1caa2593 | 78 | // DS Figure 7. Master Reset Timing Sequence |
Taisuke Yamada |
4:c60c1caa2593 | 79 | // |
Taisuke Yamada |
4:c60c1caa2593 | 80 | // - Minimum reset width is 5 CLKIN cycles |
Taisuke Yamada |
4:c60c1caa2593 | 81 | // - Minimum reset latency is 13 CLKIN cycles |
Taisuke Yamada |
4:c60c1caa2593 | 82 | // - Minimum reset delay from clkin edge is 3.5ns (= ~142MHz) |
Taisuke Yamada |
4:c60c1caa2593 | 83 | // |
Taisuke Yamada |
4:c60c1caa2593 | 84 | // Slowest reference clock supported by AD9850 is 1MHz (1us cycle time). |
Taisuke Yamada |
4:c60c1caa2593 | 85 | // Even in that case, ~20us would be enough for resetting. |
Taisuke Yamada |
4:c60c1caa2593 | 86 | // |
Taisuke Yamada |
4:c60c1caa2593 | 87 | |
Taisuke Yamada |
4:c60c1caa2593 | 88 | _reset = 0; wait_us(20); // just in case |
Taisuke Yamada |
4:c60c1caa2593 | 89 | _reset = 1; wait_us(6); |
Taisuke Yamada |
4:c60c1caa2593 | 90 | _reset = 0; wait_us(14); |
Taisuke Yamada |
4:c60c1caa2593 | 91 | |
Taisuke Yamada |
4:c60c1caa2593 | 92 | // |
Taisuke Yamada |
4:c60c1caa2593 | 93 | // DS Figure 10. Serial Load Enable Sequence |
Taisuke Yamada |
4:c60c1caa2593 | 94 | // |
Taisuke Yamada |
4:c60c1caa2593 | 95 | // - Set hardware pins to D0=1, D1=1, D2=0 |
Taisuke Yamada |
4:c60c1caa2593 | 96 | // - Send a single pulse in W_CLK, then to FU_UD |
Taisuke Yamada |
4:c60c1caa2593 | 97 | // - This is actually just a parallel load of D[012] values |
Taisuke Yamada |
4:c60c1caa2593 | 98 | // |
Taisuke Yamada |
4:c60c1caa2593 | 99 | // A dummy write to SPI followed by FU_UD update should do the trick. |
Taisuke Yamada |
4:c60c1caa2593 | 100 | // |
Taisuke Yamada |
4:c60c1caa2593 | 101 | |
Taisuke Yamada |
4:c60c1caa2593 | 102 | _fq_ud = 0; wait_us(1); |
Taisuke Yamada |
4:c60c1caa2593 | 103 | _spi.write(0); |
Taisuke Yamada |
4:c60c1caa2593 | 104 | _fq_ud = 1; wait_us(1); |
Taisuke Yamada |
4:c60c1caa2593 | 105 | _fq_ud = 0; wait_us(1); |
Taisuke Yamada |
4:c60c1caa2593 | 106 | } |
Taisuke Yamada |
4:c60c1caa2593 | 107 | |
Taisuke Yamada |
4:c60c1caa2593 | 108 | /** |
Taisuke Yamada |
4:c60c1caa2593 | 109 | * Set frequency. |
Taisuke Yamada |
4:c60c1caa2593 | 110 | * Note frequency should be <33% of reference clock when used as a clock generator. |
Taisuke Yamada |
4:c60c1caa2593 | 111 | * |
Taisuke Yamada |
4:c60c1caa2593 | 112 | * @param freq Frequency in Hz (maps to W0-W31) |
Taisuke Yamada |
4:c60c1caa2593 | 113 | * @param powerdown Power-down bit (maps to W34) |
Taisuke Yamada |
4:c60c1caa2593 | 114 | * @param phase Phase in degrees (maps to W35-W39) |
Taisuke Yamada |
4:c60c1caa2593 | 115 | */ |
Taisuke Yamada |
4:c60c1caa2593 | 116 | void |
Taisuke Yamada |
4:c60c1caa2593 | 117 | AD9850SPI::setFrequency(int freq, int powerdown, int phase) { |
Taisuke Yamada |
4:c60c1caa2593 | 118 | // |
Taisuke Yamada |
4:c60c1caa2593 | 119 | // DS Table IV. 40-Bit Serial Load Word Function Assignment |
Taisuke Yamada |
4:c60c1caa2593 | 120 | // |
Taisuke Yamada |
4:c60c1caa2593 | 121 | // - 40bit data in total |
Taisuke Yamada |
4:c60c1caa2593 | 122 | // - W0-W31: Frequency normalized to 32-bit value (LSB-first) |
Taisuke Yamada |
4:c60c1caa2593 | 123 | // - W32-W33: MUST be 0 (DS Table II. Factory Reserved Internal Test Codes) |
Taisuke Yamada |
4:c60c1caa2593 | 124 | // - W34: Power-down bit |
Taisuke Yamada |
4:c60c1caa2593 | 125 | // - W35-W39: Phase normalized to 5-bit scaled value (LSB-first) |
Taisuke Yamada |
4:c60c1caa2593 | 126 | // |
Taisuke Yamada |
4:c60c1caa2593 | 127 | // AD9850 requires LSBit-first transfer while SPI is MSBit-first. |
Taisuke Yamada |
4:c60c1caa2593 | 128 | // So bit order needs to be reversed before passing the data to SPI. |
Taisuke Yamada |
4:c60c1caa2593 | 129 | // |
Taisuke Yamada |
4:c60c1caa2593 | 130 | |
Taisuke Yamada |
4:c60c1caa2593 | 131 | unsigned int scaled_freq = __RBIT(freq * (UINT32_MAX / AD9850_CLOCK_REF)); |
Taisuke Yamada |
4:c60c1caa2593 | 132 | unsigned int scaled_phase = __RBIT(phase * (UINT5_MAX / AD9850_PHASE_MAX)); |
Taisuke Yamada |
4:c60c1caa2593 | 133 | |
Taisuke Yamada |
4:c60c1caa2593 | 134 | char buffer[5]; |
Taisuke Yamada |
4:c60c1caa2593 | 135 | *(unsigned int *)buffer = __REV(scaled_freq); |
Taisuke Yamada |
4:c60c1caa2593 | 136 | buffer[4] = 0b00111111 & ( |
Taisuke Yamada |
4:c60c1caa2593 | 137 | ((!!powerdown) << 5) | (scaled_phase >> 27) |
Taisuke Yamada |
4:c60c1caa2593 | 138 | ); |
Taisuke Yamada |
4:c60c1caa2593 | 139 | |
Taisuke Yamada |
4:c60c1caa2593 | 140 | _spi.write(buffer, sizeof(buffer), NULL, 0); |
Taisuke Yamada |
4:c60c1caa2593 | 141 | _fq_ud = 1; wait_us(1); |
Taisuke Yamada |
4:c60c1caa2593 | 142 | _fq_ud = 0; wait_us(1); |
Taisuke Yamada |
6:8c15ffb18f88 | 143 | } |