-
Notifications
You must be signed in to change notification settings - Fork 0
/
AD9835.cpp
331 lines (299 loc) · 9.39 KB
/
AD9835.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/*!
* \mainpage Frequency Synthesis Library for Arduino
* \author Lachlan Gunn
*
* \section Introduction
* The Synthesis library is intended initially to provide an
* interface to the %AD9835 Direct Digital Synthesis (DDS) IC,
* with an eye towards eventually incorporating other methods
* of synthesis. This could mean interfaces to the rest of the
* Analog Devices DDS family, or perhaps even to SCPI-enabled
* signal generators.
*
* \section Hardware
*
* One will first require an %AD9835 IC. This is available from
* Digikey and Farnell slightly below the US/AU$15 mark, however
* this is a needlessly difficult route if one is merely experimenting.
* An alternative (taken by the author) is the breakout board sold by
* Sparkfun:
*
* - Sparkfun BOB-09169, US$32.57
* AU$34.95 (from Little Bird Electronics)
*
* This is the easiest option for experimentation, as one need
* not provide their own oscillator or power supplies, nor
* solder the TSSOP package of the AD9835. Note that this board requires
* a 6--9V supply.
*
* Having acquired this device, one must connect it to their Arduino
* board. In order to use the samples provided, one must connect:
*
* - FSYNC -> Digital 8
* - FSEL -> Digital 7
* - PSEL1 -> Digital 6
* - PSEL0 -> Digital 5
* - SCLK -> Digital 4
* - SDATA -> Digital 3
*
* This particular arrangement allows the board to be connected directly with
* the aid of 0.1" header.
*
* Having connected the hardware, one would do well to examine the examples
* provided.
*/
#include "WProgram.h"
#include "AD9835.h"
#include "SPI.h"
#define HARDWARE_SPI 0
/**
* Constructor for the AD9835 class.
*
* This constructor records the pin assignments used in preparation for their
* setup in begin().
*
* \param pinFSYNC The IO pin connected to the FSYNC pin of the AD9835.
* \param pinSCLK The IO pin connected to the SPI clock of the AD9835.
* \param pinSDATA The IO pin connected to the SPI data pin of the AD9835.
*
* \param pinFSEL The IO pin connected to the frequency select pin
* of the AD9835.
*
* \param pinPSEL1 The IO pin connected to phase select pin one of the AD9835.
* \param pinPSEL0 The IO pin connected to phase select pin zero of the AD9835.
*
* \param hzMasterClockFrequency The frequency of the
* AD9835 master clock in Hz.
*
* \see AD9835::begin
*/
AD9835::AD9835(const int pinFSYNC, const int pinSCLK, const int pinSDATA,
const int pinFSEL, const int pinPSEL1, const int pinPSEL0,
const unsigned long hzMasterClockFrequency)
{
// First we initialise our class.
this->pinSCLK = pinSCLK;
this->pinSDATA = pinSDATA;
this->pinFSYNC = pinFSYNC;
this->pinFSEL = pinFSEL;
this->pinPSEL1 = pinPSEL1;
this->pinPSEL0 = pinPSEL0;
this->hzMasterClockFrequency = hzMasterClockFrequency;
}
/**
* Initialise the AD9835.
*
* The AD9835::begin method sets pin modes and clears the state of the AD9835.
*/
void AD9835::begin()
{
// We then set up our outputs. In this case, the device is of type (0,1) with FSYNC idle-high,
// so FSYNC must start high and SCLK low.
digitalWrite(pinFSYNC, HIGH);
digitalWrite(pinFSEL, LOW);
digitalWrite(pinPSEL0, LOW);
digitalWrite(pinPSEL1, LOW);
// We also set everything as an output pin.
if (!HARDWARE_SPI)
{
pinMode(pinSCLK, OUTPUT);
pinMode(pinSDATA, OUTPUT);
}
pinMode(pinFSYNC, OUTPUT);
pinMode(pinFSEL, OUTPUT);
pinMode(pinPSEL0, OUTPUT);
pinMode(pinPSEL1, OUTPUT);
if (HARDWARE_SPI)
{
// We then set our SPI settings (
SPI.setDataMode(SPI_MODE1);
SPI.setBitOrder(MSBFIRST);
SPI.setClockDivider(SPI_CLOCK_DIV2);
SPI.begin();
}
// Sleep, reset, clear.
// Sleep - device powers down.
// Reset - phase accumulator is set to 0.
// Clear - SYNC and SELSRC registers are set to zero.
writeSPI(0xF8, 0x00);
delay(1);
// Device configuration.
// SYNC - FSEL, PSELx are sampled asynchronously.
// SELSRC - FSEL, PSELx are read from the FSEL, PSELx pins.
writeSPI(0x80, 0x00);
}
/**
* Disables the AD9835.
*/
void AD9835::end()
{
disable();
}
/**
* Enables the output of the AD9835.
*/
void AD9835::enable()
{
writeSPI(0xC0, 0x00);
}
/**
* Disables the output of the AD9835.
*/
void AD9835::disable()
{
// Set the device to sleep.
writeSPI(0xE0, 0x00);
}
/**
* Sets a frequency register of the AD9835 to some frequency code.
*
* \param frequencyRegister The register to be set (can be zero or one).
* \param fcodeFrequency The frequency code to be placed in the register.
*/
void AD9835::setFrequencyCode(byte frequencyRegister,
unsigned long fcodeFrequency)
{
word overlayRegister = (frequencyRegister & 0x01) << 2;
writeSPI(0x33 | overlayRegister, (fcodeFrequency & 0xFF000000) >> 24);
writeSPI(0x22 | overlayRegister, (fcodeFrequency & 0x00FF0000) >> 16);
writeSPI(0x31 | overlayRegister, (fcodeFrequency & 0x0000FF00) >> 8);
writeSPI(0x20 | overlayRegister, (fcodeFrequency & 0x000000FF) );
}
/**
* Wrapper for setFrequencyCode.
*
* The setFrequencyHz calculates the frequency code corresponding to the
* frequency given (in Hertz) before calling setFrequencyCode.
*
* Calculating the code is relatively slow, and so it is better to call
* calculateFrequencyCodeHz directly and cache the result where possible.
*
* \param frequencyRegister The register to be set (can be zero or one).
*
* \param HzFrequency The frequency (in Hertz) to be placed in the register.
*
* \see setFrequencyCode
* \see calculateFrequencyCodeHz
*/
void AD9835::setFrequencyHz(byte frequencyRegister, unsigned long hzFrequency)
{
setFrequencyCode(frequencyRegister, calculateFrequencyCodeHz(hzFrequency));
}
/**
* Select the frequency register to be used.
*
* The selectFrequencyRegister sets the FSEL pin to the desired value,
* thereby setting the output frequency to that set via setFrequencyCode.
*
* \see setFrequencyCode
*/
void AD9835::selectFrequencyRegister(byte frequencyRegister)
{
digitalWrite(pinFSEL, frequencyRegister & 0x01);
}
/**
* Sets a phase register of the AD9835 to some phase code.
*
* \param phaseRegister The register to be set (can be 0,1,2,3).
* \param pcodePhase The phase code to be placed in the register.
*/
void AD9835::setPhaseCode(byte phaseRegister, unsigned long pcodePhase)
{
word overlayRegister = (phaseRegister & 0x03) << 1;
writeSPI(0x19 | overlayRegister, (pcodePhase & 0x0F00) >> 8);
writeSPI(0x08 | overlayRegister, (pcodePhase & 0x00FF) );
}
/**
* Wrapper for setPhaseCode.
*
* setPhaseDeg sets a phase register of the AD9835 to an angle given in degrees.
* This is slower than setPhaseCode, so using calculatePhaseCodeDeg directly and
* caching the result is desirable.
*
* \param phaseRegister The register to be set (can be 0,1,2,3).
* \param pcodePhase The phase (in degrees) to be placed in the register.
*
* \see setPhaseCode
* \see setPhaseDeg
*/
void AD9835::setPhaseDeg(byte phaseRegister, int degPhase)
{
setPhaseCode(phaseRegister, calculatePhaseCodeDeg(degPhase));
}
/**
* Select the phase register to be activated.
*
* The selectPhaseRegister drives the PSEL0 and PSEL1 pins of the AD9835,
* thereby setting the output phase to that set via setPhaseCode.
*
* \see setPhaseCode
*/
void AD9835::selectPhaseRegister(byte phaseRegister)
{
digitalWrite(pinPSEL0, phaseRegister & 0x01);
digitalWrite(pinPSEL1, phaseRegister & 0x02);
}
/**
* Converts a frequency in Hertz to a frequency code.
*
* \param hzFrequency The frequency to be converted.
*
* \return The frequency code of the desired frequency.
*
* \todo There is an off-by-one error here as we have rounded
* the frequency code down, rather than selecting the nearest.
*/
unsigned long AD9835::calculateFrequencyCodeHz(unsigned long hzFrequency)
{
unsigned long fcodeFrequency = 0;
const int bitsChunk = 4;
for (int i = 0; i <= 32/bitsChunk; i++) {
fcodeFrequency <<= bitsChunk;
fcodeFrequency |= hzFrequency / hzMasterClockFrequency;
hzFrequency = hzFrequency % hzMasterClockFrequency;
hzFrequency <<= bitsChunk;
}
return fcodeFrequency;
}
/**
* Converts a phase in degrees to a phase code.
*
* \param degPhase The phase to be converted.
*
* \return The phase code of the desired phase.
*
* \todo There is an off-by-one error here as we have rounded
* the frequency code down, rather than selecting the nearest.
*/
unsigned long AD9835::calculatePhaseCodeDeg(unsigned long degPhase)
{
unsigned long pcodePhase = 0;
const int bitsChunk = 4;
for (int i = 0; i <= 12/bitsChunk; i++) {
unsigned long divisor = degPhase / 360;
unsigned long hzRemainder = degPhase % 360;
pcodePhase <<= bitsChunk;
pcodePhase |= divisor;
degPhase = hzRemainder;
degPhase <<= bitsChunk;
}
return pcodePhase;
}
void AD9835::writeSPI(byte msb, byte lsb)
{
digitalWrite(pinSCLK, LOW);
// Deassert FSYNC to select the chip.
digitalWrite(pinFSYNC, LOW);
if (HARDWARE_SPI)
{
SPI.transfer(msb);
SPI.transfer(lsb);
}
else
{
shiftOut(pinSDATA, pinSCLK, MSBFIRST, msb);
shiftOut(pinSDATA, pinSCLK, MSBFIRST, lsb);
}
// Reassert FSYNC now that the transfer is complete.
digitalWrite(pinFSYNC, HIGH);
}