diff --git a/src/lib.rs b/src/lib.rs
index 1c0c36df5a8559a806d3a1dfdc1846925395020f..c7c4b939a132458ff41dabe5211bc763100c603b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -22,6 +22,8 @@ use state::State;
 
 use crate::internal_radio::InternalRadio;
 
+const VCXO_FREQ: u32 = 30_000_000;
+
 /// All the rx_buf methods MUST be passed the same rx_buf
 /// All the tx_buf methods MUST be passed the same tx_buf
 /// We do this because we don't keep that data locally for space reasons
@@ -80,6 +82,63 @@ where
         self.channel = channel;
     }
 
+    /// Frequency given in Hz
+    pub fn set_frequency(&mut self, freq: u32) -> Result<(), Spi::Error> {
+        let outdiv;
+        let band;
+
+        match freq {
+            x if x < 177_000_000 => {
+                outdiv = 24;
+                band = 5;
+            }
+            x if x < 239_000_000 => {
+                outdiv = 16;
+                band = 4;
+            }
+            x if x < 353_000_000 => {
+                outdiv = 12;
+                band = 3;
+            }
+            x if x < 525_000_000 => {
+                outdiv = 8;
+                band = 2;
+            }
+            x if x < 705_000_000 => {
+                outdiv = 6;
+                band = 1;
+            }
+            _ => {
+                outdiv = 4;
+                band = 0;
+            }
+        }
+
+        let f_pfd = 2 * VCXO_FREQ / outdiv;
+        let n: u8 = (freq / f_pfd - 1).try_into().unwrap();
+        let ratio = freq as f32 / f_pfd as f32;
+        let rest = ratio - n as f32;
+        let m = (rest * 524_288.0) as u32;
+
+        // set band parameter
+        self.radio
+            .send_command::<0>(&mut [SET_PROPERTY, 0x20, 0x01, 0x51, 8 + band])?;
+
+        // set pll parameters
+        self.radio.send_command::<0>(&mut [
+            SET_PROPERTY,
+            0x40,
+            0x06,
+            0x00,
+            n,
+            ((m >> 16) & 0xFF).try_into().unwrap(),
+            ((m >> 8) & 0xFF).try_into().unwrap(),
+            (m & 0xFF).try_into().unwrap(),
+        ])?;
+
+        Ok(())
+    }
+
     pub fn is_idle(&mut self) -> bool {
         matches!(self.state, InternalState::Idle)
     }