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}