diff --git a/src/config.rs b/src/config.rs
index ee071a14ef11972f6c13590c1830dad03ffe580f..1ef9b9ac69c4d1446136ebed3db00ca556ee05dc 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -250,7 +250,8 @@ pub const RADIO_CONFIG_500_4: [u8; 643] = [
     0xC, 0x11, 0x40, 0x8, 0x0, 0x3A, 0xC, 0xB1, 0x7E, 0x44, 0x44, 0x20, 0xFE,
 ];
 
-// 430.5 MHz
+// 430.000 MHz @ channel 0
+// 430.500 MHZ @ channel 20 (default CATS frequency)
 // 9600 baud 2-FSK
 // Variable packet length up to 8191 bytes
 pub const RADIO_CONFIG_CATS: [u8; 644] = [
diff --git a/src/error.rs b/src/error.rs
index 5e9b9b313652540bbd209c17f3f4eba07bc2c8a9..b7e422fbc6608845cd2664cbc6495516c4bc3e24 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -23,12 +23,21 @@ pub enum TransferError<SE: Debug> {
     SpiError(SE),
 }
 
+#[derive(Debug)]
+pub enum TxError<SE: Debug> {
+    // Given slice was bigger than the maximum packet size
+    TooMuchData,
+    SpiError(SE),
+}
+
 pub(crate) trait SpiErrorToOtherError<T, E: Debug> {
     fn re(self) -> Result<T, RfError<E>>
     where
         Self: Sized;
 
     fn te(self) -> Result<T, TransferError<E>>;
+
+    fn txe(self) -> Result<T, TxError<E>>;
 }
 
 impl<T, E: Debug> SpiErrorToOtherError<T, E> for Result<T, E> {
@@ -39,4 +48,8 @@ impl<T, E: Debug> SpiErrorToOtherError<T, E> for Result<T, E> {
     fn te(self) -> Result<T, TransferError<E>> {
         self.map_err(|e| TransferError::SpiError(e))
     }
+
+    fn txe(self) -> Result<T, TxError<E>> {
+        self.map_err(|e| TxError::SpiError(e))
+    }
 }
diff --git a/src/internal_state.rs b/src/internal_state.rs
index 43dab00dfb6a93c4686fc49ae3588b3ffe5c2974..3bdb772f50b7330dd79c4ec4a0823c9342d4c71c 100644
--- a/src/internal_state.rs
+++ b/src/internal_state.rs
@@ -11,5 +11,6 @@ pub enum InternalState<const PACKET_LEN: usize> {
     Tx {
         data: [u8; PACKET_LEN],
         i: usize,
+        len: usize,
     },
 }
diff --git a/src/lib.rs b/src/lib.rs
index afcf228a6d64139e1c8bbbf6a6c399b351126d87..6fcc0faaaf3f54488de62b23400ed995cc8f22e0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@ mod consts;
 pub mod error;
 mod internal_radio;
 mod internal_state;
+pub mod rx;
 pub(crate) mod state;
 
 use core::{convert::Infallible, fmt::Debug};
@@ -14,25 +15,27 @@ use embedded_hal::{
 };
 
 use consts::*;
-use error::{RfError, SpiErrorToOtherError, TransferError};
+use error::{RfError, SpiErrorToOtherError, TransferError, TxError};
 use internal_state::InternalState;
+use rx::RxPacket;
 use state::State;
 
 use crate::internal_radio::InternalRadio;
 
-pub struct Rf4463<const PACKET_LEN: usize, Spi, SdnPin, CsPin, Delay> {
+pub struct Rf4463<const MAX_PACKET_LEN: usize, Spi, SdnPin, CsPin, Delay> {
     radio: InternalRadio<Spi, SdnPin, CsPin, Delay>,
-    state: InternalState<PACKET_LEN>,
+    state: InternalState<MAX_PACKET_LEN>,
     rx_forever: bool,
+    channel: u8,
 }
 
 impl<
-        const PACKET_LEN: usize,
+        const MAX_PACKET_LEN: usize,
         Spi: Transfer<u8>,
         SdnPin: OutputPin<Error = Infallible>,
         CsPin: OutputPin<Error = Infallible>,
         Delay: DelayUs<u16>,
-    > Rf4463<PACKET_LEN, Spi, SdnPin, CsPin, Delay>
+    > Rf4463<MAX_PACKET_LEN, Spi, SdnPin, CsPin, Delay>
 where
     Spi::Error: Debug,
 {
@@ -44,7 +47,7 @@ where
         config: &mut [u8],
     ) -> Result<Self, RfError<Spi::Error>> {
         assert!(
-            PACKET_LEN < 8192,
+            MAX_PACKET_LEN < 8192,
             "Packet length cannot be above 8191 bytes"
         );
 
@@ -54,6 +57,7 @@ where
             radio: InternalRadio::new(spi, sdn, cs, delay, config)?,
             state: InternalState::Idle,
             rx_forever: false,
+            channel: 0,
         })
     }
 
@@ -69,16 +73,23 @@ where
         self.radio.get_rssi()
     }
 
+    pub fn set_channel(&mut self, channel: u8) {
+        self.channel = channel;
+    }
+
     pub fn is_idle(&mut self) -> bool {
         matches!(self.state, InternalState::Idle)
     }
 
-    pub fn start_rx(&mut self, rx_forever: bool) -> Result<(), Spi::Error> {
+    // Len is none when using a variable-length packet
+    pub fn start_rx(&mut self, len: Option<usize>, rx_forever: bool) -> Result<(), Spi::Error> {
         self.radio.clear_fifo()?;
         self.radio.clear_ph_and_modem_interrupts()?;
 
+        let len = len.unwrap_or(0);
+
         self.state = InternalState::Rx {
-            data: [0; PACKET_LEN],
+            data: [0; MAX_PACKET_LEN],
             i: 0,
             received: false,
             rssi: None,
@@ -86,10 +97,10 @@ where
 
         self.radio.send_command::<0>(&mut [
             START_RX,
+            self.channel,
             0,
-            0,
-            (PACKET_LEN >> 8).try_into().unwrap(),
-            (PACKET_LEN & 0xff).try_into().unwrap(),
+            (len >> 8).try_into().unwrap(),
+            (len & 0xff).try_into().unwrap(),
             State::Rx.into(),
             if rx_forever {
                 State::Rx.into()
@@ -104,26 +115,26 @@ where
         Ok(())
     }
 
-    pub fn finish_rx(&mut self) -> Result<Option<([u8; PACKET_LEN], f64)>, Spi::Error> {
+    pub fn finish_rx(&mut self) -> Result<Option<RxPacket<MAX_PACKET_LEN>>, Spi::Error> {
         let pkt = match self.state {
             InternalState::Rx {
                 data,
                 received,
                 i,
                 rssi,
-            } if i == PACKET_LEN && received => {
+            } if received => {
                 let rssi = match rssi {
                     Some(x) => x,
                     None => self.get_rssi()?,
                 };
 
-                let ret = Some((data, rssi));
+                let ret = Some(RxPacket::new(data, i, rssi));
 
                 if self.rx_forever {
                     self.radio.clear_ph_and_modem_interrupts()?;
 
                     self.state = InternalState::Rx {
-                        data: [0; PACKET_LEN],
+                        data: [0; MAX_PACKET_LEN],
                         i: 0,
                         received: false,
                         rssi: None,
@@ -141,31 +152,46 @@ where
         Ok(pkt)
     }
 
-    pub fn start_tx(&mut self, mut data: [u8; PACKET_LEN]) -> Result<(), Spi::Error> {
-        self.radio.clear_fifo()?;
-        self.radio.clear_ph_and_modem_interrupts()?;
+    pub fn start_tx(&mut self, data: &[u8]) -> Result<(), TxError<Spi::Error>> {
+        let len = data.len();
+        if len > MAX_PACKET_LEN {
+            return Err(TxError::TooMuchData);
+        }
+
+        self.radio.clear_fifo().txe()?;
+        self.radio.clear_ph_and_modem_interrupts().txe()?;
 
         // 128-byte TX buffer
         let i = data.len().min(128);
+        let mut segment = [0; 128];
+        segment[..i].copy_from_slice(&data[..i]);
 
         self.radio.with_cs(|s| {
-            s.spi_transfer_byte(WRITE_TX_FIFO)?;
-            s.spi.transfer(&mut data[0..i])?;
+            s.spi_transfer_byte(WRITE_TX_FIFO).txe()?;
+            s.spi.transfer(&mut segment[..i]).txe()?;
 
             Ok(())
         })?;
 
-        self.radio.send_command::<0>(&mut [
-            START_TX,
-            0,
-            u8::from(State::Sleep) << 4,
-            (PACKET_LEN >> 8).try_into().unwrap(),
-            (PACKET_LEN & 0xff).try_into().unwrap(),
-            0,
-            0,
-        ])?;
-
-        self.state = InternalState::Tx { data, i };
+        self.radio
+            .send_command::<0>(&mut [
+                START_TX,
+                self.channel,
+                u8::from(State::Sleep) << 4,
+                (len >> 8).try_into().unwrap(),
+                (len & 0xff).try_into().unwrap(),
+                0,
+                0,
+            ])
+            .txe()?;
+
+        let mut padded_data = [0; MAX_PACKET_LEN];
+        padded_data[0..len].copy_from_slice(data);
+        self.state = InternalState::Tx {
+            data: padded_data,
+            i,
+            len,
+        };
 
         Ok(())
     }
@@ -219,12 +245,12 @@ where
             _ => return Ok(()),
         };
 
-        let len: usize = self.radio.rx_fifo_len().te()?.into();
-        if len == 0 {
+        let fifo_len: usize = self.radio.rx_fifo_len().te()?.into();
+        if fifo_len == 0 {
             return Ok(());
         }
 
-        if len + *i > PACKET_LEN {
+        if fifo_len + *i > MAX_PACKET_LEN {
             return Err(TransferError::TooMuchData);
         }
 
@@ -232,7 +258,7 @@ where
             return Err(TransferError::FifoOverflow);
         }
 
-        let data = &mut data[*i..(*i + len)];
+        let data = &mut data[*i..(*i + fifo_len)];
         self.radio
             .with_cs(|s| {
                 s.spi_transfer_byte(READ_RX_FIFO)?;
@@ -246,14 +272,14 @@ where
             *rssi = Some(self.radio.get_rssi().te()?)
         }
 
-        *i += len;
+        *i += fifo_len;
 
         Ok(())
     }
 
     fn tx_step(&mut self) -> Result<(), TransferError<Spi::Error>> {
         let (data, i) = match &mut self.state {
-            InternalState::Tx { data, i, .. } => (data, i),
+            InternalState::Tx { data, i, len } => (&mut data[0..*len], i),
             _ => return Ok(()),
         };
 
diff --git a/src/rx.rs b/src/rx.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7677257b55524951e854a5222fb8619ba57d47fc
--- /dev/null
+++ b/src/rx.rs
@@ -0,0 +1,20 @@
+#[derive(Debug, Clone)]
+pub struct RxPacket<const N: usize> {
+    data: [u8; N],
+    i: usize,
+    rssi: f64,
+}
+
+impl<const N: usize> RxPacket<N> {
+    pub(crate) fn new(data: [u8; N], i: usize, rssi: f64) -> Self {
+        Self { data, i, rssi }
+    }
+
+    pub fn data(&self) -> &[u8] {
+        &self.data[..self.i]
+    }
+
+    pub fn rssi(&self) -> f64 {
+        self.rssi
+    }
+}