ad9850/lib.rs
1#![no_std]
2//! # `ad9850` - Embedded driver for the AD9850 DDS synthesizer chip
3//!
4//! The AD9850 is a DDS Synthesizer chip sold by Analog Devices. Check the [datasheet](https://www.analog.com/media/en/technical-documentation/data-sheets/AD9850.pdf) for general information about it.
5//!
6//! This crate implements an interface for embedded devices to control such an AD9850 chip.
7//!
8//! The only dependency of this crate is that your device has 4 digital output pins which implement the [`embedded_hal::digital::v2::OutputPin`] trait.
9//!
10//! ## Usage example
11//!
12//! This example uses the [`arduino-hal`](https://github.com/Rahix/avr-hal). The `ad9850` library is not device specific though, so
13//! it should be easy to adapt the example to other devices.
14//!
15//! ```ignore
16//! #[arduino_hal::entry]
17//! fn main() -> ! {
18//! let dp = arduino_hal::Peripherals::take().unwrap();
19//! let pins = arduino_hal::pins!(dp);
20//!
21//! // Initialize the device
22//! let ad9850 = ad9850::Ad9850::new(
23//! pins.d4.into_output(), // Connect D4 (Arduino) to RESET (AD9850)
24//! pins.d5.into_output(), // Connect D5 (Arduino) to DATA (AD9850)
25//! pins.d6.into_output(), // Connect D6 (Arduino) to FQ_UD (AD9850)
26//! pins.d7.into_output(), // Connect D7 (Arduino) to W_CLK (AD9850)
27//! ).into_serial_mode().unwrap();
28//! // ^^^^ unwrap is ok here, since `set_low`/`set_high`
29//! // are infallible in the arduino-hal.
30//!
31//! // Set output frequency to 1 MHz
32//! ad9850.set_frequency(1000000.0);
33//! }
34//! ```
35//!
36//! ## Supported features
37//!
38//! - [x] Reset the device
39//! - [x] Program in Serial mode
40//! - [ ] Program in Parallel mode
41//! - [ ] Power down / wakeup
42//!
43//! ## A note about timing
44//!
45//! Communication with the Ad9850 involves sending "pulses" on the
46//! RESET, W_CLK and FQ_UD lines. According to the datasheet, these
47//! pulses must be at least $3.5ns$ long for RESET and W_CLK and at
48//! least $7ns$ for the FQ_UD line.
49//!
50//! This implementation does not insert any "delay" between toggling
51//! the pins high and low, so the pulse width depends on the CPU frequency
52//! of the device this code is run on.
53//!
54//! Example: if the MCU runs at 16 MHz, the minimum pulse width attained
55//! this way is $\frac{1}{16MHz} = 62ns$, which is way above the
56//! required width of $7ns$.
57//!
58//! If your MCU runs at a significantly higher frequency, this approach
59//! may fail and you'll have to modify the code to insert delays.
60//!
61//! Feel free to [open an issue](https://github.com/nilclass/ad9850-rs/issues/new), if you run into timing issues.
62
63use core::marker::PhantomData;
64use embedded_hal::digital::v2::OutputPin;
65
66/// Default frequency of the oscillator connected to the AD9850.
67pub const DEFAULT_OSCILLATOR_FREQUENCY: f32 = 125e6;
68
69/// Represents a connection to a AD9850 device.
70///
71/// See [crate level documentation](crate), or check the [`new`](Ad9850::new) method for an entry point.
72pub struct Ad9850<Mode, Reset, Data, FqUd, WClk> {
73 osc_freq: f32,
74 reset: Reset,
75 data: Data,
76 fq_ud: FqUd,
77 w_clk: WClk,
78 marker: PhantomData<Mode>,
79}
80
81impl<Reset, Data, FqUd, WClk, E> Ad9850<mode::Init, Reset, Data, FqUd, WClk>
82where
83 Reset: OutputPin<Error = E>,
84 Data: OutputPin<Error = E>,
85 FqUd: OutputPin<Error = E>,
86 WClk: OutputPin<Error = E>,
87{
88 /// Construct a new Ad9850 instance, in inital mode.
89 ///
90 /// This call does not communicate with the device yet. You need to call `into_serial_mode` to initiate a reset before you can send any data.
91 ///
92 /// Example:
93 /// ```ignore
94 /// let ad9850 = Ad9850::new(
95 /// pins.d4.into_output(),
96 /// pins.d5.into_output(),
97 /// pins.d6.into_output(),
98 /// pins.d7.into_output(),
99 /// ).into_serial_mode().unwrap();
100 /// ```
101 ///
102 /// The four parameters correspond to the digital pins connected to the AD9850:
103 ///
104 /// | Signal | AD9850 Pin |
105 /// |--------|------------|
106 /// | reset | 22 |
107 /// | data | 25 |
108 /// | fq_ud | 7 |
109 /// | w_clk | 8 |
110 ///
111 /// NOTE: the "data" pin is labeled "D7" in the AD9850 datasheet. It's used either as the MSB in parallel mode, or as the single data pin in serial mode.
112 /// Breakout boards usually expose this pin twice: once as D7 on the parallel interface, and again as a pin labeled DATA. For this reason, make sure
113 /// not to ground the D7 pin if you are planning to use serial mode.
114 ///
115 pub fn new(reset: Reset, data: Data, fq_ud: FqUd, w_clk: WClk) -> Self {
116 Self {
117 reset,
118 data,
119 fq_ud,
120 w_clk,
121 osc_freq: DEFAULT_OSCILLATOR_FREQUENCY,
122 marker: PhantomData,
123 }
124 }
125
126 /// Same as [`new`](Ad9850::new), but allows the oscillator frequency to be specified.
127 ///
128 /// Use this if your board's oscillator is **not** 125 MHz.
129 ///
130 /// This value is used in calculations done by [`set_frequency`](Ad9850::set_frequency) and [`set_frequency_and_phase`](Ad9850::set_frequency_and_phase).
131 pub fn new_with_osc_freq(
132 reset: Reset,
133 data: Data,
134 fq_ud: FqUd,
135 w_clk: WClk,
136 osc_freq: f32,
137 ) -> Self {
138 Self {
139 reset,
140 data,
141 fq_ud,
142 w_clk,
143 osc_freq,
144 marker: PhantomData,
145 }
146 }
147
148 /// Reset the ad9850 device into serial mode.
149 ///
150 /// This is the only supported mode at the moment.
151 ///
152 /// Returns an error, if any of the `set_low` / `set_high` calls on one of the pins fail.
153 pub fn into_serial_mode(mut self) -> Result<Ad9850<mode::Serial, Reset, Data, FqUd, WClk>, E> {
154 // RESET pulse resets the registers & mode to default
155 self.reset.set_high()?;
156 self.reset.set_low()?;
157
158 // single W_CLK pulse followed by FQ_UD pulse switches to serial input mode
159 self.w_clk.set_high()?;
160 self.w_clk.set_low()?;
161 self.fq_ud.set_high()?;
162 self.fq_ud.set_low()?;
163
164 Ok(Ad9850 {
165 reset: self.reset,
166 data: self.data,
167 fq_ud: self.fq_ud,
168 w_clk: self.w_clk,
169 osc_freq: self.osc_freq,
170 marker: PhantomData,
171 })
172 }
173}
174
175/// Utility functions
176pub mod util {
177 pub fn frequency_to_tuning_word(output_frequency: f32, oscillator_freq: f32) -> u32 {
178 (output_frequency / (oscillator_freq / 4294967296.0)) as u32
179 }
180
181 /// Turns phase into AD9850 register format.
182 ///
183 /// This function turns the given `phase` (a 5-bit unsigned integer) into
184 /// a value suitable to be passed to [`crate::Ad9850::update`] as a control
185 /// byte.
186 ///
187 /// If the given `phase` overflows 5 bits, `None` is returned.
188 ///
189 /// Example:
190 /// ```
191 /// # use ad98509::util::phase_to_control_byte;
192 /// assert_eq!(Some(0b00001000), phase_to_control_byte(1));
193 /// assert_eq!(Some(0b10000000), phase_to_control_byte(1 << 4));
194 /// assert_eq!(None, phase_to_control_byte(1 << 5));
195 /// ```
196 pub fn phase_to_control_byte(phase: u8) -> Option<u8> {
197 if phase > 0x1F {
198 None
199 } else {
200 Some(phase << 3 & 0xF8)
201 }
202 }
203}
204
205impl<Reset, Data, FqUd, WClk, E> Ad9850<mode::Serial, Reset, Data, FqUd, WClk>
206where
207 Reset: OutputPin<Error = E>,
208 Data: OutputPin<Error = E>,
209 FqUd: OutputPin<Error = E>,
210 WClk: OutputPin<Error = E>,
211{
212 /// Set output frequency to the given value (in Hz)
213 ///
214 /// Convenience function to set only the frequency, with a phase of $0$.
215 /// Computes the proper tuning word (assuming an oscillator frequency of 125MHz)
216 /// and calls `update`.
217 pub fn set_frequency(&mut self, frequency: f32) -> Result<(), E> {
218 self.update(util::frequency_to_tuning_word(frequency, self.osc_freq), 0)
219 }
220
221 /// Set output frequency and phase to given values
222 ///
223 ///
224 ///
225 ///
226 /// # Panics
227 ///
228 /// The given `phase` must not exceed 5 bits (decimal 31, 0x1F). Otherwise this function panics.
229 ///
230 pub fn set_frequency_and_phase(&mut self, frequency: f32, phase: u8) -> Result<(), E> {
231 self.update(
232 util::frequency_to_tuning_word(frequency, self.osc_freq),
233 util::phase_to_control_byte(phase).expect("invalid phase"),
234 )
235 }
236
237 /// Update device register with given data.
238 ///
239 /// This is a low-level interface. See the `set_*` methods for a high-level wrapper.
240 ///
241 /// The Ad9850 register is 40-bit wide:
242 /// - The first 32 bit are the "tuning word", which determines the frequency
243 /// - Next two bits are "control bits", which should always be zero (enforced by this function)
244 /// - This is followed by a single "power down" bit
245 /// - The remaining 5 bits determine the "phase" by which the output signal is shifted.
246 ///
247 /// To find the correct tuning word for a given frequency that you want to set, you'll need
248 /// to know the frequency of the oscillator connected to the Ad9850.
249 /// Assuming an oscillator frequency of 125 MHz (the default on most breakout boards),
250 /// then the tuning word $tw$ for a given frequency $f$ can be found with this formula:
251 ///
252 /// $ tw = \frac{f}{2^{32} * 125MHz} $
253 pub fn update(&mut self, tuning_word: u32, control_and_phase: u8) -> Result<(), E> {
254 self.shift_out((tuning_word & 0xFF) as u8)?;
255 self.shift_out((tuning_word >> 8 & 0xFF) as u8)?;
256 self.shift_out((tuning_word >> 16 & 0xFF) as u8)?;
257 self.shift_out((tuning_word >> 24 & 0xFF) as u8)?;
258 // force control bits to be zero, to avoid surprises
259 self.shift_out(control_and_phase & 0b11111100)?;
260 self.fq_ud.set_high()?;
261 self.fq_ud.set_low()?;
262 Ok(())
263 }
264
265 fn shift_out(&mut self, byte: u8) -> Result<(), E> {
266 // shift out to DATA, at rising edge of W_CLK (LSB first)
267 for i in 0..8 {
268 self.data.set_state(((byte >> i) & 1 == 1).into())?;
269 self.w_clk.set_high()?;
270 self.w_clk.set_low()?;
271 }
272 Ok(())
273 }
274}
275
276/// Marker types for different modes.
277///
278/// These types are used for the `Mode` type parameter of [`Ad9850`].
279pub mod mode {
280 /// Initial mode. No communication has happened.
281 pub struct Init;
282 /// Serial mode. Device is reset and put into the proper mode. Updates can happen.
283 pub struct Serial;
284}