-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcedargrove_ad9833.py
executable file
·284 lines (229 loc) · 10.4 KB
/
cedargrove_ad9833.py
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
# SPDX-FileCopyrightText: Copyright (c) 2023 JG for Cedar Grove Maker Studios
#
# SPDX-License-Identifier: MIT
"""
`cedargrove_ad9833`
================================================================================
A CircuitPython driver for the AD9833 Programmable Waveform Generator.
* Author(s): JG
Implementation Notes
--------------------
**Hardware:**
* Cedar Grove Studios AD9833 Precision Waveform Generator FeatherWing
* Cedar Grove Studios AD9833 ADSR Precision Waveform Generator FeatherWing
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://circuitpython.org/downloads
"""
import digitalio
from adafruit_bus_device.spi_device import SPIDevice
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/CedarGroveStudios/CircuitPython_AD9833.git"
# pylint: disable=too-many-instance-attributes
class AD9833:
"""The driver class for the AD9833 Programmable Waveform Generator.
The AD9833 is a programmable waveform generator that produces sine, square,
and triangular waveform output from 0 MHz to 12.5MHz with 28-bit frequency
resolution. The CircuitPython class sets the waveform generator's frequency,
phase, and wave shape properties as well as providing methods for
resetting, starting, pausing, and stopping the generator."""
# pylint: disable=too-many-arguments
def __init__(
self,
wave_freq=440,
wave_phase=0,
wave_type="sine",
spi=None,
select=None,
m_clock=25000000,
):
"""Initialize SPI bus interconnect and create the SPIDevice instance.
During initialization, the generator is reset and placed in the pause
state.
:param float wave_freq: The floating point waveform frequency in Hz
ranging from 0.09 to 12.5MHz (one-half the master clock frequency).
Defaults to 440. Resolution is approximately 0.09Hz.
:param int wave_phase: The waveform phase offset in 2π Rad // 4096.
Defaults to 0.
:param str wave_type: The "sine", "triangle", or "square" wave shape.
Defaults to "sine".
:param busio.SPI spi: The `busio.SPI` definition. Defaults to `None`.
:param board select: The chip select pin designation. Defaults to `None`.
:param int m_clock: Master clock frequency in Hz. Defaults to 25MHz.
"""
self._spi = spi # Define SPI bus; 5Mhz clock frequency
self._cs = digitalio.DigitalInOut(select)
self._device = SPIDevice(
self._spi, self._cs, baudrate=5000000, polarity=1, phase=0
)
self._wave_freq = wave_freq
self._wave_phase = wave_phase
self._wave_type = wave_type
self._m_clock = m_clock # Master clock frequency
# Initiate register pointers
self._freq_reg = 0 # FREQ0
self._phase_reg = 0 # PHASE0
# Reset and pause the device
self._pause = True
self._reset = True
self._update_control_register()
# Set the master clock frequency
self._m_clock = m_clock
@property
def wave_freq(self):
"""The frequency output of the wave generator. The wave_freq value can
differ slightly from the wave generator’s output frequency due to the
internal conversion needed for loading the DAC counter register."""
return self._wave_freq
@wave_freq.setter
def wave_freq(self, new_wave_freq=440):
"""Set the wave generator output frequency.
:param float new_wave_freq: The waveform frequency in Hz. Defaults to 440."""
self._wave_freq = new_wave_freq
self._wave_freq = max(self._wave_freq, 0)
self._wave_freq = min(self._wave_freq, self._m_clock // 2)
self._update_freq_register(self._wave_freq)
@property
def raw_wave_freq(self):
"""The frequency output of the wave generator as derived from the
DAC counter register (FREQREG) value. The raw frequency is based on the
25Mhz master clock and the 28-bit DAC counter register. The raw wave
value can differ slightly from wave_freq due to the internal conversion
needed for loading the DAC counter register."""
freq_word = int(round(float(self._wave_freq * pow(2, 28)) / self._m_clock))
return freq_word * (self._m_clock / pow(2, 28))
@property
def wave_phase(self):
"""The wave generator integer output phase value."""
return self._wave_phase
@wave_phase.setter
def wave_phase(self, new_wave_phase=0):
"""Set the wave generator output phase value.
:param int new_wave_phase: The waveform phase offset. Defaults to 0."""
self._wave_phase = int(new_wave_phase)
self._wave_phase = max(self._wave_phase, 0)
self._wave_phase = min(self._wave_phase, 4095)
self._update_phase_register(self._wave_phase)
@property
def wave_type(self):
"""The wave generator waveform type value."""
return self._wave_type
@wave_type.setter
def wave_type(self, new_wave_type="sine"):
"""Set the wave generator waveform type.
:param str new_wave_type: The waveform type. Defaults to 'sine'."""
self._wave_type = new_wave_type
if self._wave_type not in ("triangle", "square", "sine"):
# Default to sine if type isn't valid
self._wave_type = "sine"
self._update_control_register()
def pause(self):
"""Pause the wave generator and freeze the output at the latest voltage
level by stopping the internal clock."""
self._pause = True # Set the pause bit
self._update_control_register()
def start(self):
"""Start the wave generator with current register contents, register
selection and wave mode setting."""
self._pause = False # Clear the clock disable bit
self._update_control_register()
def stop(self):
"""Stop the wave generator and reset the output to the midpoint
voltage level."""
self._pause = True
self._reset = True
self._update_control_register()
def reset(self):
"""Stop and reset the waveform generator. Pause the master clock.
Update all registers with default values. Set `sine` wave mode."""
self._reset = True
self._pause = True
self._freq_reg = 0
self._phase_reg = 0
self._wave_type = "sine"
self._update_control_register()
# While paused, zero the frequency and phase registers
self._update_freq_register(new_freq=0, register=0)
self._update_freq_register(new_freq=0, register=1)
self._update_phase_register(new_phase=0, register=0)
self._update_phase_register(new_phase=0, register=1)
def _send_data(self, data):
"""Send a 16-bit word through SPI bus as two 8-bit bytes.
:param int data: The 16-bit data value to write to the SPI device."""
data &= 0xFFFF
tx_msb = data >> 8
tx_lsb = data & 0xFF
with self._device:
self._spi.write(bytes([tx_msb, tx_lsb]))
def _update_control_register(self):
"""Construct the control register contents per existing local parameters
then send the new control register word to the waveform generator."""
if self._reset:
# Immediately reset before updating register
self._send_data(0x2100)
self._reset = False
# return
# Set default control register mask value (sine mode, disable reset)
control_reg = 0x2000
if self._pause:
# Disable master clock bit
control_reg |= 0x0080
control_reg |= (self._freq_reg & 0x01) << 11 # Frequency register select bit
control_reg |= (self._phase_reg & 0x01) << 10 # Phase register select bit
if self._wave_type == "triangle":
# Set triangle mode
control_reg |= 0x0002
if self._wave_type == "square":
# Set square mode
control_reg |= 0x0028
self._send_data(control_reg)
def _update_freq_register(self, new_freq, register=None):
"""Load inactive register with new frequency value then set the
register active in order to avoid partial frequency changes. Writes to
specified register if not `None`.
:param int new_freq: The new frequency value.
:param union(int, None) register: The register for the new value; FREG0
or FREG1. Selects the non-active register if register is `None`."""
self._wave_freq = new_freq
if register is None:
# Automatically toggle to use the inactive register
self._freq_reg = int(not self._freq_reg)
else:
self._freq_reg = register
freq_word = int(round(float(self._wave_freq * pow(2, 28)) / self._m_clock))
# Split frequency word into two 14-bit parts; MSB and LSB
freq_msb = (freq_word & 0xFFFC000) >> 14
freq_lsb = freq_word & 0x3FFF
if self._freq_reg == 0:
# bit-or freq register 0 select (DB15 = 0, DB14 = 1)
freq_lsb |= 0x4000
freq_msb |= 0x4000
else:
# bit-or freq register 1 select (DB15 = 1, DB14 = 0)
freq_lsb |= 0x8000
freq_msb |= 0x8000
self._send_data(freq_lsb) # Load new LSB into inactive register
self._send_data(freq_msb) # Load new MSB into inactive register
self._update_control_register()
def _update_phase_register(self, new_phase, register=None):
"""Load inactive register with new phase value then set the
register active in order to avoid partial phase changes. Writes to
specified register if not `None`.
:param int new_phase: The new phase value.
:param union(int, None) register: The register for the new value; PHASE0
or PHASE1. Selects the non-active register if register is `None`.
"""
self._wave_phase = new_phase
if register is None:
# Automatically toggle to use the inactive register
self._phase_reg = int(not self._phase_reg)
else:
self._phase_reg = register
if self._phase_reg == 0:
# bit-or phase register 0 select (DB15=1, DB14=1, DB13=0)
self._wave_phase |= 0xC000
else:
# bit-or phase register 1 select (DB15=1, DB14=1, DB13=1)
self._wave_phase |= 0xE000
self._send_data(self._wave_phase)
self._update_control_register()