diff --git a/Cargo.lock b/Cargo.lock
index 56a11438f5606a7d94fd6e971f5983790db6a4b5..4c3c0a79741afd2cf1b41a577a32b46c5033d042 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,18 @@
 # It is not intended for manual editing.
 version = 3
 
+[[package]]
+name = "ahash"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "1.1.2"
@@ -11,6 +23,12 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "allocator-api2"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -41,6 +59,16 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
 
+[[package]]
+name = "hashbrown"
+version = "0.14.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
+
 [[package]]
 name = "io-kit-sys"
 version = "0.4.0"
@@ -175,6 +203,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
 [[package]]
 name = "parking_lot"
 version = "0.12.1"
@@ -338,6 +372,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 name = "serial-bridge"
 version = "0.1.1"
 dependencies = [
+ "hashbrown",
  "lazy_static",
  "parking_lot",
  "rustmex",
@@ -427,6 +462,12 @@ version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -505,3 +546,23 @@ name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
diff --git a/Cargo.toml b/Cargo.toml
index f4c7ea97da13efa1d847524a40e57b97d79ad30f..210be2b8d8c303dd1836e08cc95a8f4df394fbf3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ license = "MIT"
 crate-type = ["cdylib"]
 
 [dependencies]
+hashbrown = "0.14.3"
 lazy_static = "1.4.0"
 parking_lot = "0.12.1"
 serialport = "4.3.0"
diff --git a/README.md b/README.md
index 29a42003bcdf7c963fb1a3a21fb9373cd5c787a4..b3b0bb07b54105a8762d9bc4ec0cb7131571268b 100644
--- a/README.md
+++ b/README.md
@@ -33,22 +33,22 @@ serialbridge can be used in matlab in the following way:
   - **"Open"**: specifies we want to open the serial port
   - string_serialPort: is the serial port we want to open (eg: "COM6")
   - uint_baudrate: is the baudrate of the port (eg: 256000)
-- `serialbridge("Write", singleArray_Data)`:
+- `serialbridge("Write", string_serialPort, singleArray_Data)`:
   - **"Write"**: specifies that we want to write to the serial port
   - singleArray_Data: is the array of singles we want to write on serial (eg: [1 2 3 4.5 5.4])
-- `singleArray_Data = serialbridge("Read", uint_nData)`;
+- `singleArray_Data = serialbridge("Read", string_serialPort, uint_nData)`;
   - **"Read"**: specifies that we want to read from the serial port
   - uint_nData: How many floats to read from serial (eg: 1)
   - singleArray_Data: array of floats read from the serial (eg: actuatorData)
-- `serialbridge("Close", string_serialPort, uint_baudrate)`:
+- `serialbridge("Close", string_serialPort)`:
   - **"Close"**: specifies we want to close the serial port
 
 **Example** in Matlab:
-```
-serialbridge("Open", "COM6", 256000); % Opens the serial port
-serialbridge("Write", [1 2 3 4]);     % Sends the array "[1 2 3 4]" to the serial device
-data = serialbridge("Read", 2);       % Receives 2 floats and stores them in the variable "data"
-serialbridge("Close");                % Closes the serial port
+```matlab
+serialbridge("Open", "dev/COM6", 256000);     % Opens the serial port
+serialbridge("Write", "dev/COM6", [1 2 3 4]); % Sends the array "[1 2 3 4]" to the serial device
+data = serialbridge("Read", "dev/COM6", 2);   % Receives 2 floats and stores them in the variable "data"
+serialbridge("Close", "dev/COM6");            % Closes the serial port
 ```
 
 Procedure in order to use the **MatlabTransceiver** module: https://git.skywarder.eu/scs/hermes/r2a-obsw/-/blob/Serial4Simulations-dev/src/tests/hardware_in_the_loop/README.md
diff --git a/src/lib.rs b/src/lib.rs
index 218efaaeee664e699a7a728b7140160953f2ce79..dc4ab4f4847213231d1e94d9640cb305b0cf649e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -85,21 +85,11 @@ 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> {
-    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()
-        .map_mexerr(|e| Error::InvalidPortName(Box::new(e)))?;
+    let port = get_port_name(1, &args)?;
     // Matlab defaults to f64 when inserting numbers, to improve UX we take a
     // f64 and cast to a u32
     let arg2: f64 = args
@@ -124,19 +114,20 @@ fn open_serial(args: Args<'_>) -> SResult<()> {
 
 /// 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)?;
+    args.assert_params_max_len(1)?;
+    let port = get_port_name(1, &args)?;
 
-    SERIAL.write().unwrap().close()?;
+    SERIAL.write().unwrap().close(&port)?;
     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)?;
-
+    args.assert_params_max_len(2)?;
+    let port = get_port_name(1, &args)?;
     // Get the number of doubles to read from the input argument
     let arg: f32 = args
-        .get(1, Error::MissingReadAmount)?
+        .get(2, Error::MissingReadAmount)?
         .into_rust()
         .map_mexerr(|e| Error::InvalidReadAmount(Box::new(e)))?;
     // Check for arg to resemble a unsigned integer (this type mismatch should be
@@ -149,7 +140,7 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> {
     let n_doubles = arg as usize;
 
     // Read n_doubles from the serial port
-    let bytes = SERIAL.read().unwrap().read_n_bytes(n_doubles * 8)?;
+    let bytes = SERIAL.read().unwrap().read_n_bytes(&port, n_doubles * 8)?;
     let doubles = bytes
         .chunks_exact(8)
         .map(|chunk| f32::from_be_bytes(chunk.try_into().unwrap()))
@@ -162,17 +153,30 @@ fn read_from_serial(mut outputs: Output<'_>, args: Args<'_>) -> SResult<()> {
 
 /// 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)?;
-
+    args.assert_params_max_len(2)?;
+    let port = get_port_name(1, &args)?;
     // Get the doubles to write from the input argument
     let data: Vec<f32> = args
-        .get(1, Error::MissingWriteData)?
+        .get(2, 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)?;
+    SERIAL.read().unwrap().write_bytes(&port, &data)?;
     warn_debug!("Wrote {} bytes to serial port", data.len());
     Ok(())
 }
+
+/// Get the mode from the input argument (Open, Close, Read, Write)
+fn get_mode(arg: MxArray) -> SResult<Mode> {
+    IntoRustType::<String>::into_rust(arg)?.parse()
+}
+
+/// Get port name from the input argument
+fn get_port_name(ix: usize, args: &Args<'_>) -> SResult<String> {
+    Ok(args
+        .get(ix, Error::MissingPortName)?
+        .into_rust()
+        .map_mexerr(|e| Error::InvalidPortName(Box::new(e)))?)
+}
diff --git a/src/serial.rs b/src/serial.rs
index 38826e7e2edd34639c2bcd26f533a1ed5aa04d16..aa1d6fde6ee9d8f0b7efc833f5705c2a4397a32b 100644
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -1,30 +1,34 @@
-use std::sync::Mutex;
-
+use hashbrown::HashMap;
+use parking_lot::{Mutex, RwLock};
 use serialport::SerialPort;
 
 use crate::error::{Error, SResult};
 
 /// Handler of the serial port
 pub struct SerialManager {
-    serial: Option<Mutex<Box<dyn SerialPort>>>,
+    serial: RwLock<HashMap<String, Mutex<Box<dyn SerialPort>>>>,
 }
 
 impl SerialManager {
     pub fn new() -> Self {
-        Self { serial: None }
+        Self {
+            serial: RwLock::new(HashMap::new()),
+        }
     }
 
     /// 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));
+        let serial = serialport::new(port, baudrate).open()?;
+        self.serial
+            .write()
+            .insert(port.to_owned(), Mutex::new(serial));
         Ok(())
     }
 
     /// Close the serial port if it is open
-    pub fn close(&mut self) -> SResult<()> {
-        if self.serial.is_some() {
-            self.serial.take();
+    pub fn close(&mut self, port: &str) -> SResult<()> {
+        if self.serial.read().contains_key(port) {
+            self.serial.write().remove(port);
             Ok(())
         } else {
             Err(Error::SerialNotOpen)
@@ -32,26 +36,18 @@ impl SerialManager {
     }
 
     /// Read `n` bytes from the serial port
-    pub fn read_n_bytes(&self, n: usize) -> SResult<Vec<u8>> {
-        let mut port = self
-            .serial
-            .as_ref()
-            .ok_or(Error::SerialNotOpen)?
-            .lock()
-            .unwrap();
+    pub fn read_n_bytes(&self, port: &str, n: usize) -> SResult<Vec<u8>> {
+        let map = self.serial.read();
+        let mut port = map.get(port).ok_or(Error::SerialNotOpen)?.lock();
         let mut buf = vec![0; n];
         port.read_exact(&mut buf)?;
         Ok(buf)
     }
 
     /// Write `data` to the serial port
-    pub fn write_bytes(&self, data: &[u8]) -> SResult<()> {
-        let mut port = self
-            .serial
-            .as_ref()
-            .ok_or(Error::SerialNotOpen)?
-            .lock()
-            .unwrap();
+    pub fn write_bytes(&self, port: &str, data: &[u8]) -> SResult<()> {
+        let map = self.serial.read();
+        let mut port = map.get(port).as_ref().ok_or(Error::SerialNotOpen)?.lock();
         port.write_all(data)?;
         Ok(())
     }