| Crates.io | adf435x |
| lib.rs | adf435x |
| version | 0.1.1 |
| created_at | 2025-12-15 02:50:19.889945+00 |
| updated_at | 2025-12-15 04:45:56.950855+00 |
| description | Type-safe Rust driver for ADF435x wideband RF PLL synthesizers using device-driver crate |
| homepage | |
| repository | https://github.com/leftger/adf435x |
| max_upload_size | |
| id | 1985437 |
| size | 812,742 |
Rust driver for the ADF435X series of Wideband RF PLL Synthesizers using the device-driver crate for compile-time code generation from YAML register definitions.
no_std environments (with optional std support)RegisterInterface traitmanifests/adf435x.yaml for easy customizationThis driver supports the ADF435x family of wideband RF PLL synthesizers:
This driver assumes CE is always HIGH (device always powered). The CE pin should be tied high or controlled externally. If you need to power down the device, use the power-down bit in register R2.
When LE goes high, the data stored in the 32-bit shift register is loaded into the register selected by the 3 control bits (bits [2:0] of the SPI word). The LE pin must be pulsed after each SPI write.
This crate provides two usage patterns:
Device type and pulse LE yourselfAdf435xDriver which handles LE pulsing automaticallyUse Adf435xDriver for automatic LE pulsing with proper timing:
use adf435x::{Adf435xDriver, RegisterInterface, OutputPin, DelayUs};
use core::convert::Infallible;
// Your SPI implementation
struct SpiInterface {
// Your SPI peripheral handle
}
impl RegisterInterface for SpiInterface {
type Error = Infallible;
type AddressType = u8;
fn write_register(
&mut self,
address: Self::AddressType,
size_bits: u32,
data: &[u8],
) -> Result<(), Self::Error> {
// Write 32-bit register value to ADF435x over SPI
// The address is encoded in bits [2:0] of the 32-bit word
Ok(())
}
fn read_register(
&mut self,
address: Self::AddressType,
size_bits: u32,
data: &mut [u8],
) -> Result<(), Self::Error> {
// Read 32-bit register from ADF435x
Ok(())
}
}
// Your GPIO pin implementation
struct LePin;
impl OutputPin for LePin {
type Error = Infallible;
fn set_high(&mut self) -> Result<(), Self::Error> {
// Set LE pin high
Ok(())
}
fn set_low(&mut self) -> Result<(), Self::Error> {
// Set LE pin low
Ok(())
}
}
// Your delay implementation
struct Delay;
impl DelayUs for Delay {
fn delay_us(&mut self, us: u32) {
// Delay for specified microseconds
}
}
// Create the driver
let spi = SpiInterface { /* ... */ };
let le_pin = LePin;
let mut delay = Delay;
let mut driver = Adf435xDriver::new(spi, le_pin);
// Configure registers using type-safe API
driver.device_mut().r_0_frequency_control()
.write(|reg| {
reg.set_integer_value(96);
reg.set_fractional_value(0);
})
.unwrap();
// Latch with automatic LE pulsing (5µs → LE high → 10µs → LE low → 5µs)
driver.latch(&mut delay).unwrap();
Use the raw Device type when you need full control:
use adf435x::{new_device, RegisterInterface};
use core::convert::Infallible;
struct SpiInterface;
impl RegisterInterface for SpiInterface {
type Error = Infallible;
type AddressType = u8;
fn write_register(&mut self, _: u8, _: u32, _: &[u8]) -> Result<(), Self::Error> { Ok(()) }
fn read_register(&mut self, _: u8, _: u32, _: &mut [u8]) -> Result<(), Self::Error> { Ok(()) }
}
let mut device = new_device(SpiInterface);
// Write register
device.r_0_frequency_control()
.write(|reg| {
reg.set_integer_value(96);
reg.set_fractional_value(0);
})
.unwrap();
// YOU must pulse LE manually:
// le_pin.set_high();
// delay.delay_us(10);
// le_pin.set_low();
The device-driver crate generates type-safe methods for all registers and fields:
// Write to frequency control register (R0)
driver.device_mut().r_0_frequency_control()
.write(|reg| {
reg.set_integer_value(100); // INT value
reg.set_fractional_value(512); // FRAC value
})
.unwrap();
driver.latch(&mut delay).unwrap();
// Read and modify registers
driver.device_mut().r_2_reference_and_charge_pump()
.modify(|reg| {
reg.set_reference_counter(10);
reg.set_charge_pump_current(5);
reg.set_ref_doubler(false);
reg.set_ref_divide_by_2(false);
})
.unwrap();
driver.latch(&mut delay).unwrap();
// Read current register values
let r0 = driver.device_mut().r_0_frequency_control().read().unwrap();
println!("INT: {}, FRAC: {}", r0.integer_value(), r0.fractional_value());
The ADF435x has six 32-bit registers (R0-R5) defined in manifests/adf435x.yaml:
R0 - Frequency Control
R1 - Phase and Modulus
R2 - Reference and Charge Pump
R3 - Function Control
R4 - Output Stage
R5 - Lock Detect and Readback
The output frequency is calculated as:
f_OUT = f_PFD × (INT + FRAC/MOD) × output_divider
Where:
f_PFD = Phase Frequency Detector frequencyf_PFD = f_REF × (1 + D) / (R × (1 + T))f_REF = Reference clock frequencyD = Reference doubler (0 or 1)R = Reference counter valueT = Reference divide-by-2 (0 or 1)let mut driver = Adf435xDriver::new(spi, le_pin);
let mut delay = Delay;
// Step 1: Configure reference path (R2)
driver.device_mut().r_2_reference_and_charge_pump()
.write(|reg| {
reg.set_reference_counter(1); // R = 1
reg.set_ref_doubler(false); // D = 0
reg.set_ref_divide_by_2(false); // T = 0
reg.set_charge_pump_current(7); // Mid-range
reg.set_power_down(false); // Ensure powered up
})
.unwrap();
driver.latch(&mut delay).unwrap();
// Step 2: Set modulus for resolution (R1)
// f_PFD = 25 MHz × (1 + 0) / (1 × (1 + 0)) = 25 MHz
driver.device_mut().r_1_phase_and_modulus()
.write(|reg| {
reg.set_modulus(4095); // Maximum resolution
reg.set_prescaler_89(false); // Use 4/5 prescaler for < 3.6 GHz
})
.unwrap();
driver.latch(&mut delay).unwrap();
// Step 3: Set frequency (R0)
// For 2.4 GHz: INT = 96, FRAC = 0
// f_OUT = 25 MHz × (96 + 0/4095) = 2.4 GHz
driver.device_mut().r_0_frequency_control()
.write(|reg| {
reg.set_integer_value(96);
reg.set_fractional_value(0);
})
.unwrap();
driver.latch(&mut delay).unwrap();
Typical initialization sequence from datasheet:
let mut driver = Adf435xDriver::new(spi, le_pin);
let mut delay = Delay;
// Write registers in reverse order (R5 → R4 → R3 → R2 → R1 → R0)
// Each write automatically includes the register address in bits [2:0]
// 1. R5: Lock detect configuration
driver.device_mut().r_5_latch_and_status()
.write(|reg| {
// Configure lock detect settings via payload
})
.unwrap();
driver.latch(&mut delay).unwrap();
// 2. R4: Output stage
driver.device_mut().r_4_output_stage()
.write(|reg| {
// Configure output power, RF divider via payload
})
.unwrap();
driver.latch(&mut delay).unwrap();
// 3. R3: Function control
driver.device_mut().r_3_function_control()
.write(|reg| {
// Configure clock divider via payload
})
.unwrap();
driver.latch(&mut delay).unwrap();
// 4. R2: Reference path
driver.device_mut().r_2_reference_and_charge_pump()
.write(|reg| {
reg.set_reference_counter(1);
reg.set_charge_pump_current(7);
reg.set_power_down(false);
})
.unwrap();
driver.latch(&mut delay).unwrap();
// 5. R1: Modulus
driver.device_mut().r_1_phase_and_modulus()
.write(|reg| {
reg.set_modulus(4095);
})
.unwrap();
driver.latch(&mut delay).unwrap();
// 6. R0: Frequency
driver.device_mut().r_0_frequency_control()
.write(|reg| {
reg.set_integer_value(96);
reg.set_fractional_value(0);
})
.unwrap();
driver.latch(&mut delay).unwrap();
This driver is platform-agnostic. For embedded systems, implement the required traits using your platform's HAL:
use embedded_hal::spi::SpiDevice;
use embedded_hal::digital::OutputPin as EmbeddedOutputPin;
use embedded_hal::delay::DelayUs as EmbeddedDelayUs;
// Implement adf435x::OutputPin for your GPIO pin type
impl<T: EmbeddedOutputPin> adf435x::OutputPin for T {
type Error = T::Error;
fn set_high(&mut self) -> Result<(), Self::Error> {
EmbeddedOutputPin::set_high(self)
}
fn set_low(&mut self) -> Result<(), Self::Error> {
EmbeddedOutputPin::set_low(self)
}
}
// Use with your platform's types
let spi = /* your SPI peripheral */;
let le_pin = /* your GPIO pin */;
let mut delay = /* your delay provider */;
let mut driver = adf435x::Adf435xDriver::new(spi, le_pin);
// Use driver...
The register definitions are generated at compile time from manifests/adf435x.yaml. To customize the driver:
manifests/adf435x.yaml to add/modify register definitionsThe crate includes two comprehensive examples demonstrating different usage patterns:
examples/basic_usage.rs)Demonstrates Adf435xDriver - the simple driver for when CE is always high:
cargo run --example basic_usage
examples/full_featured.rs)Demonstrates Adf435xDriverWithCE - the extended driver with CE control:
Fpfd and FracNcargo run --example full_featured
Both examples use mock hardware interfaces and compile without requiring actual hardware.
Licensed under either of:
at your option.