diff --git a/src/error.rs b/src/error.rs
index bb3b7f9418a2a2ff0470291b53caa8805a361af6..f75e3142c214317f321e55a9aa2f77fe2b779e13 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,10 +1,15 @@
 use rustmex::{message::AdHoc, mxArray, FromMatlabError, MxArray};
 
+/// Type alias for the result of the serialbridge functions
 pub type SResult<T> = std::result::Result<T, Error>;
 
+/// Utility trait to map errors to `rustmex::Error` more easily
 pub trait MapMexError<T, E> {
+    /// Map the error to an ad-hoc error with the given `id` and `msg`
     fn map_err_adhoc(self, id: &str, msg: &str) -> Result<T, rustmex::Error>;
+    /// Map the error to the given crate Error
     fn mexerr(self, err: Error) -> Result<T, rustmex::Error>;
+    /// Map the error to the given function that returns a crate Error
     fn map_mexerr<O>(self, err: O) -> Result<T, rustmex::Error>
     where
         O: FnOnce(E) -> Error;
@@ -44,6 +49,7 @@ impl<T> MapMexError<T, ()> for Option<T> {
     }
 }
 
+/// Error type for the serialbridge functions
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
     #[error("Too many parameters passed")]
@@ -89,6 +95,7 @@ pub enum Error {
 }
 
 impl Error {
+    /// Get the error id (useful for `rustmex::Error` AdHoc Message construction)
     fn id(&self) -> &str {
         match self {
             Error::TooManyParameters => "serialbridge:invalid_input",
diff --git a/src/lib.rs b/src/lib.rs
index 02185c575ee7a6acdb88a90d6706c173bf6665a4..a482570a032729d91220fb7dd6b965275525d726 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,7 +4,7 @@ mod types;
 
 use std::sync::RwLock;
 
-use rustmex::{char::CharArray, prelude::*, warning, MatlabClass};
+use rustmex::prelude::*;
 
 use error::{Error, MapMexError, SResult};
 use types::{IntoMatlabType, IntoRustType, Mode};
@@ -17,22 +17,25 @@ lazy_static::lazy_static! {
 
 /// this compiles only in debug mode
 #[cfg(debug_assertions)]
-#[macro_export]
 macro_rules! warn_debug {
     ($msg:literal, $($arg:expr),*) => {
         rustmex::warning("serialbridge:debug", format!($msg, $($arg),*));
     };
 }
 
+/// in release mode, this macro does nothing
 #[cfg(not(debug_assertions))]
 macro_rules! warn_debug {
     () => {};
 }
 
+/// This is a wrapper to the arguments passed to the MEX function
 struct Args<'a>(Rhs<'a, 'a>);
+/// This is a wrapper to the output arguments passed to the MEX function
 struct Output<'a>(Lhs<'a>);
 
 impl Args<'_> {
+    /// Asserts that the number of parameters is less than or equal to `len`
     fn assert_params_max_len(&self, len: usize) -> SResult<()> {
         if self.0.len() - 1 > len {
             Err(Error::TooManyParameters)
@@ -41,12 +44,14 @@ impl Args<'_> {
         }
     }
 
+    /// Get the `ix`-th parameter as owned MxArray, returning `missing_err` if it does not exist
     fn get(&self, ix: usize, missing_err: Error) -> SResult<MxArray> {
         Ok(self.0.get(ix).mexerr(missing_err)?.to_owned().to_owned())
     }
 }
 
 impl Output<'_> {
+    /// Set the `ix`-th parameter to `val`
     fn set<T: IntoMatlabType>(&mut self, val: T) -> SResult<()> {
         let Some(ret) = self.0.get_mut(0) else {
             return Err(Error::ReturnType);
@@ -57,8 +62,10 @@ impl Output<'_> {
     }
 }
 
+/// The entrypoint of the MEX function
 #[rustmex::entrypoint]
 fn serialbridge(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> {
+    // Create wrappers for the input and output arguments
     let args = Args(rhs);
     let out = Output(lhs);
 
@@ -78,16 +85,17 @@ fn serialbridge(lhs: Lhs, rhs: Rhs) -> rustmex::Result<()> {
     Ok(())
 }
 
+/// Get the mode from the input argument (Open, Close, Read, Write)
 fn get_mode(arg: MxArray) -> SResult<Mode> {
-    CharArray::from_mx_array(arg)?
-        .get_cstring()
-        .into_string()?
-        .parse()
+    IntoRustType::<String>::into_rust(arg)?.parse()
 }
 
+/// Open the serial port specified by the first argument with the baudrate
+/// specified by the second argument
 fn open_serial(args: Args<'_>) -> SResult<()> {
     args.assert_params_max_len(2)?;
 
+    // Get the port name and baudrate from the input arguments
     let port: String = args
         .get(1, Error::MissingPortName)?
         .into_rust()
@@ -114,6 +122,7 @@ fn open_serial(args: Args<'_>) -> SResult<()> {
     Ok(())
 }
 
+/// Close the serial port if it is open (didn't expect any arguments)
 fn close_serial(args: Args<'_>) -> SResult<()> {
     args.assert_params_max_len(0)?;
 
@@ -121,9 +130,11 @@ fn close_serial(args: Args<'_>) -> SResult<()> {
     Ok(())
 }
 
+/// Read `n` doubles from the serial port and return them as a vector
 fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> {
     args.assert_params_max_len(1)?;
 
+    // Get the number of doubles to read from the input argument
     let arg: f64 = args
         .get(1, Error::MissingReadAmount)?
         .into_rust()
@@ -139,6 +150,7 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> {
 
     // Read n_doubles from the serial port
     let bytes = SERIAL.read().unwrap().read_n_bytes(n_doubles)?;
+    // Here we use unsafe to cast the bytes to a slice of f64 (maybe a safer alternative helps)
     let doubles = unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const f64, n_doubles) };
     warn_debug!("Read {} bytes from serial port", n_doubles);
 
@@ -146,14 +158,17 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> {
     Ok(())
 }
 
+/// Write the doubles from the input argument to the serial port as stream of bytes
 fn write_to_serial(args: Args<'_>) -> SResult<()> {
     args.assert_params_max_len(1)?;
 
+    // Get the doubles to write from the input argument
     let data: Vec<f64> = args
         .get(1, Error::MissingWriteData)?
         .into_rust()
         .map_mexerr(|e| Error::InvalidWriteData(Box::new(e)))?;
 
+    // Convert the doubles to a stream of bytes and write them to the serial port
     let data: Vec<u8> = data.iter().flat_map(|&x| x.to_be_bytes()).collect();
     SERIAL.read().unwrap().write_bytes(&data)?;
     warn_debug!("Wrote {} bytes to serial port", data.len());
diff --git a/src/serial.rs b/src/serial.rs
index 3365897a2c26e118f75d8960ee1c8afbe9d64e78..38826e7e2edd34639c2bcd26f533a1ed5aa04d16 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -2,11 +2,9 @@ use std::sync::Mutex;
 
 use serialport::SerialPort;
 
-use crate::{
-    error::{Error, SResult},
-    warn_debug,
-};
+use crate::error::{Error, SResult};
 
+/// Handler of the serial port
 pub struct SerialManager {
     serial: Option<Mutex<Box<dyn SerialPort>>>,
 }
@@ -16,17 +14,24 @@ impl SerialManager {
         Self { serial: None }
     }
 
+    /// Open the serial port with the given `port` device path and `baudrate`
     pub fn open(&mut self, port: &str, baudrate: u32) -> SResult<()> {
         let port = serialport::new(port, baudrate).open()?;
         self.serial.replace(Mutex::new(port));
         Ok(())
     }
 
+    /// Close the serial port if it is open
     pub fn close(&mut self) -> SResult<()> {
-        self.serial.take();
-        Ok(())
+        if self.serial.is_some() {
+            self.serial.take();
+            Ok(())
+        } else {
+            Err(Error::SerialNotOpen)
+        }
     }
 
+    /// Read `n` bytes from the serial port
     pub fn read_n_bytes(&self, n: usize) -> SResult<Vec<u8>> {
         let mut port = self
             .serial
@@ -39,6 +44,7 @@ impl SerialManager {
         Ok(buf)
     }
 
+    /// Write `data` to the serial port
     pub fn write_bytes(&self, data: &[u8]) -> SResult<()> {
         let mut port = self
             .serial
diff --git a/src/types.rs b/src/types.rs
index 7060416c2e8c7ff1b0a70ee1470818ee0a8c6543..eb9173b92fb182e810374f805b514d241c803926 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -8,6 +8,7 @@ use rustmex::{
 
 use crate::error::{Error, MapMexError, SResult};
 
+/// Trait to convert a Matlab type into a Rust type
 pub trait IntoRustType<T> {
     fn into_rust(self) -> SResult<T>;
 }
@@ -53,6 +54,7 @@ impl IntoRustType<Vec<f64>> for MxArray {
     }
 }
 
+/// Trait to convert a Rust type into a Matlab type
 pub trait IntoMatlabType {
     fn into_matlab(self) -> SResult<MxArray>;
 }
@@ -67,6 +69,7 @@ impl IntoMatlabType for Vec<f64> {
     }
 }
 
+/// Functions available to the user
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Mode {
     Open,