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.

Branch:
switch-to-cmsis-bitops
Revision:
4:c60c1caa2593
Parent:
1:567d552aa1a4
Child:
6:8c15ffb18f88
--- a/AD9850SPI.cpp	Thu Jan 09 17:30:19 2020 +0000
+++ b/AD9850SPI.cpp	Fri Jan 10 10:24:30 2020 +0900
@@ -1,170 +1,143 @@
-/**
- * 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
-
-#if _BYTE_ORDER == _BIG_ENDIAN
-#define htole32(v) __bswap32(v)
-#define htobe32(v) (v)
-#else
-#define htole32(v) (v)
-#define htobe32(v) __bswap32(v)
-#endif
-
-/**
- * Reverse bit order (not endian byte order, but bit order of whole 32bits).
- * See:
- * - http://aggregate.org/MAGIC/#Bit%20Reversal
- * - https://github.com/hcs0/Hackers-Delight/blob/master/reverse.c.txt
- */
-static inline unsigned int
-reverse(unsigned int x) {
-    x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
-    x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
-    x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
-    x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
-    return ((x >> 16) | (x << 16));
-}
-
-/**
- * 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  = reverse(freq * (UINT32_MAX / AD9850_CLOCK_REF));
-    unsigned int scaled_phase = reverse(phase * (UINT5_MAX / AD9850_PHASE_MAX));
-
-    char buffer[5];
-#if _BYTE_ORDER == _BIG_ENDIAN
-    *(unsigned int *)buffer = htole32(scaled_freq);
-#else
-    *(unsigned int *)buffer = htobe32(scaled_freq);
-#endif
-    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);
+/**
+ * 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);
 }
\ No newline at end of file